Skip to main content

MEDS Logo

MEDS TorchData: A PyTorch Dataset Class for MEDS Datasets

PyTorch Python 3.11+ PyPI - Version MEDS v0.4 Documentation Status Tests Test Coverage Code Quality Hydra Contributors Pull Requests License DOI

🚀 Quick Start

Step 1: Install

pip install meds-torch-data

Optional extras:

  • meds-torch-data[lightning] — pulls in the lightning dep and enables meds_torchdata.extensions.Datamodule, a PyTorch Lightning DataModule wrapper around MEDSPytorchDataset.
  • meds-torch-data[joblib] — pulls in hydra-joblib-launcher, the joblib-based backend for the preprocessing pipeline's stage_runner_fp=<path> parallelism (see "Parallel execution" below). Other Hydra launchers (e.g. slurm) stay in their own extras, hence the backend-specific name.

Step 2: Data Tensorization

If your input MEDS dataset lives in $MEDS_ROOT and you want to store your pre-processed files in $PYD_ROOT, you run:

MTD_preprocess MEDS_dataset_dir="$MEDS_ROOT" output_dir="$PYD_ROOT"

Step 3: Use the dataset

To use a dataset, you need to (1) define your configuration object and (2) create the dataset object. The only required configuration parameters are tensorized_cohort_dir, which points to the root directory containing the pre-processed data on disk ($PYD_ROOT in the above example), and max_seq_len, which is the maximum sequence length you want to use for your model. Here's an example:

import os
from meds_torchdata import MEDSPytorchDataset, MEDSTorchDataConfig

cfg = MEDSTorchDataConfig(tensorized_cohort_dir=os.environ["PYD_ROOT"], max_seq_len=512)
pyd = MEDSPytorchDataset(cfg, split="train")

If you want to use a specific binary classification task, you can add the task_labels_dir parameter to the configuration object. This should point to a directory containing the sharded MEDS label format parquet files for the labels. The sharding scheme is arbitrary and will not be reflected in the dataset.

That's it!

📚 Documentation

Design Principles

A good PyTorch dataset class should:

  • Be easy to use
  • Have a minimal, constant resource footprint (memory, CPU, start-up time) during model training and inference, regardless of the overall dataset size.
  • Perform as much work as possible in static, reusable dataset pre-processing, rather than upon construction or in the __getitem__ method.
  • Induce effectively negligible computational overhead in the __getitem__ method relative to model training.
  • Be easily configurable, with a simple, consistent API, and cover the most common use-cases.
  • Encourage efficient use of GPU resources in the resulting batches.
  • Should be comprehensively documented, tested, and benchmarked for performance implications so users can use it reliably and effectively.

To achieve this, MEDS TorchData leverages the following design principles:

  1. Lazy Loading: Data is loaded only when needed, and only the data needed for the current batch is loaded.
  2. Efficient Loading: Data is loaded efficiently leveraging the HuggingFace Safetensors library for raw IO through the nested, ragged interface encoded in the Nested Ragged Tensors library.
  3. Configurable, Transparent Pre-processing: Mandatory data pre-processing prior to effective use in this library is managed through a simple MEDS-Transforms pipeline which can be run on any MEDS dataset, after any model-specific pre-processing, via a transparent configuration file.
  4. Continuous Integration: The library is continuously tested and benchmarked for performance implications, and the results are available to users.

Examples and Detailed Usage

To see how this works, let's look at some examples. These examples will be powered by some synthetic data defined as "fixtures" in this package's pytest stack; namely, we'll use the following fixtures:

  • simple_static_MEDS: This will point to a Path containing a simple MEDS dataset.
  • simple_static_MEDS_dataset_with_task: This will point to a Path containing a simple MEDS dataset with a boolean-value task defined. The core data is the same between both the simple_static_MEDS and this dataset, but the latter has a task defined.
  • tensorized_MEDS_dataset fixture that points to a Path containing the tensorized and schema files for the simple_static_MEDS dataset.
  • tensorized_MEDS_dataset_with_task fixture that points to a tuple containing:
    • A Path containing the tensorized and schema files for the simple_static_MEDS_dataset_with_task dataset
    • A Path pointing to the root task directory for the dataset
    • The specific task name for the dataset. Task label files will be stored in a subdir of the root task directory with this name.

You can find these in either the conftest.py file for this repository or the meds_testing_helpers package, which this package leverages for testing.

Synthetic Data

To start, let's take a look at this synthetic data. It is sharded by split, and we'll look at the train split first, which has two shards (we convert to polars just for prettier printing). It has four subjects across the two shards:

>>> import polars as pl
>>> from meds_testing_helpers.dataset import MEDSDataset
>>> D = MEDSDataset(root_dir=simple_static_MEDS)
>>> train_0 = pl.from_arrow(D.data_shards["train/0"])
>>> train_0
shape: (30, 4)
┌────────────┬─────────────────────┬────────────────────┬───────────────┐
│ subject_id ┆ time                ┆ code               ┆ numeric_value │
│ ---        ┆ ---                 ┆ ---                ┆ ---           │
│ i64        ┆ datetime[μs]        ┆ str                ┆ f32           │
╞════════════╪═════════════════════╪════════════════════╪═══════════════╡
│ 239684     ┆ null                ┆ EYE_COLOR//BROWN   ┆ null          │
│ 239684     ┆ null                ┆ HEIGHT             ┆ 175.271118    │
│ 239684     ┆ 1980-12-28 00:00:00 ┆ DOB                ┆ null          │
│ 239684     ┆ 2010-05-11 17:41:51 ┆ ADMISSION//CARDIAC ┆ null          │
│ 239684     ┆ 2010-05-11 17:41:51 ┆ HR                 ┆ 102.599998    │
│ …          ┆ …                   ┆ …                  ┆ …             │
│ 1195293    ┆ 2010-06-20 20:24:44 ┆ HR                 ┆ 107.699997    │
│ 1195293    ┆ 2010-06-20 20:24:44 ┆ TEMP               ┆ 100.0         │
│ 1195293    ┆ 2010-06-20 20:41:33 ┆ HR                 ┆ 107.5         │
│ 1195293    ┆ 2010-06-20 20:41:33 ┆ TEMP               ┆ 100.400002    │
│ 1195293    ┆ 2010-06-20 20:50:04 ┆ DISCHARGE          ┆ null          │
└────────────┴─────────────────────┴────────────────────┴───────────────┘
>>> train_1 = pl.from_arrow(D.data_shards["train/1"])
>>> train_1
shape: (14, 4)
┌────────────┬─────────────────────┬───────────────────────┬───────────────┐
│ subject_id ┆ time                ┆ code                  ┆ numeric_value │
│ ---        ┆ ---                 ┆ ---                   ┆ ---           │
│ i64        ┆ datetime[μs]        ┆ str                   ┆ f32           │
╞════════════╪═════════════════════╪═══════════════════════╪═══════════════╡
│ 68729      ┆ null                ┆ EYE_COLOR//HAZEL      ┆ null          │
│ 68729      ┆ null                ┆ HEIGHT                ┆ 160.395309    │
│ 68729      ┆ 1978-03-09 00:00:00 ┆ DOB                   ┆ null          │
│ 68729      ┆ 2010-05-26 02:30:56 ┆ ADMISSION//PULMONARY  ┆ null          │
│ 68729      ┆ 2010-05-26 02:30:56 ┆ HR                    ┆ 86.0          │
│ …          ┆ …                   ┆ …                     ┆ …             │
│ 814703     ┆ 1976-03-28 00:00:00 ┆ DOB                   ┆ null          │
│ 814703     ┆ 2010-02-05 05:55:39 ┆ ADMISSION//ORTHOPEDIC ┆ null          │
│ 814703     ┆ 2010-02-05 05:55:39 ┆ HR                    ┆ 170.199997    │
│ 814703     ┆ 2010-02-05 05:55:39 ┆ TEMP                  ┆ 100.099998    │
│ 814703     ┆ 2010-02-05 07:02:30 ┆ DISCHARGE             ┆ null          │
└────────────┴─────────────────────┴───────────────────────┴───────────────┘
>>> sorted(set(train_0["subject_id"].unique()) | set(train_1["subject_id"].unique()))
[68729, 239684, 814703, 1195293]

MEDSTorchDataConfig Configuration Object

Full API documentation for the configuration object can be found here.

The configuration object contains two kinds of parameters: Data processing parameters and file paths. Data processing parameters include:

  • max_seq_len: The maximum sequence length to use for the model.
  • seq_sampling_strategy: The strategy to use when sampling sub-sequences to return for input sequences longer than max_seq_len.
  • static_inclusion_mode: The mode to use when including static data in the output.
  • batch_mode: Whether to return sequences at the measurement level ("SM") or the event level ("SEM"). Note that here, we use "measurement" to refer to a single row (observation) in the raw MEDS data, and "event" to refer to all measurements taken at a single time-point.
  • include_window_last_observed_in_schema: If True, include the timestamp of the last observation in each sampled window in the dataset's schema_df when an index dataframe is used and the sampling strategy is deterministic. This functionality is useful for generative applications where the model needs to know what the timestamp is at the start of a generation window, for example.
  • include_numeric_value: If False (defaults to True), drop numeric_value and numeric_value_mask from the collated MEDSTorchBatch entirely — the batch surfaces None for those fields. The underlying tensors are also skipped at load time via nested_ragged_tensors's keys= subset, so code-only models don't pay the per-batch I/O or GPU transfer for data they ignore.
  • include_time_delta: If False (defaults to True), drop time_delta_days from the collated batch (surfaced as None) and skip it at load time. Useful when the model doesn't consume inter-event timing.

Of these, seq_sampling_strategy and static_inclusion_mode are restricted, and must be of the SubsequenceSamplingStrategy and StaticInclusionMode StrEnums, respectively:

  • seq_sampling_strategy: One of ["random", "balanced_random", "to_end", "from_start", "step_through"] (defaults to "random"). balanced_random draws with a flat per-event inclusion probability; step_through walks deterministic non-overlapping (or controllably-overlapping) windows across the sequence — see step_through_stride / step_through_overlap.
  • static_inclusion_mode: One of ["include", "prepend", "omit"] (defaults to "include").

File path parameters include:

  • tensorized_cohort_dir: The directory containing the tensorized data.
  • task_labels_dir: The directory containing the task labels files.

It also provides a convenient property to get the vocab size for the dataset, given by the vocab indices in the tensorized metadata. Let's start by building a configuration object for this data and inspect some of its file-path related properties and helpers:

>>> from meds_torchdata import MEDSTorchDataConfig
>>> cfg = MEDSTorchDataConfig(tensorized_MEDS_dataset, max_seq_len=5)
>>> cfg.tensorized_cohort_dir
PosixPath('/tmp/tmp...')
>>> cfg.schema_dir
PosixPath('/tmp/tmp.../tokenization/schemas')
>>> print(sorted(list(cfg.schema_fps)))
[('held_out/0', PosixPath('/tmp/tmp.../tokenization/schemas/held_out/0.parquet')),
 ('train/0', PosixPath('/tmp/tmp.../tokenization/schemas/train/0.parquet')),
 ('train/1', PosixPath('/tmp/tmp.../tokenization/schemas/train/1.parquet')),
 ('tuning/0', PosixPath('/tmp/tmp.../tokenization/schemas/tuning/0.parquet'))]
>>> print(cfg.task_labels_dir)
None
>>> print(cfg.task_labels_fps)
None
>>> print(cfg.vocab_size)
12

If we specify a task_labels_dir parameter, the config operates in task-specific mode. This allows us to use the task-specific helpers, but it also mandates we set seq_sampling_strategy to "to_end" as you shouldn't try to predict a downstream task without leveraging the most recent data.

>>> cohort_dir, tasks_dir, task_name = tensorized_MEDS_dataset_with_task
>>> cfg = MEDSTorchDataConfig(
...     cohort_dir, max_seq_len=5, task_labels_dir=(tasks_dir / task_name)
... )
Traceback (most recent call last):
    ...
ValueError: Not sampling data till the end of the sequence when predicting for a specific task is not
permitted! This is because there is no use-case we know of where you would want to do this. If you disagree,
please let us know via a GitHub issue.
>>> cfg = MEDSTorchDataConfig(
...     cohort_dir, max_seq_len=5, task_labels_dir=(tasks_dir / task_name), seq_sampling_strategy="to_end"
... )
>>> cfg.task_labels_dir
PosixPath('/tmp/tmp.../task_labels/boolean_value_task')
>>> print(list(cfg.task_labels_fps))
[PosixPath('/tmp/tmp.../task_labels/boolean_value_task/labels_A.parquet.parquet'),
 PosixPath('/tmp/tmp.../task_labels/boolean_value_task/labels_B.parquet.parquet')]

Based on the seq_sampling_strategy, batch_mode, and max_seq_len parameters, the configuration object also has the process_dynamic_data helper function to slice the subject's dynamic data appropriately. This function is used internally, and you will not need to use it yourself.

MEDSPytorchDataset Dataset Class

Full API documentation for the dataset class can be found here.

Now let's build a dataset object from the synthetic data.

Dataset "Schema"

When we build a PyTorch dataset from it for training, with no task specified, the length will be four, as it will correspond to each of the four subjects in the train split. The index variable contains the list of subject IDs and the end of the allowed region of reading for the dataset. We can also see it in dataframe format via the schema_df:

>>> import dataclasses
>>> from meds_torchdata import MEDSPytorchDataset
>>> cfg = MEDSTorchDataConfig(tensorized_cohort_dir=tensorized_MEDS_dataset, max_seq_len=5)
>>> pyd = MEDSPytorchDataset(cfg, split="train")
>>> len(pyd)
4
>>> pyd.index
[(239684, 6), (1195293, 8), (68729, 3), (814703, 3)]
>>> pyd.schema_df
shape: (4, 2)
┌────────────┬─────────────────┐
│ subject_id ┆ end_event_index │
│ ---        ┆ ---             │
│ i64        ┆ u32             │
╞════════════╪═════════════════╡
│ 239684     ┆ 6               │
│ 1195293    ┆ 8               │
│ 68729      ┆ 3               │
│ 814703     ┆ 3               │
└────────────┴─────────────────┘

Note the index is in terms of event indices, not measurement indices -- meaning it is the index of the unique timestamp corresponding to the start and end of each subject's data; not the unique measurement. We can validate that against the raw data. To do so, we'll define the simple helper function get_event_bounds that will just group by the subject_id and time columns, and then calculate the event index for each subject and show us the min and max such index, per-subject.

>>> def get_event_indices(df: pl.DataFrame) -> pl.DataFrame:
...     return (
...         df
...         .group_by("subject_id", "time", maintain_order=True).agg(pl.len().alias("n_measurements"))
...         .with_row_index()
...         .select(
...             "subject_id", "time",
...             (pl.col("index") - pl.col("index").min().over("subject_id")).alias("event_idx"),
...             "n_measurements",
...         )
...     )
>>> def get_event_bounds(df: pl.DataFrame) -> pl.DataFrame:
...     return (
...         get_event_indices(df)
...         .with_columns(
...             pl.col("event_idx").max().over("subject_id").alias("max_event_idx")
...         )
...         .filter((pl.col("event_idx") == 0) | (pl.col("event_idx") == pl.col("max_event_idx")))
...         .select("subject_id", "event_idx", "time")
...     )
>>> get_event_bounds(train_0)
shape: (4, 3)
┌────────────┬───────────┬─────────────────────┐
│ subject_id ┆ event_idx ┆ time                │
│ ---        ┆ ---       ┆ ---                 │
│ i64        ┆ u32       ┆ datetime[μs]        │
╞════════════╪═══════════╪═════════════════════╡
│ 239684     ┆ 0         ┆ null                │
│ 239684     ┆ 6         ┆ 2010-05-11 19:27:19 │
│ 1195293    ┆ 0         ┆ null                │
│ 1195293    ┆ 8         ┆ 2010-06-20 20:50:04 │
└────────────┴───────────┴─────────────────────┘
>>> get_event_bounds(train_1)
shape: (4, 3)
┌────────────┬───────────┬─────────────────────┐
│ subject_id ┆ event_idx ┆ time                │
│ ---        ┆ ---       ┆ ---                 │
│ i64        ┆ u32       ┆ datetime[μs]        │
╞════════════╪═══════════╪═════════════════════╡
│ 68729      ┆ 0         ┆ null                │
│ 68729      ┆ 3         ┆ 2010-05-26 04:51:52 │
│ 814703     ┆ 0         ┆ null                │
│ 814703     ┆ 3         ┆ 2010-02-05 07:02:30 │
└────────────┴───────────┴─────────────────────┘

The schema changes to reflect the different split if we change the split:

>>> pyd_tuning = MEDSPytorchDataset(cfg, split="tuning")
>>> pyd_tuning.schema_df
shape: (1, 2)
┌────────────┬─────────────────┐
│ subject_id ┆ end_event_index │
│ ---        ┆ ---             │
│ i64        ┆ u32             │
╞════════════╪═════════════════╡
│ 754281     ┆ 3               │
└────────────┴─────────────────┘
>>> pyd_held_out = MEDSPytorchDataset(cfg, split="held_out")
>>> pyd_held_out.schema_df
shape: (1, 2)
┌────────────┬─────────────────┐
│ subject_id ┆ end_event_index │
│ ---        ┆ ---             │
│ i64        ┆ u32             │
╞════════════╪═════════════════╡
│ 1500733    ┆ 5               │
└────────────┴─────────────────┘

If you use a non-existent split or have something misconfigured, you'll get an error upon Dataset creation:

>>> pyd_bad = MEDSPytorchDataset(cfg, split="bad_split")
Traceback (most recent call last):
    ...
FileNotFoundError: No schema files found in /tmp/.../tokenization/schemas! If your data is not sharded by
split, this error may occur because this codebase does not handle non-split sharded data. See Issue #79 for
tracking this issue.

We can also inspect the schema for a dataset built with downstream task labels:

>>> cohort_dir, tasks_dir, task_name = tensorized_MEDS_dataset_with_task
>>> cfg_with_task = MEDSTorchDataConfig(
...     cohort_dir, max_seq_len=5, task_labels_dir=(tasks_dir / task_name), seq_sampling_strategy="to_end"
... )
>>> pyd_with_task = MEDSPytorchDataset(cfg_with_task, split="train")
>>> pyd_with_task.schema_df
shape: (13, 4)
┌────────────┬─────────────────┬─────────────────────┬───────────────┐
│ subject_id ┆ end_event_index ┆ prediction_time     ┆ boolean_value │
│ ---        ┆ ---             ┆ ---                 ┆ ---           │
│ i64        ┆ u32             ┆ datetime[μs]        ┆ bool          │
╞════════════╪═════════════════╪═════════════════════╪═══════════════╡
│ 239684     ┆ 3               ┆ 2010-05-11 18:00:00 ┆ false         │
│ 239684     ┆ 4               ┆ 2010-05-11 18:30:00 ┆ true          │
│ 239684     ┆ 5               ┆ 2010-05-11 19:00:00 ┆ true          │
│ 1195293    ┆ 3               ┆ 2010-06-20 19:30:00 ┆ false         │
│ 1195293    ┆ 4               ┆ 2010-06-20 20:00:00 ┆ true          │
│ …          ┆ …               ┆ …                   ┆ …             │
│ 68729      ┆ 2               ┆ 2010-05-26 04:00:00 ┆ true          │
│ 68729      ┆ 2               ┆ 2010-05-26 04:30:00 ┆ true          │
│ 814703     ┆ 2               ┆ 2010-02-05 06:00:00 ┆ false         │
│ 814703     ┆ 2               ┆ 2010-02-05 06:30:00 ┆ true          │
│ 814703     ┆ 2               ┆ 2010-02-05 07:00:00 ┆ true          │
└────────────┴─────────────────┴─────────────────────┴───────────────┘

When we have a task or index dataframe (an index is just a task without a label), we can also ask the model to include the last observed time in our input window in the schema, with the include_window_last_observed_in_schema parameter:

>>> cfg_with_end_time = MEDSTorchDataConfig(
...     cohort_dir, max_seq_len=5, task_labels_dir=(tasks_dir / task_name), seq_sampling_strategy="to_end",
...     include_window_last_observed_in_schema=True
... )
>>> pyd_with_end_time = MEDSPytorchDataset(cfg_with_end_time, split="train")
>>> pyd_with_end_time.schema_df
shape: (13, 5)
┌────────────┬─────────────────┬─────────────────────┬───────────────┬──────────────────────┐
│ subject_id ┆ end_event_index ┆ prediction_time     ┆ boolean_value ┆ window_last_observed │
│ ---        ┆ ---             ┆ ---                 ┆ ---           ┆ ---                  │
│ i64        ┆ u32             ┆ datetime[μs]        ┆ bool          ┆ datetime[μs]         │
╞════════════╪═════════════════╪═════════════════════╪═══════════════╪══════════════════════╡
│ 239684     ┆ 3               ┆ 2010-05-11 18:00:00 ┆ false         ┆ 2010-05-11 17:48:48  │
│ 239684     ┆ 4               ┆ 2010-05-11 18:30:00 ┆ true          ┆ 2010-05-11 18:25:35  │
│ 239684     ┆ 5               ┆ 2010-05-11 19:00:00 ┆ true          ┆ 2010-05-11 18:57:18  │
│ 1195293    ┆ 3               ┆ 2010-06-20 19:30:00 ┆ false         ┆ 2010-06-20 19:25:32  │
│ 1195293    ┆ 4               ┆ 2010-06-20 20:00:00 ┆ true          ┆ 2010-06-20 19:45:19  │
│ …          ┆ …               ┆ …                   ┆ …             ┆ …                    │
│ 68729      ┆ 2               ┆ 2010-05-26 04:00:00 ┆ true          ┆ 2010-05-26 02:30:56  │
│ 68729      ┆ 2               ┆ 2010-05-26 04:30:00 ┆ true          ┆ 2010-05-26 02:30:56  │
│ 814703     ┆ 2               ┆ 2010-02-05 06:00:00 ┆ false         ┆ 2010-02-05 05:55:39  │
│ 814703     ┆ 2               ┆ 2010-02-05 06:30:00 ┆ true          ┆ 2010-02-05 05:55:39  │
│ 814703     ┆ 2               ┆ 2010-02-05 07:00:00 ┆ true          ┆ 2010-02-05 05:55:39  │
└────────────┴─────────────────┴─────────────────────┴───────────────┴──────────────────────┘
Returned items

While the raw data has codes as strings, naturally, when embedded in the pytorch dataset, they'll get converted to integers. This happens during the forementioned tensorization step. We can see how the codes are mapped to integers by looking at the output code metadata of that step:

>>> code_metadata = pl.read_parquet(tensorized_MEDS_dataset.joinpath("metadata/codes.parquet"))
>>> code_metadata.select("code", "code/vocab_index").sort("code/vocab_index")
shape: (11, 2)
┌───────────────────────┬──────────────────┐
│ code                  ┆ code/vocab_index │
│ ---                   ┆ ---              │
│ str                   ┆ u8               │
╞═══════════════════════╪══════════════════╡
│ ADMISSION//CARDIAC    ┆ 1                │
│ ADMISSION//ORTHOPEDIC ┆ 2                │
│ ADMISSION//PULMONARY  ┆ 3                │
│ DISCHARGE             ┆ 4                │
│ DOB                   ┆ 5                │
│ …                     ┆ …                │
│ EYE_COLOR//BROWN      ┆ 7                │
│ EYE_COLOR//HAZEL      ┆ 8                │
│ HEIGHT                ┆ 9                │
│ HR                    ┆ 10               │
│ TEMP                  ┆ 11               │
└───────────────────────┴──────────────────┘

We can see these vocab indices being used if we look at some elements of the pytorch dataset. Note that some elements of the returned dictionaries are JointNestedRaggedTensorDict objects, so we'll define a helper here that will use a helper from the associated library to help us pretty-print out outputs. Note that we'll also reduce precision in the numeric values to make the output more readable.

>>> from nested_ragged_tensors.ragged_numpy import pprint_dense
>>> def print_element(el: dict):
...     for k, v in el.items():
...         print(f"{k} ({type(v).__name__}):")
...         if k == "dynamic":
...             pprint_dense(v.to_dense())
...         else:
...             print(v)
>>> print_element(pyd[2])
static_code (list):
[8, 9]
static_numeric_value (list):
[nan, -0.5438239574432373]
dynamic (JointNestedRaggedTensorDict):
code
[ 5  3 10 11  4]
.
numeric_value
[        nan         nan -1.4474752  -0.34049404         nan]
.
time_delta_days
[           nan 1.17661045e+04 0.00000000e+00 0.00000000e+00
 9.78703722e-02]

This example shows what the output looks like if we set the static data inclusion mode to "include". What if we set it to "prepend" instead? To show this in a stable manner, we'll also use the seeded version of the get item function, _seeded_getitem:

pyd.config is locked (MEDSPytorchDataset calls cfg.lock() on handoff); dataclasses.replace builds a new config with the override and we rebuild the dataset around it.

>>> pyd = MEDSPytorchDataset(
...     dataclasses.replace(pyd.config, static_inclusion_mode="prepend"), split="train"
... )
>>> print_element(pyd._seeded_getitem(2, seed=0))
n_static_seq_els (int):
2
dynamic (JointNestedRaggedTensorDict):
code
[ 8  9 10 11  4]
.
numeric_value
[        nan -0.54382396 -1.4474752  -0.34049404         nan]
.
time_delta_days
[       nan        nan 0.         0.         0.09787037]
>>> pyd = MEDSPytorchDataset(
...     dataclasses.replace(pyd.config, static_inclusion_mode="include"), split="train"
... )

We can also look at what would be returned if we had included a task in the dataset:

```python
>>> print_element(pyd_with_task[0])
static_code (list):
[7, 9]
static_numeric_value (list):
[nan, 1.5770268440246582]
dynamic (JointNestedRaggedTensorDict):
code
[ 1 10 11 10 11]
.
numeric_value
[       nan -0.5697369 -1.2714673 -0.4375474 -1.1680276]
.
time_delta_days
[1.0726737e+04 0.0000000e+00 0.0000000e+00 4.8263888e-03 0.0000000e+00]
boolean_value (bool):
False

We can see in this case that the boolean_value field is included in the output, capturing the task label.

The contents of pyd[2] are stable, because index element 0, (68729, 0, 3), indicates the first subject has a sequence of length 3 in the dataset and our max_seq_len is set to 5.

>>> print_element(pyd[2])
static_code (list):
[8, 9]
static_numeric_value (list):
[nan, -0.5438239574432373]
dynamic (JointNestedRaggedTensorDict):
code
[ 5  3 10 11  4]
.
numeric_value
[        nan         nan -1.4474752  -0.34049404         nan]
.
time_delta_days
[           nan 1.17661045e+04 0.00000000e+00 0.00000000e+00
 9.78703722e-02]

If we sampled a different subject, one with more than 5 events, the output we'd get would be dependent on the config.seq_sampling_strategy option, and could be non-deterministic. By default, this is set to random, so we'll get a random subset of length 5 each time. Here, so that this code is deterministic, we'll use _seeded_getitem, an internal, seeded version of the __getitem__ call.

>>> print_element(pyd._seeded_getitem(1, seed=0))
static_code (list):
[6, 9]
static_numeric_value (list):
[nan, 0.06802856922149658]
dynamic (JointNestedRaggedTensorDict):
code
[11 10 11 10 11]
.
numeric_value
[ 0.69391906 -0.30007038  0.79735875 -0.31064537  1.0042422 ]
.
time_delta_days
[0.         0.0084838  0.         0.01167824 0.        ]
>>> print_element(pyd._seeded_getitem(1, seed=1))
static_code (list):
[6, 9]
static_numeric_value (list):
[nan, 0.06802856922149658]
dynamic (JointNestedRaggedTensorDict):
code
[11 10 11 10 11]
.
numeric_value
[ 0.79735875  0.33972722  0.7456389  -0.04626633  0.69391906]
.
time_delta_days
[0.         0.01373843 0.         0.01888889 0.        ]

Of course, if we set seq_sampling_strategy to something other than "random", this non-determinism would disappear:

>>> cfg_from_start = MEDSTorchDataConfig(
...     tensorized_cohort_dir=tensorized_MEDS_dataset, max_seq_len=5, seq_sampling_strategy="from_start"
... )
>>> pyd_from_start = MEDSPytorchDataset(cfg_from_start, split="train")
>>> print_element(pyd_from_start[1])
static_code (list):
[6, 9]
static_numeric_value (list):
[nan, 0.06802856922149658]
dynamic (JointNestedRaggedTensorDict):
code
[ 5  1 10 11 10]
.
numeric_value
[        nan         nan -0.23133166  0.79735875  0.03833488]
.
time_delta_days
[          nan 1.1688809e+04 0.0000000e+00 0.0000000e+00 1.1574074e-03]
>>> print_element(pyd_from_start[1])
static_code (list):
[6, 9]
static_numeric_value (list):
[nan, 0.06802856922149658]
dynamic (JointNestedRaggedTensorDict):
code
[ 5  1 10 11 10]
.
numeric_value
[        nan         nan -0.23133166  0.79735875  0.03833488]
.
time_delta_days
[          nan 1.1688809e+04 0.0000000e+00 0.0000000e+00 1.1574074e-03]
Batches, Collation, and Dataloaders

We can also examine not just individual elements, but full batches, that we can access with the appropriate collate function via the built in get_dataloader method. Here, we'll treat these outputs like dictionaries, but they actually return dataclass objects that have some additional properties we can use to access shapes and validate data. See the API documentation on the batch class for more information.

>>> batches = [batch for batch in pyd.get_dataloader(batch_size=2)]
>>> print(batches[1])
MEDSTorchBatch:
│ Mode: Subject-Measurement (SM)
│ Static data? ✓
│ Labels? ✗
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 5
│ │
│ │ All dynamic data: (2, 5)
│ │ Static data: (2, 2)
│
│ Data:
│ │ Dynamic:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00e+00, 1.18e+04,  ..., 0.00e+00, 9.79e-02],
│ │ │ │  [0.00e+00, 1.24e+04,  ..., 0.00e+00, 4.64e-02]]
│ │ │ code (torch.int64):
│ │ │ │ [[ 5,  3,  ..., 11,  4],
│ │ │ │  [ 5,  2,  ..., 11,  4]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[ 0.00,  0.00,  ..., -0.34,  0.00],
│ │ │ │  [ 0.00,  0.00,  ...,  0.85,  0.00]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[False, False,  ...,  True, False],
│ │ │ │  [False, False,  ...,  True, False]]
│ │
│ │ Static:
│ │ │ static_code (torch.int64):
│ │ │ │ [[8, 9],
│ │ │ │  [8, 9]]
│ │ │ static_numeric_value (torch.float32):
│ │ │ │ [[ 0.00, -0.54],
│ │ │ │  [ 0.00, -1.10]]
│ │ │ static_numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True],
│ │ │ │  [False,  True]]
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Measurement (SM)
│ Static data? ✓
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 5
│ │
│ │ All dynamic data: (2, 5)
│ │ Static data: (2, 2)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ Dynamic:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[1.07e+04, 0.00e+00,  ..., 4.83e-03, 0.00e+00],
│ │ │ │  [0.00e+00, 4.83e-03,  ..., 2.55e-02, 0.00e+00]]
│ │ │ code (torch.int64):
│ │ │ │ [[ 1, 10,  ..., 10, 11],
│ │ │ │  [11, 10,  ..., 10, 11]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[ 0.00e+00, -5.70e-01,  ..., -4.38e-01, -1.17e+00],
│ │ │ │  [-1.27e+00, -4.38e-01,  ...,  1.32e-03, -1.37e+00]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True,  ...,  True,  True],
│ │ │ │  [ True,  True,  ...,  True,  True]]
│ │
│ │ Static:
│ │ │ static_code (torch.int64):
│ │ │ │ [[7, 9],
│ │ │ │  [7, 9]]
│ │ │ static_numeric_value (torch.float32):
│ │ │ │ [[0.00, 1.58],
│ │ │ │  [0.00, 1.58]]
│ │ │ static_numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True],
│ │ │ │  [False,  True]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]

This is with the default static inclusion mode of "include", which means that the static data is included as a separate entry in the batch. What about with the other two options, "omit" and "prepend"?

If we use "omit", we can see that the static data is omitted from the output:

>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, static_inclusion_mode="omit"), split="train"
... )
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Measurement (SM)
│ Static data? ✗
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 5
│ │
│ │ All dynamic data: (2, 5)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ Dynamic:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[1.07e+04, 0.00e+00,  ..., 4.83e-03, 0.00e+00],
│ │ │ │  [0.00e+00, 4.83e-03,  ..., 2.55e-02, 0.00e+00]]
│ │ │ code (torch.int64):
│ │ │ │ [[ 1, 10,  ..., 10, 11],
│ │ │ │  [11, 10,  ..., 10, 11]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[ 0.00e+00, -5.70e-01,  ..., -4.38e-01, -1.17e+00],
│ │ │ │  [-1.27e+00, -4.38e-01,  ...,  1.32e-03, -1.37e+00]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True,  ...,  True,  True],
│ │ │ │  [ True,  True,  ...,  True,  True]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]

What if we use a static inclusion mode of "prepend"? We can see that the static data is prepended to the dynamic data:

>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, static_inclusion_mode="prepend"), split="train"
... )
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Measurement (SM)
│ Static data? ✓ (prepended)
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length (static + dynamic): 5
│ │
│ │ All [static; dynamic] data: (2, 5)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ [Static; Dynamic]:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00, 0.00,  ..., 0.00, 0.00],
│ │ │ │  [0.00, 0.00,  ..., 0.03, 0.00]]
│ │ │ code (torch.int64):
│ │ │ │ [[ 7,  9,  ..., 10, 11],
│ │ │ │  [ 7,  9,  ..., 10, 11]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[ 0.00e+00,  1.58e+00,  ..., -4.38e-01, -1.17e+00],
│ │ │ │  [ 0.00e+00,  1.58e+00,  ...,  1.32e-03, -1.37e+00]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True,  ...,  True,  True],
│ │ │ │  [False,  True,  ...,  True,  True]]
│ │ │ static_mask (torch.bool):
│ │ │ │ [[ True,  True,  ..., False, False],
│ │ │ │  [ True,  True,  ..., False, False]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]
>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, static_inclusion_mode="include"),
...     split="train",
... )  # reset to default

Thus far, our examples have all worked with the default config object, which sets (among other things) the default output to be at a measurement level, rather than an event level, by virtue of setting batch_mode to SM. Let's see what happens if we change that:

>>> pyd = MEDSPytorchDataset(
...     dataclasses.replace(pyd.config, batch_mode="SEM"), split="train"
... )
>>> print_element(pyd[2])
static_code (list):
[8, 9]
static_numeric_value (list):
[nan, -0.5438239574432373]
dynamic (JointNestedRaggedTensorDict):
time_delta_days
[           nan 1.17661045e+04 9.78703722e-02]
.
---
.
dim1/mask
[[ True False False]
 [ True  True  True]
 [ True False False]]
.
code
[[ 5  0  0]
 [ 3 10 11]
 [ 4  0  0]]
.
numeric_value
[[        nan  0.          0.        ]
 [        nan -1.4474752  -0.34049404]
 [        nan  0.          0.        ]]
>>> batches = [batch for batch in pyd.get_dataloader(batch_size=2)]
>>> print(batches[1])
MEDSTorchBatch:
│ Mode: Subject-Event-Measurement (SEM)
│ Static data? ✓
│ Labels? ✗
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 3
│ │ Event length: 3
│ │
│ │ Per-event data: (2, 3)
│ │ Per-measurement data: (2, 3, 3)
│ │ Static data: (2, 2)
│
│ Data:
│ │ Event-level:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00e+00, 1.18e+04, 9.79e-02],
│ │ │ │  [0.00e+00, 1.24e+04, 4.64e-02]]
│ │ │ event_mask (torch.bool):
│ │ │ │ [[True, True, True],
│ │ │ │  [True, True, True]]
│ │
│ │ Measurement-level:
│ │ │ code (torch.int64):
│ │ │ │ [[[ 5,  0,  0],
│ │ │ │   [ 3, 10, 11],
│ │ │ │   [ 4,  0,  0]],
│ │ │ │  [[ 5,  0,  0],
│ │ │ │   [ 2, 10, 11],
│ │ │ │   [ 4,  0,  0]]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[[ 0.00,  0.00,  0.00],
│ │ │ │   [ 0.00, -1.45, -0.34],
│ │ │ │   [ 0.00,  0.00,  0.00]],
│ │ │ │  [[ 0.00,  0.00,  0.00],
│ │ │ │   [ 0.00,  3.00,  0.85],
│ │ │ │   [ 0.00,  0.00,  0.00]]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [False,  True,  True]],
│ │ │ │  [[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [False,  True,  True]]]
│ │
│ │ Static:
│ │ │ static_code (torch.int64):
│ │ │ │ [[8, 9],
│ │ │ │  [8, 9]]
│ │ │ static_numeric_value (torch.float32):
│ │ │ │ [[ 0.00, -0.54],
│ │ │ │  [ 0.00, -1.10]]
│ │ │ static_numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True],
│ │ │ │  [False,  True]]
>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, batch_mode="SEM"), split="train"
... )
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Event-Measurement (SEM)
│ Static data? ✓
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 4
│ │ Event length: 3
│ │
│ │ Per-event data: (2, 4)
│ │ Per-measurement data: (2, 4, 3)
│ │ Static data: (2, 2)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ Event-level:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00e+00, 1.07e+04, 4.83e-03, 0.00e+00],
│ │ │ │  [0.00e+00, 1.07e+04, 4.83e-03, 2.55e-02]]
│ │ │ event_mask (torch.bool):
│ │ │ │ [[ True,  True,  True, False],
│ │ │ │  [ True,  True,  True,  True]]
│ │
│ │ Measurement-level:
│ │ │ code (torch.int64):
│ │ │ │ [[[ 5,  0,  0],
│ │ │ │   [ 1, 10, 11],
│ │ │ │   [10, 11,  0],
│ │ │ │   [ 0,  0,  0]],
│ │ │ │  [[ 5,  0,  0],
│ │ │ │   [ 1, 10, 11],
│ │ │ │   [10, 11,  0],
│ │ │ │   [10, 11,  0]]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[[ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00, -5.70e-01, -1.27e+00],
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00,  0.00e+00,  0.00e+00]],
│ │ │ │  [[ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00, -5.70e-01, -1.27e+00],
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 1.32e-03, -1.37e+00,  0.00e+00]]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]],
│ │ │ │  [[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]]]
│ │
│ │ Static:
│ │ │ static_code (torch.int64):
│ │ │ │ [[7, 9],
│ │ │ │  [7, 9]]
│ │ │ static_numeric_value (torch.float32):
│ │ │ │ [[0.00, 1.58],
│ │ │ │  [0.00, 1.58]]
│ │ │ static_numeric_value_mask (torch.bool):
│ │ │ │ [[False,  True],
│ │ │ │  [False,  True]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]
>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, static_inclusion_mode="omit"), split="train"
... )
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Event-Measurement (SEM)
│ Static data? ✗
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length: 4
│ │ Event length: 3
│ │
│ │ Per-event data: (2, 4)
│ │ Per-measurement data: (2, 4, 3)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ Event-level:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00e+00, 1.07e+04, 4.83e-03, 0.00e+00],
│ │ │ │  [0.00e+00, 1.07e+04, 4.83e-03, 2.55e-02]]
│ │ │ event_mask (torch.bool):
│ │ │ │ [[ True,  True,  True, False],
│ │ │ │  [ True,  True,  True,  True]]
│ │
│ │ Measurement-level:
│ │ │ code (torch.int64):
│ │ │ │ [[[ 5,  0,  0],
│ │ │ │   [ 1, 10, 11],
│ │ │ │   [10, 11,  0],
│ │ │ │   [ 0,  0,  0]],
│ │ │ │  [[ 5,  0,  0],
│ │ │ │   [ 1, 10, 11],
│ │ │ │   [10, 11,  0],
│ │ │ │   [10, 11,  0]]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[[ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00, -5.70e-01, -1.27e+00],
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00,  0.00e+00,  0.00e+00]],
│ │ │ │  [[ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00, -5.70e-01, -1.27e+00],
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 1.32e-03, -1.37e+00,  0.00e+00]]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]],
│ │ │ │  [[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]
>>> pyd_with_task = MEDSPytorchDataset(
...     dataclasses.replace(pyd_with_task.config, static_inclusion_mode="prepend"), split="train"
... )
>>> print(next(iter(pyd_with_task.get_dataloader(batch_size=2))))
MEDSTorchBatch:
│ Mode: Subject-Event-Measurement (SEM)
│ Static data? ✓ (prepended)
│ Labels? ✓
│
│ Shape:
│ │ Batch size: 2
│ │ Sequence length (static + dynamic): 5
│ │ Event length: 3
│ │
│ │ Per-event data: (2, 5)
│ │ Per-measurement data: (2, 5, 3)
│ │ Labels: torch.Size([2])
│
│ Data:
│ │ Event-level:
│ │ │ time_delta_days (torch.float32):
│ │ │ │ [[0.00, 0.00,  ..., 0.00, 0.00],
│ │ │ │  [0.00, 0.00,  ..., 0.00, 0.03]]
│ │ │ event_mask (torch.bool):
│ │ │ │ [[ True,  True,  ...,  True, False],
│ │ │ │  [ True,  True,  ...,  True,  True]]
│ │ │ static_mask (torch.bool):
│ │ │ │ [[ True, False,  ..., False, False],
│ │ │ │  [ True, False,  ..., False, False]]
│ │
│ │ Measurement-level:
│ │ │ code (torch.int64):
│ │ │ │ [[[ 7,  9,  0],
│ │ │ │   [ 5,  0,  0],
│ │ │ │   ...,
│ │ │ │   [10, 11,  0],
│ │ │ │   [ 0,  0,  0]],
│ │ │ │  [[ 7,  9,  0],
│ │ │ │   [ 5,  0,  0],
│ │ │ │   ...,
│ │ │ │   [10, 11,  0],
│ │ │ │   [10, 11,  0]]]
│ │ │ numeric_value (torch.float32):
│ │ │ │ [[[ 0.00e+00,  1.58e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   ...,
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00,  0.00e+00,  0.00e+00]],
│ │ │ │  [[ 0.00e+00,  1.58e+00,  0.00e+00],
│ │ │ │   [ 0.00e+00,  0.00e+00,  0.00e+00],
│ │ │ │   ...,
│ │ │ │   [-4.38e-01, -1.17e+00,  0.00e+00],
│ │ │ │   [ 1.32e-03, -1.37e+00,  0.00e+00]]]
│ │ │ numeric_value_mask (torch.bool):
│ │ │ │ [[[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   ...,
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]],
│ │ │ │  [[False,  True,  True],
│ │ │ │   [False,  True,  True],
│ │ │ │   ...,
│ │ │ │   [ True,  True,  True],
│ │ │ │   [ True,  True,  True]]]
│ │
│ │ Labels:
│ │ │ boolean_value (torch.bool):
│ │ │ │ [False,  True]

Data Tensorization and Pre-processing Details

Full documentation for the preprocessing pipeline can be found here

The MTD_preprocess command leverages hydra to manage the configuration and running via the command line. You can see the available options by running the command with the --help flag:

== MTD_preprocess ==

MTD_preprocess is a command line tool for pre-processing MEDS data for use with meds_torchdata.

== Config ==

This is the config generated for this run:

MEDS_dataset_dir: ???
output_dir: ???
stage_runner_fp: null
do_overwrite: false
do_reshard: false
log_dir: ${output_dir}/.logs

You can override everything using the hydra `key=value` syntax; for example:

MTD_preprocess MEDS_dataset_dir=/path/to/dataset output_dir=/path/to/output do_overwrite=True

The MTD_preprocess command runs the following pre-processing stages:

  1. fit_normalization: Fitting necessary parameters for normalization from the raw data (e.g., the mean and standard deviation of the numeric_value field).
  2. fit_vocabulary_indices: Assigning unique vocabulary indices to each unique code in the data so that they can be transformed to numerical indices for tensorization.
  3. normalization: Normalizing the data using the parameters fit in the fit_normalization stage to have a mean of 0 and a standard deviation of 1.
  4. tokenization: Producing the schema files necessary for the tensorization stage.
  5. tensorization: Producing the nested ragged tensor views of the data.

Advanced features

You can also use this package natively with Hydra in modeling applications by adding the meds_torchdata.MEDSTorchDataConfig to the Hydra config store. This will allow you to use it as though it were a fully defined .yaml configuration file in your application configuration. To do this, you simply need to run MEDSTorchDataConfig.add_to_config_store() in your application, specifying the group name in which you plan to use the config in your application.

E.g., if you have a config file like this:

dataset:
  _target_: meds_torchdata.MEDSPytorchDataset
  config: MEDSTorchDataConfig

Then in your main application, prior to @hydra.main, you can run:

from meds_torchdata.config import MEDSTorchDataConfig

MEDSTorchDataConfig.add_to_config_store("dataset/config")

This will add the MEDSTorchDataConfig to the Hydra config store in the nested dataset/config group, which will allow you to override its parameters from the command line and instantiate it into object form natively.

Testing Models that Use this Package

If you use this package to build your model, we also expose some pytest fixtures that can be used to test your models. These fixtures are designed to be used with the pytest testing framework. These fixtures are similar to the four fixtures we used above in the Examples and Detailed Usage section. Namely, they are:

  • tensorized_MEDS_dataset fixture that points to a Path containing the tensorized and schema files for the simple_static_MEDS dataset.
  • tensorized_MEDS_dataset_with_task fixture that points to a tuple containing:
    • A Path containing the tensorized and schema files for the simple_static_MEDS_dataset_with_task dataset
    • A Path pointing to the root task directory for the dataset
    • The specific task name for the dataset. Task label files will be stored in a subdir of the root task directory with this name.
  • sample_pytorch_dataset: This will yield a MEDSPytorchDataset object built using the tensorized_MEDS_dataset data, without a downstream task.
  • sample_pytorch_dataset_with_task: This will yield a MEDSPytorchDataset object built using the tensorized_MEDS_dataset_with_task data, with the associated downstream task.

You can rely on these fixtures to test your model in the normal way, directly having your model train using input batches derived from the sample datasets.

Performance

See https://mmcdermott.github.io/meds-torch-data/dev/bench/ for performance benchmarks for all commits in this repository. See here for the benchmarking script. Note that these benchmarks are likely to change over time so should be judged relative to the content of the associated commits, not in absolute terms (e.g., we are likely to benchmark on more or more complex synthetic data, etc.).

Metadata

Release files for meds-torch-data 0.9.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 meds-torch-data 0.9.0
File Size Uploaded Provenance
meds_torch_data-0.9.0.tar.gz 274.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for meds-torch-data 0.9.0
File Interpreter ABI Platform
meds_torch_data-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 366.9 kB

Release files / meds_torch_data-0.9.0.tar.gz

Download URL meds_torch_data-0.9.0.tar.gz
Size 274.6 kB
Tags Source
SHA-256 checksum
How to use checksums
25a64087546aa833f8e78223f9bc4955409f9e2761ffa69360784abfa8f24dd7
BLAKE2b-256 checksum
How to use checksums
d16ca72f955423bc529c2be3417c0e9957ecc9c3e5b1d74e09a158a63c97c2f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Apr 21, 2026.

Transparency log

Release files / meds_torch_data-0.9.0-py3-none-any.whl

Download URL meds_torch_data-0.9.0-py3-none-any.whl
Size 92.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3b9506eca150931da8b8a706a5e6a059f074caa639c7a58585396749088353ec
BLAKE2b-256 checksum
How to use checksums
a8e88e2c0f41a6c615f0938300fbe930974dee514711200c35d9b3f6977372c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Apr 21, 2026.

Transparency log

Security for meds-torch-data 0.9.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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for meds_torch_data-0.9.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 Apr 21, 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