Species distributions¶
Preparing occurrence records and environmental predictors for species
distribution models, fitting the models and projecting them: the ls.sdm
module. Every step reproduces the R package the field relies on for it, and
each function's notes name that package.
import landschaft as ls
occ = ls.load_table(ls.sdm.example_data("bradypus"))
env = ls.load_raster(ls.sdm.example_data("bioclim"))
data = ls.sdm.prepare(occ, env) # clean, one per cell, predictors, background, spatial folds
res = ls.sdm.fit(data, models) # cross-validated scikit-learn models (or ls.sdm.MaxNet)
suit = ls.sdm.predict(res, env) # ensemble and its uncertainty
The whole preparation¶
prepare
¶
prepare(
occ,
env,
*,
species: str | None = None,
clean=True,
one_per_cell: bool = True,
thin=None,
area=None,
predictors=10.0,
background=10000,
folds=5,
weights: str | None = "balanced",
seed: int = 1,
x: str | None = None,
y: str | None = None,
crs=None,
)
Prepare occurrence records and predictors for a species distribution model, in one call.
The steps, in order, each one a function of its own that can be called
alone (and each argument here takes False to skip the step, a number
for its main setting, a dict of that function's arguments, or a
function of yours):
clean–clean_occurrences()(drop records at centroids, capitals, institutions, in the sea, outliers...);one_per_cell–one_per_cell();thin–thin()(a distance in metres; off by default);area– the accessible area:accessible_area()(a buffer distance in metres, its arguments, or your polygons; default the whole extent ofenv);predictors–select_predictors()in the area (a VIF threshold, default 10);background–background()points in the area (a number, default 10 000);dataset()– predictor values at presences and background;folds– cross-validation folds: a number k of spatial blocks (spatial_blocks(), default 5) or a dict withmethodone of"blocks","clusters","quadrants","checkerboard","random","jackknife"and the method's arguments; blocks takesize="auto"for the predictors' range of autocorrelation (autocorrelation_range()).
Every random step uses seed, so the same call gives the same data.
data.report counts what each step removed, data.settings holds
every choice (for the methods of a paper) and data.redo(...) repeats
the preparation with some settings changed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Occurrence records: GeoDataFrame of points or a table with
coordinate columns (e.g. |
required | |
env
|
Predictors: a raster stack (band, y, x), e.g. |
required | |
species
|
str | None
|
Keep only this species (the records' |
None
|
clean
|
The steps (see above). |
True
|
|
one_per_cell
|
The steps (see above). |
True
|
|
thin
|
The steps (see above). |
True
|
|
area
|
The steps (see above). |
True
|
|
predictors
|
The steps (see above). |
True
|
|
background
|
The steps (see above). |
True
|
|
folds
|
The steps (see above). |
True
|
|
weights
|
str | None
|
Sample weights of |
'balanced'
|
seed
|
int
|
Seed of every random step. |
1
|
x
|
str | None
|
Coordinate columns and CRS of a table of records. |
None
|
y
|
str | None
|
Coordinate columns and CRS of a table of records. |
None
|
crs
|
str | None
|
Coordinate columns and CRS of a table of records. |
None
|
Returns:
| Type | Description |
|---|---|
SDMData
|
|
Examples:
>>> import landschaft as ls
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> data = ls.sdm.prepare(occ, env, clean=False, background=2000, area=1_000_000)
>>> data.report[["step", "presences", "removed"]]
step presences removed
0 input 116 0
1 one per cell 94 22
2 area 94 0
3 predictors 94 0
4 background 94 0
5 predictor values 94 0
6 folds 94 0
>>> data.predictors
['bio5', 'bio7', 'bio16', 'bio17']
>>> again = data.redo(predictors=False) # the same, keeping every predictor
>>> len(again.predictors)
9
Occurrence records¶
download_gbif
¶
download_gbif(
species,
*,
country=None,
area=None,
year=None,
basis_of_record=None,
max_uncertainty: float | None = None,
limit: int | None = None,
has_geospatial_issue: bool = False,
n_workers: int = 4,
) -> DataFrame
Occurrence records of a species from GBIF, with coordinates.
Uses GBIF's occurrence search API: no account is needed, up to 100 000
records per query (narrow it with country, area or year). For
larger queries, and for the DOI a publication must cite, request a
download on gbif.org and read it with read_occurrences().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str or int
|
Scientific name (matched to GBIF's backbone taxonomy; a synonym gives the records of the accepted name) or a GBIF taxon key. |
required |
country
|
str or list of str
|
ISO 3166-1 alpha-2 country codes ( |
None
|
area
|
GeoDataFrame or geometry
|
Only the records in its bounding box (cut to the exact shape with
|
None
|
year
|
(int, str or tuple)
|
A year, or a range |
None
|
basis_of_record
|
str or list of str
|
E.g. |
None
|
max_uncertainty
|
float
|
Drop the records whose coordinate uncertainty is above this (metres); records without one are kept. |
None
|
limit
|
int
|
At most this many records (default: all, up to 100 000). |
None
|
has_geospatial_issue
|
bool
|
False (default): leave out the records GBIF flags with geospatial issues (zero coordinates, country mismatch...). |
False
|
n_workers
|
int
|
Pages requested at the same time. |
4
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per record: |
Examples:
clean_occurrences
¶
clean_occurrences(
occ,
*,
species: str | None = None,
equal=True,
zeros=0.5,
capitals=10000,
centroids=1000,
seas=True,
urban=False,
countries=False,
outliers=True,
gbif=1000,
institutions=100,
duplicates=False,
aohi=False,
action: str = "drop",
x: str | None = None,
y: str | None = None,
crs=None,
)
Flag or drop occurrence records with suspicious coordinates.
The tests of CoordinateCleaner (Zizka et al. 2019, Methods in Ecology
and Evolution 10: 744-751), the standard for GBIF data: records at
country or province centroids and capitals (coordinates filled in from
the name of a place), at biodiversity institutions (the address of a
museum or herbarium) or GBIF's headquarters, at 0/0 or with equal
longitude and latitude, in the sea, in towns, outside the country they
claim, far from the other records of their species, or duplicated.
Records without valid coordinates are always flagged (invalid).
Each test takes False (skip it), True (its defaults), a number
(its main option: a radius) or a dict of its options:
============== ========================================================================
test options (defaults)
============== ========================================================================
equal (none)
zeros buffer 0.5 (degrees around 0/0)
capitals buffer 10 000 m, geod True, ref None, verify False
centroids buffer 1 000 m, detail "both" / "country" / "provinces",
geod True, ref None, verify False
seas scale 50 (Natural Earth land, 110 / 50 / 10), ref None
urban ref None (Natural Earth urban areas)
countries column (the records' country codes, required), ref None,
ref_col None (iso_a2_eh for two-letter codes, else iso_a3_eh)
outliers method "quantile" / "mad" / "distance", mltpl 5, tdi 1000 km,
size 7 (species with more records are tested), min_occs 7
(distinct locations), thinning False, thinning_res 0.5
gbif buffer 1 000 m, geod True, verify False
institutions buffer 100 m, geod False, ref None, verify False,
verify_mltpl 10
duplicates additions None (more columns that must also be equal)
aohi buffer 10 000 m, geod True, taxa (Aves, Insecta, Mammalia, Plantae)
============== ========================================================================
ref replaces the reference data (a table or GeoDataFrame of points, or
of polygons for seas / urban / countries); passing it in place of the
options also works (seas=my_land). The reference data are downloaded
on first use and cached (see reference_data()).
The results are CoordinateCleaner's (clean_coordinates with the same
options), except where it errs: the zeros radius is in degrees as
documented (terra >= 1.8 made it 0.5 m), centroids "provinces"
selects the provinces, circles that cross the antimeridian or hold a pole
are drawn correctly, a record on a border between two countries is not
misaligned, the country codes are matched against iso_a3_eh /
iso_a2_eh (with iso_a3 France and Norway are "-99" and all their
records failed), species too small for the outlier test are not flagged,
the outlier test also runs on species with 10 000 records or more, and
institutions' verify works. Invalid coordinates are flagged instead of
stopping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Occurrence records: GeoDataFrame of points, or a table with
coordinate columns ( |
required | |
species
|
str | None
|
Column with the species, for the outlier and duplicate tests and
|
None
|
equal
|
The tests and their options (see above). |
True
|
|
zeros
|
The tests and their options (see above). |
True
|
|
capitals
|
The tests and their options (see above). |
True
|
|
centroids
|
The tests and their options (see above). |
True
|
|
seas
|
The tests and their options (see above). |
True
|
|
urban
|
The tests and their options (see above). |
True
|
|
countries
|
The tests and their options (see above). |
True
|
|
outliers
|
The tests and their options (see above). |
True
|
|
gbif
|
The tests and their options (see above). |
True
|
|
institutions
|
The tests and their options (see above). |
True
|
|
duplicates
|
The tests and their options (see above). |
True
|
|
aohi
|
The tests and their options (see above). |
True
|
|
action
|
('drop', 'flag')
|
|
"drop"
|
x
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
y
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
crs
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame or DataFrame
|
Same type and index as |
Examples:
reference_data
cached
¶
A geographic reference data set for cleaning occurrence records.
Downloaded once (about 5 MB) from the CoordinateCleaner archive on CRAN
and cached (see cache_dir()).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
('countryref', 'institutions', 'aohi', 'buffland', 'buffsea', 'land110', 'land50', 'land10', 'urban50', 'countries50')
|
|
"countryref"
|
refresh
|
bool
|
Rebuild the cached copy from the archive. |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame or GeoDataFrame
|
|
Examples:
one_per_cell
¶
one_per_cell(
occ,
raster,
*,
x: str | None = None,
y: str | None = None,
drop_missing: bool = False,
)
Keep one occurrence record per cell of the predictor grid.
Several records in the same cell carry the same environmental values:
kept, they weigh that cell more than the others (pseudo-replication) and
put the same values on both sides of a validation split. The first record
of each cell is kept, in the order of the table, as
occ[!duplicated(terra::cellFromXY(r, xy)), ] in R. Records without
coordinates or outside the grid are dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Occurrence records: a GeoDataFrame of points, or a table with
coordinate columns ( |
required | |
raster
|
The predictors (or any raster on their grid): a DataArray, one band or a stack. The records are reprojected to its CRS to find their cells. |
required | |
x
|
str | None
|
Names of the coordinate columns of a table (default: found by name). |
None
|
y
|
str | None
|
Names of the coordinate columns of a table (default: found by name). |
None
|
drop_missing
|
bool
|
Also drop the records whose cell has no value (NoData) in some band. |
False
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame or DataFrame
|
The records kept, with their original index. |
Examples:
thin
¶
thin(
occ,
distance: float,
*,
reps: int = 10,
seed: int | None = None,
x: str | None = None,
y: str | None = None,
crs=None,
return_all: bool = False,
)
Thin occurrence records so that no two kept records are closer than distance.
Records cluster where people sample (near roads, towns, universities),
and a model fitted to them learns that bias along with the species'
preferences. Thinning removes records until the remaining ones are at
least distance apart while keeping as many as possible: the
randomised heuristic of spThin (Aiello-Lammens et al. 2015, Ecography
38: 541-545), which repeatedly drops a record with the most neighbours
(a random one among ties) and keeps the best of reps repetitions.
The same records as spThin::thin for the same seed; distances on a
sphere of radius 6378.388 km, as spThin's fields::rdist.earth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Occurrence records: GeoDataFrame of points, or a table with coordinate columns. |
required | |
distance
|
float
|
Minimum distance between kept records, in metres. |
required |
reps
|
int
|
Repetitions of the heuristic; the one keeping the most records is returned (the first among equals). More repetitions keep more records, rarely many more beyond 10-100. |
10
|
seed
|
int | None
|
Seed of R's generator ( |
None
|
x
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
y
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
crs
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
return_all
|
bool
|
Return the records kept by every repetition (a list, largest first) instead of the best one. |
False
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame or DataFrame
|
The records kept, with their original index; |
Examples:
Study area¶
accessible_area
¶
accessible_area(
occ,
*,
method: str = "buffer",
distance: float | None = None,
regions=None,
within=None,
resolution: int = 64,
x: str | None = None,
y: str | None = None,
crs=None,
)
The area the species could have reached: where its background is drawn.
A model learns to tell the conditions where the species was found from those available to it. Drawing the background from areas the species never had access to (beyond a mountain range, on another continent) makes the model learn geography instead of ecology and inflates its scores (Barve et al. 2011, Ecological Modelling 222: 1810-1819). The accessible area bounds the background sampling, the predictor selection and the evaluation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Occurrence records: GeoDataFrame of points, or a table with coordinate columns. |
required | |
method
|
('buffer', 'convex_hull', 'regions')
|
|
"buffer"
|
distance
|
float | None
|
Radius in metres. Required for |
None
|
regions
|
GeoDataFrame of polygons, for |
None
|
|
within
|
Polygons the result is cut to (a country, a continent, the land). |
None
|
|
resolution
|
int
|
Vertices of each circle ( |
64
|
x
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
y
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
crs
|
str | None
|
Coordinate columns and CRS of a table (default: found by name; longitude / latitude in EPSG:4326). |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
One (multi)polygon in longitude / latitude (EPSG:4326), with the method and distance as columns. |
Examples:
Predictors¶
select_predictors
¶
select_predictors(
env,
*,
vif: float | None = 10.0,
cor: float | None = None,
keep=None,
area=None,
method: str = "pearson",
size: int | None = _SAMPLE_SIZE,
seed: int | None = None,
)
Drop predictors that the others already explain (collinearity).
Climate layers are strongly correlated (the 19 bioclimatic variables carry far fewer than 19 independent signals); collinear predictors make a model's coefficients unstable and its projections to new conditions unreliable (Dormann et al. 2013, Ecography 36: 27-46). Two filters of the R package usdm, with the same results:
cor(usdm'svifcor): while the most correlated pair has|r| >= cor, drop the one of the two with the larger VIF;vif(usdm'svifstep): while the largest VIF is>= vif, drop that variable.
With both, the correlation filter runs first. Variables in keep are
never dropped. Where usdm errs, the correct form: the loops stop when
one variable (or only kept ones) remains instead of failing, names with
spaces work, rasters of 7000-10 000 cells are sampled at random (terra
took their first cells) and tables are sampled from 7000 rows on in both
filters (usdm's vifstep already from 6000).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Raster stack (band, y, x) or a table with one column per predictor. |
required | |
vif
|
float | None
|
VIF threshold (usdm's default 10; 5 is stricter); None: no VIF filter. |
10.0
|
cor
|
float | None
|
Correlation threshold (e.g. 0.7); None: no correlation filter. |
None
|
keep
|
Names of predictors to keep whatever their collinearity. |
None
|
|
area
|
Polygons the raster is cut to first: collinearity in the accessible area, not in the whole raster. |
None
|
|
method
|
('pearson', 'spearman', 'kendall')
|
Correlation coefficient of the |
"pearson"
|
size
|
int | None
|
Cells sampled from rasters of 7000 cells or more (usdm's 5000; None: all cells). |
_SAMPLE_SIZE
|
seed
|
int | None
|
Seed of R's generator for the sample (default random). |
None
|
Returns:
| Type | Description |
|---|---|
DataArray or DataFrame
|
The predictors kept (the same type as |
Examples:
vif
¶
Variance inflation factor of each predictor: how well the others predict it.
VIF = 1 / (1 - R²) of each variable regressed on all the others: 1 for an
independent variable, above 5-10 for one the others nearly determine
(its coefficient in a linear model becomes unstable). The same values as
the R package usdm's vif() (R's lm and summary.lm, to the last
bit), on the same cells: a raster's cells with a value in every band
(5000 of them, drawn as terra's spatSample with set.seed(seed),
when it has 7000 or more; weighted by the cosine of the latitude for
longitude / latitude grids), or the complete rows of a table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Raster stack (band, y, x) or a table with one column per predictor. |
required | |
area
|
Polygons the raster is cut to first (the accessible area). |
None
|
|
size
|
int | None
|
Cells sampled from rasters of 7000 cells or more (None: all). |
_SAMPLE_SIZE
|
seed
|
int | None
|
Seed of R's generator for the sample (default random). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
Background and data¶
background
¶
background(
env,
n: int = 10000,
*,
occ=None,
area=None,
bias=None,
distance=None,
envelope=None,
exclude_occurrences: bool = True,
tryf: float = 3,
lonlat_correction: bool = True,
seed: int | None = None,
x: str | None = None,
y: str | None = None,
crs=None,
)
Background points: a sample of the conditions available to the species.
Presence-only models (MaxEnt, presence-background regressions, machine
learning) contrast the presences with a sample of the cells the species
could have been found in. The sample is drawn as dismo::randomPoints
draws it, with the same cells for the same seed: cells with a value in
every predictor, without replacement, on longitude / latitude grids
weighted by the cells' east-west width (so that each square kilometre
counts the same), leaving out the cells of the occurrences. Where to draw
from is the choice that matters most (Barve et al. 2011; VanDerWal et al.
2009): restrict it to the accessible area, to a distance band around the
records, or weight it by sampling effort.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Predictors (raster stack or one band): the grid and the cells with data. |
required | |
n
|
int
|
Number of points (fewer, with a warning, when the area has fewer cells). |
10000
|
occ
|
Occurrence records: their cells are left out ( |
None
|
|
area
|
Polygons to draw from (the accessible area, see
|
None
|
|
bias
|
A raster of sampling weights on the grid of |
None
|
|
distance
|
Metres: only cells within this distance of an occurrence, or a pair
|
None
|
|
envelope
|
Leave out the cells inside the occurrences' environmental envelope
(every predictor between its |
None
|
|
exclude_occurrences
|
bool
|
Leave out the cells holding occurrences (dismo's |
True
|
tryf
|
float
|
Candidates drawn per point before excluding the occurrence cells
(dismo's |
3
|
lonlat_correction
|
bool
|
Weight longitude / latitude cells by their area (dismo's
|
True
|
seed
|
int | None
|
Seed of R's generator (default random; recorded in |
None
|
x
|
str | None
|
Coordinate columns and CRS of a table of occurrences. |
None
|
y
|
str | None
|
Coordinate columns and CRS of a table of occurrences. |
None
|
crs
|
str | None
|
Coordinate columns and CRS of a table of occurrences. |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
Points at the centres of the cells drawn (CRS of |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> m = ls.sdm.accessible_area(occ, distance=500_000)
>>> bg = ls.sdm.background(env, 1000, occ=occ, area=m, seed=1)
>>> len(bg), bg.crs.to_epsg()
(1000, 4326)
dataset
¶
dataset(
occ,
background,
env,
*,
weights: str | None = "balanced",
folds=None,
categorical=None,
x: str | None = None,
y: str | None = None,
crs=None,
) -> SDMData
Put presences, background points and predictor values together.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
occ
|
Presence records (GeoDataFrame of points, or a table with coordinate
columns). A table with a column |
required | |
background
|
Background points or absences (e.g. |
required | |
env
|
Predictors (raster stack): their values at the points become |
required | |
weights
|
('balanced', 'equal', None)
|
|
"balanced"
|
folds
|
Cross-validation folds, one per row (or set them later with
|
None
|
|
categorical
|
Names of categorical predictors (codes, not quantities). |
None
|
|
x
|
str | None
|
Coordinate columns and CRS of tables. |
None
|
y
|
str | None
|
Coordinate columns and CRS of tables. |
None
|
crs
|
str | None
|
Coordinate columns and CRS of tables. |
None
|
Returns:
| Type | Description |
|---|---|
SDMData
|
Rows without a value in some predictor are dropped (counted in
|
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> bg = ls.sdm.background(env, 2000, occ=occ, seed=1)
>>> data = ls.sdm.dataset(occ, bg, env)
>>> data
SDMData(116 presences, 2000 background, 9 predictors: bio1, bio5, bio6, bio7, bio8, bio9, bio12, bio16, bio17; folds: none)
SDMData
dataclass
¶
SDMData(
X: DataFrame,
y: ndarray,
coords: object,
sample_weight: ndarray | None = None,
folds: ndarray | None = None,
predictors: list = list(),
categorical: list = list(),
area: object = None,
report: DataFrame = DataFrame(),
settings: dict = dict(),
_prepare: dict | None = None,
)
Bases: Saveable
Presences and background (or absences) with their predictor values, ready for a model.
Every scikit-learn estimator takes it as it is::
model.fit(data.X, data.y, sample_weight=data.sample_weight)
GridSearchCV(model, grid, cv=data.cv).fit(data.X, data.y)
Attributes:
| Name | Type | Description |
|---|---|---|
X |
DataFrame
|
One row per record, one column per predictor. |
y |
ndarray
|
1 for a presence, 0 for a background point or absence. |
coords |
GeoDataFrame
|
The points (CRS of the predictors), with |
sample_weight |
ndarray or None
|
Weight of each row (see |
folds |
ndarray or None
|
Cross-validation fold of each row (1..k; 0 = in every training set). |
predictors |
list of str
|
The predictor names (columns of |
categorical |
list of str
|
The categorical predictors. |
area |
GeoDataFrame or None
|
The accessible area the background was drawn from. |
report |
DataFrame
|
One row per preparation step: records before and after, and why. |
settings |
dict
|
Every choice made (thresholds, distances, seeds): enough to repeat the preparation or to write the methods of a paper. |
cv
property
¶
The folds as scikit-learn takes them: a list of (train rows, test rows).
Rows with fold 0 (e.g. background points in a leave-one-out design) are always in training.
redo
¶
redo(**changes) -> SDMData
Prepare again from the same inputs, with some steps changed.
Only for data made by prepare(). data.redo(thin=40_000)
repeats every step with the same settings (and seeds) except the
thinning distance; any argument of prepare() can be changed.
table
¶
Everything as one table: coordinates, y, weight, fold and the predictors.
Cross-validation folds¶
spatial_blocks
¶
spatial_blocks(
data,
*,
size: float | None = None,
rows_cols=(10, 10),
k: int = 5,
hexagon: bool = True,
flat_top: bool = False,
selection: str = "random",
iteration: int = 100,
column="y",
offset=(0.0, 0.0),
extend: float = 0.0,
deg_to_metre: float = 111325.0,
seed: int | None = None,
)
Folds made of spatial blocks: no fold is a neighbour of the records it tests.
Random cross-validation puts test records next to training records,
where spatial autocorrelation makes them nearly copies: the scores
overestimate how well the model predicts elsewhere (Roberts et al. 2017,
Ecography 40: 913-929). Blocks larger than the range of
autocorrelation are assigned to folds whole. The blocks and folds of
blockCV::cv_spatial (Valavi et al. 2019), the same for the same seed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
|
required | |
size
|
float | None
|
Block width in metres (on longitude / latitude data, divided by
|
None
|
rows_cols
|
(rows, columns) of blocks when |
(10, 10)
|
|
k
|
int
|
Number of folds. |
5
|
hexagon
|
bool
|
Hexagons (blockCV's default) or squares. |
True
|
flat_top
|
bool
|
Hexagons with a flat top instead of a pointy one. |
False
|
selection
|
('random', 'systematic', 'checkerboard')
|
How blocks go to folds: at random (the best of |
"random"
|
iteration
|
int
|
Random tries. |
100
|
column
|
Classes kept balanced across folds in the random selection: |
'y'
|
|
offset
|
Shift of the blocks, as fractions of |
(0.0, 0.0)
|
|
extend
|
float
|
Widen the extent of the blocks by this percentage (up to 5). |
0.0
|
deg_to_metre
|
float
|
Metres per degree, to turn |
111325.0
|
seed
|
int | None
|
Seed of R's generator (default random). |
None
|
Returns:
| Type | Description |
|---|---|
SDMData or ndarray
|
The data with |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 1000, occ=occ, seed=1), env)
>>> data = ls.sdm.spatial_blocks(data, size=500_000, k=5, seed=1)
>>> [len(test) for _, test in data.cv]
[235, 229, 214, 213, 225]
autocorrelation_range
¶
autocorrelation_range(
env, *, n: int = 5000, seed: int | None = None, deg_to_metre: float = 111325.0
)
The range of spatial autocorrelation of each predictor (and their median), in metres.
A variogram is fitted to a random sample of each band's cells (the
models tried by automap: spherical, exponential, Gaussian and Matern
with Stein's parameterisation over a range of smoothness, the one with
the smallest weighted squared error kept); its range parameter is how
far values stay alike. Spatial blocks for cross-validation should be
about as wide as the median range (Roberts et al. 2017; Valavi et al.
2019). As blockCV::cv_spatial_autocor: the same sample (R's
generator, set.seed(seed)) and variogram classes, and gstat's
Gauss-Newton fit step by step: the ranges agree to about 1e-6 (the
Matern model's derivative is numerical). Climate layers on a continent
often vary so smoothly that the fitted range exceeds the study area:
use the result as a guide, not blindly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Predictors (raster stack). |
required | |
n
|
int
|
Cells sampled per band. |
5000
|
seed
|
int | None
|
Seed of R's generator (default random). |
None
|
deg_to_metre
|
float
|
Metres per degree, for the block size on longitude / latitude grids. |
111325.0
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
spatial_clusters
¶
spatial_clusters(
data,
k: int = 5,
*,
env=None,
scale: bool = True,
raster_cluster: bool = False,
num_sample: int = 10000,
algorithm: str = "Hartigan-Wong",
seed: int | None = None,
)
Folds from k-means clusters of the records' coordinates (or of their environment).
blockCV::cv_cluster: k-means (R's kmeans, Hartigan-Wong, 25
random starts, up to 500 iterations, the same clusters for the same
seed) groups the records into k spatial clusters, each a fold. With
env, the clusters are made in environmental space instead
(predictors standardised as terra::scale; with raster_cluster,
also on num_sample random cells, so that folds cover environmental
gradients): the test folds then hold conditions the training data lack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
|
required | |
k
|
int
|
Number of folds. |
5
|
env
|
Predictors to cluster in environmental space (default: coordinates). |
None
|
|
scale
|
bool
|
Standardise the predictors. |
True
|
raster_cluster
|
bool
|
Cluster the records together with a sample of the raster's cells. |
False
|
num_sample
|
int
|
Cells sampled for |
10000
|
algorithm
|
('Hartigan-Wong', 'Lloyd', 'Forgy', 'MacQueen')
|
R's k-means algorithm. |
"Hartigan-Wong"
|
seed
|
int | None
|
Seed of R's generator (default random). |
None
|
Returns:
| Type | Description |
|---|---|
SDMData or ndarray
|
The data with |
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 500, occ=occ, seed=1), env)
>>> np.bincount(ls.sdm.spatial_clusters(data, 4, seed=2).folds).tolist()
[0, 201, 127, 197, 91]
buffer_folds
¶
buffer_folds(
data, size: float, *, presence_background: bool = True, add_background: bool = False
)
Leave-one-out folds with a buffer: each presence is a test, nothing within size trains.
blockCV::cv_buffer: for each target record, the training set is
every record farther than size (metres; Euclidean in projected
coordinates, great-circle distance on longitude / latitude) and the test
set is the record itself (and, with add_background, the background
within the buffer). With presence_background the targets are the
presences, else every record.
Returns:
| Type | Description |
|---|---|
list of (train, test) index arrays
|
One fold per target; pass it to scikit-learn as |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 300, occ=occ, seed=1), env)
>>> cv = ls.sdm.buffer_folds(data, 200_000)
>>> len(cv), len(cv[0][1])
(116, 1)
quadrant_folds
¶
Four folds from the occurrences split at their median latitude and longitude (ENMeval "block").
ENMeval::get.block: the occurrences are split into two halves along
the first axis of orientation, each half into two along the second;
the background goes to the group whose quadrant it lies in. The same
groups as ENMeval.
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 500, occ=occ, seed=1), env)
>>> import numpy as np
>>> np.bincount(ls.sdm.quadrant_folds(data).folds[data.y == 1])[1:].tolist()
[29, 29, 29, 29]
checkerboard_folds
¶
Folds from a checkerboard of aggregated cells (ENMeval "checkerboard").
ENMeval::get.checkerboard: the predictor grid aggregated by
aggregation cells becomes a checkerboard; records on white squares
form fold 1, on black ones fold 2. With two factors (hierarchical), the
squares are aggregated again and the four combinations are folds 1-4.
Presences and background follow the same grid.
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 500, occ=occ, seed=1), env)
>>> sorted(set(ls.sdm.checkerboard_folds(data, env, aggregation=(4, 2)).folds.tolist()))
[1, 2, 3, 4]
random_folds
¶
Random k-fold of the presences, background always training (ENMeval "randomkfold").
predicts::folds: groups of (nearly) equal size, assigned in the
order of runif() draws, the same as R for the same seed. Not spatial:
use it as a non-spatial baseline.
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 300, occ=occ, seed=1), env)
>>> import numpy as np
>>> np.bincount(ls.sdm.random_folds(data, 4, seed=3).folds[data.y == 1]).tolist()
[0, 29, 29, 29, 29]
jackknife_folds
¶
Leave-one-out: each presence its own fold, background always training (ENMeval "jackknife").
For very few records (fewer than about 25, Shcheglovitova & Anderson 2013).
Examples:
Models¶
MaxNet
¶
MaxNet(
classes: str = "default",
regmult: float = 1.0,
add_samples_to_background: bool = True,
type: str = "cloglog",
clamp: bool = True,
categorical=None,
threshold: float = 0.5,
n_threads: int | None = None,
)
Bases: _Classifier, BaseEstimator
Maxent fitted as R's maxnet does it, natively: the same model, faster.
Maxent (Phillips et al. 2006) as an infinitely weighted logistic
regression (Phillips et al. 2017): presences against background points
with the features of maxnet (linear l, quadratic q, product
p, hinge h, threshold t, categorical), maxnet's default
regularisation and glmnet's lasso path, keeping its last lambda. Every
step is a C++ port of R's maxnet 0.1.4 and glmnet 4.1-10 that gives R's
results bit for bit on x86-64 (coefficients, alpha, entropy, the four
output types with and without clamping), many times faster (hashing of
the duplicate rows, the gradient checks screened by a bound and run in
parallel, fused residual updates).
A scikit-learn classifier (fit, predict_proba, predict,
decision_function, get_params / set_params, cloning and
pickling), usable in GridSearchCV and pipelines; scikit-learn itself
is not needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classes
|
str
|
Feature classes: |
'default'
|
regmult
|
float
|
Regularisation multiplier (larger: smoother, fewer features). |
1.0
|
add_samples_to_background
|
bool
|
Add the presences to the background, unless the same values already
are there (maxnet's |
True
|
type
|
('cloglog', 'logistic', 'exponential', 'link')
|
Output of |
"cloglog"
|
clamp
|
bool
|
Clamp predictors and features to their training ranges when predicting. |
True
|
categorical
|
list of str or int
|
Columns that are categorical (besides those of pandas |
None
|
threshold
|
float
|
Suitability at or above which |
0.5
|
n_threads
|
int
|
Threads (None or 0: all cores but one). The result does not depend on it. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
betas_ |
dict
|
Feature name (maxnet's column names, e.g. |
alpha_ |
float
|
Normalising constant (the exponential output sums to 1 over the background). |
entropy_ |
float
|
Entropy of the exponential output over the background. |
lambda_ |
float
|
The lambda of the model (the last of glmnet's path). |
lambdas_ |
ndarray
|
The 200 lambdas of the path. |
penalty_factor_ |
ndarray
|
Regularisation of every feature ( |
feature_names_ |
list of str
|
Every feature (column of maxnet's model matrix). |
feature_classes_ |
str
|
The feature classes used ( |
feature_names_in_ |
ndarray
|
The predictor names (when fitted on a DataFrame). |
n_features_in_ |
int
|
|
classes_ |
ndarray
|
|
n_passes_ |
int
|
Coordinate-descent passes of the path (glmnet's |
var_min_, var_max_ |
dict
|
Range of each continuous predictor (clamping). |
sample_means_ |
dict
|
Mean of each continuous predictor and most frequent level of each
categorical one over the presences (maxnet's |
levels_ |
dict
|
Levels of each categorical predictor. |
Notes
sample_weight is accepted for compatibility and ignored: maxnet
weighs presences 1 and background points 100. Rows with a missing value
are not allowed in fit; predict_* gives NaN for them (R drops
them). Duplicate presences among the background are found by exact
comparison of the values (R compares formatted text when there are
categorical predictors, which can miss some).
References
Phillips, S. J., Anderson, R. P., Dudík, M., Schapire, R. E. & Blair, M. E. (2017). Opening the black box: an open-source release of Maxent. Ecography 40, 887-893.
Friedman, J., Hastie, T. & Tibshirani, R. (2010). Regularization paths for generalized linear models via coordinate descent. Journal of Statistical Software 33, 1-22.
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> bg = ls.sdm.background(env, 5000, occ=occ, seed=1)
>>> data = ls.sdm.dataset(occ, bg, env)
>>> model = ls.sdm.MaxNet().fit(data.X, data.y)
>>> model.feature_classes_, len(model.betas_)
('lqph', 29)
>>> suitability = model.predict_proba(data.X)[:, 1] # cloglog, in [0, 1]
>>> bool(suitability[data.y == 1].mean() > suitability[data.y == 0].mean())
True
fit
¶
Fit to presences (y 1) and background points (y 0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame or array - like(n, p)
|
Predictors; an |
required |
y
|
array - like(n)
|
1 for a presence, 0 for a background point. |
None
|
sample_weight
|
ignored
|
maxnet's weights are fixed (presences 1, background 100). |
None
|
Returns:
| Type | Description |
|---|---|
MaxNet
|
self |
predict_output
¶
maxnet's predict(): the output of one type for every row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame or array - like(n, p)
|
Predictors (the columns of |
required |
type
|
('cloglog', 'logistic', 'exponential', 'link')
|
Default: the estimator's |
"cloglog"
|
clamp
|
bool
|
Default: the estimator's |
None
|
Returns:
| Type | Description |
|---|---|
ndarray(n)
|
NaN where a predictor the model uses is missing. |
predict_proba
¶
Suitability as a probability: column 1 the type output (cloglog or logistic), column 0 its complement.
Returns:
| Type | Description |
|---|---|
ndarray(n, 2)
|
|
predict
¶
1 where the type output reaches threshold, else 0 (NaN rows: 0).
Returns:
| Type | Description |
|---|---|
numpy.ndarray (n,) of int
|
|
decision_function
¶
The link (linear predictor plus alpha), clamped as clamp says.
Returns:
| Type | Description |
|---|---|
ndarray(n)
|
|
Evaluation¶
evaluate
¶
evaluate(presences, background, *, thresholds=None) -> Evaluation
Evaluate predictions at presences against background points (or absences).
The statistics of predicts::pa_evaluate / dismo::evaluate, the
same values: AUC (Mann-Whitney, ties counted one half), the
point-biserial correlation and, for every candidate threshold (the
prediction values minus 0.0001), the confusion matrix with kappa,
sensitivity, specificity, TSS (sensitivity + specificity - 1, Allouche
et al. 2006) and the rest; plus the continuous Boyce index (see
boyce()) and the usual thresholds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
presences
|
Predictions at the presences and at the background points (or absences); missing values are dropped. |
required | |
background
|
Predictions at the presences and at the background points (or absences); missing values are dropped. |
required | |
thresholds
|
Candidate thresholds (default: from the predictions, as predicts). |
None
|
Returns:
| Type | Description |
|---|---|
Evaluation
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> p = np.array([0.9, 0.8, 0.8, 0.7, 0.6, 0.55, 0.3])
>>> a = np.array([0.8, 0.5, 0.4, 0.4, 0.3, 0.2, 0.1, 0.05])
>>> e = ls.sdm.evaluate(p, a)
>>> round(e.stats["auc"], 4), round(e.stats["max_tss"], 4)
(0.8482, 0.7321)
>>> e.thresholds["max_spec_sens"]
0.5499
Evaluation
dataclass
¶
Bases: Saveable
How well predictions separate presences from background (or absences).
Attributes:
| Name | Type | Description |
|---|---|---|
stats |
dict
|
|
table |
DataFrame
|
One row per candidate threshold: the confusion matrix ( |
thresholds |
dict
|
|
boyce
¶
boyce(
presences,
available,
*,
nclass: int = 0,
window: float | None = None,
res: int = 100,
method: str = "spearman",
rm_duplicate: bool = True,
) -> dict
Continuous Boyce index: does predicted suitability rise with the frequency of presences?
For moving windows of suitability, the ratio of the share of presences
to the share of available cells (background) predicted in the window;
the index is the rank correlation of that ratio with suitability: 1 for
a model whose higher predictions always hold relatively more presences,
0 for a random one. The standard metric for presence-only data (Boyce et
al. 2002; Hirzel et al. 2006). ecospat.boyce, the same windows, ratios
and correlation, except for its "+1 trick": ecospat shifts its last window
by one unit of suitability, which removes that window for predictions on
0-1 and keeps it on 0-1000, so the index depends on the scale of the
predictions; here the last window is [max - width, max] on any scale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
presences
|
Predictions at the presences (test records). |
required | |
available
|
Predictions at the background points (or every cell of the area). |
required | |
nclass
|
int
|
0: moving windows (default); k > 0: k classes of equal width. |
0
|
window
|
float | None
|
Width of the moving window (default: a tenth of the range of
|
None
|
res
|
int
|
Number of window steps (100). |
100
|
method
|
('spearman', 'pearson', 'kendall')
|
Correlation. |
"spearman"
|
rm_duplicate
|
bool
|
Ignore successive equal ratios in the correlation. |
True
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
threshold
¶
threshold(
presences, background, method: str = "max_spec_sens", *, sensitivity: float = 0.9
) -> float
A threshold that turns continuous predictions into presence / absence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
presences
|
Predictions at the presences and the background (or absences). |
required | |
background
|
Predictions at the presences and the background (or absences). |
required | |
method
|
str
|
|
'max_spec_sens'
|
sensitivity
|
float
|
For |
0.9
|
Examples:
omission_rate
¶
Share of test presences predicted below a threshold set on the training presences.
ENMeval's or.mtp (threshold="mtp": below the lowest training
prediction) and or.10p ("p10": below the 10th percentile of the
training predictions). A well-fitted model omits about 0 and 0.1 of the
test presences; much more means overfitting (Radosavljevic & Anderson
2014).
Examples:
Extrapolation¶
mess
¶
Multivariate environmental similarity surface: how far conditions lie outside the reference.
For each variable, the similarity of a cell's value to the distribution
of the reference values (positive inside the reference range, 100 at its
median; negative outside it, in percent of the range); the MESS is the
smallest over the variables, so negative values mark cells where at
least one variable is outside anything the model saw (Elith et al. 2010,
Methods in Ecology and Evolution 1: 330-342). predicts::mess, the
same values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
The conditions the model was fitted to: |
required | |
env
|
The conditions to project to: raster stack (e.g. future climate) or a table, with the reference's variables. |
required | |
full
|
bool
|
Also return the similarity of every variable (bands |
False
|
Returns:
| Type | Description |
|---|---|
DataArray or DataFrame
|
Band |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 2000, occ=occ, seed=1), env)
>>> m = ls.sdm.mess(data, env + 30) # 3 degrees warmer (and 30 mm wetter)
>>> bool((m.sel(band="mess") < 0).sum() > 0)
True
mop
¶
mop(
reference,
env,
*,
type: str = "basic",
distance: str | None = "euclidean",
where: str = "in_range",
percentage: float = 1.0,
scale: bool = False,
center: bool = False,
rescale: bool = False,
)
Mobility-oriented parity: where new conditions are outside or far from the reference ones.
The variables outside the reference range (simple: how many;
basic: 1 where any) and, for each cell, the mean distance to its
nearest reference conditions (the closest percentage percent of
them): large distances mean dissimilar conditions even within the ranges
(Owens et al. 2013, Ecological Modelling 263: 10-18). The R package
mop's mop(), the same values; the Mahalanobis distance uses the
covariance of the reference conditions (mop takes that of the cells
tested, so a cell's distance depended on which other cells were passed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
Conditions the model was fitted to ( |
required | |
env
|
Conditions to test (raster stack or table). |
required | |
type
|
('basic', 'simple', 'detailed')
|
|
"basic"
|
distance
|
('euclidean', 'mahalanobis', None)
|
Distance to the reference (None: none). |
"euclidean"
|
where
|
('in_range', 'out_range', 'all')
|
Cells whose distance is computed. |
"in_range"
|
percentage
|
float
|
Share of the nearest reference conditions averaged, in percent. |
1.0
|
scale
|
bool
|
Standardise the variables (over reference and tested conditions together, as mop) before the Euclidean distance. |
False
|
center
|
bool
|
Standardise the variables (over reference and tested conditions together, as mop) before the Euclidean distance. |
False
|
rescale
|
bool
|
Rescale the distances to 0-1. |
False
|
Returns:
| Type | Description |
|---|---|
DataArray or DataFrame
|
Bands |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 2000, occ=occ, seed=1), env)
>>> out = ls.sdm.mop(data, env + 30)
>>> [str(b) for b in out.band.values]
['mop', 'basic']
exdet
¶
Extrapolation detection: univariate (NT1) and combinatorial (NT2) novelty.
Mesgaran et al. (2014, Diversity and Distributions 20: 1147-1159):
NT1 < 0 where some variable is outside its reference range (more
negative, farther out); inside every range, NT2 is the Mahalanobis
distance to the reference centre relative to the largest one among the
reference points, > 1 for a combination of values the reference never
had. mic names the variable most responsible (1-based; 0 for analogue
conditions). The ExDet of the R package dsmextra, except that its
combinatorial mic pointed at the wrong variable (it numbered the
variables backwards).
Returns:
| Type | Description |
|---|---|
DataArray or DataFrame
|
Bands |
Examples:
>>> import landschaft as ls
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> data = ls.sdm.dataset(occ, ls.sdm.background(env, 2000, occ=occ, seed=1), env)
>>> out = ls.sdm.exdet(data, env.sel(band=["bio1", "bio12", "bio16"]))
>>> [str(b) for b in out.band.values]
['exdet', 'mic']
Models and projections¶
fit
¶
Fit models to SDMData, cross-validate them on its folds, and fit them to all the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
SDMData
|
From |
required |
models
|
A dict of estimators (name -> estimator) with scikit-learn's API
( |
None
|
|
cv
|
bool
|
Cross-validate on |
True
|
on
|
|
None
|
Returns:
| Type | Description |
|---|---|
SDMResult
|
|
Examples:
>>> import landschaft as ls
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> data = ls.sdm.prepare(occ, env, clean=False, background=2000)
>>> glm = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
>>> res = ls.sdm.fit(data, {"glm": glm})
>>> round(float(res.summary.loc["glm", ("auc_test", "mean")]), 2)
0.87
SDMResult
dataclass
¶
SDMResult(
models: dict,
metrics: DataFrame,
summary: DataFrame,
thresholds: dict,
predictors: list,
ranges: DataFrame,
data: SDMData | None = None,
)
Bases: Saveable
Fitted models, their cross-validated scores and what is needed to project them.
Attributes:
| Name | Type | Description |
|---|---|---|
models |
dict
|
Each model fitted to all the data (name -> estimator). |
metrics |
DataFrame
|
One row per model and fold: |
summary |
DataFrame
|
Their mean and standard deviation per model. |
thresholds |
dict
|
Per model, the maximum-TSS threshold of its predictions on all data. |
predictors |
list of str
|
The predictors, in the order the models take them. |
ranges |
DataFrame
|
Minimum and maximum of each predictor in the data (for clamping). |
weights
¶
Ensemble weights: each model's mean test score above chance (AUC - 0.5, TSS, Boyce), 0 if below.
predict
¶
predict(
result: SDMResult,
env,
*,
models=None,
ensemble: str | None = "weighted",
metric: str = "auc_test",
clamp: bool = True,
sd: bool = True,
block_rows: int = 256,
)
Project fitted models onto rasters of predictors (present or future conditions).
Predicted in blocks of rows, so rasters larger than memory (lazy,
dask-backed: ls.load_raster(..., chunks="auto")) work; the result is
the same as predicting every cell at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
SDMResult
|
From |
required |
env
|
Predictors with the bands the models were fitted with. |
required | |
models
|
Names of the models to project (default all). |
None
|
|
ensemble
|
('weighted', 'mean', 'median', None)
|
Combine the models: weighted by their cross-validated |
"weighted"
|
metric
|
str
|
Score weighting the ensemble ( |
'auc_test'
|
clamp
|
bool
|
Clip each predictor to its range in the data first (projections do not extrapolate the response beyond what was observed). |
True
|
sd
|
bool
|
Add the standard deviation across models: where they disagree. |
True
|
block_rows
|
int
|
Rows of cells predicted at a time. |
256
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Bands: |
Examples:
>>> import landschaft as ls
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> data = ls.sdm.prepare(occ, env, clean=False, background=2000)
>>> res = ls.sdm.fit(data, {"glm": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))})
>>> suit = ls.sdm.predict(res, env)
>>> [str(b) for b in suit.band.values], suit.shape
(['glm'], (1, 192, 186))
response_curves
¶
response_curves(
result: SDMResult, *, predictors=None, n: int = 100, fixed: str = "mean"
) -> DataFrame
How each model's suitability changes along one predictor, the others held at their mean (or median).
Returns:
| Type | Description |
|---|---|
DataFrame
|
Long table: |
Examples:
>>> import landschaft as ls
>>> from sklearn.linear_model import LogisticRegression
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> data = ls.sdm.prepare(occ, env, clean=False, background=2000, folds=False)
>>> res = ls.sdm.fit(data, {"glm": LogisticRegression(max_iter=5000)})
>>> curves = ls.sdm.response_curves(res, n=20)
>>> curves.groupby("predictor").size().tolist()
[20, 20, 20, 20, 20]
importance
¶
importance(result: SDMResult, *, n_repeats: int = 5, seed: int = 1) -> DataFrame
Permutation importance: how much AUC drops when a predictor's values are shuffled.
On the data the models were fitted to; n_repeats shuffles per
predictor (the mean and standard deviation of the drop).
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
>>> import landschaft as ls
>>> from sklearn.linear_model import LogisticRegression
>>> occ = ls.load_table(ls.sdm.example_data("bradypus"))
>>> env = ls.load_raster(ls.sdm.example_data("bioclim"))
>>> data = ls.sdm.prepare(occ, env, clean=False, background=2000, folds=False)
>>> res = ls.sdm.fit(data, {"glm": LogisticRegression(max_iter=5000)})
>>> imp = ls.sdm.importance(res)
>>> imp.columns.tolist()
['model', 'predictor', 'importance', 'sd']
Area of habitat¶
area_of_habitat
¶
area_of_habitat(
ranges,
land_cover,
natural: Iterable[str],
*,
suitability=None,
original=None,
elevation=None,
elevation_limits=None,
include_unlisted: bool = True,
cell_area=None,
loss: bool = False,
n_threads: int | None = None,
) -> HabitatAreas
Area of habitat of species: their ranges refined by habitats and elevations.
The area of habitat (AOH; Brooks et al. 2019) of a species is the part
of its range covered by the land-cover classes it lives in, within its
elevation limits. With land cover given as shares of classes per cell
(as plangea's restoration model uses it), every cell of the range counts
for its area times the share of the species' natural classes. The
potential habitat is the same with the original vegetation, and the
gain map says how much habitat a species wins per unit of a cell
restored: what a restoration plan needs (plangea's harmonize_bd).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ranges
|
The species' ranges: a |
required | |
land_cover
|
The current share of every class in each cell (as
|
required | |
natural
|
Iterable[str]
|
The natural classes: a species' habitat is among them. |
required |
suitability
|
The classes each species lives in: a table of two columns (species, class), a mapping species -> classes. Classes that are not natural are left out. |
None
|
|
original
|
The original vegetation: a |
None
|
|
elevation
|
An elevation raster on the same grid. |
None
|
|
elevation_limits
|
A table with the species and their lower and upper elevations
(columns |
None
|
|
include_unlisted
|
bool
|
Species without a row in |
True
|
cell_area
|
The area of a cell: a number, a raster, or None: from the grid in km²
(see |
None
|
|
loss
|
bool
|
Also the habitat lost per unit of natural land converted ( |
False
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
HabitatAreas
|
The areas by species and the gain matrix. |
Notes
The sums are plangea's, bit for bit: the shares of a species' classes
added in their order, times the cell's area, summed over its range in
R's 80-bit sum().
Examples:
>>> import numpy as np
>>> import xarray as xr
>>> import landschaft as ls
>>> cover = xr.DataArray(np.array([[[0.6, 0.0]], [[0.2, 0.1]], [[0.2, 0.9]]]),
... dims=("band", "y", "x"), coords={"band": ["forest", "savanna", "crops"]})
>>> ranges = xr.DataArray(np.array([[[1.0, 1.0]], [[0.0, 1.0]]]),
... dims=("band", "y", "x"), coords={"band": ["toucan", "rhea"]})
>>> veg = ls.original_vegetation(cover, ["forest", "savanna"], ["crops"])
>>> aoh = ls.sdm.area_of_habitat(ranges, cover, ["forest", "savanna"], original=veg, cell_area=1.0,
... suitability={"toucan": ["forest"], "rhea": ["savanna"]})
>>> aoh.table[["species", "current", "potential"]].round(3)
species current potential
0 toucan 0.6 0.75
1 rhea 0.1 1.00
HabitatAreas
dataclass
¶
HabitatAreas(
table: DataFrame,
gain: csr_matrix | None = None,
_template: DataArray | None = None,
loss: csr_matrix | None = None,
)
Bases: Saveable
Result of area_of_habitat().
Attributes:
| Name | Type | Description |
|---|---|---|
table |
DataFrame
|
One row per species: |
gain |
csr_matrix | None
|
The habitat each species gains per unit of a cell restored (km² per
unit of the cell's area restored): a sparse matrix, species (in the
order of |
loss |
csr_matrix | None
|
The habitat each species loses per unit of a cell's area of natural
land lost (converted, or not conserved): the cell's area times the
share of the species' classes in its current natural cover, over the
cells of its range with natural land (the same layout as |
gain_map
¶
The habitat a species gains per unit of each cell restored, as a raster (0 elsewhere).
cell_area_km2
¶
The area of every cell of a raster's grid in km²: the product of the resolutions in a projected coordinate system (in its linear unit), the cell on the ellipsoid in a geographic one.
Example data¶
example_data
¶
Path of an example data set for species distribution modelling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
('bradypus', 'acaule', 'bioclim', 'biome')
|
|
"bradypus"
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples: