Technical specification for developers working with LiPD files.
A LiPD file (.lpd) is a ZIP archive that bundles tabular proxy data with rich
structured metadata, following the
BagIt
specification for data integrity. The metadata lives in a JSON-LD file
(metadata.jsonld); data values live in one or more headerless CSV files.
LiPD is supported by lipdR (R), pylipd (Python), and the LiPD Playground (browser).
Inside every .lpd ZIP archive:
myfile.lpd (ZIP archive)
└── bag/
├── bagit.txt ← BagIt version declaration
├── bag-info.txt ← bagging date, software agent
├── manifest-md5.txt ← MD5 checksums of data/ files
├── tagmanifest-md5.txt ← MD5 checksums of tag files
└── data/
├── metadata.jsonld ← all metadata (JSON-LD)
├── *.csv ← one CSV per table
└── ...| File | Purpose |
|---|---|
bagit.txt | Declares BagIt version (0.97) and encoding (UTF-8) |
bag-info.txt | Bagging date and software agent name |
manifest-md5.txt | MD5 checksum for every file under data/ |
tagmanifest-md5.txt | MD5 checksums of the three tag files above |
MD5 checksums must be recomputed whenever metadata.jsonld or any CSV is
modified. The manifest format is one line per file:
<md5hex> data/filename.csv
All metadata lives in data/metadata.jsonld as a JSON-LD object.
| Field | Type | Notes |
|---|---|---|
archiveType | string | Archive material type. See controlled vocabulary. |
dataSetName | string | Dataset name (e.g. "ODP1098B13") |
datasetId | string | Globally unique identifier for the dataset. Should be assigned once and never
changed after the dataset is published or shared, as other systems may reference
it. No specific format is enforced — any unique string is valid.
Common conventions by tool:
|
geo | object | Geographic location. See Geo Object. |
lipdVersion | number | LiPD format version (current: 1.3) |
paleoData and/or chronData | array | At least one must be present |
| Field | Type | Notes |
|---|---|---|
pub | array | Publications. See Publications. |
changelog | array | Modification history. See Changelog. |
createdBy | string | Software that created the file (e.g. "lipdR", "pylipd") |
datasetVersion | string | Semantic version string (e.g. "1.0.0") |
funding | array | Funding sources (agency, grant, country, investigator) |
investigators | string/array | Principal investigators |
collectionName | string | Database collection this dataset belongs to (e.g. "PAGES2k") |
collectionYear | number | Year added to a collection |
originalDataUrl | string | URL to the original data source |
The geo object uses GeoJSON Feature syntax:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [longitude, latitude, elevation]
},
"properties": {
"siteName": "Palmer Deep",
"country": "Antarctica",
"ocean": "Southern Ocean",
"continent": "Antarctica",
"countryOcean": "Southern Ocean",
"locationType": "marine",
"description": "Continental shelf basin",
"notes": "Core taken at 1011 m water depth"
}
}[longitude, latitude, elevation] — elevation is optional; negative = below sea levelgeo.latitude and geo.longitude in memory but writes the GeoJSON form to disklocationType values: "marine", "lacustrine", "terrestrial", "ice", etc.pub is an unnamed array at the root level. Each entry:
{
"author": [{"name": "Smith, J."}, {"name": "Jones, A."}],
"title": "My paleotemperature record",
"year": 2024,
"journal": "Paleoceanography",
"doi": "10.1029/2024PA000001",
"volume": "39",
"issue": "2",
"pages": "1-22",
"abstract": "We present...",
"citeKey": "smith2024paleo",
"publicationType": "article-journal",
"urls": ["https://doi.org/10.1029/2024PA000001"],
"dataUrls": ["https://pangaea.de/10.1594/PANGAEA.12345"]
}author (array of {"name": "..."} objects), not a plain string, not authorsdoi (lowercase), not DOI or Doipub must be an unnamed array, not a named object (pub1, pub2)identifier[0].id for the DOI — normalize to doiBoth use the same nested structure. paleoData holds proxy measurements; chronData holds age and chronological data.
"paleoData": [
{
"measurementTable": [
{
"tableName": "paleo0measurement0",
"filename": "MyDataset.paleo0measurement0.csv",
"missingValue": "nan",
"columns": [ ... ]
}
],
"model": [
{
"summaryTable": [ ... ],
"ensembleTable": [ ... ],
"distributionTable": [ ... ]
}
]
}
]paleoData and chronData are unnamed arrays, not keyed objectsmeasurementTable, ensembleTable, summaryTable, distributionTable are also arrayspaleo0measurement0, chron0model0ensemble0filename references the CSV in the same data/ directorymissingValue defaults to "nan" if absentvalues is not stored in the JSON file — data lives in the CSVEach item in a columns array:
{
"number": 3,
"variableName": "SST",
"TSid": "LPD1028d531",
"units": "degC",
"description": "Sea surface temperature",
"proxy": "TEX86",
"proxyGeneral": "biomarker",
"inferredVariableType": "temperature",
"hasMinValue": 15.3,
"hasMaxValue": 28.5,
"hasMeanValue": 22.1,
"hasMedianValue": 22.0,
"hasResolution": {
"hasMinValue": 20.0,
"hasMeanValue": 100.0,
"hasMedianValue": 80.0,
"hasMaxValue": 500.0
},
"uncertainty": {"analytical": 0.5},
"calibration": { ... },
"physicalSample": { ... },
"interpretation": [
{
"variable": "temperature",
"variableDetail": "surface",
"seasonality": "Annual",
"direction": "positive",
"scope": "climate"
}
]
}| Field | Required | Notes |
|---|---|---|
number | Yes | 1-indexed CSV column position. May be an array in ensemble tables — see Ensemble Tables. |
variableName | Yes | See controlled vocabulary. |
TSid | Yes | Unique time-series identifier. See TSid. |
units | Recommended | See controlled vocabulary. |
proxy | Recommended | See controlled vocabulary. |
proxyGeneral | Optional | Broader proxy category. See controlled vocabulary. |
interpretation | Recommended | Array of interpretation objects. Each has
variable, variableDetail,
seasonality, direction, scope. |
calibration | Optional | Transfer function details. See Calibration Object. |
physicalSample | Optional | Physical sample provenance. See Physical Sample. |
hasMinValue, hasMaxValue, hasMeanValue, hasMedianValue | Optional | Precomputed statistics on the column values |
hasResolution | Optional | Object with min/mean/median/max resolution and optional units |
Documents the transfer function used to convert proxy values to climate variables:
{
"equation": "SST = (TEX86 - 0.10) / 0.015",
"equationSlope": 0.015,
"equationIntercept": -0.10,
"equationSlopeUncertainty": 0.001,
"equationR2": 0.87,
"method": "linear regression",
"methodDetail": "Calibrated against modern core-top assemblages",
"uncertainty": 1.5,
"proxyDataset": "TEX86",
"targetDataset": "SST",
"doi": "10.1016/j.gca.2002.07.003",
"seasonality": "Annual",
"notes": "Kim et al. (2010) calibration"
}equation: full equation as a stringequationSlope / equationIntercept: numeric regression coefficients, more machine-readable than equation aloneequationR2: goodness of fituncertainty: calibration uncertainty in units of the target variabledoi: DOI of the calibration publicationLinks a column measurement to its physical sample, enabling sample-level provenance:
{
"hasname": "DSDP-594-1H-1",
"hasidentifier": "IGSN:AU1234567",
"housedat": "IODP Bremen Core Repository"
}| Field | Notes |
|---|---|
hasname | Sample name or lab code |
hasidentifier | IGSN (International GeoSample Number) for globally unique sample identification |
housedat | Institution or repository where the physical sample is curated |
Each measurement or model table has a corresponding .csv file in data/.
number field (1-indexed)NaN (case-insensitive on read)labID, materialDated) may be quoted or unquotedDatasetName.paleo0measurement0.csvExample — depth (col 1), age (col 2), SST (col 3):
0.11,80.0,18.5
0.21,134.0,19.2
0.31,189.0,NaN
0.41,243.0,17.8Ensemble tables store large numbers of age model ensemble members (often 1000+).
Rather than defining one column object per member, a single column uses an
array for its number field, encoding all member
column positions at once:
{
"tableName": "chron0model0ensemble0",
"filename": "MyDataset.chron0model0ensemble0.csv",
"columns": [
{
"number": 1,
"variableName": "depth",
"TSid": "LPDabc12345",
"units": "cm"
},
{
"number": [2, 3, 4, 5, ..., 1001],
"variableName": "age",
"TSid": "LPDdef67890",
"units": "yr BP"
}
]
}The CSV for this table has 1001 columns: column 1 is depth, columns 2–1001 are the 1000 age ensemble members.
number as the arrayTwo styles appear in real files. Both must be handled on read.
{
"version": "1.1.0",
"date": "2024-03-15",
"name": "Researcher Name",
"notes": "Added TEX86 calibration",
"changes": {
"addedColumn": [["SST added to paleo0measurement0"]],
"editedMetadata": [["Updated archiveType"], ["Fixed DOI"]]
}
}The changes field is an object whose keys are change-type labels.
Values are arrays of arrays of strings (matrix format used by lipdR).
{
"version": "1.1.0",
"timestamp": "2024-03-15T10:30:00Z",
"curator": "Researcher Name",
"notes": "Added TEX86 calibration and fixed DOI",
"lastVersion": "1.0.0"
}Here timestamp supplements or replaces date;
curator supplements or replaces name;
lastVersion records the previous version.
| Version | Notes |
|---|---|
| 1.0 | Legacy. Limited table support. |
| 1.1 | Multiple measurement/model tables added. |
| 1.2 | Structure refinements. |
| 1.3 | Current standard. Full BagIt. Adds createdBy and lipdVersion fields. |
lipdR and pylipd auto-upgrade older versions to 1.3 on read.
Every column must have a unique TSid. It serves as a global identifier
for the time series across the LiPD ecosystem. TSids must be unique within a dataset
and ideally globally unique. No specific format is enforced — any unique string is valid.
| Format | Example | Source |
|---|---|---|
WEB<hex> | WEB23db2e14 | LiPD web tool (short form) |
WEB-<uuid> | WEB-a1b2c3d4-... | LiPD web tool (long form) |
LPD<hex> | LPD02c025e9 | LiPD desktop tool |
PYT<alnum> | PYTJJYN30KI | Geochronology tools |
R-<alnum> | R-abc1234567890... | lipdR |
| (free-form) | any unique string | pylipd and others |
LiPD files use JSON-LD. The @context field in
metadata.jsonld typically references "context.jsonld"
(a relative path inside the ZIP).
The primary semantic framework is the LinkedEarth ontology:
http://linked.earth/ontology#Note: The LinkedEarth ontology does not cover all LiPD concepts.
Many real-world fields — including collectionName,
investigators, citeKey, housedat,
and custom interpretation fields — are not defined in the ontology.
It is fully acceptable and expected to use keys not present in the ontology;
they are preserved as-is by lipdR and pylipd.