skills/scikit-survival/references/data-handling.md
Verified for scikit-survival 0.28.0 on 2026-07-23.
scikit-survival estimators expect a one-dimensional NumPy structured array with exactly two fields:
True=event observed, False=right-censored);Field names are configurable, but field order and meaning are fixed.
from sksurv.util import Surv
y = Surv.from_arrays(
event=[True, False, True],
time=[2.5, 4.0, 7.25],
name_event="event",
name_time="time",
)
assert y.dtype.names == ("event", "time")
Surv.from_arrays() accepts boolean or strict 0/1 event values.
Surv.from_dataframe(event, time, data) accepts pandas and, in 0.28, Polars
DataFrames:
y = Surv.from_dataframe("event", "time", frame)
Do not use astype(bool) on unvalidated strings: "False" is a non-empty string
and therefore converts to True. Validate accepted values explicitly.
Surv is not the input contract for nonparametric competing-risk cumulative
incidence. Use two arrays:
# 0 = right-censored; 1..K = mutually exclusive causes
event_code = frame["status"].to_numpy(dtype=int)
observed_time = frame["time"].to_numpy(dtype=float)
Positive cause codes must be understood before modeling. Do not collapse them to
boolean until the estimand explicitly requires all-cause event status or a
cause-specific hazard outcome. See competing-risks.md.
Before splitting:
The bundled validator performs these checks on bounded local CSV input:
python skills/scikit-survival/scripts/validate_survival_csv.py \
--input data.csv \
--event-column event \
--time-column time \
--feature-columns x1,x2,group \
--structured-output outcome.npy
The .npy file contains a non-object structured array and can be loaded with
numpy.load(path, allow_pickle=False).
This order is mandatory:
Computing medians, category levels, scaling moments, univariate scores, or regularization paths on all rows leaks validation/test information.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
stratify=y["event"],
random_state=20260723,
)
Event stratification does not guarantee balanced follow-up times. Inspect each split. Use group-aware splitting for repeated entities and time-respecting splitting for future-deployment questions. A random split is not automatically appropriate.
Use scikit-learn's ColumnTransformer when numeric and categorical columns need
different imputers or when unseen categories must be handled explicitly.
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sksurv.linear_model import CoxPHSurvivalAnalysis
numeric_pipe = make_pipeline(
SimpleImputer(strategy="median"),
StandardScaler(),
)
categorical_pipe = make_pipeline(
SimpleImputer(strategy="most_frequent"),
OneHotEncoder(
handle_unknown="ignore",
drop="first",
sparse_output=False,
),
)
preprocess = ColumnTransformer(
[
("numeric", numeric_pipe, numeric_columns),
("categorical", categorical_pipe, categorical_columns),
],
sparse_threshold=0.0,
)
pipeline = make_pipeline(
preprocess,
CoxPHSurvivalAnalysis(alpha=0.1, ties="efron"),
)
pipeline.fit(X_train, y_train)
Keep the estimator in the same pipeline used by cross-validation so each fold fits its own preprocessing state.
sksurv.preprocessing.OneHotEncoder(allow_drop=True):
category/object and Polars categorical/enum/string columns as
categorical;fit and transform to use the same DataFrame library;get_feature_names_out() and pipeline use.It is convenient for already clean DataFrames:
from sklearn.pipeline import make_pipeline
from sksurv.preprocessing import OneHotEncoder
pipeline = make_pipeline(
OneHotEncoder(),
CoxPHSurvivalAnalysis(alpha=0.1),
)
pipeline.fit(X_train, y_train)
For custom files, an explicit ColumnTransformer usually makes missing-value,
unknown-category, and scaling behavior easier to audit.
encode_categorical() is a one-shot transformation, not a fitted train/test
transformer. Do not call it separately on all data or independently on train and
test when category sets can differ.
Missingness can be informative. A convenient imputer does not justify a missing-at-random assumption or transportability claim.
Feature selection is learned preprocessing and belongs inside inner CV:
pipeline = make_pipeline(
preprocess,
selector,
estimator,
)
Do not use a standard classification SelectKBest score with structured survival
outcomes unless the score function explicitly supports censoring. Coxnet or
componentwise boosting can perform embedded selection, but regularization strength
still requires fold-contained tuning.
Fixed "events per variable" thresholds are not universal guarantees. Consider effective degrees of freedom, censoring, shrinkage, separation, stability, and external validation instead of declaring a model valid from one ratio.
Current loaders:
load_aids(endpoint=...)load_bmt()load_cgvhd()load_breast_cancer()load_flchain()load_gbsg2()load_whas500()load_veterans_lung_cancer()load_arff_files_standardized(...)load_bmt() returns event codes 0, 1, 2 and load_cgvhd() returns 0, 1, 2, 3;
these are competing-risk outcomes. The remaining listed study loaders return the
standard boolean right-censored outcome (subject to endpoint options).
These packaged datasets are useful for reproducing documentation, but they are real study datasets. The bundled CLIs and tests do not use them; they use synthetic data only. Do not treat examples as clinical advice or a substitute for data-use review.
In 0.28, dataset loaders accept an output_type option where documented, allowing
pandas (default) or Polars feature output.
Standard estimators do not directly encode:
Surv estimator outcome.Some nonparametric APIs expose entry-time arguments, but that does not make every estimator support left truncation. Choose a method whose likelihood and input contract match the observation process.
sklearn.py or sksurv.py.Official sources checked 2026-07-23: