Skip to main content

eICU MEDS Extraction ETL

PyPI - Version Documentation Status Static Badge codecov tests code-quality python license PRs contributors DOI

This repository contains the code for downloading the eICU dataset from PhysioNet and transforming it into the Medical Event Data Standard (MEDS) format.

pip install eICU-MEDS
export DATASET_DOWNLOAD_USERNAME=$PHYSIONET_USERNAME
export DATASET_DOWNLOAD_PASSWORD=$PHYSIONET_PASSWORD
MEDS_extract-eICU root_output_dir=data/eicu_meds

When you run this, the program will:

  1. Download the raw eICU-CRD tables this ETL reads (see Which eICU tables are extracted) for the currently supported version into $ROOT_OUTPUT_DIR/raw_input (via MEDS-Extract's download layer; files that already exist and verify against PhysioNet's SHA256SUMS.txt are skipped, so an interrupted run resumes rather than restarting).
  2. Construct the MEDS cohort directly from those raw files — every transformation, join, and pseudotime derivation is declared in src/eICU_MEDS/configs/event_configs.yaml — and write it to $ROOT_OUTPUT_DIR/MEDS_output.

The public demo needs no credentials:

MEDS_extract-eICU root_output_dir=data/eicu_demo do_demo=True

You can also point the raw and output directories somewhere else directly. root_output_dir is still required, since the intermediate pre_MEDS tables and the logs are written under it:

MEDS_extract-eICU root_output_dir=$ROOT_OUTPUT_DIR \
    raw_input_dir=$RAW_INPUT_DIR MEDS_output_dir=$MEDS_OUTPUT_DIR

A note on time in eICU

eICU contains no absolute timestamps. Every table records an integer offset in minutes relative to unit admission, and health-system stays are ordered only at year granularity. This pipeline therefore:

  • uses the health system stay (patienthealthsystemstayid) as the MEDS subject_id, since events are well ordered only within a stay; and
  • anchors each stay at a constant, arbitrary pseudo-date (December 31 of the recorded discharge year, at the recorded discharge clock time) and derives every event time by offsetting from it.

Only relative time differences within a subject are meaningful. Absolute dates in the output are not real and must not be interpreted as such.

Death

MEDS_DEATH is emitted at hospital discharge when hospitalDischargeStatus == "Expired", and from no other field. The documentation describes Expired as the patient's condition on leaving the hospital, and on the full release it agrees with the independently populated hospitalDischargeLocation == "Death" on every one of the 200,859 rows (18,004 deaths).

Hospital rather than unit status is the key because it is the more complete of the two: 7,315 rows record a unit discharge Alive followed by a hospital death, which unit status would miss.

The converse is deliberately not used. 222 rows (0.1%) have unitDischargeStatus == "Expired" while hospital status is Alive or blank, and these emit no MEDS_DEATH. They contradict themselves: in 142 of them the hospital stay continues past the unit discharge at which the patient supposedly died, some to a discharge home. Rows with a blank hospital status (1,751) are likewise not treated as deaths. See #11.

Vital signs: only measurements that happened

vitalPeriodic and vitalAperiodic are wide tables — one row per timestamp, one column per vital sign — and most cells are empty. A row that recorded a heart rate still carries columns for intracranial pressure, end-tidal CO2 and the pulmonary-artery pressures, which need invasive lines that most patients never have.

Each vital's code is therefore guarded so that a missing cell emits no event, rather than an event asserting the measurement was taken with no value attached. Without the guard the full cohort carries 2.60 billion vitals rows of which 69.8% are empty; with it, 785 million, all of them real. Per-vital emptiness in the raw data ranges from 0.5% (heart rate) to 98.2% (intracranial pressure).

The guard is written if $x == $x, which is true for any present value — including 0, inf and NaN — and null for a missing one, which nulls the code and drops the row. It reads oddly because dftly has no null test; if $x will not type-check against a float column and a bare null parses as the string "null". Tracked in dftly#113; this becomes if is_present($x) once that lands.

NaN and inf are deliberately preserved rather than dropped — they are recorded values, and changing that is a data-modelling decision rather than a bug fix.

Numeric-value guards

numeric_value is strict-cast downstream, so a String source column containing a single non-numeric row aborts the stage. Which columns are String is not inferable from the demo — it depends on what junk happens to appear — so the guarded set comes from the official eICU DDL (MIT-LCP/eicu-code, postgres_create_tables.sql): age, medication.dosage, and infusiondrug.{infusionrate, patientweight}. Numeric-typed columns are passed through bare, since the schema forbids junk in them.

The guard regex is exactly Rust's f64::from_str grammar, so it is value-for-value identical to polars' non-strict cast. A narrower hand-rolled pattern silently drops real values such as 1e5, 25. and +5; tests/test_numeric_guard.py pins the equivalence by differential fuzzing.

Which eICU tables are extracted

eICU-CRD v2.0 ships 31 tables. This pipeline reads 15 of them. The rest are excluded deliberately — the reasoning is recorded here rather than in a code comment, because "this table is missing" is otherwise indistinguishable from an oversight.

Extracted: patient, hospital (joined for site attributes), admissionDx, allergy, carePlanEOL, carePlanGeneral, carePlanGoal, carePlanInfectiousDisease, diagnosis, infusionDrug, lab, medication, treatment, vitalAperiodic, vitalPeriodic.

Excluded — deliberate, on the merits:

Table Rows Why not
admissionDrug 874,920 Documented as "extremely infrequently used".
apacheApsVar 171,177 Inputs to the APACHE score. We prefer the raw measurements they were computed from.
apachePatientResult 297,064 Pre-computed APACHE scores and predictions — derived values, not observations.
apachePredVar 171,177 Further APACHE inputs; same reasoning.
carePlanCareProvider 502,765 Cannot be linked to the specific care-plan entries it describes, and its offsets record when a provider was entered into the plan rather than any clinical event.
customLab 1,082 Documentation is very sparse, and it holds only the lab measurements that could not be mapped to the standard set in lab.
intakeOutput 12,030,289 The documentation carries significant warnings about duplicate and cumulative values. Excluded until those are handled properly rather than silently.
microLab 16,996 Time leakage: culture-taken time is not culture-result time, so a model would see organism/sensitivity results before they could exist. Also documented as poorly populated.
note 2,254,179 Largely duplicative of the structured tables — narrative notes were mostly removed for PHI reasons.

Excluded — not yet done, not on the merits. These carry real clinical signal and were stubbed out but never finished (the original config shipped commented-out blocks for them marked NOT YET DONE, because their cell-label/value structure needs a code scheme of its own):

Table Rows Contains
nurseCharting 151,604,232 Nursing-charted observations, including vitals not in vitalPeriodic. By far the largest omission.
respiratoryCharting 20,168,176 Ventilator and respiratory observations.
nurseAssessment 15,602,498 Structured nursing assessments.
physicalExam 9,212,316 Physical examination findings.
nurseCare 8,311,132 Nursing care entries.
pastHistory 1,149,180 Comorbidity / prior history, as a /-separated hierarchy.
respiratoryCare 865,381 Ventilator settings and airway management.

MEDS-transforms settings

If you want to convert a large dataset, you can use parallelization with MEDS-transforms (the MEDS-transformation step that takes the longest).

Local parallelization uses the hydra-joblib-launcher package, which is installed as a dependency of eICU-MEDS. You can set the number of workers as an environment variable:

export N_WORKERS=8

The number of subjects per shard is fixed at 1,000 in the split_and_shard_subjects stage of src/eICU_MEDS/configs/ETL.yaml; no environment variable or command-line option changes it. Other MEDS ETLs do the same (MIMIC-IV fixes it in its spec's etl: block): MEDS-Extract treats shard size as a property of the dataset, while worker counts belong to the machine running it. To use a different shard size, edit that file in a local checkout (see Development).

Development

Contributors should use uv to set up a development environment from the lockfile:

git clone https://github.com/Medical-Event-Data-Standard/eICU_MEDS.git
cd eICU_MEDS
uv sync --group dev
uv run pre-commit install
uv run pytest src/ tests/ --doctest-modules

The end-to-end test (tests/e2e_demo_test.py) downloads the public eICU demo from PhysioNet, so it is slow on a first run.

Metadata

Release files for eICU-MEDS 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for eICU-MEDS 0.1.0
File Size Uploaded Provenance Actions
eicu_meds-0.1.0.tar.gz 188.8 kB With provenance Details for eicu_meds-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for eICU-MEDS 0.1.0
File Interpreter ABI Platform Actions
eicu_meds-0.1.0-py3-none-any.whl Python 3 none any Details for eicu_meds-0.1.0-py3-none-any.whl

Total release size: 212.1 kB

Release files / eicu_meds-0.1.0.tar.gz

Download URL eicu_meds-0.1.0.tar.gz
Size 188.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f90843a8c275b87a59e67585aaead0261e666761ce1dad6cf358618b5de7c323
BLAKE2b-256 checksum
How to use checksums
32944755333785a30d54ca9161785f82b4ac5e56f0765d7c8d9e29efb9b5ffa8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / eicu_meds-0.1.0-py3-none-any.whl

Download URL eicu_meds-0.1.0-py3-none-any.whl
Size 23.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dce6e950444aba30e40d367c46d7269f29537b62adb91aee994e6fb18f51567f
BLAKE2b-256 checksum
How to use checksums
323c03fc8fa7d066d3aad66278f7494ef61f7ed24bf54c865d7e4c679056102f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for eICU-MEDS 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for eicu_meds-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page