OMOP or MEDS for Patient World Models? Time, Actions and Meaning in Three MIMIC-IV Conversions

Xinye Yang · · 10 min read

TL;DR. OMOP CDM tells you what an event is, in concepts that mean the same thing across hospitals. MEDS tells you in what order things happened to one patient, in the shape a sequence model reads. A patient world model needs both, plus three things neither format guarantees: when each fact became available, which events were actions rather than observations, and a way back to the source row. The format matters less than the decisions the converter made. In MIMIC-IV the same diagnosis is dated at ED registration or admission by the OHDSI ETL and at discharge by the MEDS community's ETL and by EHR2Trace. On the full MIMIC-IV, back-filling diagnoses to admission moved an in-hospital mortality model from AUROC 0.829 to 0.965 in validation, and the back-filled model fell to 0.642 on realistic inputs.


What a patient world model asks of its data

A patient world model learns how a patient's state evolves, and how it responds to what clinicians do. Trained on EHR event streams, it is asked to roll a patient forward from a point in time, sometimes under a different action. That puts five demands on the data:

  1. Time that means what the model will assume. At inference the model sees only what existed at the decision point. Training inputs must obey the same rule.
  2. Actions separated from observations. An order, a dispense and an administration are three different events. A counterfactual question ("what if the drug was not given?") needs the administration, not the order.
  3. Meaning that survives a change of hospital. A model that learns "item 50912" has learned one lab's creatinine, not creatinine.
  4. Values with units you can trust. A token that sometimes means g/L and sometimes % teaches the model noise.
  5. A way back to the source. When a rollout looks wrong, you need to find the rows that produced it.

The two formats, briefly

OMOP CDM 5.4 is a relational model for observational research. Facts live in domain tables (condition_occurrence, drug_exposure, measurement, and so on), are attached to visits, and are mapped to standard concepts (SNOMED, LOINC, RxNorm) so that a cohort definition written once runs at any site. Each row has a type concept that records where the information came from: for drugs, 32838 is an EHR prescription, 32825 an EHR dispensing record, 32818 an EHR administration record.

MEDS is a minimal event stream for machine learning: subject_id, time, code, and optional numeric_value and text_value, in Parquet shards where each subject sits in one shard, sorted by time. time may be null for static facts. The schema is open, so extension columns are allowed. Code metadata can carry parent_codes that point to external vocabularies, including OMOP. Tasks are defined as labels with a prediction_time, the inclusive end of the data a model may use, often extracted with ACES.

NeedOMOP CDM 5.4MEDS 0.4
Event order per patientReconstructed by joining tablesNative: one sorted stream per subject
When a fact became availableNo field for itNo field for it; time is what as-of joins use
Action vs observationDrug type concept (prescription, dispense, administration)Only what the code string or an extension column says
Cross-site meaningStandard concepts, the format's main strengthSource codes by default; parent_codes can link to OMOP
Source row*_source_value keeps the value, not the rowOnly if an extension column carries it
Task definitionCohorts in ATLASLabels with prediction_time, ACES

Neither format has a field for availability. Both leave it to the converter.

Same fact, different timestamps

MIMIC-IV assigns diagnosis codes at discharge and gives them no time of their own. A converter has to choose one, and the three open-source converters do not all agree:

ConverterDiagnosis timeLab result time
OHDSI MIMIC ETL (OMOP)ED registration, or admission if none (condition_start_datetime)Specimen collection (charttime)
MIMIC_IV_MEDS 0.2 (MEDS)DischargeTwo events: collection at charttime, the result at storetime
EHR2Trace (MEDS and OMOP)Discharge, with the admission time kept beside it for auditOne event: time at collection, available_time at the earliest store time

The OHDSI choice is reasonable for what OMOP is for: a condition that was present during a stay is dated to the stay, and cohort studies ask whether a patient had it, not when it was coded. For prediction it is a leak, and so is dating a lab result at collection: the value was not visible until the lab stored it, often hours later. A MEDS view built from that OMOP database would place every discharge diagnosis at the start of the admission, and an as-of join at hour six would hand the model the answer.

We measured the cost on the full MIMIC-IV (131,007 patients, in-hospital mortality predicted six hours after admission). Trained and tested with diagnoses back-filled to admission, the model scored AUROC 0.965. Trained and tested on inputs restricted to what was available, it scored 0.829. The back-filled model, tested on the realistic inputs, scored 0.642. The first number is the one a paper would report; the last is the one a hospital would get.

Two ways to encode availability

The MEDS ETL and EHR2Trace both avoid the leak, in different ways. The difference matters for a world model.

For diagnoses the two agree: MIMIC-IV says nothing about when a code was decided, so both place it at discharge rather than date a diagnosis before the evidence for it existed. They differ for everything that has two real times, such as labs.

Move the event to when it became known. MIMIC_IV_MEDS splits each lab into a collection event (no value) and a result event at store time. Every MEDS tool handles this correctly, because time already means "visible from". The cost is the clinical timeline: the value is attached to when it was reported, not to when the blood was drawn, so a model learning physiology sees it shifted by the lab's turnaround time, which varies by test and by hour.

Keep the event time and add an availability time. EHR2Trace writes time as when the event happened and available_time as when it could first be seen; where the source gives no separate time, the two are equal and the row is flagged as assumed. In the MIMIC-IV demo, 71% of events have an availability later than their event time. This keeps the dynamics a world model is supposed to learn, and lets you build inputs with a strict as-of filter. The cost is that every downstream tool has to know about the extra column. A tool that reads only time, which is how standard MEDS task extraction works, will leak.

The second form converts to the first with one rewrite, so you can keep both:

import polars as pl

events = pl.scan_parquet("work/mimiciv/meds/data/**/*.parquet")
as_known = (
    events
    .with_columns(
        event_time=pl.col("time"),
        time=pl.coalesce("available_time", "time"),   # visible-from time
    )
    .sort(["subject_id", "time"])
)
# Write as_known out as shards for ACES or any tool that filters on `time`.

Use the as-known view for task extraction and baselines. Use the two-time view when the model's job is to learn what happens to the patient, not only what the record shows.

Actions are not observations

Medications show how much the converter decides. On the 100-patient MIMIC-IV demo:

ConversionMedication records
OHDSI ETL (OMOP)18,229 drug_exposure rows, all from prescriptions, all typed 32838 (EHR prescription)
Published MIMIC-IV demo in MEDS (January 2025, MEDS-Transforms 0.0.9)63,696 medication events; whether each was an order or an administration has to be inferred from which ID column is filled
Current MIMIC_IV_MEDS 0.2 configeMAR administrations at chart time, with the action text in the code; pharmacy start and stop events
EHR2Trace17,498 orders, 15,225 dispenses and 81,638 administrations, each with an event_kind

For a cohort study, "this patient was prescribed vancomycin" is often the right grain. For a world model it is the wrong one. A prescription is an intention; an administration is the intervention; an administration recorded as not given is information about the patient. A model trained on prescriptions alone learns that drugs act at the moment of the order, and a counterfactual that removes the order removes nothing that happened.

If your data has only one medication stage, say so in the task definition. If it has several, keep them as separate events and let the action be the administration.

Meaning across hospitals

This is where OMOP earns its keep. In the OHDSI demo conversion, 98.3% of rows carry a standard concept. In EHR2Trace's, built without manual mappings, 18.9% do: it maps a term only when the mapping is exact or a person confirmed it, and queues the rest for review. For OHDSI analyses on MIMIC-IV today, the OHDSI ETL is the better choice.

For a world model the question is narrower: will the vocabulary the model learns at one hospital line up with another's? A MEDS code such as LAB//RESULT//50912//mg/dL is precise and local. Two things help:

Conversions that cannot be undone

If you need both formats, derive both from one layer that holds the richest version of each event: both times, the action stage, the source row and the unit as recorded. Then the two outputs disagree only where the formats force them to, and you can check that they agree everywhere else. EHR2Trace is built this way, and one of its validation checks confirms that every term resolves to the same concept in MEDS as in OMOP.

Eight checks before you train

Whatever converter produced your data, these take minutes and catch the problems above. The snippets assume a MEDS dataset loaded as events = pl.scan_parquet("data/**/*.parquet").

  1. Where are diagnoses dated? Compare diagnosis times with admission and discharge events for the same stay. If most sit at admission, an as-of join will leak.
  2. Are results visible before they were reported? Look for a store or result time in the source and check which one became time.
  3. How many medication stages are there? Count medication events by stage: events.group_by("event_kind").len().collect() if an event-kind column exists, otherwise by the prefix of the medication codes. One stage means you have intentions or administrations, not both.
  4. Does one code carry several units? Group numeric events by code and count distinct units, where a unit column exists; otherwise compare value ranges per code. Any code with two units and no conversion is two measurements sharing one token.
  5. Are there events after death? Join each subject's MEDS_DEATH time and count later events. Some are legitimate (results stored after death); all should be explainable.
  6. Are splits by subject? Every subject_id should appear in exactly one split in metadata/subject_splits.parquet.
  7. Is the label in the inputs? Search event codes and extension columns for the cohort name, the outcome and file or partition names.
  8. Can you trace one patient back? Pick a strange rollout, find its events, and see whether you can reach the source rows. If not, you will be debugging the model when the problem is the data.

What is still open

FAQ

Should I use OMOP or MEDS to train a patient world model? Train on MEDS: it is already a per-patient, time-sorted event stream, which is the shape a sequence model consumes. Keep OMOP for what it is good at, standard concepts that mean the same thing across sites and cohort definitions in OHDSI tools. Ideally derive both from one canonical event layer so they agree.

Does MEDS prevent time leakage? Only as well as its time column. MEDS tasks use an as-of join on time: everything at or before the prediction time is visible. If a converter places a diagnosis at admission, a model predicting at hour six sees it. The current MIMIC_IV_MEDS ETL places diagnoses at discharge and lab results at their store time; check what your converter does.

How should I record when a fact became available? Either move the event to the time it became known (split collection and result into two events), which every MEDS tool handles, or keep the event time and add an available_time column, which keeps the clinical timeline but requires every downstream tool to filter on it. You can convert the second into the first with one rewrite before handing data to standard tools.

Can I convert OMOP to MEDS losslessly? Mechanically yes (meds_etl does it), but MEDS inherits every decision the OMOP ETL made: when diagnoses are dated, which drug records exist, and whatever source detail was dropped. Information OMOP did not keep cannot be recovered downstream.

Sources

Earlier post: How to convert MIMIC-IV to MEDS. Corrections are welcome as issues on the EHR2Trace repository.