mpspline
Python implementation of mass-preserving equal-area quadratic splines for harmonizing soil profiles to standard depth intervals.
Port of the mpspline2 R package by Lauren O'Brien, based on the method of Bishop et al. (1999), as generalized in Malone et al. (2009).
Installation
pip install mpspline
Quick Start
from mpspline import mpspline
# Single profile
result = mpspline({
'horizons': [
{'hzname': 'A', 'upper': 0, 'lower': 20, 'clay': 24.5},
{'hzname': 'B', 'upper': 20, 'lower': 50, 'clay': 35.2},
]
}, output_type='wide')
print(result) # {'clay_0_5': 21.4, 'clay_5_15': 27.8, ...}
Usage
Pass a dict for a single profile or a list for multiple profiles.
1. Single Profile (Wide Format)
from mpspline import mpspline
# Single profile (returns dict)
result = mpspline({
'cokey': 12345,
'horizons': [
{'hzname': 'A', 'upper': 0, 'lower': 20, 'clay': 24.5},
{'hzname': 'B', 'upper': 20, 'lower': 50, 'clay': 35.2},
]
}, output_type='wide')
print(result)
# {'cokey': 12345, 'clay_0_5': 22.6, 'clay_5_15': 24.1, 'clay_15_30': 29.9, ...}
2. Multiple Profiles (Long Format - Default)
profiles = [
{
'cokey': 1001,
'horizons': [
{'hzname': 'A', 'upper': 0, 'lower': 15, 'clay': 18.5},
{'hzname': 'Bt1', 'upper': 15, 'lower': 45, 'clay': 35.0},
{'hzname': 'Bt2', 'upper': 45, 'lower': 80, 'clay': 32.5},
]
},
{
'cokey': 1002,
'horizons': [
{'hzname': 'Ap', 'upper': 0, 'lower': 20, 'clay': 15.2},
{'hzname': 'Bw', 'upper': 20, 'lower': 50, 'clay': 28.4},
{'hzname': 'C', 'upper': 50, 'lower': 100, 'clay': 25.0},
]
},
]
# Returns DataFrame with columns: [cokey, var_name, upper, lower, value]
df_long = mpspline(profiles, var_name=['clay'])
print(df_long[['cokey', 'var_name', 'upper', 'lower', 'value']].head())
Output:
cokey var_name upper lower value
1001 clay 0 5 16.168706
1001 clay 5 15 19.762784
1001 clay 15 30 31.118765
1001 clay 30 60 36.090372
1001 clay 60 100 31.251860
3. Multiple Profiles (Wide Format)
# Returns DataFrame with flattened columns: [cokey, clay_0_5, clay_5_15, ...]
df_wide = mpspline(profiles, var_name=['clay'], output_type='wide')
print(df_wide[['cokey', 'clay_0_5', 'clay_5_15', 'clay_15_30']].head())
Output:
cokey clay_0_5 clay_5_15 clay_15_30
1001 16.168706 19.762784 31.118765
1002 12.784555 14.721798 22.437913
4. Advanced Modes (1cm Resolution)
# Get high-resolution predictions at every 1cm interval
df_1cm = mpspline(profiles, var_name=['clay'], mode='1cm')
print(df_1cm[['cokey', 'var_name', 'depth', 'value']].head())
Output:
cokey var_name depth value
1001 clay 0 15.935577
1001 clay 1 15.974431
1001 clay 2 16.090996
1001 clay 3 16.285271
1001 clay 4 16.557255
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
obj |
dict or list | Required | Profile(s) with a 'horizons' key |
var_name |
str or list | None | Properties to harmonize (all numeric if None) |
target_depths |
list | GlobalSoilMap | Depth intervals as (top, bottom). Default: [(0,5), (5,15), (15,30), (30,60), (60,100), (100,200)] |
lam |
float | 0.1 | Smoothing parameter (0.01=smooth, 1.0=fit data closely) |
vlow |
float | 0.0 | Minimum constraint for values |
vhigh |
float | 1000.0 | Maximum constraint for values |
parallel |
bool | False | Use multiprocessing for multiple profiles |
batch_size |
int | 100 | Profiles per batch |
strict |
bool | False | Raise exceptions on validation errors (False = skip) |
Horizon Input Format
Required fields:
- Depth:
upperandlower(ortopandbottom) in cm - Properties: numeric fields to harmonize
Optional:
hzname: horizon name
Example:
{
'hzname': 'Ap',
'upper': 0,
'lower': 20,
'clay': 24.5,
'sand': 42.3,
}
Examples
# Custom depth intervals
result = mpspline(profile, target_depths=[(0, 10), (10, 30), (30, 60)])
# Adjust smoothing (0.01=smooth, 1.0=closer to data)
result = mpspline(profile, lam=0.01)
# Constrain values to realistic ranges
result = mpspline(profile, var_name=['clay'], vlow=0, vhigh=100)
# Parallel processing for many profiles
df = mpspline(profiles, parallel=True, batch_size=500)
# Only process specific properties
df = mpspline(profiles, var_name=['clay', 'sand'])
# Error handling: skip bad profiles or raise errors
df = mpspline(profiles, strict=False) # Skip problematic profiles
try:
df = mpspline(profiles, strict=True) # Raise on errors
except ValueError as e:
print(f"Validation error: {e}")
Troubleshooting
Horizons not ordered by depth: Horizons must be sequential with no gaps or overlaps.
# Wrong
{'upper': 0, 'lower': 30}, {'upper': 20, 'lower': 50}
# Right
{'upper': 0, 'lower': 20}, {'upper': 20, 'lower': 50}
Values outside expected ranges: Use vlow and vhigh to constrain predictions:
result = mpspline(profile, vlow=0, vhigh=100)
Processing is slow: Enable parallel processing:
df = mpspline(profiles, parallel=True, batch_size=500)
How it works
The algorithm fits a mass-preserving equal-area quadratic spline to horizon data. This converts discrete observations at varying depths to standard depth intervals while preserving the average value across each depth range.
See the algorithm documentation for details.
References
- O'Brien, L. (2025). mpspline2: Mass-Preserving Spline Functions for Soil Data. R package version 0.1.9. https://doi.org/10.32614/CRAN.package.mpspline2
- Bishop, T.F.A., McBratney, A.B., & Laslett, G.M. (1999). Modelling soil attribute depth functions with equal-area quadratic smoothing splines. Geoderma, 89(3-4), 185-208. https://doi.org/10.1016/S0016-7061(99)00003-8
- Malone, B.P., McBratney, A.B., & Minasny, B. (2009). Mapping continuous depth functions of soil carbon storage and available water capacity. Geoderma, 154(3-4), 138-152. https://doi.org/10.1016/j.geoderma.2009.10.007
License
GPL 3.0
Release files for mpspline 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mpspline-0.2.0.tar.gz | 39.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mpspline-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 73.2 kB
Release files / mpspline-0.2.0.tar.gz
| Download URL | mpspline-0.2.0.tar.gz |
|---|---|
| Size | 39.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7aae9b8199be8b052619c7c9e055375eb8af4af914ebf9b6cd08663cd2db9a12
|
|
BLAKE2b-256 checksum How to use checksums |
81a3ec5e3705eb0146bfcb775edaaf58bcdfaeba241fd91ef709ed1181a7fa5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 14, 2026.
Transparency logRelease files / mpspline-0.2.0-py3-none-any.whl
| Download URL | mpspline-0.2.0-py3-none-any.whl |
|---|---|
| Size | 34.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
371f450c346a57210272daf03e95e25eccf251c2adce66a4bc959e56005a0966
|
|
BLAKE2b-256 checksum How to use checksums |
29baac4d0f987c5037d09d7bbb7a78882b9c2fae05f2d468ef79be87ad758fb2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 14, 2026.
Transparency log