Skip to content

Input & output

Reading ring-width and chronology files, inspecting metadata, and writing data back out. See the Reading & writing data guide for worked examples.

readers

Read a common ring-width data file (.csv, .rwl) into a pandas DataFrame.

Parameters:

Name Type Description Default
filename str

a data file (.CSV, .RWL or .RAW). May be a local path or an http(s) URL (e.g. from the NOAA/ITRDB archive) -- both are read the same way.

required
header bool or None

whether a 3-line site-metadata header sits at the top of the file. None auto-detects it the way dplR does; pass True/False to force.

None
skip_lines int

how many of the first lines of the file to skip when reading it.

0
format (None, tucson, csv, tridas)

force the file format regardless of suffix. None infers it: .csv as CSV, .rwl/.raw as Tucson, .xml as TRiDaS, any other suffix by sniffing content. ("rwl"/"raw" are aliases for "tucson"; "xml" for "tridas".) A TRiDaS file is parsed and, for the common single-table case, returned as one ring-width DataFrame; a multi-table or chronology TRiDaS file raises with a pointer to dpl.read_tridas(), which returns the full TridasProject.

None
strict bool

True refuses a file with an unrecoverable problem (fail-loud). False (salvage) recovers as much as possible: a self-overlapping or precision-shifted series is dropped, a duplicate series ID is renamed and kept, and a file with nothing usable returns None. Every action is warned about and recorded on df.attrs["dplpy_salvage"].

True
join bool

True (matching dplR) merges a series written as two or more disjoint same-ID blocks into one. False keeps them as separate series (the 2nd+ block renamed) -- the per-segment view a crossdating QA needs.

True
encoding str or None

character encoding. None reads as UTF-8 (covers ASCII) and falls back to latin1 with a warning for non-UTF-8 bytes (e.g. an accented investigator name). Pass an explicit encoding to silence the warning. The encoding used is recorded in df.attrs["dplpy_encoding"].

None

Returns:

Name Type Description
data pandas dataframe

Year-indexed ring widths. Non-fatal advisories about the file are attached to df.attrs (and a concise heads-up is printed): dplpy_beyond_column, dplpy_interior_gaps, dplpy_case_merged, dplpy_case_variant_ids, dplpy_blank_lines and dplpy_encoding (plus dplpy_salvage in salvage mode).

Examples:

>>> import dplpy as dpl
>>> data = dpl.readers("../tests/data/csv/file.csv")
>>> data = dpl.readers("../tests/data/rwl/file.rwl")           # header auto-detected
>>> data = dpl.readers("https://www.ncei.noaa.gov/pub/data/paleo/treering/"
...                    "measurements/northamerica/usa/ak132x.rwl")  # read from a URL
References

.. [1] https:/opendendro.org/dplpy-man/#readers

readers_url

Read a Tucson (.rwl) file directly from a URL.

Deprecated: readers() now accepts an http(s) URL directly, so dpl.readers(url) does the same thing. This thin wrapper is kept for backward compatibility and simply forwards to readers.

metadata

Extract site/sample metadata from a Tucson (.rwl) file's header.

Returns a dict with best-effort fields (site_id, site_name, species_code, species_name, country_region, elevation_m, latitude, longitude, first_year, last_year, investigators) plus n_header_lines and the raw header lines. Unreadable fields are None. Reads only the header, so it is cheap and independent of loading the data; accepts a local path or an http(s) URL. This is a prototype -- see df.attrs['dplpy_metadata'] for the same result captured at read time.

read_crn

Read a Tucson (.crn) chronology file into a DataFrame.

Parameters:

Name Type Description Default
filename str

Path (or http/https URL) to a .crn chronology file. Standard single chronologies and combined multi-block files (stacked ARSTAN types, or many sites concatenated) are both handled.

required
strict bool

True (the default) refuses a file with no readable chronology; False returns None instead. (Individual malformed rows are skipped with a warning either way.)

True
split_by_site bool

If True, return a dict mapping each site ID to its own DataFrame (each framed as a single-site file: value columns named by chronology type plus a shared samp_depth). Useful for multi-site bundles, where the default single wide frame is unwieldy. A single-site file yields a one-entry dict.

False

Returns:

Type Description
pandas.DataFrame or dict of pandas.DataFrame

By default one DataFrame indexed by year: one value column per chronology (named by chronology type for a single-site file, e.g. 'std'/'res'/'ars'; by site, or 'site.type', for a multi-site file), index values divided by 1000 and 9990 mapped to NaN, with sample depths as 'samp_depth' (shared when several chronologies carry identical depth) or ' samp_depth' otherwise. With split_by_site=True, a dict {site_id: DataFrame}. df.attrs carries dplpy_crn (a summary) and dplpy_crn_stats (embedded statistics lines).

Examples:

>>> dpl.read_crn("ga004.crn")
>>> dpl.read_crn("celaque_prelim_v01.rwl_crns.txt")
>>> by_site = dpl.read_crn("ALL.CRNS", split_by_site=True)   # {site: DataFrame}
References

.. [1] https:/opendendro.org/dplpy-man/#read_crn

read_ids

Derive tree and core IDs from series names (site-tree-core convention).

Split each series name into a tree identifier (site + tree number, so trees and sites never collide) and a core designator, and return the mapping dpl.rwi_stats() and dpl.sss() use to know which cores belong to the same tree. Two parsing modes: the pattern default (stc=None) splits on letter/digit boundaries (leading letters = site, first digit run = tree, trailing characters = core); an STC mask (stc=(site_len, tree_len[, core_len])) splits by fixed character positions.

IMPORTANT: the pattern default cannot detect a DIGIT core. A name like "CAM031" is read as tree "031" with an empty core (no warning), leaving "CAM031" and "CAM032" as separate trees instead of grouping them under "CAM03". For digit cores, or site codes that contain digits, supply an explicit stc mask (e.g. stc=(3, 2, 1) for "CAM031").

Parameters:

Name Type Description Default
data pandas dataframe or iterable of str

a dataframe whose column names are the series names (e.g. from dpl.readers()), or any iterable of series-name strings.

required
stc tuple of int, or None

None to use the pattern default; otherwise a (site_len, tree_len) or (site_len, tree_len, core_len) character mask.

None

Returns:

Name Type Description
ids pandas dataframe indexed by series name, with columns 'tree' and

'core'. Pass this directly as the ids argument of dpl.rwi_stats(), dpl.rwi_stats_running(), or dpl.sss().

Examples:

>>> import dplpy as dpl
>>> data = dpl.readers("../tests/data/csv/file.csv")
>>> ids = dpl.read_ids(data)                 # letter-core names
>>> ids = dpl.read_ids(data, stc=(3, 2, 1))  # digit-core names (e.g. CAM031)
>>> dpl.rwi_stats(dpl.detrend(data, plot=False), ids=ids)
References

.. [1] https://rdrr.io/cran/dplR/man/read.ids.html

combine_rwl

Combine ring-width datasets on the union of their years (dplR combine.rwl).

Parameters:

Name Type Description Default
x pandas.DataFrame or list/tuple of DataFrames

Either the first dataset (with y the second), or a list/tuple of datasets to combine in order.

required
y DataFrame

The second dataset, when x is a single DataFrame.

None
warn_duplicates bool

Emit a warning if the combined frame ends up with duplicate series names (the columns are still kept, matching dplR -- this only flags them).

True

Returns:

Type Description
DataFrame

All series side by side, indexed by a contiguous year range spanning the earliest to the latest year of the inputs (years with no data are NaN).

Notes

Series columns are concatenated as-is: like dplR, duplicate series IDs are neither merged nor renamed. Deduplicate or rename beforehand if that matters for your workflow.

writers

Output dplpy datasets to .csv, .rwl and .crn files.

Write a dataframe to a .csv, .rwl, .crn or .txt file as set by format, created in the current directory unless a path is included in label. For 'csv'/'rwl', data is a ring-width dataframe (years x series); for 'crn', data is a chronology (from dpl.chron()) and a header of site metadata is REQUIRED to populate the Tucson .crn header.

Parameters:

Name Type Description Default
data pandas dataframe

ring widths (csv/rwl) or a chronology from dpl.chron() (crn).

required
label str

name (can include file path) to give the file; no extension.

required
format str

'csv', 'rwl', 'crn', or 'txt'.

required
header dict, required for format='crn'

site metadata with the keys: 'site_id', 'site_name', 'species_code', 'state_country', 'species', 'elevation', 'latitude', 'longitude', 'investigators'; optionally 'completion_date'. First/last year are taken from the chronology.

None
chronology_type str

for format='crn', the ITRDB chronology type written in header record 2: 'standard' (blank code), 'arstan' ('A'), or 'residual' ('R').

'standard'
column str

for format='crn', which chronology column to write (e.g. 'std', 'res').

'std'
prec float

for format='rwl', the measurement precision in mm: 0.001 (values x1000, end marker -9999) or 0.01 (values x100, end marker 999); the precision->marker mapping follows dplR's write.tucson. Note dplPy defaults to 0.001 (dplR defaults to 0.01) and encodes interior gaps via gaps.

0.001
gaps (int, split)

for format='rwl', how to encode a true interior gap (a NaN inside a series; a real 0 is a locally absent ring and is always written as 0): a negative integer (default -99) writes that sentinel in the gap (it must be negative so it is not read as a ring width, and not a stop marker; the reader turns it back into NaN), or "split" closes the block with the end marker and reopens a new block at the next present year.

int
sep

for format='txt', the column delimiter (default tab), printf float format (default '%.4f', None for full precision), and missing-value text (default 'NA'). For 'txt', data may be a DataFrame, a single Series, or a list of year-aligned Series.

'\t'
float_format

for format='txt', the column delimiter (default tab), printf float format (default '%.4f', None for full precision), and missing-value text (default 'NA'). For 'txt', data may be a DataFrame, a single Series, or a list of year-aligned Series.

'\t'
na_rep

for format='txt', the column delimiter (default tab), printf float format (default '%.4f', None for full precision), and missing-value text (default 'NA'). For 'txt', data may be a DataFrame, a single Series, or a list of year-aligned Series.

'\t'

Returns:

Type Description
None

summary

Summarize a ring-width dataset.

Reports per-series summary information for a dataframe of tree-ring widths.

Parameters:

Name Type Description Default
inp str or pandas dataframe

a data file (.CSV or .RWL) or a pandas dataframe imported from dpl.readers().

required

Returns:

Name Type Description
data pandas dataframe

Examples:

>>> import dplpy as dpl
>>> data = dpl.readers("../tests/data/csv/file.csv")
>>> dpl.summary(data)
References

.. [1] https:/opendendro.org/dplpy-man/#summary

report

Generates a report

Extended Summary

Generates a text report about the input dataset that includes: Number of dated series Number of measurements Avg series length (years) Range (total years) Span (start-end year) Mean (Standard Deviation) series intercorrelation Mean (Standard Deviation) AR1 Years with locally absent rings (zero values), with per-series and total counts Years with internal NA (missing measurements), with per-series and total counts

Parameters:

Name Type Description Default
inp str or pandas dataframe

a data file (.CSV or .RWL) or a pandas dataframe imported from dpl.readers().

required

Returns:

Type Description
None

Examples:

>>> import dplpy as dpl
>>> data = dpl.readers("../tests/data/csv/file.csv")
>>> dpl.report(data) 
References

.. [1] https:/opendendro.org/dplpy-man/#report

check

Check a ring-width collection for defects, reported as a table of findings.

The data counterpart to :func:report: report prints what a file looks like, check returns what is wrong with it, one row per finding. Every finding carries a stable check id and a severity ("error", "warning", "note"), and a check that fails becomes a RWL_CHECK_ERROR finding rather than stopping the run. Analog of dplR 1.8.0's rwl.check.

Parameters:

Name Type Description Default
x pandas.DataFrame, str, or list of str

a ring-width collection (readers output), a path to a Tucson file, or a list of paths / a directory for an archive sweep.

required
file str or None

label for the file column when x is a DataFrame.

None
checks list of str or None

which groups to run. Default all: structure, series, values, zeros, crossdating, file, provenance.

None
summary bool

if True, return one row per file (counts per check and per severity) instead of the findings. pd.concat these over an archive to rank it.

False
min_depth int
2
depth_run int
2
min_length int
2
run_min int
2
lag_max int
2
min_overlap int
2
spline_nyrs int
2
r_dating float
0.35
r_margin float
0.35
outlier_mad float
0.35
outlier_gap float
0.35
r_cohesion float
0.35
plausible_mean float
0.35
small_thresh Optional[float]

tunables; defaults follow dplR's rwl.check.control.

None
big_thresh Optional[float]

tunables; defaults follow dplR's rwl.check.control.

None
max_year Optional[float]

tunables; defaults follow dplR's rwl.check.control.

None

Returns:

Type Description
DataFrame

the findings (columns file, check, severity, group, series, year_from, year_to, n, value, message), or, with summary=True, one row per file.

References

.. [1] Bunn, A. G. (2026) dplR 1.8.0, rwl.check.

SiteMetadata

Site/sample metadata shared by dplPy's file exporters.

Field names mirror dpl.metadata() / df.attrs['dplpy_metadata']. Use :meth:from_rwl to auto-fill from a frame read by dpl.readers(), :meth:from_metadata from a metadata dict, or construct one directly and set fields by hand. :meth:to_crn_header renders the dict the Tucson .crn writer expects (which uses a few different key names).

from_metadata classmethod

from_metadata(meta: dict) -> SiteMetadata

Build from a metadata dict (dpl.metadata() output or the same shape). Unknown keys (e.g. n_header_lines) are ignored.

from_rwl classmethod

from_rwl(data) -> SiteMetadata

Build from a frame read by dpl.readers() (uses df.attrs['dplpy_metadata']), or from a metadata dict directly.

coerce classmethod

coerce(obj) -> SiteMetadata

Return obj if it is already a SiteMetadata, or build one from a metadata dict. Used by writers so they accept either form.

to_crn_header

to_crn_header() -> dict

Render the header dict the Tucson .crn writer expects. The writer uses a few different key names (state_country/species/ elevation); this maps them. Fields that are None are omitted, so the writer's own "missing required key" check reports what still needs filling rather than writing the literal 'None'.

read_tridas

Read a TRiDaS (Tree Ring Data Standard) XML file.

Parameters:

Name Type Description Default
filename str

path to a .xml TRiDaS file.

required
warn_units bool

warn when a measurement series uses a variable that is not a width in a length unit (e.g. a density), which dplPy does not convert to mm. Such series are kept out of the ring-width tables.

True

Returns:

Type Description
TridasProject

A typed container: .measurements (ring-width tables), .derived (chronologies), .undated (floating series), .metadata. For the common single-table file, dpl.readers() returns the ring-width DataFrame directly instead.

Notes

Missing values become NaN (not 0); units are converted to millimetres; years use the dplPy continuous integer axis. See dev/tridas_design_2026-10-14.md for the full design.

References

.. [1] Jansma, Brewer & Zandhuis (2010), Dendrochronologia 28, 99-130.

write_tridas

Write a TRiDaS 1.2.2 file from ring widths and/or a chronology.

Parameters:

Name Type Description Default
fname str

output path. .xml is appended if no extension is given.

required
rwl pandas DataFrame

ring widths (Year-indexed, series in columns), as from dpl.readers(). Written as measurementSeries under one object (site).

None
crn pandas DataFrame

a chronology from dpl.chron(); columns named samp_depth carry sample depth and pair with the preceding chronology column. Written as derivedSeries, unitless, with sample depth as each value's count.

None
metadata TridasMetadata or dict

project/site metadata. Mandatory fields missing with validate=True raise a ValueError naming them (type/investigator are filled with "unknown" where the standard allows).

None
prec float

measurement precision in mm (0.001, 0.01, 0.02, 0.05, 0.1, 1, 10, 1000). None (default) writes millimetres without rounding.

None
ids pandas DataFrame

tree/core[/radius/measurement] grouping, as from dpl.read_ids(). Default: each series is its own element (tree).

None
taxon str

taxon for the elements. Mandatory per the standard when writing rwl.

None
variable str

the measured variable (controlled vocabulary).

"ring width"
measuring_method str

free-text measuring method (mandatory per the standard).

"unknown"
validate bool

enforce the mandatory-field set.

True

Returns:

Type Description
str

the path written.

TridasProject

Parsed TRiDaS file.

Attributes:

Name Type Description
measurements dict of str -> DataFrame

Dated measurement series grouped into ring-width tables, keyed by "object | variable | unit | taxon". Each frame is Year-indexed with series in columns (the dplPy rwl shape) and carries df.attrs["kind"] = "rwl".

derived dict of str -> DataFrame

Chronologies (TRiDaS derivedSeries), keyed by series title. Each frame is Year-indexed with the chronology column and, when the file records per value counts, a samp_depth column; df.attrs["kind"] = "crn".

undated dict of str -> Series

Measurement series with no calendar years (floating series), keyed by series id. Position-indexed; feed these to dpl.xdate_floater().

metadata TridasMetadata

The full metadata hierarchy.

rwl

rwl() -> pd.DataFrame

Return the single ring-width table, or raise if the file holds more than one measurement table (then use :attr:measurements).

TridasMetadata

The project/object/element/sample/radius/series metadata of a TRiDaS file.

Project-level fields are named (they are the mandatory ones in Brewer's list and what a .crn-style export needs). The object hierarchy is kept as nested dicts under :attr:objects so the whole tree stays navigable without a deep class hierarchy. :attr:laboratories and :attr:extra capture the rest.