clinfolio

Open source · Python · Apache-2.0

Clinical data at scale, with its metadata intact.

clinfolio stores CDISC datasets in Parquet, Avro and HDF5, and keeps labels, codelists, origins and lineage attached through every filter, join and derivation.

$ pip install clinfolio  (coming soon)

Pre-release. The name is reserved; the first version is in development.

Three formats, one metadata model

Each format stores the same CDISC metadata natively, so any Arrow-aware tool can read it and conversion between formats is lossless.

Parquet

For analysis at scale

Columnar SDTM and ADaM, partitioned by study, subject or parameter. Queries read only the columns and row groups they need.

Avro

For streaming and ingestion

Row-based records for EDC, eCOA and device feeds, validated on arrival, with schema evolution tied to define.xml versions.

HDF5

For signals and dense arrays

PSG, actigraphy, ECG and glucose waveforms stored beside tabular SDTM, chunked and compressed, with links back to subject records.

Process a billion rows without losing the define

clinfolio adds a CDISC layer on top of polars and DuckDB. Work stays lazy and out of core, and every derived column records where it came from.

import clinfolio as cf

advs = cf.scan("advs/")          # lazy, nothing loaded
adsl = cf.scan("adsl.parquet")

out = (advs
  .filter(cf.col("PARAMCD") == "SYSBP")
  .merge_adsl(adsl, ["TRT01A", "SAFFL"])
  .derive_change(base_flag="ABLFL")
  .window_visits(spec="windows.yaml"))

out.write_parquet("advs_sysbp/", partition_by=["AVISITN"])
out.lineage().to_define("define.xml")

Lineage for CHG

VS.VSSTRESNCollected · Vital Signs
ADVS.AVALPredecessor · Analysis Value
ADVS.BASEDerived · AVAL where ABLFL = "Y"
ADVS.CHGDerived · AVAL − BASE, post-baseline visits

What happens to metadata in each operation

Propagation rules are written down and tested, so the output always has a complete, correct specification.

OperationLabels and typesKeys and sort orderOrigin and lineage
filterKept unchangedKept; row count updatedFilter condition recorded
merge_adslCarried from ADSLLeft dataset's keys keptPredecessor: ADSL.variable
derive_*Set from the methodUnchangedDerived, with the method text
unionChecked for conflicts; stops on a mismatchRe-sorted by keysBoth sources recorded
write_*Written natively to the formatRow groups sorted by keysContent hash added

From raw signal to analysis value, traceably

Device data rarely fits in a SAS dataset. clinfolio keeps waveforms in HDF5 and links every derived measure back to the samples it came from.

1 · Store

Raw waveform

EDF or device export imported with sampling rate, units and start time.

2 · Epoch

30-second epochs

Resampled, time-aligned to the dosing clock, and scored or summarized.

3 · Link

SDTM and ADaM rows

Summary measures become records with a link back to the source epochs.

USUBJID PARAMCD AVAL SRC 101-001 SLPEFF 87.2 EEG_C3[0:960]

Roadmap

The first release focuses on one thing: CDISC metadata that survives processing.

  1. Parquet and metadataMetadata model, Parquet codec, XPT and Dataset-JSON bridges
  2. Lazy frameMetadata propagation and core CDISC operations
  3. Validate and lineageChecks that scale, plus column lineage
  4. Avro and streamingValidated ingestion and incremental rebuilds
  5. HDF5 and signalsWaveforms, epochs and PSG examples
  6. BenchmarksResults at 10⁸–10⁹ rows and a proposal to CDISC