misscp / src /sepsis_mcp /grud_data.py
Anonymous
Initial anonymous MissCP release
32f5a65
Raw
History Blame Contribute Delete
5.74 kB
from __future__ import annotations
from typing import Any
import numpy as np
import pandas as pd
from sepsis_mcp.constants import STATIC_COLUMNS
from sepsis_mcp.dataset import build_labeled_patient_frame
def _build_window_matrices(
patient_frame: pd.DataFrame,
sample_index: int,
dynamic_columns: list[str],
lookback_hours: int,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
values = np.zeros((lookback_hours, len(dynamic_columns)), dtype=np.float32)
masks = np.zeros((lookback_hours, len(dynamic_columns)), dtype=np.float32)
deltas = np.zeros((lookback_hours, len(dynamic_columns)), dtype=np.float32)
window_start = max(0, sample_index - lookback_hours + 1)
actual_window = patient_frame.iloc[window_start : sample_index + 1]
pad_length = lookback_hours - len(actual_window)
for feature_offset, column in enumerate(dynamic_columns):
series = actual_window[column].tolist()
for time_offset, value in enumerate(series, start=pad_length):
if pd.notna(value):
values[time_offset, feature_offset] = float(value)
masks[time_offset, feature_offset] = 1.0
if time_offset == 0:
deltas[time_offset, feature_offset] = 0.0
elif masks[time_offset, feature_offset] == 1.0:
deltas[time_offset, feature_offset] = 0.0
else:
previous_delta = deltas[time_offset - 1, feature_offset]
deltas[time_offset, feature_offset] = previous_delta + 1.0
return values, masks, deltas
def _global_missing_rate(masks: np.ndarray) -> float:
return float(1.0 - masks.mean())
def build_patient_grud_samples(
patient_frame: pd.DataFrame,
patient_id: str,
dynamic_columns: list[str],
lookback_hours: int = 12,
horizon_hours: int = 6,
) -> list[dict[str, Any]]:
labeled = build_labeled_patient_frame(
patient_frame,
patient_id=patient_id,
horizon_hours=horizon_hours,
)
samples: list[dict[str, Any]] = []
for _, row in labeled.iterrows():
sample_index = int(row["sample_index"])
values, masks, deltas = _build_window_matrices(
patient_frame,
sample_index=sample_index,
dynamic_columns=dynamic_columns,
lookback_hours=lookback_hours,
)
current_row = patient_frame.iloc[sample_index]
static = np.array(
[
float(current_row[column]) if pd.notna(current_row[column]) else 0.0
for column in STATIC_COLUMNS
],
dtype=np.float32,
)
samples.append(
{
"patient_id": patient_id,
"sample_index": sample_index,
"values": values,
"masks": masks,
"deltas": deltas,
"static": static,
"label": int(row["target_in_6h"]),
"global_missing_rate": _global_missing_rate(masks),
}
)
return samples
def stack_grud_samples(samples: list[dict[str, Any]]) -> dict[str, Any]:
return {
"values": np.stack([sample["values"] for sample in samples]).astype(np.float32),
"masks": np.stack([sample["masks"] for sample in samples]).astype(np.float32),
"deltas": np.stack([sample["deltas"] for sample in samples]).astype(np.float32),
"static": np.stack([sample["static"] for sample in samples]).astype(np.float32),
"labels": np.array([sample["label"] for sample in samples], dtype=np.float32),
"global_missing_rates": np.array(
[sample["global_missing_rate"] for sample in samples],
dtype=np.float32,
),
"patient_ids": [sample["patient_id"] for sample in samples],
"sample_indices": [sample["sample_index"] for sample in samples],
}
def fit_grud_scaler(stacked: dict[str, Any]) -> dict[str, np.ndarray]:
values = stacked["values"]
masks = stacked["masks"]
static = stacked["static"]
dynamic_mean = np.zeros(values.shape[-1], dtype=np.float32)
dynamic_std = np.ones(values.shape[-1], dtype=np.float32)
for feature_index in range(values.shape[-1]):
observed = values[:, :, feature_index][masks[:, :, feature_index] == 1]
if observed.size:
dynamic_mean[feature_index] = np.float32(observed.mean())
feature_std = np.float32(observed.std())
dynamic_std[feature_index] = feature_std if feature_std > 0 else 1.0
static_mean = np.nanmean(static, axis=0).astype(np.float32)
static_std = np.nanstd(static, axis=0).astype(np.float32)
static_std[static_std == 0] = 1.0
return {
"dynamic_mean": dynamic_mean,
"dynamic_std": dynamic_std,
"static_mean": static_mean,
"static_std": static_std,
}
def transform_grud_stacked(
stacked: dict[str, Any],
scaler: dict[str, np.ndarray],
) -> dict[str, Any]:
transformed = dict(stacked)
values = stacked["values"].copy()
masks = stacked["masks"]
for feature_index in range(values.shape[-1]):
observed = masks[:, :, feature_index] == 1
values[:, :, feature_index][observed] = (
values[:, :, feature_index][observed] - scaler["dynamic_mean"][feature_index]
) / scaler["dynamic_std"][feature_index]
values[:, :, feature_index][~observed] = 0.0
static = stacked["static"].copy()
static = np.where(np.isfinite(static), static, scaler["static_mean"])
static = (static - scaler["static_mean"]) / scaler["static_std"]
transformed["values"] = values.astype(np.float32)
transformed["static"] = static.astype(np.float32)
return transformed