Skip to content

Biodiversity analysis

Species (labels) counted in groups (grid cells, sites or other units): the data behind analyses of diversity, endemism and phylogenetic patterns. They live in the ls.bio module, a port of Biodiverse (Laffan, Lubarsky and Rosauer 2010).

Reading data gives the same groups, labels and sample counts as Biodiverse with the same settings: coordinates are snapped to the cell grid the same way and groups and labels get the same names (coordinates or name parts joined with :), so results can be compared one to one. The biodiversity tutorial walks through a complete study.

import landschaft as ls

bd = ls.bio.BaseData.from_table("sites.csv", group_columns=["x", "y"],
                                label_columns=["genus", "species"],
                                cell_sizes=[100000, 100000])
tree = ls.bio.read_tree("tree.nex")
bd.to_raster(bd.group_richness)       # species richness map

# a GBIF download, in 50 km cells of an equal-area projection
bd = ls.bio.BaseData.from_occurrences("0012345-260928.zip", [50_000, 50_000],
                                      to_crs="EPSG:6933", max_uncertainty=10_000)
# a stack of species distribution models, presence above 0.5
bd = ls.bio.BaseData.from_raster("sdm_stack.tif", threshold=0.5)

Sample counts per group

BaseData

BaseData(
    counts: spmatrix,
    groups: Sequence[str],
    labels: Sequence[str],
    coords: ndarray,
    cell_sizes: Sequence[float],
    cell_origins: Sequence[float] | None = None,
    crs: Any = None,
    group_axes: Sequence[Sequence[str]] | None = None,
)

Groups × labels sample counts with the group coordinates.

Build one with from_table() (Biodiverse's text import), from_dataframe(), from_sparse(), from_xarray(), from_raster() or from_points().

Attributes:

Name Type Description
groups list of str

Group names, sorted (Biodiverse's names: coordinates joined with :).

labels list of str

Label names, sorted.

counts csr_matrix

Sample counts, (n_groups, n_labels).

coords ndarray

Group coordinates (n_groups, n_axes); text axes hold their rank.

cell_sizes, cell_origins tuple of float

Cell size per axis (0: coordinates kept as given, negative: text axis) and grid origin.

crs optional

Coordinate reference system of the group coordinates (anything pyproj/rasterio accept), or None.

label_range property

label_range: Series

Number of groups each label occurs in (Biodiverse's range).

label_sample_count property

label_sample_count: Series

Total sample count of each label (summed over groups in name order).

group_richness property

group_richness: Series

Number of labels in each group.

group_sample_count property

group_sample_count: Series

Total sample count of each group (summed over labels in name order).

numeric_labels property

numeric_labels: bool

True when every label looks like a number.

set_label_properties

set_label_properties(properties: DataFrame) -> None

Assign label properties (Biodiverse's assign_element_properties).

Parameters:

Name Type Description Default
properties DataFrame

Indexed by label name, one column per property (see read_properties()). Labels not in the basedata are ignored, values that do not look like numbers are dropped, and the columns INCLUDE, EXCLUDE and REMAP are not properties. RANGE and ABUNDANCE also set the label ranges and abundances.

required

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b"], "x": [0.5, 1.5], "y": [0.5, 0.5]})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> bd.set_label_properties(pd.DataFrame({"MASS": [1.5, 20]}, index=["a", "b"]))
>>> bd.label_properties["MASS"].tolist()
['1.5', '20']

set_group_properties

set_group_properties(properties: DataFrame) -> None

Assign group properties (Biodiverse's assign_element_properties).

Parameters:

Name Type Description Default
properties DataFrame

Indexed by group name, one column per property (see read_properties()); as set_label_properties().

required

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b"], "x": [0.5, 1.5], "y": [0.5, 0.5]})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> bd.set_group_properties(pd.DataFrame({"TEMP": [12.5, 14]}, index=bd.groups))
>>> bd.group_properties["TEMP"].tolist()
['12.5', '14']

group_index

group_index(name: str) -> int

Row of group name.

label_index

label_index(name: str) -> int

Column of label name.

labels_in_group

labels_in_group(group: str) -> dict[str, float]

Labels of group with their sample counts.

groups_with_label

groups_with_label(label: str) -> dict[str, float]

Groups where label occurs, with their sample counts.

group_axes

group_axes(group: str) -> list[str]

Axis values of a group as Biodiverse stores them (strings).

to_table

to_table() -> DataFrame

Long table: one row per (group, label) with its count and coordinates.

to_dataframe

to_dataframe() -> DataFrame

Wide groups × labels table of counts (0 where absent).

to_raster

to_raster(
    values: Mapping[str, float] | Series | ndarray,
    name: str | None = None,
    fill: float = nan,
)

Grid of per-group values (two numeric axes needed).

Parameters:

Name Type Description Default
values Series (indexed by group), mapping or array (group order)

The values to place in each group's cell.

required
name str

Name of the returned DataArray.

None
fill float

Value of cells without a group.

nan

Returns:

Type Description
DataArray

y decreasing, x increasing, cell centres as coordinates, crs in attrs when known.

to_geodataframe

to_geodataframe(values: DataFrame | Series | None = None)

Cells as polygons (needs geopandas), optionally with per-group values.

from_hex classmethod

from_hex(
    data: DataFrame,
    x: Hashable,
    y: Hashable,
    label_column: Hashable | Sequence[Hashable],
    size: float,
    *,
    orientation: str = "pointy",
    origin: Sequence[float] = (0.0, 0.0),
    count_column: Hashable | None = None,
    crs: Any = None,
) -> BaseData

Records aggregated into a hexagonal grid.

Groups are the hexagons' centres ("x:y"), so the distance-based spatial conditions (sp_circle...) work on them; cells are drawn as hexagons by to_geodataframe().

Parameters:

Name Type Description Default
data DataFrame

One row per record.

required
x column names

Record coordinates (projected units).

required
y column names

Record coordinates (projected units).

required
label_column column name(s)

Label (species) column(s).

required
size float

Distance from a hexagon's centre to its vertices.

required
orientation ('pointy', 'flat')
"pointy"
origin (float, float)

A hexagon centre.

(0.0, 0.0)
count_column column name

Sample counts (default 1 per record).

None
crs optional

Coordinate reference system of the coordinates.

None

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a"], "x": [0.1, 0.2, 5.0], "y": [0.0, 0.3, 0.0]})
>>> bd = ls.bio.BaseData.from_hex(df, "x", "y", "sp", 2.0)
>>> bd.groups
['0:0', '3.4641016151:0']

from_h3 classmethod

from_h3(
    data: DataFrame,
    lat: Hashable,
    lon: Hashable,
    label_column: Hashable | Sequence[Hashable],
    resolution: int,
    *,
    count_column: Hashable | None = None,
) -> BaseData

Records aggregated into H3 cells (needs the h3 package).

Groups are the cells' centres ("lon:lat", EPSG:4326); the cell ids are in grid["cells"] and the cells are drawn as hexagons by to_geodataframe().

Parameters:

Name Type Description Default
data DataFrame

One row per record.

required
lat column names

Record coordinates in degrees.

required
lon column names

Record coordinates in degrees.

required
label_column column name(s)
required
resolution int

H3 resolution (0-15).

required
count_column column name
None

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b"], "lat": [-33.86, -33.87], "lon": [151.21, 151.2]})
>>> bd = ls.bio.BaseData.from_h3(df, "lat", "lon", "sp", 5)
>>> bd.grid["cells"]
['85be0e37fffffff']

save

save(path) -> None

Save to landschaft's native format (a zip of Parquet tables; needs pyarrow).

Examples:

>>> import tempfile, pathlib
>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b"], "x": [0.5, 1.5], "y": [0.5, 0.5]})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> path = pathlib.Path(tempfile.mkdtemp()) / "sites.lsbio"
>>> bd.save(path)
>>> ls.bio.BaseData.load(path).groups
['0.5:0.5', '1.5:0.5']

load classmethod

load(path) -> BaseData

Load a basedata saved with save().

Examples:

See save().

from_table classmethod

from_table(
    source: str | PathLike | DataFrame,
    group_columns: Sequence[int | str],
    label_columns: Sequence[int | str] | None = None,
    sample_count_columns: Sequence[int | str] | None = None,
    cell_sizes: Sequence[float] | None = None,
    cell_origins: Sequence[float] | None = None,
    *,
    sep: str | None = None,
    quote: str | None = None,
    matrix_form: bool = False,
    label_start_col: int | str | None = None,
    label_end_col: int | str | None = None,
    include_columns: Sequence[int | str] | None = None,
    exclude_columns: Sequence[int | str] | None = None,
    binarise_counts: bool = False,
    allow_empty_groups: bool = False,
    allow_empty_labels: bool = False,
    skip_lines_with_undef_groups: bool = True,
    crs: Any = None,
) -> BaseData

Delimited text (or a DataFrame) read like Biodiverse's import_data.

Parameters:

Name Type Description Default
source path or DataFrame

CSV-like file (separator and quote guessed unless given) or a DataFrame (all values are read as text, like the file).

required
group_columns sequence

Column positions (0-based, as in Biodiverse) or names. Several label columns are joined with :; sample counts are summed (1 per record when there are none).

required
label_columns sequence

Column positions (0-based, as in Biodiverse) or names. Several label columns are joined with :; sample counts are summed (1 per record when there are none).

required
sample_count_columns sequence

Column positions (0-based, as in Biodiverse) or names. Several label columns are joined with :; sample counts are summed (1 per record when there are none).

required
cell_sizes sequence of float

One per group column: > 0 snaps to cells of that size, 0 keeps the values, < 0 makes a text axis. Default 0 for every axis.

None
cell_origins sequence of float

Grid origin per axis (default 0).

None
matrix_form bool

Site × species layout: the columns label_start_col to label_end_col are labels, their values the counts.

False
include_columns sequence

Keep only records where any include column is true / drop records where any exclude column is true (Perl truth: not empty, not 0).

None
exclude_columns sequence

Keep only records where any include column is true / drop records where any exclude column is true (Perl truth: not empty, not 0).

None
binarise_counts bool

Counts become 1 (after summing per group and label).

False
allow_empty_groups bool

Keep groups/labels that only appear with zero counts.

False
allow_empty_labels bool

Keep groups/labels that only appear with zero counts.

False
skip_lines_with_undef_groups bool

Skip records with an empty or NA coordinate.

True
crs optional

CRS of the coordinates.

None

Returns:

Type Description
BaseData

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a"], "x": [10, 15, 150], "y": [5, 5, 5]})
>>> bd = ls.bio.BaseData.from_table(df, group_columns=["x", "y"], label_columns=["sp"],
...                                 cell_sizes=[100, 100])
>>> bd.groups
['150:50', '50:50']
>>> bd.labels_in_group("50:50")
{'a': 1.0, 'b': 1.0}

from_dataframe classmethod

from_dataframe(
    df: DataFrame,
    group_columns: Sequence[Hashable],
    label_column: Hashable | Sequence[Hashable] | None = None,
    count_column: Hashable | None = None,
    cell_sizes: Sequence[float] | None = None,
    cell_origins: Sequence[float] | None = None,
    *,
    label_columns: Sequence[Hashable] | None = None,
    binarise_counts: bool = False,
    crs: Any = None,
) -> BaseData

Long table (one row per record) or wide table (labels as columns).

Parameters:

Name Type Description Default
df DataFrame

Numeric coordinates in group_columns.

required
group_columns sequence

Coordinate columns (one axis each).

required
label_column column name or list of names

Long format: label of each record (several columns are joined with :).

None
count_column column name

Long format: sample count of each record (default 1).

None
label_columns sequence

Wide format: these columns are labels and their values the counts (zeros and missing values are skipped).

None
cell_sizes Sequence[float] | None

As in from_table().

None
cell_origins Sequence[float] | None

As in from_table().

None
binarise_counts Sequence[float] | None

As in from_table().

None
crs Sequence[float] | None

As in from_table().

None

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> wide = pd.DataFrame({"x": [0.5, 1.5], "y": [0.5, 0.5], "sp1": [2, 0], "sp2": [1, 3]})
>>> bd = ls.bio.BaseData.from_dataframe(wide, ["x", "y"], label_columns=["sp1", "sp2"],
...                                     cell_sizes=[1, 1])
>>> bd.to_dataframe()
         sp1  sp2
group
0.5:0.5  2.0  1.0
1.5:0.5  0.0  3.0

from_sparse classmethod

from_sparse(
    counts: spmatrix | ndarray,
    coords: ndarray,
    labels: Sequence[str],
    cell_sizes: Sequence[float] | None = None,
    cell_origins: Sequence[float] | None = None,
    *,
    crs: Any = None,
) -> BaseData

Community matrix (sites × species) with site coordinates.

Sites falling in the same cell are merged (counts summed).

Examples:

>>> import numpy as np
>>> import landschaft as ls
>>> m = np.array([[1, 0], [2, 5]])
>>> bd = ls.bio.BaseData.from_sparse(m, [[0.5, 0.5], [0.7, 0.2]], ["a", "b"], cell_sizes=[1, 1])
>>> bd.labels_in_group("0.5:0.5")
{'a': 3.0, 'b': 5.0}

from_xarray classmethod

from_xarray(
    data,
    label_dim: str | None = None,
    cell_sizes: Sequence[float] | None = None,
    cell_origins: Sequence[float] | None = None,
    *,
    threshold: float | None = None,
    x: str = "x",
    y: str = "y",
    block_rows: int = 1024,
) -> BaseData

Stack of per-label grids (a DataArray with a label dimension, or a Dataset).

Each slice (or data variable) is a label and its cell values the counts (Biodiverse's "labels as bands": e.g. a stack of species distribution model outputs); with threshold values >= it count 1 (presence), smaller values 0. NaN and nodata are skipped. Cells are aggregated to cell_sizes (default: the grid resolution). The stack is read block_rows rows at a time, so lazy (dask, NetCDF, Zarr) arrays larger than memory work.

Examples:

>>> import numpy as np, xarray as xr
>>> import landschaft as ls
>>> da = xr.DataArray(np.array([[[0.9, 0.1]], [[0.2, 0.8]]]), dims=("species", "y", "x"),
...                   coords={"species": ["a", "b"], "y": [0.5], "x": [0.5, 1.5]})
>>> bd = ls.bio.BaseData.from_xarray(da, "species", threshold=0.5)
>>> bd.to_dataframe()
         a    b
group
0.5:0.5  1.0  0.0
1.5:0.5  0.0  1.0

from_raster classmethod

from_raster(
    files: str | PathLike | Sequence[str | PathLike],
    labels_as_bands: bool = True,
    cell_sizes: Sequence[float] | None = None,
    cell_origins: Sequence[float] | None = None,
    *,
    given_label: str | None = None,
    strip_file_extensions: bool = True,
    threshold: float | None = None,
    block_rows: int = 1024,
) -> BaseData

Raster files read like Biodiverse's import_data_raster.

Parameters:

Name Type Description Default
files path or list of paths

Any GDAL raster.

required
labels_as_bands bool

True: each band is a label (its description, else the file name for single-band files, else band<i>) and cell values are summed as counts per group. False: cell values are labels and each cell counts 1.

True
cell_sizes optional

Group cells (default: the raster resolution, origin 0).

None
cell_origins optional

Group cells (default: the raster resolution, origin 0).

None
given_label str

Label for every band.

None
strip_file_extensions bool

Drop the extension of file names used as labels.

True
threshold float

With labels_as_bands: values >= threshold count 1, others 0 (presence from species distribution model outputs).

None
block_rows int

Rows read at a time (Cloud-Optimised GeoTIFFs and other large rasters are never read whole).

1024

from_vector classmethod

from_vector(
    source,
    label_columns: Hashable | Sequence[Hashable],
    cell_sizes: Sequence[float],
    cell_origins: Sequence[float] | None = None,
    *,
    count_columns: Sequence[Hashable] | None = None,
    measure: str | None = None,
    layer: str | None = None,
    to_crs: Any = None,
    binarise_counts: bool = False,
) -> BaseData

Vector features (points, lines, polygons) read like Biodiverse's feature import.

Points fall in the cell containing them. Lines and polygons are laid over a grid of cells spanning the layer: by default every cell they touch counts 1; measure="area" (polygons) or measure="length" (lines) counts the area or length of the part inside each cell instead.

Parameters:

Name Type Description Default
source GeoDataFrame or path

Read with geopandas (.parquet as GeoParquet; GeoPackage, shapefile, GeoJSON...).

required
label_columns column name or list

Label of each feature (several columns joined with :); names match case-insensitively.

required
cell_sizes sequence of 2 floats

Group cells in the units of the (possibly reprojected) CRS; 0 keeps point coordinates as they are.

required
cell_origins sequence of 2 floats

Grid origin (default 0).

None
count_columns list of column names

Sample count of each feature (summed); default 1 per point/cell, or the measured area/length.

None
measure (None, 'area', 'length')

See above.

None
layer str

Layer of a multi-layer file.

None
to_crs optional

Reproject first.

None
binarise_counts bool

Counts become 1.

False

Examples:

>>> import geopandas as gpd
>>> from shapely.geometry import box
>>> import landschaft as ls
>>> gdf = gpd.GeoDataFrame({"sp": ["a"]}, geometry=[box(0, 0, 15, 5)])
>>> ls.bio.BaseData.from_vector(gdf, "sp", cell_sizes=[10, 10]).groups
['15:5', '5:5']
>>> ls.bio.BaseData.from_vector(gdf, "sp", [10, 10], measure="area").labels_in_group("15:5")
{'a': 25.0}

from_points classmethod

from_points(
    source,
    label_columns: Hashable | Sequence[Hashable],
    cell_sizes: Sequence[float],
    cell_origins: Sequence[float] | None = None,
    *,
    count_column: Hashable | None = None,
    layer: str | None = None,
    to_crs: Any = None,
) -> BaseData

Point records (GeoDataFrame, GeoParquet, GeoPackage, shapefile...).

from_vector() for points; count_column gives the sample count of each point (default 1).

from_occurrences classmethod

from_occurrences(
    source,
    cell_sizes: Sequence[float],
    cell_origins: Sequence[float] | None = None,
    *,
    to_crs: Any = None,
    label: str | Sequence[str] = "species",
    count: str | None = None,
    max_uncertainty: float | None = None,
    basis_of_record: Iterable[str] | None = None,
    exclude_issues: Iterable[str] | None = None,
    present_only: bool = True,
    drop_duplicates: bool = True,
    chunksize: int = 500000,
) -> BaseData

Occurrence records of a GBIF download or Darwin Core Archive, in cells.

The records are cleaned by read_occurrences() (same arguments), optionally reprojected from WGS 84 to to_crs, and snapped to cells of cell_sizes (degrees without to_crs) as the text import does.

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> occ = pd.DataFrame({"species": ["A a", "B b", "A a"],
...                     "decimalLongitude": [10.1, 10.4, 12.2],
...                     "decimalLatitude": [-5.2, -5.9, -5.1]})
>>> bd = ls.bio.BaseData.from_occurrences(occ, cell_sizes=[1, 1])
>>> bd.to_dataframe()
           A a  B b
group
10.5:-5.5  1.0  1.0
12.5:-5.5  1.0  0.0
>>> ls.bio.BaseData.from_occurrences(occ, [100_000, 100_000], to_crs="EPSG:3857").groups
['1150000:-550000', '1150000:-650000', '1350000:-550000']

from_file classmethod

from_file(path: str | PathLike, **kwargs) -> BaseData

Dispatch on the extension: text tables, rasters, NetCDF/Zarr, vector features.

A .zip is read as a GBIF download or Darwin Core Archive (from_occurrences()).

snap_coordinates

snap_coordinates(values: ndarray, cell_size: float, origin: float = 0.0) -> ndarray

Cell centres as Biodiverse's text import computes them (before naming).

size * floor(round((x - origin) / size * 1e10) / 1e10) + size / 2 + origin.

example_data

example_data(name: str = 'sites') -> Path

Path of Biodiverse's example data (GPL-3, Laffan et al.).

Parameters:

Name Type Description Default
name ('sites', 'tree')

"sites": 365 records of 31 species (columns num, genus, species, x, y; projected metres, Australia); "tree": a Nexus phylogeny of those species (Genus:sp1...).

"sites"

Returns:

Type Description
Path

Examples:

>>> import landschaft as ls
>>> bd = ls.bio.BaseData.from_table(ls.bio.example_data("sites"), group_columns=["x", "y"],
...                                 label_columns=["genus", "species"], cell_sizes=[100000, 100000])
>>> bd
BaseData(127 groups, 31 labels, cell_sizes=[100000.0, 100000.0], 365 records)
>>> len(ls.bio.read_tree(ls.bio.example_data("tree")).terminal_names)
31

Occurrence records

GBIF downloads (the "simple CSV" zip) and Darwin Core Archives are read in chunks and cleaned (absences, missing coordinates, uncertainty, basis of record, GBIF issues, duplicates) before they become sample counts per cell with BaseData.from_occurrences.

read_occurrences

read_occurrences(
    source: str | PathLike | DataFrame,
    *,
    label: str | Sequence[str] = "species",
    count: str | None = None,
    max_uncertainty: float | None = None,
    basis_of_record: Iterable[str] | None = None,
    exclude_issues: Iterable[str] | None = None,
    present_only: bool = True,
    drop_duplicates: bool = True,
    extra_columns: Sequence[str] = (),
    chunksize: int = 500000,
) -> DataFrame

Occurrence records of a GBIF download or a Darwin Core Archive, cleaned.

Parameters:

Name Type Description Default
source path or DataFrame

A GBIF "simple CSV" download (the zip or the tab-separated file), a Darwin Core Archive (zip or folder with meta.xml; the core table is read) or a DataFrame with Darwin Core column names (e.g. from pygbif).

required
label str or list of str

Column(s) naming the taxon: "species" (default), "scientificName", "taxonKey", "speciesKey"...

'species'
count str

Column with the number of individuals ("individualCount"); empty values count 1. Default: 1 per record.

None
max_uncertainty float

Drop records with coordinateUncertaintyInMeters above this (records without an uncertainty are kept).

None
basis_of_record list of str

Keep only these basisOfRecord values (e.g. ["HUMAN_OBSERVATION", "PRESERVED_SPECIMEN"]).

None
exclude_issues list of str

Drop records flagged with any of these GBIF issues (column issue, e.g. "ZERO_COORDINATE", "COUNTRY_COORDINATE_MISMATCH").

None
present_only bool

Drop records whose occurrenceStatus is ABSENT.

True
drop_duplicates bool

Keep one record per label and coordinate pair.

True
extra_columns list of str

Other columns to keep.

()
chunksize int

Rows read at a time (large downloads never sit whole in memory).

500000

Returns:

Type Description
DataFrame

label, decimalLongitude, decimalLatitude, count (and the extra columns), in file order.

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"species": ["A a", "A a", "B b", None],
...                    "decimalLongitude": ["10.1", "10.1", "11.5", "12"],
...                    "decimalLatitude": ["-5.2", "-5.2", "-5.9", "-6"],
...                    "basisOfRecord": ["HUMAN_OBSERVATION"] * 4})
>>> ls.bio.read_occurrences(df)
  label  decimalLongitude  decimalLatitude  count
0   A a              10.1             -5.2    1.0
2   B b              11.5             -5.9    1.0

Neighbourhoods

Spatial conditions are written as in Biodiverse, in its Perl syntax: sp_circle (radius => 200000), sp_block (size => 500000), $D <= 3 && $nbr_y > $y, sp_match_text (text => 'wet', axis => 2)... They select the neighbours of each processing group (up to two sets, the second leaving out the first), optionally only for the groups passing a definition query. The neighbour lists are the ones Biodiverse uses, including its reuse of lists between groups for non-overlapping conditions.

neighbours

neighbours(
    bd,
    conditions: str | SpatialCondition | Sequence[str | SpatialCondition],
    definition_query: str | SpatialCondition | None = None,
    n_threads: int | None = None,
    *,
    recycle: bool = True,
    results_recyclable: bool | None = None,
) -> Neighbourhoods

Neighbour sets of every group, as Biodiverse's spatial analysis finds them.

Parameters:

Name Type Description Default
bd BaseData
required
conditions condition or list of up to two conditions

The second neighbour set leaves out the groups of the first.

required
definition_query condition

Only groups passing it are processed (all groups can be neighbours).

None
n_threads int

Threads (default: all cores but one).

None
recycle bool

Reuse neighbour lists as Biodiverse does for non-overlapping, text match and "always the same" conditions (its default; it changes the result when such a condition is combined with a location-dependent term). False evaluates every group.

True
results_recyclable bool

Whether the analysis copies results between groups sharing their neighbours (the default: when every set is recyclable), which decides which groups trigger the recycling.

None

Returns:

Type Description
Neighbourhoods

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a"] * 4, "x": [0.5, 1.5, 2.5, 0.5], "y": [0.5, 0.5, 0.5, 1.5]})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> nb = ls.bio.neighbours(bd, ["sp_circle (radius => 1)", "sp_select_all()"])
>>> nb.of("0.5:0.5"), nb.of("0.5:0.5", 1)
(['0.5:0.5', '0.5:1.5', '1.5:0.5'], ['2.5:0.5'])

definition_query

definition_query(
    bd, query: str | SpatialCondition, n_threads: int | None = None
) -> ndarray

Boolean mask of the groups passing a definition query.

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "c"], "x": [0.5, 1.5, 2.5], "y": [0.5, 0.5, 0.5]})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> ls.bio.definition_query(bd, "$x > 1")
array([False,  True,  True])

SpatialCondition

SpatialCondition(text: str)

A Biodiverse spatial condition (Perl syntax) compiled for a BaseData.

The supported Perl: the variables $D $C $Dsqr $Csqr $d[i] $D[i] $c[i] $C[i] $x $y $z $nbr_x $nbr_y $nbr_z $coord[i] $nbrcoord[i] $coord_id1 $coord_id2, my variables, arithmetic, comparisons, eq/ne, && || // ! and or not, ?:, abs sqrt sin cos atan2 log exp int and the functions sp_circle sp_circle_cell sp_rectangle sp_annulus sp_square sp_square_cell sp_ellipse sp_block sp_self_only sp_select_all sp_select_element sp_select_sequence sp_is_left_of sp_is_right_of sp_in_line_with sp_match_text sp_match_regex sp_group_not_empty sp_richness_greater_than sp_redundancy_greater_than sp_point_in_poly sp_point_in_poly_shape sp_points_in_same_poly_shape.

Parameters:

Name Type Description Default
text str

The condition, e.g. "sp_circle (radius => 200000)", "$D <= 3" or "sp_block (size => 500000)".

required

Examples:

>>> import landschaft as ls
>>> c = ls.bio.SpatialCondition("sp_circle (radius => 1.5)")
>>> c.max_distance
1.5

max_distance property

max_distance: float

Largest distance (any axis) at which a neighbour can be (inf if unknown).

result_type property

result_type: str

Biodiverse's result type of the condition (see result_type()).

kind

kind() -> str

self_only, always_true, always_false, always_same or complex.

compile

compile(bd, procs: ndarray | None = None, def_query: bool = False) -> dict

Program for the C++ evaluator (see src/cpp/bio/conditions.hpp).

Neighbourhoods

Neighbourhoods(
    bd, groups: ndarray, sets: list[tuple[ndarray, ndarray]], conditions, stored=None
)

Neighbour sets of the processing groups.

Attributes:

Name Type Description
groups ndarray

Indices of the processing groups (those passing the definition query), in name order.

sets list of (indptr, indices)

One CSR per condition: the neighbours of groups[r] are indices[indptr[r]:indptr[r + 1]] (group indices, ascending); the second set excludes the first.

of

of(group: str, set_index: int = 0, stored: bool = False) -> list[str]

Neighbour names of a processing group (stored: as Biodiverse keeps them).

Spatial analysis

Indices computed over the neighbourhood of every group, as in Biodiverse's spatial analysis: richness, endemism (weighted, corrected, absolute, hierarchical), rarity, taxonomic turnover (Sorenson, Jaccard, Bray-Curtis, Kulczynski, S2, beta diversity, nestedness, range-weighted turnover), Simpson and Shannon diversity, Rao's quadratic entropy, richness estimators (Chao1, Chao2, ACE, ICE, Hurlbert's rarefaction) and label and sample count summaries. With a tree (tree=): phylogenetic diversity (PD, local PD, terminal lists), phylogenetic endemism (PE, central, single, lists, PD endemism, corrected weighted endemism), clade contributions and losses, evolutionary distinctiveness (ES, ED, AED, AED-T, corrected weighted rarity), phylogenetic abundance, phylogenetic turnover (Sorenson, Jaccard, S2, range-weighted), relative PD and PE (RPD, RPE, against trees with equalised branch lengths), RWiBaLD and the PhyloCom indices (MPD, MNTD, NRI, NTI, net VPD, with the exact expected values of Tsirogiannis et al. or, where Biodiverse samples random sets of tips, its Mersenne Twister draws: pass prng_seed). With label or group properties: their values, frequencies, summary statistics, quantiles and Getis-Ord Gi* (also weighted by local ranges); with numeric labels: their statistics, quantiles, harmonic and geometric means, dissimilarity between neighbour sets, Gi* and the inter-event interval statistics of BiodiverseX (iei_stats, iei_data: the intervals between the numeric labels of each group); with a label matrix (matrix=): matrix statistics, Rao's quadratic entropy and the comparison of two neighbour sets. Values are identical to Biodiverse's. SpatialResult.to_biodiverse_csv writes the results as Biodiverse's "Delimited text" export does, the same text byte for byte.

spatial_analysis

spatial_analysis(
    bd,
    conditions: str | SpatialCondition | Sequence[str | SpatialCondition],
    calculations: str | Sequence[str],
    definition_query: str | SpatialCondition | None = None,
    *,
    tree=None,
    matrix=None,
    prng_seed: int | None = None,
    n_threads: int | None = None,
    recycle: bool = True,
) -> SpatialResult

Biodiverse's spatial analysis: indices over the neighbourhood of every group.

Parameters:

Name Type Description Default
bd BaseData
required
conditions condition or list of up to two conditions

Neighbourhoods (see neighbours()); indices comparing two sets (sorenson, bray_curtis...) need two.

required
calculations str or list of str

Calculation names, with or without Biodiverse's calc_ prefix (see list_calculations()). Calculations that need a second neighbour set are skipped when there is only one, as in Biodiverse.

required
definition_query condition

Only groups passing it are processed.

None
tree Tree

Phylogeny for the phylogenetic indices (pd, pe...); its node names are matched to the labels.

None
matrix Matrix

Label matrix for the matrix indices (matrix_stats, compare_dissim_matrix_values, mx_rao_qe).

None
prng_seed int

Seed of the Mersenne Twister Biodiverse draws random sets of tips with, for the expected NTI on trees that are not ultrametric and for the net VPD (Biodiverse's prng_seed; the same seed gives Biodiverse's values). Random when not given.

None
n_threads int

Threads (default: all cores but one).

None
recycle bool

Reuse neighbour lists as Biodiverse does (see neighbours()).

True

Returns:

Type Description
SpatialResult

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a", "c"], "x": [0.5, 0.5, 1.5, 2.5], "y": [0.5] * 4})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.spatial_analysis(bd, "sp_circle (radius => 1)", ["richness", "endemism_whole"])
>>> res.table[["RICHNESS_ALL", "ENDW_WE"]]
         RICHNESS_ALL  ENDW_WE
group
0.5:0.5           2.0      2.0
1.5:0.5           3.0      3.0
2.5:0.5           2.0      1.5

list_calculations

list_calculations(n_neighbour_sets: int | None = None) -> DataFrame

The calculations available to spatial_analysis().

Parameters:

Name Type Description Default
n_neighbour_sets int

Only those valid with this many neighbour sets (some compare two sets).

None

Returns:

Type Description
DataFrame

One row per calculation: its indices (scalar and list) and the number of neighbour sets it needs.

Examples:

>>> import landschaft as ls
>>> calcs = ls.bio.list_calculations()
>>> calcs.loc["endemism_whole", "indices"]
['ENDW_CWE', 'ENDW_RICHNESS', 'ENDW_SINGLE', 'ENDW_WE']

SpatialResult

SpatialResult(
    bd, table: DataFrame, lists: dict[str, DataFrame], nbrs, calculations: list[str]
)

Bases: Saveable

Results of spatial_analysis().

Attributes:

Name Type Description
table DataFrame

Scalar indices, one row per processed group (Biodiverse's names: ENDW_WE, SORENSON...). NaN where Biodiverse gives no value.

lists dict of str to pandas.DataFrame

List indices (ENDW_WTLIST, EL_LIST_SET1...) as long tables with columns group, key, value.

neighbourhoods Neighbourhoods

The neighbour sets used.

list

list(index: str, group: str) -> dict

One group's entries of a list index ({key: value}).

to_raster

to_raster(index: str, fill: float = nan)

A scalar index as a grid (see to_raster()).

to_geodataframe

to_geodataframe(indices: Sequence[str] | None = None)

Group cells with their scalar indices (needs geopandas).

to_biodiverse_csv

to_biodiverse_csv(
    path,
    list_name: str = "SPATIAL_RESULTS",
    sep: str = ",",
    quote: str = '"',
    no_data_value=None,
) -> None

Write a list as Biodiverse's "Delimited text" export does.

Parameters:

Name Type Description Default
path str or path - like

Output file.

required
list_name str

SPATIAL_RESULTS (the scalar indices) or a list index (ENDW_WTLIST...).

'SPATIAL_RESULTS'
sep str

Separator and quote character ("tab" and "space" accepted).

','
quote str

Separator and quote character ("tab" and "space" accepted).

','
no_data_value optional

Written where a group has no value (empty by default).

None
Notes

The text is the same as Biodiverse writes: one row per group of the BaseData (sorted), columns ELEMENT, Axis_0... and the keys in natural order. Keys missing from a group get no_data_value; an array list (IEI_DATA_ARRAY) gives the count of each value.

Examples:

>>> import tempfile, pathlib
>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a"], "x": [0.5, 1.5, 1.5], "y": [0.5] * 3})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.spatial_analysis(bd, "sp_self_only()", ["richness"])
>>> path = pathlib.Path(tempfile.mkdtemp()) / "richness.csv"
>>> res.to_biodiverse_csv(path)
>>> print(path.read_text())
ELEMENT,Axis_0,Axis_1,RICHNESS_ALL,RICHNESS_SET1
0.5:0.5,0.5,0.5,1,1
1.5:0.5,1.5,0.5,2,2

Randomisations

The basedata is randomised and the analysis run again with the same neighbourhoods; per group and index, the comparisons give p-ranks, z-scores and, with PE and RPE, the CANAPE categories. The randomisations follow Biodiverse's (its Mersenne Twister, seeded with seed), so the same seed gives Biodiverse's results.

randomise

randomise(
    result: SpatialResult,
    function: str = "rand_csr_by_group",
    iterations: int = 99,
    *,
    seed: int | None = None,
    name: str | None = None,
    n_threads: int | None = None,
    **args,
) -> Randomisation

Randomise the basedata of a spatial analysis and compare the results.

Parameters:

Name Type Description Default
result SpatialResult

The observed analysis (its neighbourhoods are kept).

required
function str

Randomisation: rand_structured (Biodiverse's default: labels go to random groups keeping each group's richness, optionally scaled by richness_multiplier and richness_addition, and label ranges; with spatial_conditions_for_label_allocation the occurrences go around seed groups), its spatial forms rand_spatially_structured, rand_diffusion and rand_random_walk (options spatial_conditions_for_label_allocation, spatial_allocation_order, label_allocation_backtracking, spatial_allocation_reseed_prob, spatial_conditions_for_seed_location), the swap algorithms rand_independent_swaps, rand_independent_swaps_modified and rand_curveball (both richness and ranges kept; options swap_count, max_swap_attempts, stop_on_all_swapped and, for curveball, use_max_swap), rand_csr_by_group (whole assemblages to random groups) or rand_nochange. All take spatial_conditions_for_subset and definition_query (randomise within subsets of groups), labels_not_to_randomise, randomise_group_props_by (no_change, by_set, by_item) and randomise_trees_by (shuffle_terminal_names).

'rand_csr_by_group'
iterations int

Iterations to run now (see run() for more).

99
seed int

Seed of Biodiverse's Mersenne Twister (the same seed gives Biodiverse's randomisations). Random when not given.

None
name str

Name of the randomisation (default: the function).

None
n_threads int

Threads for the analyses (default: all cores but one).

None
**args

Arguments of the randomisation function.

{}

Returns:

Type Description
Randomisation

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a", "c", "c"], "x": [0.5, 0.5, 1.5, 2.5, 3.5], "y": [0.5] * 5})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.spatial_analysis(bd, "sp_self_only()", ["richness"])
>>> rnd = ls.bio.randomise(res, "rand_csr_by_group", iterations=9, seed=1)
>>> rnd.p_rank()["RICHNESS_ALL"].round(2).tolist()
[0.56, 0.67, 0.89, 0.89]

Randomisation

Randomisation(result: SpatialResult, function: str, name: str, seed: int, args: dict)

Bases: Saveable

Randomisations of a SpatialResult (see randomise()).

Attributes:

Name Type Description
result SpatialResult

The observed analysis.

function str

Randomisation function.

name str

Prefix of Biodiverse's result lists (name>>SPATIAL_RESULTS...).

iterations int

Iterations run so far.

run

run(iterations: int = 1, n_threads: int | None = None) -> Randomisation

Run more iterations (the random stream continues).

comparison

comparison(list_name: str | None = None) -> DataFrame

The counters: C (observed greater), Q (comparisons), T (ties), P (C / Q), SUMX and SUMXX (sums of the randomised values and their squares).

For the scalar indices (list_name None) the columns are C_<index>..., one row per group; for a list index, a long table.

p_rank

p_rank(list_name: str | None = None) -> DataFrame

Rank of the observed values among the randomised ones: P when above 0.5, else the proportion of randomisations not below (Biodiverse's p_rank lists).

z_scores

z_scores(list_name: str | None = None) -> DataFrame

Standardised difference of the observed values from the randomised ones.

canape

canape(central: bool = False, diff: bool = False) -> DataFrame

CANAPE categories (Mishler et al. 2014) from the p-ranks of PE and RPE.

Needs pe and phylo_rpe2 in the analysis (pe_central and phylo_rpe_central for central); diff uses the RPE difference instead of the null PE. Codes: 0 not significant, 1 neo, 2 palaeo, 3 mixed, 4 super.

Cluster analyses

Groups joined into a dendrogram by the dissimilarity of their labels (cluster) or grown into regions that maximise an index (region_grower), as Biodiverse does, including its tie handling (seeded by prng_seed), spatially constrained clustering and the indices of each node's groups. ClusterResult.cut splits the dendrogram into a number of clusters.

cluster

cluster(
    bd,
    index: str = "SORENSON",
    linkage: str = "link_average",
    conditions: str | Sequence[str] | None = None,
    definition_query: str | None = None,
    *,
    calculations: Sequence[str] | None = None,
    tree=None,
    matrix=None,
    prng_seed: int | None = None,
    tie_breaker: Sequence[str] | None = None,
    clear_singletons: bool = False,
    n_threads: int | None = None,
    _region_grower: bool = False,
) -> ClusterResult

Cluster the groups by the dissimilarity of their labels (Biodiverse's cluster analysis).

Pairs of groups (or clusters) with the lowest index value are joined until one tree remains; cluster-to-cluster values follow the linkage. With several spatial conditions, pairs within the first condition are joined first, then the second and so on.

Parameters:

Name Type Description Default
bd BaseData
required
index str

Dissimilarity index (SORENSON, JACCARD, S2, BRAY_CURTIS, PHYLO_SORENSON...).

'SORENSON'
linkage str

link_average (UPGMA), link_average_unweighted (WPGMA), link_minimum, link_maximum or link_recalculate (the index recomputed for the merged groups).

'link_average'
conditions str or list of str

Spatial conditions limiting the pairs (default sp_select_all()).

None
definition_query str

Only groups passing it are clustered.

None
calculations list of str

Indices computed for the groups of each node.

None
tree optional

Phylogeny and label matrix for the index and the calculations.

None
matrix optional

Phylogeny and label matrix for the index and the calculations.

None
prng_seed int

Seed of Biodiverse's Mersenne Twister, which breaks ties between equal values (random when not given).

None
tie_breaker list of str

Biodiverse's cluster_tie_breaker pairs, such as ["random", "maximise"]; by default ties are broken by random shuffles.

None
clear_singletons bool

Leave out groups that joined nothing when joining several roots.

False
n_threads int

Threads for the index values and calculations.

None

Returns:

Type Description
ClusterResult

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a", "b", "c", "c", "d"],
...                    "x": [0.5, 0.5, 1.5, 1.5, 2.5, 3.5, 3.5], "y": [0.5] * 7})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.cluster(bd, prng_seed=1)
>>> res.tree.to_newick(lengths=False)
"(('0.5:0.5','1.5:0.5')0___,('2.5:0.5','3.5:0.5')1___)2___;"

region_grower

region_grower(
    bd,
    index: str = "RICHNESS_ALL",
    conditions: str | Sequence[str] | None = None,
    definition_query: str | None = None,
    *,
    calculations: Sequence[str] | None = None,
    tree=None,
    matrix=None,
    prng_seed: int | None = None,
    tie_breaker: Sequence[str] | None = None,
    n_threads: int | None = None,
) -> ClusterResult

Grow regions by joining the pairs with the highest value of an index (Biodiverse's RegionGrower).

Like cluster(), but pairs are joined in decreasing order of an index computed for the pair's groups together (such as RICHNESS_ALL or PE_WE), recomputed for each merged region. With one condition that selects all groups, joining stops once a region reaches the value of all the groups together.

Parameters:

Name Type Description Default
bd BaseData
required
index str

The index to maximise.

'RICHNESS_ALL'
conditions str | Sequence[str] | None

As for cluster().

None
definition_query str | Sequence[str] | None

As for cluster().

None
calculations str | Sequence[str] | None

As for cluster().

None
tree str | Sequence[str] | None

As for cluster().

None
matrix str | Sequence[str] | None

As for cluster().

None
prng_seed str | Sequence[str] | None

As for cluster().

None
tie_breaker str | Sequence[str] | None

As for cluster().

None
n_threads str | Sequence[str] | None

As for cluster().

None

Returns:

Type Description
ClusterResult

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a", "c", "d"], "x": [0.5, 0.5, 1.5, 2.5, 3.5], "y": [0.5] * 5})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.region_grower(bd, prng_seed=1)
>>> res.nodes.loc[res.tree.names[res.tree.root], "TOTAL_LENGTH"]
np.float64(4.0)

ClusterResult

ClusterResult(tree, nodes: DataFrame, table: DataFrame, lists)

Bases: Saveable

Results of cluster().

Attributes:

Name Type Description
tree Tree

The dendrogram (terminals are the groups; internal nodes are named <join number>___).

nodes DataFrame

Per node: parent, length and Biodiverse's node values (TOTAL_LENGTH, JOIN_NUMBER, MATRIX_ITER_USED, TERMINAL_NODE_FIRST, TERMINAL_NODE_LAST, JOIN_NOT_METRIC).

table DataFrame

Scalar indices of each node's groups (calculations), or empty.

lists dict

List indices of each node's groups.

cut

cut(k: int) -> Series

Cut the dendrogram into k clusters (the highest joins are undone first).

Parameters:

Name Type Description Default
k int

Number of clusters (at most the number of groups).

required

Returns:

Type Description
Series

Cluster number (1 to k, in the dendrogram's terminal order) of each group.

Examples:

>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a", "b", "c", "c", "d"],
...                    "x": [0.5, 0.5, 1.5, 1.5, 2.5, 3.5, 3.5], "y": [0.5] * 7})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> ls.bio.cluster(bd, prng_seed=1).cut(2).to_dict()
{'0.5:0.5': 1, '1.5:0.5': 1, '2.5:0.5': 2, '3.5:0.5': 2}

Phylogenies

Tree

Tree(
    names: Sequence[str],
    parent: Sequence[int],
    length: Sequence[float],
    children: Sequence[Sequence[int]],
    name: str = "",
    bootstrap: Sequence[str | None] | None = None,
)

A rooted phylogenetic tree.

Nodes are numbered in the order the reader creates them. names, parent (-1 for the root), length and children describe the topology; node_order lists the nodes sorted by name, the order in which landschaft sums over nodes (Biodiverse's own order with sorted keys).

Parameters:

Name Type Description Default
names Sequence[str]

Node names, parent index per node (-1 for the root), branch lengths and child indices per node (in the order they were given).

required
parent Sequence[str]

Node names, parent index per node (-1 for the root), branch lengths and child indices per node (in the order they were given).

required
length Sequence[str]

Node names, parent index per node (-1 for the root), branch lengths and child indices per node (in the order they were given).

required
children Sequence[str]

Node names, parent index per node (-1 for the root), branch lengths and child indices per node (in the order they were given).

required
name str

Tree name.

''
bootstrap list

The text in square brackets after each node, if any.

None

node_order property

node_order: ndarray

Node indices sorted by name.

total_length property

total_length: float

Sum of all branch lengths (root included), summed in name order.

index

index(name: str) -> int

Node index of name.

postorder

postorder() -> ndarray

Nodes with every child before its parent (children in their given order).

depth

depth() -> ndarray

Number of edges from the root to each node.

from_biopython classmethod

from_biopython(tree) -> Tree

From a Bio.Phylo tree (names and branch lengths kept exactly).

Examples:

>>> import io
>>> from Bio import Phylo
>>> import landschaft as ls
>>> bp = Phylo.read(io.StringIO("((a:1.5,b:2)x:0.25,c:3);"), "newick")
>>> ls.bio.Tree.from_biopython(bp).terminal_names
['a', 'b', 'c']

to_biopython

to_biopython()

As a Bio.Phylo tree.

Examples:

>>> import landschaft as ls
>>> t = ls.bio.read_tree("((a:1,b:2):0.5,c:3);", format="newick")
>>> sorted(c.name for c in t.to_biopython().get_terminals())
['a', 'b', 'c']

from_dendropy classmethod

from_dendropy(tree) -> Tree

From a dendropy tree (taxon labels for terminals, node labels inside).

Examples:

>>> import dendropy
>>> import landschaft as ls
>>> dt = dendropy.Tree.get(data="((a:1,b:2)x:0.5,c:3);", schema="newick")
>>> ls.bio.Tree.from_dendropy(dt).terminal_names
['a', 'b', 'c']

to_dendropy

to_dendropy()

As a dendropy tree (underscores kept in names).

Examples:

>>> import landschaft as ls
>>> t = ls.bio.read_tree("((a:1,b:2):0.5,c:3);", format="newick")
>>> sorted(tx.label for tx in t.to_dendropy().taxon_namespace)
['a', 'b', 'c']

from_ete classmethod

from_ete(tree) -> Tree

From an ete3/ete4 tree.

Examples:

>>> from ete3 import Tree as EteTree
>>> import landschaft as ls
>>> et = EteTree("((a:1,b:2)x:0.5,c:3);", format=1)
>>> ls.bio.Tree.from_ete(et).terminal_names
['a', 'b', 'c']

to_ete

to_ete()

As an ete3 tree.

Examples:

>>> import landschaft as ls
>>> t = ls.bio.read_tree("((a:1,b:2):0.5,c:3);", format="newick")
>>> sorted(t.to_ete().get_leaf_names())
['a', 'b', 'c']

to_newick

to_newick(lengths: bool = True) -> str

Newick string (names quoted when needed).

read_tree

read_tree(
    source: str | PathLike, format: str | None = None, index: int | str = 0
) -> Tree

Read one tree (the first, or the one with index/name index).

Examples:

>>> import landschaft as ls
>>> t = ls.bio.read_tree("((a:1,b:2)ab:1,c:3)root;")
>>> t.terminal_names
['a', 'b', 'c']

read_trees

read_trees(source: str | PathLike, format: str | None = None) -> list[Tree]

Read every tree in a Newick, Phylip (several Newicks) or Nexus file.

Parameters:

Name Type Description Default
source str or path

File path, or the text itself.

required
format ('nexus', 'newick')

Detected from the text when omitted (#NEXUS header).

"nexus"

Returns:

Type Description
list of Tree

Examples:

>>> import landschaft as ls
>>> [t] = ls.bio.read_trees("((a:1,b:2):1,c:3);")
>>> t.names
['a', 'b', '0___', 'c', '1___']
>>> t.total_length
7.0

Dissimilarity matrices

Matrix

Matrix(elements: Sequence[str], values: ndarray, self_similarity: float | None = None)

A symmetric matrix of values between elements (usually labels).

Missing pairs are NaN. A pair holds the first value read for it (either order), like Biodiverse; the diagonal is self_similarity unless a value was given.

Parameters:

Name Type Description Default
elements sequence of str

Element names (stored sorted).

required
values array(n, n)

Values in the order of elements (NaN for missing pairs).

required
self_similarity float

Value of (a, a) pairs that were not given.

None

n_pairs property

n_pairs: int

Number of defined pairs (each unordered pair once, diagonal included).

get

get(a: str, b: str) -> float

Value of the pair (NaN if missing; self_similarity for a missing (a, a)).

from_pairs classmethod

from_pairs(pairs, self_similarity: float | None = None) -> Matrix

From (element1, element2, value) records (first value per pair wins).

from_dataframe classmethod

from_dataframe(df: DataFrame, self_similarity: float | None = None) -> Matrix

Square DataFrame (index = columns = elements); the upper-left value of a pair wins.

read_matrix

read_matrix(
    source: str | PathLike,
    element_columns: Sequence[int] = (1, 2),
    values_start_col: int | None = None,
    *,
    sparse: bool = False,
    row_columns: Sequence[int] | None = None,
    col_columns: Sequence[int] | None = None,
    value_column: int | None = None,
    sep: str | None = None,
    quote: str | None = None,
    self_similarity: float | None = None,
) -> Matrix

Read a Biodiverse matrix file (full or lower/upper triangle, or sparse pairs).

Parameters:

Name Type Description Default
source path

Delimited text.

required
element_columns sequence of int

Columns forming the element name of each row (joined with :); Biodiverse's default is columns 1 and 2 (genus, species).

(1, 2)
values_start_col int

First value column (default: after the last element column). The value columns follow the order of the rows.

None
sparse bool

Sparse format: one pair per line, names in row_columns and col_columns, value in value_column.

False

Returns:

Type Description
Matrix

Grids and files

Records can be aggregated into hexagons (BaseData.from_hex) or H3 cells (BaseData.from_h3, needs h3); cells are drawn as hexagons by BaseData.to_geodataframe. Basedatas are saved with BaseData.save and whole studies (basedatas, trees, matrices and results) with save_session, as zips of Parquet tables. Trees convert to and from Bio.Phylo, DendroPy and ete (Tree.from_biopython, Tree.to_dendropy...).

hex_centres

hex_centres(
    x, y, size: float, orientation: str = "pointy", origin: Sequence[float] = (0.0, 0.0)
)

Centres of the hexagons (circumradius size) containing each point.

Parameters:

Name Type Description Default
x array - like

Point coordinates (projected units).

required
y array - like

Point coordinates (projected units).

required
size float

Distance from a hexagon's centre to its vertices.

required
orientation ('pointy', 'flat')

Pointy-topped hexagons (rows) or flat-topped (columns).

"pointy"
origin (float, float)

Centre of the hexagon at axial coordinates (0, 0).

(0.0, 0.0)

Returns:

Type Description
(ndarray, ndarray)

The centre coordinates.

Examples:

>>> import landschaft as ls
>>> cx, cy = ls.bio.hex_centres([0.1, 1.6], [0.0, 0.1], 1.0)
>>> [round(float(v), 4) for v in cx], [round(float(v), 4) for v in cy]
([0.0, 1.7321], [0.0, 0.0])

h3_cells

h3_cells(lat, lon, resolution: int) -> list[str]

H3 cell of each point (h3 package).

Examples:

>>> import landschaft as ls
>>> ls.bio.h3_cells([-33.86], [151.21], 5)
['85be0e37fffffff']

save_session

save_session(path: str | PathLike, **objects) -> None

Save basedatas, trees, matrices and analysis results in one file.

Parameters:

Name Type Description Default
path path

The file to write (a zip of Parquet tables and JSON).

required
**objects

Named BaseData, Tree, Matrix, SpatialResult or ClusterResult objects. Results keep their tables (and their basedata, saved with them).

{}

Examples:

>>> import tempfile, pathlib
>>> import pandas as pd
>>> import landschaft as ls
>>> df = pd.DataFrame({"sp": ["a", "b", "a"], "x": [0.5, 1.5, 1.5], "y": [0.5] * 3})
>>> bd = ls.bio.BaseData.from_dataframe(df, ["x", "y"], "sp", cell_sizes=[1, 1])
>>> res = ls.bio.spatial_analysis(bd, "sp_self_only()", ["richness"])
>>> path = pathlib.Path(tempfile.mkdtemp()) / "study.lsbio"
>>> ls.bio.save_session(path, sites=bd, richness=res)
>>> s = ls.bio.load_session(path)
>>> s["richness"].table["RICHNESS_ALL"].tolist()
[1.0, 2.0]

load_session

load_session(path: str | PathLike) -> dict

Load the objects saved with save_session() (a dict by name).

Examples:

See save_session().

Element properties

Label and group properties (Biodiverse's element properties files) are assigned with BaseData.set_label_properties and BaseData.set_group_properties.

read_properties

read_properties(
    source: str | PathLike,
    element_columns: Sequence[int],
    property_columns: Mapping[str, int],
    *,
    sep: str | None = None,
    quote: str | None = None,
) -> DataFrame

Read a Biodiverse element properties file (label or group properties).

Property names are upper-cased and only values that look like numbers are kept, as Biodiverse does; empty fields are kept as empty strings (the property is set but undefined: listed, not used in statistics). A later row for the same element overrides the earlier one.

Parameters:

Name Type Description Default
source path

Delimited text with a header line.

required
element_columns sequence of int

Columns forming the element name (joined with :).

required
property_columns mapping

Property name to column.

required
sep str

Separator and quote character (guessed by default).

None
quote str

Separator and quote character (guessed by default).

None

Returns:

Type Description
DataFrame

One row per element, one column per property; values are the strings read ("" for empty fields, None where not set); elements without any property are left out.

Examples:

>>> import tempfile, pathlib
>>> import landschaft as ls
>>> path = pathlib.Path(tempfile.mkdtemp()) / "props.csv"
>>> _ = path.write_text("genus,species,mass\nGenus,sp1,1.5\nGenus,sp2,x\nGenus,sp3,\n")
>>> props = ls.bio.read_properties(path, [0, 1], {"mass": 2})
>>> props["MASS"].to_dict()
{'Genus:sp1': '1.5', 'Genus:sp3': ''}