Skip to content

Commit

Permalink
Throw exception when event time is negative
Browse files Browse the repository at this point in the history
Fixes #406
  • Loading branch information
sebp committed Oct 1, 2023
1 parent 8a22157 commit 619b4df
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 6 deletions.
8 changes: 7 additions & 1 deletion sksurv/nonparametric.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,13 @@ def kaplan_meier_estimator(
Survival Function Based on Transformations", Scandinavian Journal of
Statistics. 1990;17(1):35–41.
"""
event, time_enter, time_exit = check_y_survival(event, time_enter, time_exit, allow_all_censored=True)
event, time_enter, time_exit = check_y_survival(
event,
time_enter,
time_exit,
allow_all_censored=True,
allow_time_zero=reverse or time_enter is not None,
)
check_consistent_length(event, time_enter, time_exit)

if conf_type is not None and reverse:
Expand Down
3 changes: 0 additions & 3 deletions sksurv/svm/survival_svm.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,6 @@ def fit(self, X, y):
if self.optimizer in {"simple", "PRSVM"}:
raise ValueError(f"optimizer {self.optimizer!r} does not implement regression objective")

if (time <= 0).any():
raise ValueError("observed time contains values smaller or equal to zero")

# log-transform time
time = np.log(time)
assert np.isfinite(time).all()
Expand Down
14 changes: 13 additions & 1 deletion sksurv/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def from_dataframe(event, time, data):
)


def check_y_survival(y_or_event, *args, allow_all_censored=False):
def check_y_survival(y_or_event, *args, allow_all_censored=False, allow_time_zero=False):
"""Check that array correctly represents an outcome for survival analysis.
Parameters
Expand All @@ -114,6 +114,9 @@ def check_y_survival(y_or_event, *args, allow_all_censored=False):
allow_all_censored : bool, optional, default: False
Whether to allow all events to be censored.
allow_time_zero : bool, optional, default: False
Whether to allow event times to be zero.
Returns
-------
event : array, shape=[n_samples,], dtype=bool
Expand Down Expand Up @@ -156,6 +159,15 @@ def check_y_survival(y_or_event, *args, allow_all_censored=False):
if not np.issubdtype(yt.dtype, np.number):
raise ValueError(f"time must be numeric, but found {yt.dtype} for argument {i + 2}")

if allow_time_zero:
cond = yt < 0
msg = "observed time contains values smaller zero"
else:
cond = yt <= 0
msg = "observed time contains values smaller or equal to zero"
if np.any(cond):
raise ValueError(msg)

return_val.append(yt)

return tuple(return_val)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ def test_brier_coxph():


def test_brier_score_int_dtype():
times = np.arange(30, dtype=int)
times = np.arange(1, 31, dtype=int)
rnd = np.random.RandomState(1)
times = rnd.choice(times, 20)

Expand Down
11 changes: 11 additions & 0 deletions tests/test_survival_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from sksurv.linear_model import CoxnetSurvivalAnalysis
from sksurv.testing import all_survival_estimators
from sksurv.util import Surv


def all_survival_function_estimators():
Expand All @@ -30,3 +31,13 @@ def test_survival_functions(estimator, make_whas500):
arr = np.row_stack([fn(times) for fn in fns_cls])

assert_array_almost_equal(arr, fns_arr)


@pytest.mark.parametrize("estimator", all_survival_function_estimators())
@pytest.mark.parametrize("y_time", [-1e-8, -1, np.finfo(float).min])
def test_fit_negative_survial_time_raises(estimator, y_time):
X = np.random.randn(7, 3)
y = Surv.from_arrays(event=np.ones(7, dtype=bool), time=[1, 9, 3, y_time, 1, 8, 1e10])

with pytest.raises(ValueError, match="observed time contains values smaller or equal to zero"):
estimator.fit(X, y)

0 comments on commit 619b4df

Please sign in to comment.