Feature engineering

Three model-input routes, two library surfaces: your own code (direct CLIF tables or clifpy wide matrices) and flair fe-meds.

ELF: Event Language Format is CLIF's event format for event-based modeling. It converts CLIF tables into a standardized clinical event stream with shared mCIDE codes, so models can use the same event vocabulary across sites. This is especially useful for transformer and sequence models: the tokens are stable, transformable, and portable, which supports cross-site generalizability. See ELF on GitHub. To see which ELF codes FLAIR sites actually record, and to match your own MEDS vocabulary against them, use vocabulary coverage (beta).

FLAIR supports three model-input routes: direct CLIF tables, wide time-series matrices assembled with clifpy, and Medical Event Data Standard (MEDS)-based event streams (ELF). Models trained on other data can be submitted if they accept inputs derived from the same clinical concepts. In the library, the first two are your own code over cohort.parquet (clifpy's ClifOrchestrator.create_wide_dataset builds the wide matrices); the event-stream route is flair fe-meds for medications, or the vendored CLIF→MEDS ETL for every domain. The cohort records a feature cutoff (feature_cutoff_dttm) for each prediction, and feature-building code must exclude later observations on every route: event conversion alone does not enforce this per-prediction restriction. Pick by how much control vs. convenience you want.

modehowoutputwhen to use
directyour own code over cohort.parquet + flair_benchmark.cohort.clif loaders / clifpywhatever you buildfull control; custom or novel features; all wide time-series
fe-medsflair fe-medsELF medication events - raw dose + raw unit, no conversionlightweight; only selected meds; you convert doses downstream

Wide time-series with clifpy

Vitals, labs, assessments and respiratory support go through clifpy directly; FLAIR does not wrap it. Pass every member hospitalization of each stitched hospitalization in the cohort (members_of_joins in flair_benchmark/cohort/stitch.py expands the cohort's hospitalization_join_ids), so sibling hospitalizations are not dropped:

from clifpy import ClifOrchestrator
from flair_benchmark.cohort.clif import read_clif_config

cfg = read_clif_config("clif_config.json")
# hosp_ids: members_of_joins(...) over cohort["hospitalization_join_id"]
co = ClifOrchestrator(data_directory=..., filetype="parquet", timezone=cfg["timezone"])
co.create_wide_dataset(
    tables_to_load=["vitals", "labs", "respiratory_support"],
    category_filters={"vitals": ["heart_rate", "spo2"], "labs": ["creatinine", "lactate"]},
    hospitalization_ids=hosp_ids,  # every member of each stitched hospitalization
    output_format="dataframe",
)
wide = co.wide_df
clifpy applies no leakage guard. Join cohort.parquet on hospitalization_join_id and keep only rows with event_time ≤ feature_cutoff_dttm for each prediction row.

fe-meds command

flair fe-meds \
  --feature-config feature_config.yaml \
  --task cohort.parquet \
  --out meds.parquet

It reads cohort.parquet (which must carry prediction_dttm and feature_cutoff_dttm; a cohort without the cutoff column is rejected - regenerate it with flair load-task), expands each hospitalization_join_id to all its member hospitalizations, loads CLIF via clifpy, cuts every event at the stitched hospitalization's latest feature_cutoff_dttm, and writes ELF parquet.

The per-row cut is yours. For a daily task, a stitched hospitalization's events run up to its last day's cutoff, so a row predicting on day 2 can see day 5's events. Before training, join on hospitalization_join_id and keep time ≤ feature_cutoff_dttm for each row.

ELF: Event Language Format

fe-meds emits one row per clinical event. Matching this shape in your own wide extraction keeps downstream feature code reusable across tasks and sites:

columntypenotes
subject_idstr= patient_id
timedatetime (tz-naive)≤ the stitched hospitalization's latest feature_cutoff_dttm
codestr<DOMAIN>//<category>//<unit>; just <DOMAIN>//<category> when a row has no dose and no unit
numeric_valuefloatnullable (numeric events)
unitstrrequired where numeric_value is set
text_valuestrnullable (categorical events)
hospitalization_idstrraw hospitalization key
hospitalization_join_idstrstitched-hospitalization (stay-level) key

feature_config.yaml

flair init-config scaffolds it. It drives fe-meds:

clif_config: clif_config.json
output_directory: ./output/features

# fe-meds: lightweight med stream, raw dose + raw unit, NO conversion
meds_stream:
  medication_admin_continuous:
    categories: [norepinephrine, propofol]
  medication_admin_intermittent:
    categories: [vancomycin, cefepime, piperacillin_tazobactam]

Categories are CLIF 2.1 mCIDE values; unknown ones warn but don't fail. A relative clif_config path resolves against the YAML file's own folder.

Units

fe-meds does no conversion at extraction: it keeps the raw per-row med_dose_unit (charted as "UNK" when a dose has no unit) in both the unit column and the code, so a downstream step can convert. For dose normalization, see the vendored ETL below, which wraps clifpy's convert_dose_units_by_med_category.

Beyond fe-meds: the vendored CLIF→MEDS ETL

The package also ships a full CLIF→MEDS ETL in flair_benchmark/meds_etl/ (build_meds_events), driven by per-domain concept YAMLs (VITAL, LAB, RESP, PA, CRRT, HOSP, HOSP_DX, PATIENT, MED_CON, MED_INT). It is a library-level API, not a flair command, and its interface is still moving; treat fe-meds plus clifpy as the stable path. Its output is MEDS-shaped rather than the ELF table above (no unit column), and it applies no per-row cutoff either.

From ELF to model features

The reference baseline turns ELF codes into a dense, point-in-time feature matrix. Before featurizing, codes are truncated to fixed levels so units and order-types don't leak in as separate features:

  • numeric codes → 2 levels: LAB//lactate//mmol/l//bmp → LAB//lactate
  • categorical codes → 3 levels: RESP//device_category//imv (unchanged)

Then, per prediction_id, only events from the same stitched hospitalization with time ≤ feature_cutoff_dttm are used, and each code becomes features by its role:

  • numeric codes (labs, vitals, respiratory values) → four columns: min, max, mean, median of the value (missing stays missing, not 0)
  • medications (MED_CON, MED_INT) → one event count
  • respiratory device / mode category → one flag: 1 if it ever occurred

One extra feature records the hours from the hospitalization's first event to the prediction time. The reference models use one fixed vocabulary, built from MIMIC-CLIF and shared by every task and site, so every model has the same input columns.