Skip to content

Reading & writing data

dplPy reads the standard tree-ring file formats into a pandas DataFrame indexed by year, with one column per measurement series — the shape every other function expects. This guide covers loading ring widths, loading chronologies, combining collections, and writing data back out.

Ring-width files with readers

readers loads Tucson (.rwl), CSV, and compact-format ring-width files:

import dplpy as dpl

rwl = dpl.readers("ca533.rwl", header=True)
ca533.rwl successfully extracted as rwl file with 34 series covering the period from 626 to 1983

Key arguments:

  • header — set True when a Tucson file carries the three ITRDB header lines, so they are read as metadata rather than data.
  • strict (default True) — mirror dplR's strict validation: dplPy warns and then raises on malformed rows, naming the offending series and year. Set strict=False to fall back to a best-effort parse that returns None on unrecoverable files instead of raising.
  • join (default True) — when a series ID appears in more than one block, join the pieces into a single column (including blocks split by a mid-file letter-case change in the ID; see Case-flipped series IDs below). dplPy reports what was merged.
  • format — normally auto-detected; override with "csv", "rwl", etc. if needed.

To load directly from a URL (for example an ITRDB file), use readers_url:

rwl = dpl.readers_url("https://www.ncei.noaa.gov/pub/data/paleo/treering/.../ca533.rwl")

NOAA template files

dplPy recognizes the *-noaa.rwl NOAA-template files and refuses them with an informative error — they are not decadal Tucson data. Use the plain .rwl file (the same name without -noaa).

Data-quality heads-ups

Some files parse cleanly yet contain small oddities a human should look at. readers reads them successfully and prints a short, non-fatal heads-up (in both strict and salvage mode), with the full detail left on df.attrs for programmatic use:

  • Characters beyond the last data column. The ten decadal values occupy fixed columns 13–72; column 73 is a reserved blank separator before the optional site-ID field (columns 74–78). A non-blank character in column 73 means the last value overflowed its six-character field and was truncated — for example a 3035 written with three leading spaces leaves 5 past the column, so only 303 is read. dplPy flags these (a human must decide whether the true value was 3035 or the extra digit was a stray keystroke); content that begins at the site-ID columns (74+) is not flagged, since that is standard. Detail is on df.attrs["dplpy_beyond_column"].
  • Short interior gaps. A blank year (or two) sitting between a series' measured years — a hole in an otherwise continuous run — is often a value dropped in data entry, so dplPy flags it. Long interior gaps (many blank years in a row) are almost always intentional (a rotten or unmeasurable section, or two disjoint dated segments) and are only recorded, not announced. Every interior gap, short or long, is on df.attrs["dplpy_interior_gaps"].

These are advisories, not errors: the returned DataFrame is complete, and the missing years appear as NaN.

Case-flipped series IDs

Some files write one core's ID with inconsistent letter case partway through (e.g. FDD08A for the older decades, then FDD08a). A case-sensitive read would split that into two separate series. When the case variants have disjoint years and one block is missing its stop marker — together a strong sign of a single core split by a case slip — dplPy merges them back into one series under join=True (the default) and records it on df.attrs["dplpy_case_merged"]. The merge also resolves the spurious "no stop marker" precision warning that the unterminated block would otherwise raise.

Case variants that are not merged — under join=False, or when both blocks are already properly terminated (so they may be genuinely distinct cores) — are kept separate and listed on df.attrs["dplpy_case_variant_ids"] so you can decide.

Chronology files with read_crn

read_crn reads Tucson chronology (.crn) files — the standardized site chronologies produced by ARSTAN and friends:

crn = dpl.read_crn("ca533.crn")

It handles the standard single-chronology ITRDB file, the "combined" multi-block files (an ARSTAN run's stacked std/res/ars chronologies for one site), and files that concatenate many sites' chronologies. Bunched negative (BC/BCE) years parse correctly, so long chronologies read as expected. Use split_by_site=True to return separate frames per site block.

Combining collections with combine_rwl

combine_rwl merges ring-width datasets onto a common year index, following dplR's combine.rwl (duplicate series IDs are kept as-is, not renamed):

both = dpl.combine_rwl(site_a, site_b)

Writing data with writers

writers writes a ring-width or chronology DataFrame back to disk. Specify the output format — "rwl" (Tucson), "crn", or "csv":

dpl.writers(rwl, label="ca533_clean", format="rwl", prec=0.001)

For .rwl output, prec sets the measurement precision (0.001 writes values ×1000 with a -9999 end marker; 0.01 writes ×100 with a 999 marker), and gaps controls how a true interior gap (a NaN inside a series' span) is encoded. See the writers reference for the full option set.

Quick metadata

metadata extracts the ITRDB header fields (site, species, investigators, location) from a headered Tucson file without loading the measurements, and summary/report give per-series overviews. See the Input & output reference.