48 lines
1.8 KiB
Python
48 lines
1.8 KiB
Python
"""General-purpose utilities for the V2 hypertower pipeline."""
|
|
from __future__ import annotations
|
|
|
|
import random as pyrandom
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import torch
|
|
|
|
|
|
def seed_everything(seed: int) -> None:
|
|
pyrandom.seed(seed)
|
|
np.random.seed(seed)
|
|
torch.manual_seed(seed)
|
|
if torch.cuda.is_available():
|
|
torch.cuda.manual_seed_all(seed)
|
|
|
|
|
|
def choose_device(device_arg: str | None) -> torch.device:
|
|
if device_arg and device_arg != "auto":
|
|
return torch.device(device_arg)
|
|
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
|
|
def _drop_mixed_label_patients(df: pd.DataFrame, *, patient_col: str, label_col: str):
|
|
"""Remove patients whose rows carry conflicting labels. Returns (clean_df, mixed_pids)."""
|
|
per_patient = (
|
|
df.groupby(patient_col)[label_col]
|
|
.agg(lambda s: set(pd.to_numeric(s, errors="coerce").dropna().astype(int).tolist()))
|
|
)
|
|
mixed = [pid for pid, labels in per_patient.items() if len(labels) > 1]
|
|
if not mixed:
|
|
return df, []
|
|
return df[~df[patient_col].isin(mixed)].reset_index(drop=True), mixed
|
|
|
|
|
|
def _relabel_mixed_patients_to_max(df: pd.DataFrame, *, patient_col: str, label_col: str):
|
|
"""Set all rows for each patient to that patient's max observed label."""
|
|
out = df.copy()
|
|
labels = pd.to_numeric(out[label_col], errors="coerce")
|
|
patient_max = labels.groupby(out[patient_col]).transform("max")
|
|
changed_rows = int((labels != patient_max).fillna(False).sum())
|
|
out[label_col] = patient_max.astype(int)
|
|
per_patient_unique = out.groupby(patient_col)[label_col].nunique(dropna=True)
|
|
still_mixed = per_patient_unique[per_patient_unique > 1].index.tolist()
|
|
return out.reset_index(drop=True), changed_rows, still_mixed
|