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 or path - like

a data file (.CSV, .RWL or .RAW). May be a local path (str or pathlib.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 site-metadata header sits at the top of the file. None auto-detects it the way dplR does (any number of leading non-data lines, up to 3). False reads every line as data. True asserts a header is present: the auto-detected header is used, and when none is detected the standard 3-line ITRDB header is skipped anyway (dplR's header=TRUE).

None
skip_lines int

how many of the first PHYSICAL lines of the file to skip before anything else (blank and comment lines count). The skipped lines are recorded on df.attrs["dplpy_skipped_lines"].

0
format (None, tucson, csv, tridas, pos)

force the file format regardless of suffix (case-insensitive). None infers it: .csv as CSV, .rwl/.raw as Tucson, .xml as TRiDaS, .pos (or a directory of them) as CooRecorder, 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(). A CooRecorder .pos file (or a directory of them) is read into one whole-ring DataFrame; use dpl.read_pos() for the full result (earlywood/latewood, attributes, coordinates).

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"]. Applies to URLs too.

None
verbose bool

print the one-line success summary (file, format, series count, period) and the report of merged/split duplicate IDs. Advisories about the file (a truncated value, a short interior gap, case-variant IDs, an assumed encoding) are always issued as warnings, never printed.

True

Returns:

Name Type Description
data pandas dataframe

Year-indexed ring widths, df.attrs["kind"] == "rwl". Non-fatal advisories about the file are attached to df.attrs: dplpy_beyond_column (first 50; total in dplpy_beyond_column_n), dplpy_interior_gaps (per series, year lists capped with n_ totals), dplpy_case_merged, dplpy_case_variant_ids, dplpy_blank_lines and dplpy_skipped_lines ({"n", "first"}), dplpy_encoding and dplpy_precision (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.github.io/dplPy/reference/io/#readers

readers_url

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

Deprecated: readers() accepts an http(s) URL directly, so dpl.readers(url) does the same thing. This thin wrapper is kept for backward compatibility: it emits a DeprecationWarning and forwards every keyword (header, skip_lines, strict, join, encoding, format, verbose) 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 (str or pathlib.Path) or an http(s) URL. header and skip_lines mean what they mean in readers() (skip_lines counts physical lines). The same result is captured at read time on df.attrs['dplpy_metadata'].

read_crn

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

Parameters:

Name Type Description Default
filename str or path - like

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, as are BC chronologies in dplR's layout (a 5-character site ID with the year's sign in column 6).

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
verbose bool

print the one-line success summary. Advisories are warnings regardless.

True

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 kind == "crn", 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.github.io/dplPy/reference/io/#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). attrs are carried from the first input (kind == "rwl"); the per-series dplpy_precision dicts of all inputs are merged.

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, .crn, .txt and .tridas (.xml) files.

Write a dataframe to a 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', 'txt' or 'tridas' (case-insensitive).

required
header dict or SiteMetadata, 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. Coordinates may be decimal degrees (packed to the ITRDB DDMM/DDDMM field) or already-packed strings; elevation in metres.

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 or None

for format='rwl'/'tridas', the measurement precision in mm. None uses the precision the data were read at (df.attrs["dplpy_precision"]; mixed precisions raise, pass prec explicitly), else 0.001 for rwl (values x1000, end marker -9999; 0.01 writes x100 with a 999 marker, the dplR mapping) and unrounded millimetres for tridas.

None
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
str

the path of the file written.

References

.. [1] https://opendendro.github.io/dplPy/reference/io/#writers

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.github.io/dplPy/reference/io/#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
DataFrame

the per-series statistics table the report is built from (the output of dpl.stats()), so the printed report is also available as data.

Examples:

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

.. [1] https://opendendro.github.io/dplPy/reference/io/#report

check

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

The data counterpart to 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"). Analog of dplR 1.8.0's rwl.check.

The crossdating group slides the leave-one-out master past each series over -lag..+lag (xdate._lag_table()), so a RWL_DATING_LAG with a NEGATIVE lag means a MISSING ring in the series, as in COFECHA, xdate and series_corr.

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 int
2
min_overlap int
2
spline_period 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 except lag (10, dplPy's package-wide lag search) and spline_period (32, the high-pass spline). lag_max= and spline_nyrs= are accepted as deprecated aliases.

None
big_thresh Optional[float]

tunables; defaults follow dplR's rwl.check.control except lag (10, dplPy's package-wide lag search) and spline_period (32, the high-pass spline). lag_max= and spline_nyrs= are accepted as deprecated aliases.

None
max_year Optional[float]

tunables; defaults follow dplR's rwl.check.control except lag (10, dplPy's package-wide lag search) and spline_period (32, the high-pass spline). lag_max= and spline_nyrs= are accepted as deprecated aliases.

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.

Notes

Every check group runs inside a guard: an exception inside a group (or in reading the file) is converted to a RWL_CHECK_ERROR finding carrying the error text, so one failing check never hides the others' findings and a sweep over an archive never stops on one bad file (audit G4). Check the engine group of the result to see whether anything failed to run. A misdated series may draw both RWL_DATING_LAG and RWL_SERIES_OUTLIER (its lag-0 correlation is low because of the lag); both are kept.

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 from_rwl() to auto-fill from a frame read by dpl.readers(), from_metadata() from a metadata dict, or construct one directly and set fields by hand. to_crn_header() renders the dict the Tucson .crn writer expects (which uses a few different key names). Coordinates are decimal degrees and elevation is metres; the .crn writer packs them into the ITRDB fields (DDMM / DDDMM, "%dM") itself.

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. Values are passed through unchanged (decimal degrees, metres); write_crn packs them into the ITRDB fields via pack_crn_coordinates() / pack_crn_elevation(). 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) uses the precision rwl was read at (rwl.attrs["dplpy_precision"], when every series shares one); else millimetres without rounding (lossless; mixed precisions warn).

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 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 objects so the whole tree stays navigable without a deep class hierarchy. laboratories and extra capture the rest.

read_pos

Read CooRecorder .pos file(s) into a PosCollection.

path may be a single .pos file or a directory tree (every .pos under it is read and stacked into one ring-width frame). Series are named by file stem.

PosCollection

Result of read_pos(): whole-ring widths plus the pieces a .pos file carries that a Tucson .rwl drops. Mirrors TridasProject's role for TRiDaS.