API reference
Style
Apply a journal style to matplotlib.
This is the only eofigs function that changes global state (matplotlib's
rcParams and the eofigs defaults for panel labels). Use :func:style to apply
a style temporarily.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
journal
|
str or None
|
Journal preset: |
"elsevier"
|
font
|
str
|
|
None
|
bold
|
bool
|
Bold text for labels, titles and tick labels. |
False
|
base_size
|
float
|
Base font size in points. Defaults to the journal preset; tick labels are one point smaller but never below the journal minimum. |
None
|
labels
|
str or None
|
Default panel-label format: |
"(a)"
|
label_bold
|
bool
|
Bold panel labels. |
True
|
max_ticks
|
int
|
A house rule for the largest number of tick labels per axis (for example 5).
When set, :func: |
None
|
panel_titles
|
bool
|
Whether recipes put a short title above each panel (for example "Before
(3 months)" in :func: |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown journal or label format, or |
Examples:
>>> import eofigs
>>> eofigs.set_style("elsevier", font="serif")
>>> eofigs.set_style(None) # restore the previous matplotlib settings
Apply a journal style inside a with block and restore all settings afterwards.
Takes the same arguments as :func:set_style.
Examples:
>>> import eofigs
>>> with eofigs.style("frontiers", font="DejaVu Sans"):
... fig, ax = eofigs.new_figure("single")
Create a figure with its printed size in millimetres.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
str or float
|
A named width of the journal preset ( |
"single"
|
height_mm
|
float
|
Height in mm. If omitted, the height is |
None
|
aspect
|
float
|
Height-to-width ratio. Defaults to |
None
|
nrows
|
int
|
Panel grid, passed to :func: |
1
|
ncols
|
int
|
Panel grid, passed to :func: |
1
|
journal
|
str
|
Journal preset for the widths; defaults to the one set by :func: |
None
|
**kwargs
|
Any
|
Passed to :func: |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
fig |
Figure
|
|
axes |
Axes or numpy.ndarray of Axes
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the width is unknown or too wide, or the height exceeds the journal's maximum figure height. |
Examples:
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single", height_mm=60)
>>> float(round(fig.get_figwidth() * 25.4, 1))
90.0
Add panel labels such as (a), (b), (c) to axes, in row-major order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axes
|
Axes or sequence of Axes
|
The panels to label, for example the array returned by :func: |
required |
labels
|
str or sequence of str
|
A format ( |
None
|
bold
|
bool
|
Bold labels; defaults to the :func: |
None
|
loc
|
('outside', 'inside')
|
Place labels just above the top-left corner of each panel, or inside it. |
"outside"
|
fontsize
|
float
|
Label size in points; defaults to the base font size. |
None
|
Returns:
| Type | Description |
|---|---|
list of matplotlib.text.Text
|
The label artists (empty when labelling is switched off). |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown label format or location, or fewer explicit labels than panels. |
Examples:
>>> import eofigs
>>> fig, axes = eofigs.new_figure("double", ncols=2)
>>> [t.get_text() for t in eofigs.label_panels(axes, "(a)")]
['(a)', '(b)']
Limit axes and colour bars to a few ticks at round values.
Every eofigs recipe calls this; call it on your own plots for the same look. Only
automatically placed ticks are changed: ticks set explicitly (ax.set_xticks),
log axes and map axes (cartopy GeoAxes, which use graticules) are left as they are.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*items
|
Axes, Colorbar or sequence of them
|
Axes (or an array of axes from :func: |
()
|
max_ticks
|
int
|
Largest number of tick labels per axis. Defaults to the |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single")
>>> _ = ax.plot([0, 0.95], [-3.3, 7.1])
>>> eofigs.tidy_axes(ax)
>>> len(ax.get_yticks()) <= 5
True
Colours
Colour palettes and colour maps built into eofigs.
Each palette is a tuple of hex colours, chosen for one job:
| Palette | Use |
|---|---|
okabe_ito |
lines, points, error bars and small symbols (Okabe & Ito 2008; black last) |
muted |
filled areas: bars, stacked areas, class maps with up to five classes plus grey |
slate |
ordered magnitude in a few classes, light (low) to dark (high) |
terra_slate |
signed values around zero: terracotta (negative), off-white (zero), slate teal |
slate and terra_slate are also registered as matplotlib colour maps
"eofigs.slate" and "eofigs.terra_slate" (and their "_r" reversals), so they can
be passed as cmap= anywhere. neutrals holds the colours for text, secondary lines,
grid lines and no-data areas.
Examples:
>>> from eofigs import palettes
>>> palettes.muted[0]
'#768EAE'
>>> import matplotlib as mpl
>>> mpl.colormaps["eofigs.terra_slate"].N
255
Write one image showing a figure as seen with colour-vision deficiencies.
The image has five panels: normal vision, protan, deutan and tritan simulations
(Machado et al. 2009, full severity, the same as :func:check_figure) and greyscale
(CIELAB lightness). Use it to confirm that every series, class and colour-map step
is still distinguishable, and that sequential data keep their order in greyscale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fig_or_path
|
Figure or path
|
A figure, or a PNG, TIFF or JPEG file. |
required |
out
|
str or Path
|
Output PNG. Defaults to |
None
|
dpi
|
float
|
Resolution at which a figure is rendered for the preview. |
150
|
Returns:
| Type | Description |
|---|---|
Path
|
The written PNG. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For a figure without |
FileNotFoundError
|
If the input file does not exist. |
Examples:
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single")
>>> _ = ax.plot([0, 1], [0, 1])
>>> eofigs.cvd_preview(fig, "fig1_cvd.png")
Variables
Look up a variable by name, alias or xarray attributes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
(str, Variable or DataArray)
|
A library name or alias (case-insensitive, |
required |
Returns:
| Type | Description |
|---|---|
Variable
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If nothing matches; the message suggests close names. |
Examples:
>>> import eofigs
>>> eofigs.variable("GPP").label
'Gross primary productivity (gC m$^{-2}$ d$^{-1}$)'
>>> eofigs.variable("ndvi").trend("decade").units
'decade$^{-1}$'
Add a variable to the library, or replace one with overwrite=True.
Registered variables are found by :func:variable and by every recipe for the
rest of the session. This is the only way the library changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Key of the new variable. |
required |
overwrite
|
bool
|
Replace an existing variable (bundled or registered) of the same name. |
False
|
**fields
|
Any
|
:class: |
{}
|
Returns:
| Type | Description |
|---|---|
Variable
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the name exists and |
Examples:
>>> import eofigs
>>> v = eofigs.register_variable(
... "cci", long_name="Chlorophyll carotenoid index", symbol="CCI", vmin=-0.5, vmax=0.5,
... overwrite=True)
>>> eofigs.variable("CCI").long_name
'Chlorophyll carotenoid index'
How one variable is named, labelled and coloured.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Key in the library, for example |
long_name |
str
|
Full name, for example |
symbol |
str
|
Short symbol in matplotlib mathtext, for example |
units |
str
|
Units in mathtext ( |
cmap |
str
|
Registered matplotlib colour map name (cmcrameri maps are |
anomaly_cmap |
str
|
Diverging colour map for anomalies and trends, centred on zero. |
kind |
{'continuous', 'diverging', 'categorical', 'mask'}
|
How the data behave; sets the colour scaling and the raster reduction. |
vmin, vmax |
float or None
|
Default colour limits; |
standard_name |
str or None
|
CF standard name used to recognise the variable in xarray attributes. |
aliases |
tuple of str
|
Other names that refer to the variable. |
label
property
Axis or colour-bar label: long name with units in brackets.
short_label
property
Compact label: symbol with units in brackets.
__post_init__()
Validate the fields.
anomaly()
Return the variable as an anomaly: zero-centred diverging colours, same units.
get_cmap()
Return the colour map as a matplotlib object.
trend(per='decade')
Return the variable as a trend per year or per decade.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
per
|
('decade', 'year')
|
Time unit of the trend; units become for example |
"decade"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For any other |
Large rasters
Reduce a raster to the resolution of the printed figure.
A map printed 90 mm wide at 600 dpi shows about 2 100 pixels across, however large
the raster is. fit_raster reads or aggregates the raster to that size before
plotting, so large rasters fit in memory, plot quickly and give small vector PDFs.
It never upsamples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
(str, Path, DataArray or Dataset)
|
A GeoTIFF/COG path or URL ( |
required |
width_mm
|
float
|
Printed width of the map area in mm. |
required |
height_mm
|
float
|
Printed height in mm; if given, the raster fits inside width x height. |
None
|
dpi
|
float
|
Output resolution. |
600
|
kind
|
('continuous', 'categorical', 'mask')
|
Reduction: mean of valid pixels (continuous), most frequent class (categorical)
or fraction of True pixels (mask, e.g. p < 0.05). Inferred if omitted: boolean
data are masks; data with |
"continuous"
|
min_valid
|
float
|
Minimum fraction of valid (not nodata/NaN) source pixels; output pixels with fewer valid pixels are NaN. |
0.5
|
band
|
int
|
Band to read from a file. |
1
|
Returns:
| Type | Description |
|---|---|
DataArray or Dataset
|
The reduced raster with CRS and transform. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For a raster without a CRS, an unknown |
FileNotFoundError
|
If the file does not exist. |
Notes
Files are read with rasterio at the output size, so GDAL uses overviews when the
file has them and never loads the full-resolution array. Pre-built overviews keep
the resampling they were built with (often nearest or average), which then applies
instead of kind. Integer files with continuous or mask data are averaged exactly by
eofigs, a few rows at a time, because some GDAL versions round integer averages.
Arrays are aggregated by an integer factor; up to factor - 1
edge rows and columns (less than one printed pixel) are dropped so the grid stays
regular.
Examples:
>>> import numpy as np, xarray as xr, rioxarray # noqa: F401
>>> from eofigs import fit_raster
>>> da = xr.DataArray(np.ones((4000, 6000)), dims=("y", "x"),
... coords={"y": np.arange(4000.0)[::-1], "x": np.arange(6000.0)})
>>> da = da.rio.write_crs("EPSG:32643")
>>> fit_raster(da, width_mm=90, dpi=300).shape # 1063 px needed; factor 5 keeps 1200
(800, 1200)
Maps
Draw a publication-ready study area map.
The map shows the study area outline over a grey hillshade with semi-transparent
elevation colours, an optional single-colour overlay (for example forest), labelled
points, a graticule, a north arrow pointing to true north, a scale bar, a legend and a
locator inset. Rasters are reduced to the printed resolution first (:func:fit_raster),
so large DEMs are fine. The figure height follows the shape of the study area, up to
three quarters of the journal's maximum height.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
aoi
|
(GeoDataFrame, GeoSeries or path)
|
Study area outline(s), with a CRS. The map uses the AOI's CRS if it is projected, otherwise the AOI's UTM zone. |
required |
dem
|
(path, URL or DataArray)
|
Elevation in metres, with a CRS; drawn as hillshade plus elevation colours with a colour bar. The colours are cmcrameri batlow, or the light half of lapaz when an overlay is drawn, so the overlay stands out. |
None
|
overlay
|
(path, URL or DataArray)
|
A raster drawn in one colour where it is True, or where it reaches
|
None
|
points
|
GeoDataFrame or path
|
Places to mark, with a CRS. |
None
|
labels
|
str or None
|
Column of |
"name"
|
aoi_label
|
str
|
Legend entries. |
'Study area'
|
overlay_label
|
str
|
Legend entries. |
'Study area'
|
points_label
|
str
|
Legend entries. |
'Study area'
|
overlay_threshold
|
float
|
Value from which the overlay is drawn. |
None
|
overlay_colour
|
str
|
Overlay colour; check_figure tests that it stays distinguishable from the elevation colours. |
"#009E73" (Okabe-Ito bluish green)
|
inset
|
('country', GeoDataFrame or None)
|
Locator inset with the surrounding country (or the given outlines); |
"country"
|
worldview
|
str
|
Natural Earth worldview for the inset boundaries (see :func: |
None
|
graticule
|
bool
|
Map elements to draw. The graticule has at most four labelled lines per side. |
True
|
north_arrow
|
bool
|
Map elements to draw. The graticule has at most four labelled lines per side. |
True
|
scale_bar
|
bool
|
Map elements to draw. The graticule has at most four labelled lines per side. |
True
|
legend
|
bool
|
Map elements to draw. The graticule has at most four labelled lines per side. |
True
|
layout
|
('auto', 'inside', 'side')
|
|
"auto"
|
width
|
str or float
|
Named journal width or width in mm. |
"single"
|
height_mm
|
float
|
Figure height; by default it follows the study area's shape, up to 75 % of the journal's maximum height. |
None
|
journal
|
str
|
Journal preset (defaults to the one set by :func: |
None
|
ax
|
cartopy GeoAxes
|
Draw into existing map axes (inside layout, no automatic size). |
None
|
subfigure
|
matplotlib SubFigure
|
Draw into a subfigure, for example one panel of |
None
|
Returns:
| Type | Description |
|---|---|
Figure
|
The map (the whole figure when drawn into a subfigure). |
Raises:
| Type | Description |
|---|---|
ValueError
|
For inputs without a CRS, an unknown layout, |
Notes
Boundaries and names shown do not imply any opinion on the legal status of any country or territory.
Add a north arrow that points to true north.
On a cartopy map the arrow is rotated by the grid convergence at the map centre, so it points to true north rather than to grid north (in UTM the difference is a few degrees away from the central meridian). On ordinary axes it points up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
Axes
|
A cartopy GeoAxes (or ordinary axes). |
required |
loc
|
str
|
Matplotlib location code, for example |
"upper right"
|
size_mm
|
float
|
Length of the arrow in mm (the bold "N" is added above it). |
7
|
bbox_to_anchor
|
tuple
|
Box in axes coordinates that |
None
|
Returns:
| Type | Description |
|---|---|
AnchoredOffsetbox
|
The arrow artist; |
Examples:
>>> import cartopy.crs as ccrs
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single", subplot_kw={"projection": ccrs.UTM(43)})
>>> ax.set_extent([240_000, 300_000, 3_820_000, 3_960_000], crs=ccrs.UTM(43)) # Swat
>>> arrow = eofigs.add_north_arrow(ax)
>>> round(arrow.angle, 1) # west of the central meridian: true north is clockwise of up
1.5
Add a scale bar that shows true ground distance at the map centre.
The map's projection scale factor at its centre is taken into account, so the bar is correct in any projection near the centre (for UTM the correction is below 0.1 %).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
cartopy GeoAxes
|
The map. |
required |
loc
|
str
|
Matplotlib location code. |
"lower left"
|
length_km
|
float
|
Bar length in km. Defaults to a round length (1, 2, 2.5 or 5 x 10^n) near
|
None
|
fraction
|
float
|
Target share of the map width when the length is chosen automatically. |
0.22
|
bbox_to_anchor
|
tuple
|
Box in axes coordinates that |
None
|
Returns:
| Type | Description |
|---|---|
ScaleBar
|
The scale bar; |
Raises:
| Type | Description |
|---|---|
ValueError
|
For axes that are not a map. |
Examples:
>>> import cartopy.crs as ccrs
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single", subplot_kw={"projection": ccrs.UTM(43)})
>>> ax.set_extent([240_000, 300_000, 3_820_000, 3_960_000], crs=ccrs.UTM(43))
>>> eofigs.add_scale_bar(ax).length_km # about 22 % of a 60 km wide map
10
Add a locator inset: the country (or countries) around the AOI, with the AOI marked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
Axes
|
The main map; the inset is placed inside it unless |
required |
aoi
|
(GeoDataFrame, GeoSeries or path)
|
The area of interest, with a CRS. |
required |
loc
|
str
|
Corner of |
"upper right"
|
width_mm
|
float
|
Inset width in mm; defaults to a third of the map width. |
None
|
rect
|
(left, bottom, width, height)
|
Inset position in figure coordinates instead of |
None
|
worldview
|
str
|
|
None
|
countries
|
GeoDataFrame
|
Your own country outlines (any CRS), instead of Natural Earth. |
None
|
Returns:
| Type | Description |
|---|---|
cartopy GeoAxes
|
The inset axes; |
Notes
Boundaries and names shown do not imply any opinion on the legal status of any country or territory. Choose the worldview that suits the journal and readership.
Trends
Theil–Sen slope and Mann–Kendall significance for every pixel of a time series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Values with a datetime coordinate along |
required |
dim
|
str
|
Time dimension. Its coordinate must be datetime64; slopes are computed against the real dates in years, so gaps and irregular steps are handled. |
"time"
|
per
|
('decade', 'year')
|
Unit of the slope. |
"decade"
|
alpha
|
float
|
Significance level for the |
0.05
|
min_valid
|
int
|
Minimum number of valid (non-NaN) observations; pixels with fewer get NaN. |
8
|
memory_mb
|
float
|
Memory budget for the pairwise slopes. Pixels are processed in blocks that fit this budget, so any series length works; long series are only slower. |
256
|
seasonal
|
('month', 'none')
|
Seasonal Mann–Kendall test and seasonal Kendall slope (Hirsch et al. 1982) for
sub-annual series: the test and the slope compare only values of the same season
in different years, so a seasonal cycle does not distort them. |
"month"
|
seasonal_correction
|
bool
|
With |
True
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Warns:
| Type | Description |
|---|---|
TrendWarning
|
For sub-annual data without |
Notes
The slope is the median of all pairwise slopes (Sen 1968) and the intercept is the
median of y - slope * t (scipy.stats.theilslopes(method="joint")). The
Mann–Kendall statistic S uses the variance with the tie correction and the continuity
correction for Z (Kendall 1975). Observations are assumed serially independent: no
correction for autocorrelation is applied (modified Mann–Kendall or prewhitening are
not part of v0.1), and seasonality is not removed, so monthly data should be
deseasonalised or aggregated to annual values first. Memory grows with the number of
pairs, n(n-1)/2, per pixel; time grows with pixels x pairs.
Examples:
>>> import numpy as np, pandas as pd, xarray as xr
>>> from eofigs import trend
>>> time = pd.date_range("2001-01-01", periods=20, freq="YS")
>>> da = xr.DataArray(0.02 * np.arange(20.0), dims="time", coords={"time": time})
>>> result = trend(da, per="decade")
>>> round(float(result.slope), 3), bool(result.significant)
(0.2, True)
Map per-pixel trends with zero-centred colours and significance marks.
The trend is computed at the data's full resolution (Theil–Sen slope, Mann–Kendall
test, :func:eofigs.trend) and only then reduced to the printed size: slopes are
averaged, p-values never are. Significance is shown at print size: a dot (or hatching)
marks each cell of about dot_spacing_mm where at least sig_fraction of the
valid full-resolution pixels are significant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Time-series cube with a datetime dimension |
None
|
slope
|
DataArray
|
A precomputed slope grid (with a CRS) and, optionally, its p-values on the same grid (same coordinates; dimensions in another order are transposed to the slope's, and a p-value grid with a different CRS is refused). Without p-values no significance marks are drawn. |
None
|
pvalue
|
DataArray
|
A precomputed slope grid (with a CRS) and, optionally, its p-values on the same grid (same coordinates; dimensions in another order are transposed to the slope's, and a p-value grid with a different CRS is refused). Without p-values no significance marks are drawn. |
None
|
alpha
|
float
|
Significance level of each pixel's test. |
0.05
|
fdr
|
'bh'
|
|
"bh"
|
fdr_q
|
float
|
False discovery rate for |
None
|
dim
|
str
|
Time dimension of |
"time"
|
per
|
('decade', 'year')
|
Unit of the slope computed from |
"decade"
|
seasonal
|
Any
|
Passed to :func: |
None
|
seasonal_correction
|
Any
|
Passed to :func: |
None
|
variable
|
str or Variable
|
The variable (for example |
None
|
limits
|
('p98', float or None)
|
Symmetric colour limits: the 98th percentile of the absolute slope rounded up to
a round value, a number, or |
"p98"
|
stipple
|
('dots', 'hatch')
|
How significance is marked. |
"dots"
|
sig_fraction
|
float
|
Share of significant pixels a cell needs for a mark. |
0.5
|
dot_spacing_mm
|
float
|
Size of the significance cells on the page. |
1.6
|
exclude
|
('auto', float, DataArray or None)
|
Pixels drawn in grey and left out of the colours, marks and profile. |
"auto"
|
exclude_label
|
str
|
Legend entry for the excluded pixels. |
"Not vegetated"
|
aoi
|
(GeoDataFrame, GeoSeries or path)
|
Study area outline drawn on top; the map then shows the study area with a margin. |
None
|
aoi_label
|
str
|
Legend entry for the outline. |
"Study area"
|
latitude_profile
|
bool
|
Add a panel beside the map with the median and interquartile range of the full-resolution slope in latitude bands (valid pixels only; bands with fewer than 20 pixels are skipped). |
False
|
graticule
|
bool
|
Map elements to draw. |
True
|
north_arrow
|
bool
|
Map elements to draw. |
True
|
scale_bar
|
bool
|
Map elements to draw. |
True
|
legend
|
bool
|
Map elements to draw. |
True
|
labels
|
str or list of str
|
Panel labels for the map and profile (style default when omitted). |
None
|
width
|
str or float
|
Named journal width or width in mm. |
"onehalf"
|
height_mm
|
float
|
Figure height; by default it follows the map's shape, up to 75 % of the journal's maximum height. |
None
|
journal
|
str
|
Journal preset (defaults to the one set by :func: |
None
|
ax
|
cartopy GeoAxes
|
Draw into existing map axes (no profile, no automatic size). |
None
|
subfigure
|
matplotlib SubFigure
|
Draw within a subfigure, for example one panel of |
None
|
Returns:
| Type | Description |
|---|---|
Figure
|
The figure. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For missing or conflicting inputs, grids without a CRS, mismatched grids, an
unknown |
Examples:
>>> import numpy as np, pandas as pd, xarray as xr, rioxarray
>>> import eofigs
>>> time = pd.date_range("2001-01-01", periods=12, freq="YS")
>>> y, x = np.arange(3_900_000, 3_880_000, -500.0), np.arange(500_000, 520_000, 500.0)
>>> values = 0.5 + 0.01 * np.arange(12)[:, None, None] * np.ones((12, y.size, x.size))
>>> cube = xr.DataArray(values, dims=("time", "y", "x"),
... coords={"time": time, "y": y, "x": x}, name="ndvi")
>>> fig = eofigs.trend_map(cube.rio.write_crs("EPSG:32643"))
>>> fig.eofigs_layout["significant_percent"]
100.0
Benjamini–Hochberg false discovery rate control over a field of p-values.
The family of tests is every finite p-value (within valid when given); NaN is
"not tested". A test is significant when its Benjamini–Hochberg adjusted p-value is
at most q (Benjamini and Hochberg 1995; computed with
scipy.stats.false_discovery_control). For spatially correlated fields Wilks (2016,
BAMS 97: 2263–2273) recommends q = 2 × alpha, for example 0.10 for a field test at
0.05.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pvalues
|
array - like or DataArray
|
P-values of any shape. |
required |
q
|
float
|
False discovery rate, in (0, 1). |
0.10
|
valid
|
array-like of bool
|
Pixels that belong to the family of tests (same shape as |
None
|
Returns:
| Type | Description |
|---|---|
FdrResult
|
|
Examples:
>>> import numpy as np
>>> from eofigs.stats import fdr_mask
>>> p = np.array([0.001, 0.008, 0.039, 0.041, 0.042, 0.06, 0.074, 0.205, 0.212, 0.216])
>>> result = fdr_mask(p, q=0.10)
>>> result.n_rejected, result.p_threshold
(6, 0.06)
Bases: UserWarning
Warning from :func:trend about seasonal data.
Raised for sub-annual data without seasonal, and for records too short for the
Hirsch-Slack correction.
Event composites
Composite anomaly maps before, during and after a set of events, with their series.
For each event the anomalies are averaged over the steps before the event (the whole
pre-event part of the window), during it and after it (the post-event part); the maps
show the mean of these per-event means across events. Anomalies are computed at full
resolution against a baseline climatology from the steps outside every event window,
after removing a per-pixel linear trend fitted to those steps. Significance is tested
across events per pixel and marked at print size as in :func:trend_map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Time series cube ( |
required |
events
|
list of (start, end) or boolean time series
|
Event periods as date pairs (inclusive), or a boolean series on the data's time steps (True during an event; each run of True values is one event). |
required |
window
|
(int, int)
|
Steps before the event start and after the event end. |
(-3, 6)
|
freq
|
str
|
A pandas offset alias (for example |
None
|
baseline
|
(start, end)
|
Period for the climatology; only its steps outside every event window are used. Default: all steps outside every event window. |
None
|
kind
|
('anomaly', 'zscore')
|
Anomalies, or anomalies divided by the baseline standard deviation of the same calendar month (pixels with fewer than 5 baseline values for it get NaN). |
"anomaly"
|
climatology
|
('auto', 'month')
|
Remove a climatology per calendar month ( |
"auto"
|
detrend
|
'linear'
|
Remove a per-pixel linear trend fitted to the baseline steps first, so that a long greening or warming trend does not make late events look better than early ones. |
"linear"
|
test
|
('ttest', 'wilcoxon')
|
Significance across events per pixel: one-sample t-test or Wilcoxon signed-rank
test of the event-mean anomalies against zero. Both need at least 3 events, and a
two-sided Wilcoxon test at 0.05 can only reach significance with at least 6 events.
|
"ttest"
|
variable
|
str or Variable
|
The variable (for example |
None
|
maps
|
tuple of {"before", "during", "after"}
|
Phase maps to draw, in order. |
all three
|
series
|
bool
|
Add the composite series: the area mean for each event (thin grey), their mean (black) and spread (interquartile range, or the range with fewer than 4 events). Shading marks the event period: darker while all events are ongoing (up to the shortest event), lighter while only some are (up to the longest). |
True
|
event_rule
|
str
|
How the events were chosen, for the caption sentence. |
None
|
alpha
|
float
|
As in :func: |
0.05
|
sig_fraction
|
float
|
As in :func: |
0.05
|
dot_spacing_mm
|
float
|
As in :func: |
0.05
|
stipple
|
float
|
As in :func: |
0.05
|
limits
|
float
|
As in :func: |
0.05
|
exclude
|
float
|
As in :func: |
0.05
|
exclude_label
|
float
|
As in :func: |
0.05
|
fdr
|
str | None
|
As in :func: |
None
|
fdr_q
|
str | None
|
As in :func: |
None
|
aoi
|
(GeoDataFrame, GeoSeries or path)
|
Study area outline: drawn on each map, and the area the series averages over. |
None
|
aoi_label
|
str
|
Legend entry for the outline. |
"Study area"
|
graticule
|
bool
|
Map elements; the north arrow and scale bar go on the first map. |
True
|
north_arrow
|
bool
|
Map elements; the north arrow and scale bar go on the first map. |
True
|
scale_bar
|
bool
|
Map elements; the north arrow and scale bar go on the first map. |
True
|
legend
|
bool
|
Map elements; the north arrow and scale bar go on the first map. |
True
|
labels
|
str or list of str
|
Panel labels (style default when omitted). The phase titles above the maps
follow the |
None
|
width
|
str or float
|
Named journal width or width in mm. |
"double"
|
height_mm
|
float
|
Figure height; by default it follows the maps' shape, up to 75 % of the journal's maximum height. |
None
|
journal
|
str
|
Journal preset (defaults to the one set by :func: |
None
|
ax
|
None
|
Not supported: this recipe has several panels; use |
None
|
subfigure
|
matplotlib SubFigure
|
Draw within a subfigure of a larger figure. |
None
|
Returns:
| Type | Description |
|---|---|
Figure
|
The figure. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For data without a CRS or datetime axis, no usable events, an unknown |
Examples:
>>> import numpy as np, pandas as pd, xarray as xr, rioxarray
>>> import eofigs
>>> time = pd.date_range("2001-01-01", periods=120, freq="MS")
>>> y, x = np.arange(3_900_000, 3_880_000, -1000.0), np.arange(500_000, 520_000, 1000.0)
>>> values = 0.5 + np.zeros((120, y.size, x.size))
>>> cube = xr.DataArray(values, dims=("time", "y", "x"),
... coords={"time": time, "y": y, "x": x}, name="ndvi")
>>> events = [("2003-06-01", "2003-07-01"), ("2006-06-01", "2006-06-01"),
... ("2009-07-01", "2009-08-01")]
>>> fig = eofigs.event_composite(cube.rio.write_crs("EPSG:32643"), events)
>>> len(fig.eofigs_layout["events"])
3
Accuracy scatters
Scatter of observed against predicted values with the 1:1 line, a fit and statistics.
By default the predicted values go on the x axis and the observed values on the y axis, and the fitted line is the ordinary least-squares regression of observed on predicted, so that its slope and intercept read directly as the model's consistency and bias (Piñeiro et al. 2008, Ecological Modelling 216: 316–322).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observed
|
array-like, pandas Series or xarray DataArray
|
Paired values of the same shape; they are flattened, and pairs with a missing
value in either are left out (and counted in the record). Two pandas objects are
paired by their index labels and two DataArrays by their coordinates ( |
required |
predicted
|
array-like, pandas Series or xarray DataArray
|
Paired values of the same shape; they are flattened, and pairs with a missing
value in either are left out (and counted in the record). Two pandas objects are
paired by their index labels and two DataArrays by their coordinates ( |
required |
variable
|
str or Variable
|
The variable (for example |
None
|
groups
|
array - like
|
A category for each pair (up to 6), drawn in Okabe-Ito colours with distinct
markers. Statistics for each group are in the record and the caption. When the
inputs are paired by labels, a pandas Series or DataArray of groups is aligned
with them in the same way; a plain array follows the order of |
None
|
density
|
('auto', True, False)
|
Colour points by the number of points in each cell of a 60 × 60 grid (log scale,
densest on top, rasterised in vector files). |
"auto"
|
stats
|
sequence of str
|
Statistics in the box: |
("r2", "rmse", "bias", "n")
|
fit
|
('ols', 'rma', None)
|
Fitted line: ordinary least squares of the y-axis values on the x-axis values, reduced major axis, or none. |
"ols"
|
orientation
|
('observed_y', 'observed_x')
|
Observed values on the y axis (Piñeiro et al. 2008) or on the x axis. |
"observed_y"
|
one_to_one
|
bool
|
Draw the 1:1 line. |
True
|
limits
|
(float, float)
|
Shared limits of both axes. By default the data range, rounded outwards to ticks. |
None
|
observed_label
|
str
|
Axis labels; by default "Observed" and "Predicted" with the variable and units. |
None
|
predicted_label
|
str
|
Axis labels; by default "Observed" and "Predicted" with the variable and units. |
None
|
stats_loc
|
str
|
Corner of the statistics box: |
"auto"
|
legend
|
bool
|
Legend for the lines and groups, above the plot. |
True
|
labels
|
str or list of str
|
Panel label. A single-panel figure has none unless given. |
None
|
width
|
str or float
|
Named journal width or width in mm. |
"single"
|
height_mm
|
float
|
Figure height; by default it follows the square plot. |
None
|
journal
|
str
|
Journal preset (defaults to the one set by :func: |
None
|
ax
|
matplotlib Axes
|
Draw into an existing axes, for example one panel of a larger figure. |
None
|
Returns:
| Type | Description |
|---|---|
Figure
|
The figure. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For pandas inputs whose index labels differ or repeat, DataArrays whose
dimensions or coordinates differ, inputs of different shapes, fewer than 3 pairs,
observed values that are all equal, an unknown statistic, |
Examples:
>>> import numpy as np
>>> import eofigs
>>> rng = np.random.default_rng(0)
>>> observed = rng.uniform(20, 300, 200)
>>> predicted = observed + rng.normal(0, 25, 200)
>>> fig = eofigs.accuracy_scatter(observed, predicted, variable="agb")
>>> fig.eofigs_layout["stats"]["n"]
200
Figure sets
Open (or start) the figure set of a manuscript in a folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str or Path
|
Output folder; created if needed. An existing |
required |
journal
|
str
|
Journal preset for formats, resolution and checks; defaults to the one set by
:func: |
None
|
formats
|
sequence of str
|
Output formats; default from the journal preset (for example PDF + TIFF). |
None
|
dpi
|
float
|
Raster resolution; default from the journal preset and artwork type. |
None
|
art
|
('combination', 'line', 'halftone')
|
Default artwork type of the figures. |
"combination"
|
Returns:
| Type | Description |
|---|---|
FigureSet
|
|
Examples:
>>> import tempfile, eofigs
>>> folder = tempfile.mkdtemp()
>>> figs = eofigs.figure_set(folder, journal="custom", formats=("png",), dpi=100)
>>> fig, ax = eofigs.new_figure("single", journal="custom")
>>> [p.name for p in figs.add("study_area", fig, caption="Study area.")]
['Fig01_study_area.png']
>>> [p.name for p in figs.add("accuracy", fig, supplementary=True)]
['FigS01_accuracy.png']
The figures of one manuscript, numbered and saved in one folder.
Create it with :func:figure_set. Main figures are saved as Fig01_<name> and
supplementary figures as FigS01_<name>, numbered in the order they are added
(change it with :meth:order). Every change rewrites figures_manifest.csv, so
reopening the folder in a new session continues where the last one stopped.
entries
property
Manifest rows: main figures first, then supplementary figures.
manifest_path
property
Path of figures_manifest.csv.
__len__()
Return the number of figures in the set.
__repr__()
Short description.
add(name, fig, supplementary=False, caption='', art=None, check=True)
Save a figure into the set and record it in the manifest.
Adding a name that is already in the set replaces that figure (its number stays the same unless it moves between main and supplementary).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Short name used in the file names: letters, digits, |
required |
fig
|
Figure
|
The figure to save. |
required |
supplementary
|
bool
|
Number it as a supplementary figure ( |
False
|
caption
|
str
|
Caption kept in the manifest. |
""
|
art
|
('combination', 'line', 'halftone')
|
Artwork type for this figure; defaults to the set's |
"combination"
|
check
|
bool
|
Run the figure check and record the result in the manifest. |
True
|
Returns:
| Type | Description |
|---|---|
list of pathlib.Path
|
The files written. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an invalid name. |
exists(name)
Check whether the figure is in the set and all its files are on disk.
Use it to skip rebuilding expensive figures when a notebook is run again.
order(names, supplementary=False)
Set the order of the main (or supplementary) figures and rename their files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
sequence of str
|
All names of that group, in the new order. |
required |
supplementary
|
bool
|
Reorder the supplementary figures instead of the main ones. |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Export
Save a figure in one or more formats at its exact printed size.
The figure is never trimmed (no bbox_inches="tight"), so the saved size is the
size set with :func:eofigs.new_figure. PDFs embed TrueType fonts and carry no
creation date, so the same figure always gives the same file. In vector files
(PDF, EPS, SVG), dense layers are rasterised at the output resolution while text,
axes and lines stay vector, which keeps files small and fast to open.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fig
|
Figure
|
The figure to save. |
required |
path
|
str or Path
|
Output path. With a suffix ( |
required |
formats
|
sequence of str
|
Formats such as |
None
|
dpi
|
float
|
Resolution of raster outputs; defaults to the preset's export resolution (600)
or the resolution the journal requires for |
None
|
size_px
|
int or (int, int)
|
Exact pixel width (or width and height) of raster outputs; sets the dpi. |
None
|
check
|
bool
|
Run :func: |
True
|
journal
|
str
|
Journal preset; defaults to the one used to create the figure. |
None
|
art
|
('combination', 'line', 'halftone')
|
Artwork type. Line art (plots without images) needs more resolution in raster files: for example 1000 dpi for Elsevier and 1200 dpi for Springer and Taylor & Francis. Vector PDF output is not affected. |
"combination"
|
rasterize_dense
|
bool
|
In vector files, rasterise mesh plots ( |
True
|
Returns:
| Type | Description |
|---|---|
CheckReport or None
|
The combined check report, or |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unsupported format, or a |
Examples:
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single", journal="custom")
>>> _ = ax.plot([0, 1], [0, 1])
>>> report = eofigs.save_figure(fig, "fig1", formats=("pdf", "png"))
Figure check
Check a figure, or an exported PDF/PNG/TIFF, against a journal preset.
For a live figure the checks are: printed size, resolution, smallest font size,
fonts in use, colour-blind safety of categorical colours, colour maps, and, when a
tick limit is set with set_style(max_ticks=...), the number of tick labels per
axis. For
files: size and resolution (raster files), size and embedded fonts (PDF).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fig_or_path
|
Figure or path
|
A figure, or a |
required |
journal
|
str
|
Journal preset; defaults to the one used to create the figure, or the one set by
:func: |
None
|
dpi
|
float
|
Export resolution to check for a live figure; defaults to the resolution
:func: |
None
|
art
|
('combination', 'line', 'halftone')
|
Artwork type, which sets the resolution the journal requires: |
"combination"
|
Returns:
| Type | Description |
|---|---|
CheckReport
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unsupported file type. |
FileNotFoundError
|
If the file does not exist. |
Notes
Categorical colours (lines, markers, bars, filled areas) are compared pairwise with CIEDE2000 in normal vision (warning below 10) and under simulated protan, deutan and tritan vision (Machado et al. 2009, full severity; warning below 6). Close pairs are a warning, not a failure: they are safe when series also differ by marker, line style or direct labels. Colour maps are classified first; sequential maps need monotonic lightness, diverging maps monotonic lightness on each side of the centre, cyclic and categorical maps are exempt, and rainbow-type maps (jet, rainbow, hsv, turbo, ...) fail. Overlays drawn in one colour (for example forest on a map) are compared with the colour maps drawn beneath them, as they appear on the page with their transparency, using the same thresholds; greyscale shading is not compared.
Examples:
>>> import eofigs
>>> fig, ax = eofigs.new_figure("single", journal="elsevier")
>>> _ = ax.plot([0, 1], [0, 1])
>>> eofigs.check_figure(fig, journal="elsevier").passed
True