convert_to_subject_sharded
Stage: convert event-sharded raw data into subject-sharded format.
This is the second half of the initial ingestion phase. After convert_to_parquet
has normalized each raw source table to parquet, this stage re-groups rows by
subject: for every (split, table) pair, it reads every
file of that table, applies the table’s subject_id expression (and
any join it needs), filters down to the rows whose subject is in the split,
and writes the result to <split>/<table>.parquet.
For example, with a vitals table joined to stays on stay_id:
.. code-block:: text
data/vitals/[0-2).parquet ─┐
data/vitals/[2-4).parquet ─┼─► data/train/0/vitals.parquet
data/vitals/[4-6).parquet ─┘ (vitals rows for training subjects, with
the joined subject_id materialized)
Each shard is independent, so this stage parallelizes trivially across
(split, table) pairs.
_filter_to_subjects(df, *, table, subjects)
Filter df to the rows whose subject_id is in subjects.
Uses the table’s subject_id_polars_expr inline in filter so that
the output keeps its original source columns untouched — no new
materialized subject_id column gets added.
Source code in MEDS_extract/convert_to_subject_sharded/convert_to_subject_sharded.py
_read_and_join(fps, *, table, input_dir)
Scan the given subshards and apply the table’s join (if any).
This is a straight read — no filtering — so a row-level rwlock
wrapper can reuse the same function across multiple stages. Subject
filtering lives in :func:_filter_to_subjects on the compute side.
Source code in MEDS_extract/convert_to_subject_sharded/convert_to_subject_sharded.py
main(cfg)
Re-shard raw data by subject. See module docstring for details.
All arguments come through the Hydra cfg object; this stage has no
stage-specific options beyond the global MESSY_config_fp.
Source code in MEDS_extract/convert_to_subject_sharded/convert_to_subject_sharded.py
sink_df(df, out_fp)
Atomically sink a lazy plan to parquet on polars’ streaming engine.
The drop-in replacement for MEDS-transforms’ eager write_df on this stage —
same .tmp + os.replace atomicity — but the plan executes streamed, so
scan → join → subject-filter pipelines in chunks and peak memory is O(rows
written to this shard), not O(full joined table) — measured 5.2x lower on a
30M-row table, with the eager path’s exact row order. Order determinism is
load-bearing and doubly pinned: the ordered join in JoinConfig.apply
(maintain_order="left_right") plus maintain_order=True here — without
both, streaming execution reorders nondeterministically and merge’s stable sort
would propagate that order into the final MEDS bytes for same-time events.