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.
| mode | how | output | when to use |
|---|---|---|---|
| direct | your own code over cohort.parquet + flair_benchmark.cohort.clif loaders / clifpy | whatever you build | full control; custom or novel features; all wide time-series |
| fe-meds | flair fe-meds | ELF medication events - raw dose + raw unit, no conversion | lightweight; 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_dfcohort.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.parquetIt 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.
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:
| column | type | notes |
|---|---|---|
subject_id | str | = patient_id |
time | datetime (tz-naive) | ≤ the stitched hospitalization's latest feature_cutoff_dttm |
code | str | <DOMAIN>//<category>//<unit>; just <DOMAIN>//<category> when a row has no dose and no unit |
numeric_value | float | nullable (numeric events) |
unit | str | required where numeric_value is set |
text_value | str | nullable (categorical events) |
hospitalization_id | str | raw hospitalization key |
hospitalization_join_id | str | stitched-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,medianof 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.