Skip to content

Summary Statistics

Overview

Compute comprehensive summary statistics for trait vectors. Essential for analyzing root trait distributions across multiple plants or time points.

Key function: get_summary - Compute 9 summary statistics (min, max, mean, median, std, percentiles)

Quick Example

import sleap_roots as sr
import numpy as np

series = sr.Series.load("plant", primary_path="primary.slp", lateral_path="lateral.slp")
primary_pts = series.get_primary_points()

# Compute lengths for all plants/frames
lengths = sr.get_root_lengths(primary_pts)  # (plants, frames)

# Flatten to get all length values
all_lengths = lengths.flatten()

# Get summary statistics
stats = sr.get_summary(all_lengths)
print(f"Mean length: {stats['mean']:.2f} px")
print(f"Median length: {stats['median']:.2f} px")
print(f"Std deviation: {stats['std']:.2f} px")
print(f"5th percentile: {stats['p5']:.2f} px")
print(f"95th percentile: {stats['p95']:.2f} px")

Output:

Mean length: 245.67 px
Median length: 238.50 px
Std deviation: 45.32 px
5th percentile: 178.23 px
95th percentile: 325.89 px

Using Prefixes for Multiple Traits

# Compute multiple traits
lengths = sr.get_root_lengths(primary_pts).flatten()
angles = sr.get_root_angle(primary_pts).flatten()

# Get summaries with prefixes
length_stats = sr.get_summary(lengths, prefix="length_")
angle_stats = sr.get_summary(angles, prefix="angle_")

# Combine into single dictionary
all_stats = {**length_stats, **angle_stats}

print(f"Length mean: {all_stats['length_mean']:.2f} px")
print(f"Angle mean: {all_stats['angle_mean']:.2f}°")

Handling Edge Cases

# Empty arrays
empty_stats = sr.get_summary(np.array([]))
print(empty_stats['mean'])  # nan

# Arrays with NaN values
with_nans = np.array([10.0, 20.0, np.nan, 30.0])
stats = sr.get_summary(with_nans)
print(f"Mean (ignoring NaN): {stats['mean']:.2f}")  # 20.00

Pipeline Integration

# Use with pipeline output
pipeline = sr.DicotPipeline()
traits = pipeline.fit_series(series)

# Get summary of lateral lengths
lateral_lengths = traits['lateral_length']  # Array of all lateral lengths
lateral_stats = sr.get_summary(lateral_lengths, prefix="lateral_length_")

print(f"Lateral length mean: {lateral_stats['lateral_length_mean']:.2f} px")
print(f"Lateral length range: {lateral_stats['lateral_length_min']:.2f} - {lateral_stats['lateral_length_max']:.2f} px")

API Reference

get_summary

get_summary

get_summary(
    X: ndarray, prefix: Optional[str] = None
) -> Dict[str, float]

Get summary of a vector of observations.

Parameters:

Name Type Description Default
X ndarray

Vector of values as a numpy array of shape (n,).

required
prefix Optional[str]

Prefix of the variable name. If not None, this string will be appended to the key names of the returned dictionary.

None

Returns:

Type Description
Dict[str, float]

A dictionary of summary statistics of the input vector with keys: "min", "max", "mean", "median", "std", "p5", "p25", "p75", "p95"

Dict[str, float]

If prefix was specified, the keys will be prefixed with the string.

Source code in sleap_roots/summary.py
def get_summary(
    X: np.ndarray,
    prefix: Optional[str] = None,
) -> Dict[str, float]:
    """Get summary of a vector of observations.

    Args:
        X: Vector of values as a numpy array of shape `(n,)`.
        prefix: Prefix of the variable name. If not `None`, this string will be appended
            to the key names of the returned dictionary.

    Returns:
        A dictionary of summary statistics of the input vector with keys:
            "min", "max", "mean", "median", "std", "p5", "p25", "p75", "p95"

        If `prefix` was specified, the keys will be prefixed with the string.
    """
    if prefix is None:
        prefix = ""

    X = np.atleast_1d(X)

    if len(X) == 0 or np.all(np.isnan(X)):
        return {
            f"{prefix}min": np.nan,
            f"{prefix}max": np.nan,
            f"{prefix}mean": np.nan,
            f"{prefix}median": np.nan,
            f"{prefix}std": np.nan,
            f"{prefix}p5": np.nan,
            f"{prefix}p25": np.nan,
            f"{prefix}p75": np.nan,
            f"{prefix}p95": np.nan,
        }
    elif np.issubdtype(X.dtype, np.number):
        return {
            f"{prefix}min": np.nanmin(X),
            f"{prefix}max": np.nanmax(X),
            f"{prefix}mean": np.nanmean(X),
            f"{prefix}median": np.nanmedian(X),
            f"{prefix}std": np.nanstd(X),
            f"{prefix}p5": np.nanpercentile(X, 5),
            f"{prefix}p25": np.nanpercentile(X, 25),
            f"{prefix}p75": np.nanpercentile(X, 75),
            f"{prefix}p95": np.nanpercentile(X, 95),
        }
    else:
        print("X contains non-numeric values")
        return {
            f"{prefix}min": np.nan,
            f"{prefix}max": np.nan,
            f"{prefix}mean": np.nan,
            f"{prefix}median": np.nan,
            f"{prefix}std": np.nan,
            f"{prefix}p5": np.nan,
            f"{prefix}p25": np.nan,
            f"{prefix}p75": np.nan,
            f"{prefix}p95": np.nan,
        }

Returns:

A dictionary with the following keys (prefixed if prefix is specified):

Key Description
min Minimum value
max Maximum value
mean Mean (average)
median Median (50th percentile)
std Standard deviation
p5 5th percentile
p25 25th percentile (Q1)
p75 75th percentile (Q3)
p95 95th percentile

Notes: - Uses np.nan* functions to ignore NaN values - Returns all NaN values if input is empty or all NaN - Handles non-numeric arrays gracefully


See Also