<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>python | Sebastian Pölsterl</title><link>https://k-d-w.org/tags/python/</link><atom:link href="https://k-d-w.org/tags/python/index.xml" rel="self" type="application/rss+xml"/><description>python</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© Sebastian Pölsterl 2026</copyright><lastBuildDate>Sun, 05 Jul 2026 18:22:05 +0200</lastBuildDate><image><url>https://k-d-w.org/img/icon-192.png</url><title>python</title><link>https://k-d-w.org/tags/python/</link></image><item><title>scikit-survival 0.28.0 released</title><link>https://k-d-w.org/blog/2026/07/scikit-survival-0.28.0-released/</link><pubDate>Sun, 05 Jul 2026 18:22:05 +0200</pubDate><guid>https://k-d-w.org/blog/2026/07/scikit-survival-0.28.0-released/</guid><description>&lt;p&gt;I am pleased to announce the release of &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/stable/release_notes/v0.28.html#scikit-survival-0-28-0-2026-07-05&#34; target=&#34;_blank&#34;&gt;scikit-survival 0.28.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A highlight of this release is the support for &lt;em&gt;Polars DataFrames&lt;/em&gt; alongside pandas DataFrames via
the &lt;a href=&#34;https://narwhals-dev.github.io/narwhals/&#34; target=&#34;_blank&#34;&gt;Narwhals&lt;/a&gt; dataframe abstraction layer.
In addition, this release adds support for &lt;em&gt;scikit-learn 1.9&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;support-for-polars&#34;&gt;Support for Polars&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.pola.rs/&#34; target=&#34;_blank&#34;&gt;Polars&lt;/a&gt; is a data frame library similar to &lt;a href=&#34;https://pandas.pydata.org/&#34; target=&#34;_blank&#34;&gt;pandas&lt;/a&gt;,
but with its core written in Rust instead of Python, which often gives Polars an advantage in terms of performance.&lt;/p&gt;
&lt;p&gt;All datasets shipped with scikit-survival can now be loaded as a Polars DataFrame by specifying the
&lt;code&gt;output_type&lt;/code&gt; argument.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_gbsg2
# return X as a polars DataFrame
X, y = load_gbsg2(output_type=&amp;quot;polars&amp;quot;)
X.head()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;shape: (5, 8)
┌──────┬────────┬───────┬──────────┬────────┬─────────┬────────┬───────┐
│ age ┆ estrec ┆ horTh ┆ menostat ┆ pnodes ┆ progrec ┆ tgrade ┆ tsize │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ enum ┆ enum ┆ f64 ┆ f64 ┆ enum ┆ f64 │
╞══════╪════════╪═══════╪══════════╪════════╪═════════╪════════╪═══════╡
│ 70.0 ┆ 66.0 ┆ no ┆ Post ┆ 3.0 ┆ 48.0 ┆ II ┆ 21.0 │
│ 56.0 ┆ 77.0 ┆ yes ┆ Post ┆ 7.0 ┆ 61.0 ┆ II ┆ 12.0 │
│ 58.0 ┆ 271.0 ┆ yes ┆ Post ┆ 9.0 ┆ 52.0 ┆ II ┆ 35.0 │
│ 59.0 ┆ 29.0 ┆ yes ┆ Post ┆ 4.0 ┆ 60.0 ┆ II ┆ 17.0 │
│ 73.0 ┆ 65.0 ┆ no ┆ Post ┆ 1.0 ┆ 26.0 ┆ II ┆ 35.0 │
└──────┴────────┴───────┴──────────┴────────┴─────────┴────────┴───────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;transforming-dataframes&#34;&gt;Transforming DataFrames&lt;/h3&gt;
&lt;p&gt;scikit-learn enables transformers to return Polars data frames via the
&lt;a href=&#34;https://scikit-learn.org/stable/modules/df_output_transform.html#df-output-transform&#34; target=&#34;_blank&#34;&gt;set_output API&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sklearn import set_config
from sklearn.preprocessing import StandardScaler
set_config(transform_output=&amp;quot;polars&amp;quot;)
# standarize the columns of a polars DataFrame
X_standarized = StandardScaler().fit_transform(
X.select(&amp;quot;age&amp;quot;, &amp;quot;tsize&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;scikit-surival has two transformers that now accept polars and pandas data frames as input:
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.28.0/api/generated/sksurv.kernels.ClinicalKernelTransform.html&#34; target=&#34;_blank&#34;&gt;ClinicalKernelTransform&lt;/a&gt;, and
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.28.0/api/generated/sksurv.preprocessing.OneHotEncoder.html&#34; target=&#34;_blank&#34;&gt;OneHotEncoder&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;clinicalkerneltransform&#34;&gt;ClinicalKernelTransform&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ClinicalKernelTransform&lt;/code&gt; computes a kernel matrix, so the output will be a numpy array, as before.
With scikit-survival 0.28.0, it is aware of the
&lt;a href=&#34;https://docs.pola.rs/user-guide/concepts/data-types-and-structures/#appendix-full-data-types-table&#34; target=&#34;_blank&#34;&gt;polars colum types&lt;/a&gt;
&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Categorical&lt;/code&gt;, &lt;code&gt;Enum&lt;/code&gt; and &lt;code&gt;Object&lt;/code&gt;.
However, polars does not have a concept similar to &lt;a href=&#34;https://pandas.pydata.org/docs/user_guide/categorical.html#sorting-and-order&#34; target=&#34;_blank&#34;&gt;ordered categories&lt;/a&gt; in pandas: &lt;code&gt;pd.Categorical([…], ordered=True)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When computing the clinical kernel for a polars data frame, you can specify the order
of categories with the &lt;code&gt;ordinal_categories&lt;/code&gt; argument, otherwise all non-numeric columns
will be treated as &lt;em&gt;nominal&lt;/em&gt; columns, where values have no specific order
(e.g. the column &lt;code&gt;horTh&lt;/code&gt; with values &amp;ldquo;yes&amp;rdquo; and &amp;ldquo;no&amp;rdquo; in the example below).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.kernels import ClinicalKernelTransform
K = ClinicalKernelTransform(
ordinal_categories={&amp;quot;tgrade&amp;quot;: [&amp;quot;I&amp;quot;, &amp;quot;II&amp;quot;, &amp;quot;III&amp;quot;]},
).fit_transform(
X.select(&amp;quot;age&amp;quot;, &amp;quot;horTh&amp;quot;, &amp;quot;tgrade&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;onehotencoder&#34;&gt;OneHotEncoder&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;OneHotEncoder&lt;/code&gt; encodes the string-type columns to numeric columns.
It automatically returns a data frame of the same type as the input:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.preprocessing import OneHotEncoder
X_onehot = OneHotEncoder().fit_transform(X.select(&amp;quot;horTh&amp;quot;, &amp;quot;tgrade&amp;quot;))
X_onehot.head()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;shape: (5, 3)
┌───────────┬───────────┬────────────┐
│ horTh=yes ┆ tgrade=II ┆ tgrade=III │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═══════════╪═══════════╪════════════╡
│ 0.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 0.0 ┆ 1.0 ┆ 0.0 │
└───────────┴───────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;fitting-survival-models&#34;&gt;Fitting survival models&lt;/h3&gt;
&lt;p&gt;Finally, you can pass a polars data frame to the fit and predict functions of any estimator.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sklearn.pipeline import make_pipeline
from sksurv.linear_model import CoxPHSurvivalAnalysis
pipe = make_pipeline(
OneHotEncoder(), StandardScaler(), CoxPHSurvivalAnalysis()
)
pipe.fit(X[:500], y[:500])
risk_scores = pipe.predict(X[500:])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this only works for &lt;em&gt;eager&lt;/em&gt; data frames, &lt;em&gt;lazy&lt;/em&gt; data frames will give an error.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;X_lazy = X.lazy()
pipe.fit(X_lazy[:500], y[:500])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python-traceback&#34;&gt;Traceback (most recent call last):
File &amp;quot;example.py&amp;quot;, line 21, in &amp;lt;module&amp;gt;
pipe.fit(X_lazy[:500], y[:500])
File &amp;quot;…/site-packages/sklearn/base.py&amp;quot;, line 1403, in wrapper
return fit_method(estimator, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
…
File &amp;quot;…/site-packages/sksurv/_dataframe/_input.py&amp;quot;, line 96, in ensure_eager_dataframe
_reject_polars_lazyframe(obj)
File &amp;quot;…/site-packages/sksurv/_dataframe/_input.py&amp;quot;, line 84, in _reject_polars_lazyframe
raise TypeError(_LAZYFRAME_NOT_SUPPORTED_MSG)
TypeError: polars.LazyFrame is not supported; call .collect() before passing to scikit-survival.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;new-contributors&#34;&gt;New Contributors&lt;/h2&gt;
&lt;p&gt;A big shoutout to our two new contributors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cakedev0&#34; target=&#34;_blank&#34;&gt;cakedev0&lt;/a&gt; for adding support for scikit-learn 1.9&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/55Kamiryo&#34; target=&#34;_blank&#34;&gt;55Kamiryo&lt;/a&gt; for adding support for Polars data frames.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;updated-dependencies&#34;&gt;Updated Dependencies&lt;/h2&gt;
&lt;p&gt;With this release, the minimum supported version are:&lt;/p&gt;
&lt;table style=&#34;width: auto&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package&lt;/th&gt;&lt;th&gt;Minimum Version&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;narwhals&lt;/td&gt;&lt;td style=&#34;text-align: right&#34;&gt;2.0.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;scikit-learn&lt;/td&gt;&lt;td style=&#34;text-align: right&#34;&gt;1.9.0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;scikit-survival is available for Linux, macOS, and Windows
and can be installed either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c conda-forge scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.26.0 released</title><link>https://k-d-w.org/blog/2025/12/scikit-survival-0.26.0-released/</link><pubDate>Wed, 17 Dec 2025 21:26:48 +0100</pubDate><guid>https://k-d-w.org/blog/2025/12/scikit-survival-0.26.0-released/</guid><description>&lt;p&gt;I am pleased to announce that &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.26.0 has been released.&lt;/p&gt;
&lt;p&gt;This is a maintainance release that adds support for Python 3.14 and
includes updates to make scikit-survival compatible with new versions
of pandas and osqp.
It adds support for the &lt;a href=&#34;https://pandas.pydata.org/docs/user_guide/migration-3-strings.html#string-migration-guide&#34; target=&#34;_blank&#34;&gt;pandas string dtype&lt;/a&gt;,
and &lt;a href=&#34;https://pandas.pydata.org/docs/user_guide/copy_on_write.html#copy-on-write&#34; target=&#34;_blank&#34;&gt;copy-on-write&lt;/a&gt;, which is going to become the default with pandas 3.
In addition, &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.26.0/api/generated/sksurv.preprocessing.OneHotEncoder.html#sksurv.preprocessing.OneHotEncoder&#34; title=&#34;sksurv.preprocessing.OneHotEncoder&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;sksurv.preprocessing.OneHotEncoder&lt;/code&gt;&lt;/a&gt;
now supports converting columns with the object dtype.&lt;/p&gt;
&lt;p&gt;With this release, the minimum supported version are:&lt;/p&gt;
&lt;table style=&#34;width: auto&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package&lt;/th&gt;&lt;th&gt;Minimum Version&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td style=&#34;text-align: right&#34;&gt;3.11&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;pandas&lt;/td&gt;&lt;td style=&#34;text-align: right&#34;&gt;2.0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;osqp&lt;/td&gt;&lt;td style=&#34;text-align: right&#34;&gt;1.0.2&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;scikit-survival is available for Linux, macOS, and Windows
and can be installed either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c conda-forge scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.25.0 with improved documentation released</title><link>https://k-d-w.org/blog/2025/08/scikit-survival-0.25.0-with-improved-documentation-released/</link><pubDate>Fri, 22 Aug 2025 23:55:06 +0200</pubDate><guid>https://k-d-w.org/blog/2025/08/scikit-survival-0.25.0-with-improved-documentation-released/</guid><description>&lt;p&gt;I am pleased to announce that &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.25.0 has been released.&lt;/p&gt;
&lt;p&gt;This release adds support for scikit-learn 1.7, in addition to version 1.6.
However, the most significant changes in this release affect the documentation.
The &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/index.html&#34; target=&#34;_blank&#34;&gt;API documentation&lt;/a&gt; has been completely overhauled to improve clarity and consistency.
I hope this marks a significant improvement for users new to scikit-survival.&lt;/p&gt;
&lt;p&gt;One of the biggest pain points for users seems to be understanding which metric can be used to evaluate the performance of a given estimator.
The &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/user_guide/evaluating-survival-models.html&#34; target=&#34;_blank&#34;&gt;user guide&lt;/a&gt;
now summarizes the different options.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2025/08/scikit-survival-0.25.0-with-improved-documentation-released/img/metrics-diagram.svg&#34;/&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;which-performance-metrics-exist&#34;&gt;Which Performance Metrics Exist?&lt;/h2&gt;
&lt;p&gt;The performance metrics for evaluating survival models can be broadly divided into three groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concordance Index (C-index)&lt;/strong&gt;: Measures the rank correlation between predicted risk scores and observed event times.
Two implementations are available in scikit-survival:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.concordance_index_censored.html&#34; target=&#34;_blank&#34;&gt;concordance_index_censored()&lt;/a&gt;:
This implements Harrell&amp;rsquo;s estimator, which can be
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/user_guide/evaluating-survival-models.html#Bias-of-Harrell&#39;s-Concordance-Index&#34; target=&#34;_blank&#34;&gt;optimistic with high censoring&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.concordance_index_ipcw.html&#34; target=&#34;_blank&#34;&gt;concordance_index_ipcw()&lt;/a&gt;:
An inverse probability of censoring weighted (IPCW) alternative that provides a less biased estimate, especially with high censoring.
It is the preferred estimator of the C-Index.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cumulative/Dynamic Area Under the ROC Curve (AUC)&lt;/strong&gt;:
Extends the AUC to survival data, quantifying how well a model distinguishes subjects who experience an event by a given time from those who do not. It can handle &lt;em&gt;time-dependent risk scores&lt;/em&gt;
and is implemented in &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.cumulative_dynamic_auc.html&#34; target=&#34;_blank&#34;&gt;cumulative_dynamic_auc()&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Brier Score&lt;/strong&gt;:
An extension of the mean squared error to right-censored data.
The Brier score assesses both discrimination and calibration based on a model&amp;rsquo;s estimated survival functions.
You can either compute the Brier score at specific time point(s) using
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.brier_score.html&#34; target=&#34;_blank&#34;&gt;brier_score()&lt;/a&gt;
or compute an overall measure by integrating the Brier score over a range of time points via
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.integrated_brier_score.html&#34; target=&#34;_blank&#34;&gt;integrated_brier_score()&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;what-do-survival-models-predict&#34;&gt;What Do Survival Models Predict?&lt;/h2&gt;
&lt;p&gt;Survival models can predict several quantities, depending on the model being used.
First of all, every estimator has a &lt;code&gt;predict()&lt;/code&gt; method,
which either returns a unit-less risk score
or the predicted time of an event.&lt;/p&gt;
&lt;p&gt;If predictions are &lt;em&gt;risk scores&lt;/em&gt;, higher values indicate an
increased risk of experiencing an event. The scores have no unit
and are only meaningful for ranking samples by their risk of experiencing an event.
This is for example the case for
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.linear_model.CoxPHSurvivalAnalysis.html#sksurv.linear_model.CoxPHSurvivalAnalysis.predict&#34; target=&#34;_blank&#34;&gt;CoxPHSurvivalAnalysis&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_veterans_lung_cancer
from sksurv.linear_model import CoxPHSurvivalAnalysis
from sksurv.metrics import concordance_index_censored
from sksurv.preprocessing import OneHotEncoder
# Load data
X, y = load_veterans_lung_cancer()
Xt = OneHotEncoder().fit_transform(X)
# Fit model
estimator = CoxPHSurvivalAnalysis().fit(Xt, y)
# Predict risk score
predicted_risk = estimator.predict(Xt)
# Evaluate risk scores
cindex = concordance_index_censored(
y[&amp;quot;Status&amp;quot;], y[&amp;quot;Survival_in_days&amp;quot;], predicted_risk
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If predictions directly relate to the time point of an event,
lower scores indicate shorter survival, while higher scores indicate longer survival.
See for example &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.linear_model.IPCRidge.html#sksurv.linear_model.IPCRidge.predict&#34; target=&#34;_blank&#34;&gt;IPCRidge&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_veterans_lung_cancer
from sksurv.linear_model import IPCRidge
from sksurv.metrics import concordance_index_censored
from sksurv.preprocessing import OneHotEncoder
# Load the data
X, y = load_veterans_lung_cancer()
Xt = OneHotEncoder().fit_transform(X)
# Fit the model
estimator = IPCRidge().fit(Xt, y)
# Predict time of an event
predicted_time = estimator.predict(Xt)
# Flip sign of predictions to obtain a risk score
cindex = concordance_index_censored(
y[&amp;quot;Status&amp;quot;], y[&amp;quot;Survival_in_days&amp;quot;], -1 * predicted_time
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both types of predictions can be evaluated by
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.cumulative_dynamic_auc.html&#34; target=&#34;_blank&#34;&gt;cumulative_dynamic_auc()&lt;/a&gt; too
but &lt;em&gt;not the Brier score&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While the concordance index is easy to interpret,
it is not a useful measure of performance if a specific time range
is of primary interest (e.g. predicting death within 2 years).
This is particularly relevant for survival models that can
make &lt;em&gt;time-dependent predictions&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For instance,
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt;,
can also predict survival functions (via &lt;code&gt;predict_survival_function()&lt;/code&gt;)
or cumulative hazard functions (via &lt;code&gt;predict_cumulative_hazard_function()&lt;/code&gt;).
These functions return lists of
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.functions.StepFunction.html&#34; target=&#34;_blank&#34;&gt;StepFunction&lt;/a&gt; instances.
Each instance can be evaluated at a set of time points to obtain predicted
survival probabilities (or cumulative hazards).
The Brier score and
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/generated/sksurv.metrics.cumulative_dynamic_auc.html&#34; target=&#34;_blank&#34;&gt;cumulative_dynamic_auc()&lt;/a&gt;
are capable of evaluating time-dependent predictions, but &lt;em&gt;not the C-Index&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import numpy as np
from sksurv.datasets import load_veterans_lung_cancer
from sksurv.ensemble import RandomSurvivalForest
from sksurv.metrics import integrated_brier_score
from sksurv.preprocessing import OneHotEncoder
# Load the data
X, y = load_veterans_lung_cancer()
Xt = OneHotEncoder().fit_transform(X)
# Fit the model
estimator = RandomSurvivalForest().fit(Xt, y)
# predict survival functions
surv_funcs = estimator.predict_survival_function(Xt)
# select time points to evaluate performance at
times = np.arange(7, 365)
# create predictions at selected time points
preds = np.asarray(
[[sfn(t) for t in times] for sfn in surv_funcs]
)
# compute integral
score = integrated_brier_score(y, y, preds, times)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more details on evaluating survival models, please have a look at the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/user_guide/evaluating-survival-models.html&#34; target=&#34;_blank&#34;&gt;user guide&lt;/a&gt;
and the &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.25.0/api/index.html&#34; target=&#34;_blank&#34;&gt;API documentation&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>scikit-survival 0.24.0 released</title><link>https://k-d-w.org/blog/2025/02/scikit-survival-0.24.0-released/</link><pubDate>Wed, 26 Feb 2025 22:26:45 +0100</pubDate><guid>https://k-d-w.org/blog/2025/02/scikit-survival-0.24.0-released/</guid><description>&lt;p&gt;It&amp;rsquo;s my pleasure to announce the release of &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.24.0.&lt;/p&gt;
&lt;p&gt;A highlight of this release the addition of
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.24.0/api/generated/sksurv.nonparametric.cumulative_incidence_competing_risks.html#sksurv.nonparametric.cumulative_incidence_competing_risks&#34; target=&#34;_blank&#34;&gt;cumulative_incidence_competing_risks()&lt;/a&gt;
which implements a non-parameteric estimator of the cumulative incidence function in the presence of competing risks.
In addition, the release adds support for scikit-learn 1.6, including the support for missing values for
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.24.0/api/generated/sksurv.ensemble.ExtraSurvivalTrees.html#sksurv.ensemble.ExtraSurvivalTrees&#34; target=&#34;_blank&#34;&gt;ExtraSurvivalTrees&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;analysis-of-competing-risks&#34;&gt;Analysis of Competing Risks&lt;/h2&gt;
&lt;p&gt;In classical survival analysis, the focus is on the time until a specific event occurs. If no event is observed during the study period, the time of the event is considered censored. A common assumption is that censoring is non-informative, meaning that censored subjects have a similar prognosis to those who were not censored.&lt;/p&gt;
&lt;p&gt;Competing risks arise when each subject can experience an event due to one of $K$ ($K \geq 2$) mutually exclusive causes, termed competing risks. Thus, the occurrence of one event prevents the occurrence of other events. For example, after a bone marrow transplant, a patient might relapse or die from transplant-related causes (transplant-related mortality). In this case, death from transplant-related mortality precludes relapse.&lt;/p&gt;
&lt;p&gt;The bone marrow transplant data from &lt;a href=&#34;https://doi.org/10.1038/sj.bmt.1705727&#34; target=&#34;_blank&#34;&gt;Scrucca et al., Bone Marrow Transplantation (2007)&lt;/a&gt; includes data
from 35 patients grouped into two cancer types: Acute Lymphoblastic Leukemia (ALL; coded as 0), and Acute Myeloid Leukemia (AML; coded as 1).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_bmt
bmt_features, bmt_outcome = load_bmt()
diseases = bmt_features[&amp;quot;dis&amp;quot;].cat.rename_categories(
{&amp;quot;0&amp;quot;: &amp;quot;ALL&amp;quot;, &amp;quot;1&amp;quot;: &amp;quot;AML&amp;quot;}
)
diseases.value_counts().to_frame()
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;table-responsive&#34;&gt;
&lt;table class=&#34;dataframe&#34; style=&#34;width: 20%;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dis&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;AML&lt;/th&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;ALL&lt;/th&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;During the follow-up period, some patients might experience a relapse of the original leukemia or die
while in remission (transplant related death).
The outcome is defined similarly to standard time-to-event data, except that the event indicator specifies the type of event, where 0 always indicates censoring.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import pandas as pd
status_labels = {
0: &amp;quot;Censored&amp;quot;,
1: &amp;quot;Transplant related mortality&amp;quot;,
2: &amp;quot;Relapse&amp;quot;,
}
risks = pd.DataFrame.from_records(bmt_outcome).assign(
label=lambda x: x[&amp;quot;status&amp;quot;].replace(status_labels)
)
risks[&amp;quot;label&amp;quot;].value_counts().to_frame()
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;table-responsive&#34;&gt;
&lt;table class=&#34;dataframe&#34; style=&#34;width: 40%;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;label&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;Relapse&lt;/th&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;Censored&lt;/th&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;Transplant related mortality&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The table above shows the number of observations for each status.&lt;/p&gt;
&lt;h3 id=&#34;non-parametric-estimator-of-the-cumulative-incidence-function&#34;&gt;Non-parametric Estimator of the Cumulative Incidence Function&lt;/h3&gt;
&lt;p&gt;If the goal is to estimate the probability of relapse, transplant-related death is a competing risk event. This means that the occurrence of relapse prevents the occurrence of transplant-related death, and vice versa. We aim to estimate curves that illustrate how the likelihood of these events changes over time.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s begin by estimating the probability of relapse using the complement of the Kaplan-Meier estimator. With this approach, we treat deaths as censored observations. One minus the Kaplan-Meier estimator provides an estimate of the probability of relapse before time $t$.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import matplotlib.pyplot as plt
from sksurv.nonparametric import kaplan_meier_estimator
times, km_estimate = kaplan_meier_estimator(
bmt_outcome[&amp;quot;status&amp;quot;] == 1, bmt_outcome[&amp;quot;ftime&amp;quot;]
)
plt.step(times, 1 - km_estimate, where=&amp;quot;post&amp;quot;)
plt.xlabel(&amp;quot;time $t$&amp;quot;)
plt.ylabel(&amp;quot;Probability of relapsing before time $t$&amp;quot;)
plt.ylim(0, 1)
plt.grid()
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2025/02/scikit-survival-0.24.0-released/img/bmt-kaplan-meier.svg&#34; width=&#34;400&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;However, this approach has a significant drawback: considering death as a censoring event violates the assumption that censoring is non-informative. This is because patients who died from transplant-related mortality have a different prognosis than patients who did not experience any event. Therefore, the estimated probability of relapse is often biased.&lt;/p&gt;
&lt;p&gt;The cause-specific &lt;strong&gt;cumulative incidence function (CIF)&lt;/strong&gt; addresses this problem by estimating the cause-specific hazard of each event separately. The cumulative incidence function estimates the probability that the event of interest occurs before time $t$, and that it occurs before any of the competing causes of an event. In the bone marrow transplant dataset, the cumulative incidence function of relapse indicates the probability of relapse before time $t$, given that the patient has not died from other causes before time $t$.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.nonparametric import cumulative_incidence_competing_risks
times, cif_estimates = cumulative_incidence_competing_risks(
bmt_outcome[&amp;quot;status&amp;quot;], bmt_outcome[&amp;quot;ftime&amp;quot;]
)
plt.step(times, cif_estimates[0], where=&amp;quot;post&amp;quot;, label=&amp;quot;Total risk&amp;quot;)
for i, cif in enumerate(cif_estimates[1:], start=1):
plt.step(times, cif, where=&amp;quot;post&amp;quot;, label=status_labels[i])
plt.legend()
plt.xlabel(&amp;quot;time $t$&amp;quot;)
plt.ylabel(&amp;quot;Probability of event before time $t$&amp;quot;)
plt.ylim(0, 1)
plt.grid()
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2025/02/scikit-survival-0.24.0-released/img/bmt-cumulative-incidence.svg&#34; width=&#34;400&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;The plot shows the estimated probability of experiencing an event at time $t$ for both the individual risks and for the total risk.&lt;/p&gt;
&lt;p&gt;Next, we want to to estimate the cumulative incidence curves for the two cancer types — acute lymphoblastic leukemia (ALL) and acute myeloid leukemia (AML) — to examine how the probability of relapse depends on the original disease diagnosis.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;_, axs = plt.subplots(2, 2, figsize=(7, 6), sharex=True, sharey=True)
for j, disease in enumerate(diseases.unique()):
mask = diseases == disease
event = bmt_outcome[&amp;quot;status&amp;quot;][mask]
time = bmt_outcome[&amp;quot;ftime&amp;quot;][mask]
times, cif_estimates, conf_int = cumulative_incidence_competing_risks(
event,
time,
conf_type=&amp;quot;log-log&amp;quot;,
)
for i, (cif, ci, ax) in enumerate(
zip(cif_estimates[1:], conf_int[1:], axs[:, j]), start=1
):
ax.step(times, cif, where=&amp;quot;post&amp;quot;)
ax.fill_between(times, ci[0], ci[1], alpha=0.25, step=&amp;quot;post&amp;quot;)
ax.set_title(f&amp;quot;{disease}: {status_labels[i]}&amp;quot;, size=&amp;quot;small&amp;quot;)
ax.grid()
for ax in axs[-1, :]:
ax.set_xlabel(&amp;quot;time $t$&amp;quot;)
for ax in axs[:, 0]:
ax.set_ylim(0, 1)
ax.set_ylabel(&amp;quot;Probability of event before time $t$&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2025/02/scikit-survival-0.24.0-released/img/bmt-cumulative-incidence-by-diagnosis.svg&#34; width=&#34;450&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;The left column shows the estimated cumulative incidence curves (solid lines) for patients diagnosed with ALL, while the right column shows the curves for patients diagnosed with AML, along with their 95% pointwise confidence intervals. The plot indicates that the estimated probability of relapse at $t=40$ days is more than three times higher for patients diagnosed with ALL compared to AML.&lt;/p&gt;
&lt;p&gt;If you want to run the examples above yourself, you can
&lt;a href=&#34;https://mybinder.org/v2/gh/sebp/scikit-survival/master?urlpath=lab/tree/notebooks/competing-risks.ipynb&#34; target=&#34;_blank&#34;&gt;execute them interactively in your browser using binder&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>scikit-survival 0.23.0 released</title><link>https://k-d-w.org/blog/2024/06/scikit-survival-0.23.0-released/</link><pubDate>Sun, 30 Jun 2024 13:36:08 +0200</pubDate><guid>https://k-d-w.org/blog/2024/06/scikit-survival-0.23.0-released/</guid><description>&lt;p&gt;I am pleased to announce the release of &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.23.0.&lt;/p&gt;
&lt;p&gt;This release adds support for scikit-learn 1.4 and 1.5, which includes missing value support for &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html#sksurv.ensemble.RandomSurvivalForest&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt;.
For more details on missing values support, see the section
in the &lt;a href=&#34;https://k-d-w.org/blog/2023/10/scikit-survival-0.22.0-released/#missing-values-support-in-survivaltree&#34; target=&#34;_blank&#34;&gt;release announcement for 0.23.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Moreover, this release fixes critical bugs. When fitting &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;, the &lt;code&gt;sample_weight&lt;/code&gt; is now correctly considered when computing the log-rank statistic for each split. This change also affects &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html#sksurv.ensemble.RandomSurvivalForest&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt; and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.ensemble.ExtraSurvivalTrees.html#sksurv.ensemble.ExtraSurvivalTrees&#34; target=&#34;_blank&#34;&gt;ExtraSurvivalTrees&lt;/a&gt; which pass &lt;code&gt;sample_weight&lt;/code&gt; to the individual trees in the ensemble. Therefore, the outputs produced by &lt;em&gt;SurvivalTree&lt;/em&gt;,
&lt;em&gt;RandomSurvivalForest&lt;/em&gt;, and &lt;em&gt;ExtraSurvivalTrees&lt;/em&gt; will differ from previous releases.&lt;/p&gt;
&lt;p&gt;This release fixes a bug in &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.ensemble.ComponentwiseGradientBoostingSurvivalAnalysis.html#sksurv.ensemble.ComponentwiseGradientBoostingSurvivalAnalysis&#34; target=&#34;_blank&#34;&gt;ComponentwiseGradientBoostingSurvivalAnalysis&lt;/a&gt; and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.23.0/api/generated/sksurv.ensemble.GradientBoostingSurvivalAnalysis.html#sksurv.ensemble.GradientBoostingSurvivalAnalysis&#34; target=&#34;_blank&#34;&gt;GradientBoostingSurvivalAnalysis&lt;/a&gt; when dropout is used. Previously, dropout was only applied starting with the third iteration, now dropout is applied in the second iteration too.&lt;/p&gt;
&lt;p&gt;Finally, this release adds compatibility with numpy 2.0 and drops support for Python 3.8.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;scikit-survival is available for Linux, macOS, and Windows
and can be installed either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c conda-forge scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.22.0 released</title><link>https://k-d-w.org/blog/2023/10/scikit-survival-0.22.0-released/</link><pubDate>Mon, 02 Oct 2023 22:39:15 +0200</pubDate><guid>https://k-d-w.org/blog/2023/10/scikit-survival-0.22.0-released/</guid><description>&lt;p&gt;I am pleased to announce the release of &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.22.0.
The highlights for this release include&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compatibility with scikit-learn 1.3.&lt;/li&gt;
&lt;li&gt;Missing value support for &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A reduced memory mode for &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html#sksurv.ensemble.RandomSurvivalForest&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt;, &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.ensemble.ExtraSurvivalTrees.html#sksurv.ensemble.ExtraSurvivalTrees&#34; target=&#34;_blank&#34;&gt;ExtraSurvivalTrees&lt;/a&gt;, and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Support for &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.meta.Stacking.html#sksurv.meta.Stacking.predict_cumulative_hazard_function&#34; target=&#34;_blank&#34;&gt;predict_cumulative_hazard_function()&lt;/a&gt; and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.meta.Stacking.html#sksurv.meta.Stacking.predict_survival_function&#34; target=&#34;_blank&#34;&gt;predict_survival_function()&lt;/a&gt; in &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.meta.Stacking.html&#34; target=&#34;_blank&#34;&gt;Stacking&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;missing-values-support-in-survivaltree&#34;&gt;Missing Values Support in SurvivalTree&lt;/h2&gt;
&lt;p&gt;Based on the &lt;a href=&#34;https://scikit-learn.org/1.3/modules/tree.html#tree-missing-value-support&#34; target=&#34;_blank&#34;&gt;missing value support&lt;/a&gt;
in scikit-learn 1.3, a &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;
can now deal with missing values if it is fit with &lt;code&gt;splitter=&#39;best&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the training data contained no missing values, then during prediction missing values are mapped
to the child node with the most samples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;X, y = load_veterans_lung_cancer()
X_train = np.asarray(X.loc[:, [&amp;quot;Karnofsky_score&amp;quot;]], dtype=np.float32)
est = SurvivalTree(max_depth=1)
est.fit(X_train, y)
X_test = np.array([[np.nan]])
surv_fn = est.predict_survival_function(X_test, return_array=True)
mask = X_train[:, 0] &amp;gt; est.tree_.threshold[0]
km_x, km_y = kaplan_meier_estimator(
y[mask][&amp;quot;Status&amp;quot;], y[mask][&amp;quot;Survival_in_days&amp;quot;]
)
plt.step(km_x, km_y, where=&amp;quot;post&amp;quot;, linewidth=5)
plt.step(
est.unique_times_, surv_fn[0], where=&amp;quot;post&amp;quot;, linewidth=3, linestyle=&amp;quot;dotted&amp;quot;
)
plt.ylim(0, 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2023/10/scikit-survival-0.22.0-released/img/survival-tree-missing-values-1.svg&#34; width=&#34;400&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;If a tree is fit to training data with missing values, the splitter will evaluate
each split with all samples with missing values going to the left or the right child node.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;X, y = load_veterans_lung_cancer()
X_train = np.asarray(X.loc[:, [&amp;quot;Age_in_years&amp;quot;]], dtype=np.float32)
X_train[-50:, :] = np.nan
est = SurvivalTree(max_depth=1)
est.fit(X_train, y)
X_test = np.array([[np.nan]])
surv_fn = est.predict_survival_function(X_test, return_array=True)
mask = X_train[:, 0] &amp;gt; est.tree_.threshold[0]
mask |= np.isnan(X_train[:, 0])
km_x, km_y = kaplan_meier_estimator(
y[mask][&amp;quot;Status&amp;quot;], y[mask][&amp;quot;Survival_in_days&amp;quot;]
)
plt.step(km_x, km_y, where=&amp;quot;post&amp;quot;, linewidth=5)
plt.step(
est.unique_times_, surv_fn[0], where=&amp;quot;post&amp;quot;, linewidth=3, linestyle=&amp;quot;dotted&amp;quot;
)
plt.ylim(0, 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2023/10/scikit-survival-0.22.0-released/img/survival-tree-missing-values-2.svg&#34; width=&#34;400&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;These rules are identical to those of &lt;a href=&#34;https://scikit-learn.org/1.3/modules/tree.html#tree-missing-value-support&#34; target=&#34;_blank&#34;&gt;scikit-learn&amp;rsquo;s missing value support&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;low-memory-mode-for-survivaltree-and-randomsurvivalforest&#34;&gt;Low-memory Mode for SurvivalTree and RandomSurvivalForest&lt;/h2&gt;
&lt;p&gt;The last release already saw
&lt;a href=&#34;https://k-d-w.org/blog/2023/06/scikit-survival-0.21.0-released/&#34; target=&#34;_blank&#34;&gt;performance improvments to SurvivalTree and RandomSurvivalForest&lt;/a&gt;.
This release adds the &lt;code&gt;low_memory&lt;/code&gt; option to
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html#sksurv.ensemble.RandomSurvivalForest&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt;, &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.ensemble.ExtraSurvivalTrees.html#sksurv.ensemble.ExtraSurvivalTrees&#34; target=&#34;_blank&#34;&gt;ExtraSurvivalTrees&lt;/a&gt;, and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.22.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If low-memory mode is &lt;em&gt;disabled&lt;/em&gt;, which is the default, calling &lt;code&gt;predict&lt;/code&gt; on a sample will require memory
in the order of unique event times in the training data, because the cumulative hazard function
is computed as an intermediate value.
If low-memory mode is &lt;em&gt;enabled&lt;/em&gt;, then the risk score is computed directly, without computing
the cumulative hazard function. However, low-memory mode disables using
&lt;code&gt;predict_cumulative_hazard_function&lt;/code&gt; and &lt;code&gt;predict_survival_function&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;Pre-built conda packages are available for Linux, macOS, and Windows, either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c sebp scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.21.0 released</title><link>https://k-d-w.org/blog/2023/06/scikit-survival-0.21.0-released/</link><pubDate>Sun, 11 Jun 2023 18:43:30 +0200</pubDate><guid>https://k-d-w.org/blog/2023/06/scikit-survival-0.21.0-released/</guid><description>&lt;p&gt;Today marks the release of &lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.21.0.
This release features some exciting new features and significant performance improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pointwise confidence intervals for the Kaplan-Meier estimator.&lt;/li&gt;
&lt;li&gt;Early stopping in GradientBoostingSurvivalAnalysis.&lt;/li&gt;
&lt;li&gt;Improved performance of fitting SurvivalTree and RandomSurvivalForest.&lt;/li&gt;
&lt;li&gt;Reduced memory footprint of concordance_index_censored.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;pointwise-confidence-intervals-for-the-kaplan-meier-estimator&#34;&gt;Pointwise Confidence Intervals for the Kaplan-Meier Estimator&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.nonparametric.kaplan_meier_estimator.html#sksurv.nonparametric.kaplan_meier_estimator&#34; target=&#34;_blank&#34;&gt;kaplan_meier_estimator()&lt;/a&gt;
can now estimate pointwise confidence intervals by specifying the &lt;code&gt;conf_type&lt;/code&gt; parameter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import matplotlib.pyplot as plt
from sksurv.datasets import load_veterans_lung_cancer
from sksurv.nonparametric import kaplan_meier_estimator
_, y = load_veterans_lung_cancer()
time, survival_prob, conf_int = kaplan_meier_estimator(
y[&amp;quot;Status&amp;quot;], y[&amp;quot;Survival_in_days&amp;quot;], conf_type=&amp;quot;log-log&amp;quot;
)
plt.step(time, survival_prob, where=&amp;quot;post&amp;quot;)
plt.fill_between(time, conf_int[0], conf_int[1], alpha=0.25, step=&amp;quot;post&amp;quot;)
plt.ylim(0, 1)
plt.ylabel(&amp;quot;est. probability of survival $\hat{S}(t)$&amp;quot;)
plt.xlabel(&amp;quot;time $t$&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2023/06/scikit-survival-0.21.0-released/img/km-with-ci.svg&#34;
alt=&#34;Kaplan-Meier curve with pointwise confidence intervals.&#34;/&gt; &lt;figcaption&gt;
&lt;p&gt;Kaplan-Meier curve with pointwise confidence intervals.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;early-stopping-in-gradientboostingsurvivalanalysis&#34;&gt;Early Stopping in GradientBoostingSurvivalAnalysis&lt;/h2&gt;
&lt;p&gt;Early stopping enables us to determine when the model is sufficiently complex.
This is usually done by continuously evaluating the model on held-out data.
For &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.ensemble.GradientBoostingSurvivalAnalysis.html#sksurv.ensemble.GradientBoostingSurvivalAnalysis&#34; target=&#34;_blank&#34;&gt;GradientBoostingSurvivalAnalysis&lt;/a&gt;,
the easiest way to achieve this is by setting &lt;code&gt;n_iter_no_change&lt;/code&gt; and
optionally &lt;code&gt;validation_fraction&lt;/code&gt; (defaults to 0.1).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_whas500
from sksurv.ensemble import GradientBoostingSurvivalAnalysis
X, y = load_whas500()
model = GradientBoostingSurvivalAnalysis(
n_estimators=1000, max_depth=2, subsample=0.8, n_iter_no_change=10, random_state=0,
)
model.fit(X, y)
print(model.n_estimators_)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, &lt;code&gt;model.n_estimators_&lt;/code&gt; indicates that fitting stopped after 73 iterations,
instead of the maximum 1000 iterations.&lt;/p&gt;
&lt;p&gt;Alternatively, one can provide a custom callback function to the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.ensemble.GradientBoostingSurvivalAnalysis.html#sksurv.ensemble.GradientBoostingSurvivalAnalysis.fit&#34; target=&#34;_blank&#34;&gt;fit&lt;/a&gt;
method. If the callback returns &lt;code&gt;True&lt;/code&gt;, training is stopped.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;model = GradientBoostingSurvivalAnalysis(
n_estimators=1000, max_depth=2, subsample=0.8, random_state=0,
)
def early_stopping_monitor(iteration, model, args):
&amp;quot;&amp;quot;&amp;quot;Stop training if there was no improvement in the last 10 iterations&amp;quot;&amp;quot;&amp;quot;
start = max(0, iteration - 10)
end = iteration + 1
oob_improvement = model.oob_improvement_[start:end]
return all(oob_improvement &amp;lt; 0)
model.fit(X, y, monitor=early_stopping_monitor)
print(model.n_estimators_)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, early stopping is determined by checking
the last 10 entries of the &lt;code&gt;oob_improvement_&lt;/code&gt; attribute.
It contains the improvement in loss on the out-of-bag samples
relative to the previous iteration.
This requires setting &lt;code&gt;subsample&lt;/code&gt; to a value smaller 1, here 0.8.
Using this approach, training stopped after 114 iterations.&lt;/p&gt;
&lt;h2 id=&#34;improved-performance-of-survivaltree-and-randomsurvivalforest&#34;&gt;Improved Performance of SurvivalTree and RandomSurvivalForest&lt;/h2&gt;
&lt;p&gt;Another exciting feature of scikit-survival 0.21.0 is due to a re-write of
the training routine of &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.tree.SurvivalTree.html#sksurv.tree.SurvivalTree&#34; target=&#34;_blank&#34;&gt;SurvivalTree&lt;/a&gt;.
This results in roughly &lt;strong&gt;3x faster training times&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://k-d-w.org/blog/2023/06/scikit-survival-0.21.0-released/img/fit-times.svg&#34;
alt=&#34;Runtime comparison of fitting SurvivalTree.&#34;/&gt; &lt;figcaption&gt;
&lt;p&gt;Runtime comparison of fitting SurvivalTree.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The plot above compares the time required to fit a single SurvivalTree on data with
25 features and varying number of samples.
The performance difference becomes notable for data with 1000 samples and above.&lt;/p&gt;
&lt;p&gt;Note that this improvement also speeds-up fitting
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.ensemble.RandomSurvivalForest.html#sksurv.ensemble.RandomSurvivalForest&#34; target=&#34;_blank&#34;&gt;RandomSurvivalForest&lt;/a&gt;
and &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.ensemble.ExtraSurvivalTrees.html#sksurv.ensemble.ExtraSurvivalTrees&#34; target=&#34;_blank&#34;&gt;ExtraSurvivalTrees&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;improved-concordance-index&#34;&gt;Improved concordance index&lt;/h2&gt;
&lt;p&gt;Another performance improvement is due to &lt;a href=&#34;https://github.com/cpoerschke&#34; target=&#34;_blank&#34;&gt;Christine Poerschke&lt;/a&gt;
who significantly reduced the memory footprint of
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/api/generated/sksurv.metrics.concordance_index_censored.html#sksurv.metrics.concordance_index_censored&#34; target=&#34;_blank&#34;&gt;concordance_index_censored()&lt;/a&gt;.
With scikit-survival 0.21.0, memory usage &lt;strong&gt;scales linear, instead of quadratic&lt;/strong&gt;, in the number of samples, making performance evaluation on large datasets much more manageable.&lt;/p&gt;
&lt;p&gt;For a full list of changes in scikit-survival 0.21.0, please see the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.21.0/release_notes.html#scikit-survival-0-21-0-2023-06-11&#34; target=&#34;_blank&#34;&gt;release notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;Pre-built conda packages are available for Linux, macOS (Intel), and Windows, either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c sebp scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.18.0 released</title><link>https://k-d-w.org/blog/2022/08/scikit-survival-0.18.0-released/</link><pubDate>Mon, 15 Aug 2022 17:18:26 +0200</pubDate><guid>https://k-d-w.org/blog/2022/08/scikit-survival-0.18.0-released/</guid><description>&lt;p&gt;I&amp;rsquo;m pleased to announce the release of
&lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.18.0,
which adds support for scikit-learn 1.1.&lt;/p&gt;
&lt;p&gt;In addition, this release adds the &lt;code&gt;return_array&lt;/code&gt; argument to all models providing
&lt;code&gt;predict_survival_function&lt;/code&gt; and &lt;code&gt;predict_cumulative_hazard_function&lt;/code&gt;.
That means you can now choose, whether you want to have the survival
(cumulative hazard function) automatically evaluated at the unique event
times. This is particular useful for plotting. Previously, you would have
to evaluate each survival function before plotting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;estimator = CoxPHSurvivalAnalysis()
estimator.fit(X_train, y_train)
pred_surv = estimator.predict_survival_function(
X_test
)
times = pred_surv[0].x
for surv_func in pred_surv:
plt.step(times, surv_func(times), where=&amp;quot;post&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can pass &lt;code&gt;return_array=True&lt;/code&gt; and directly get probabilities
of the survival function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;estimator = CoxPHSurvivalAnalysis()
estimator.fit(X_train, y_train)
pred_surv_probs = estimator.predict_survival_function(
X_test, return_array=True
)
times = estimator.event_times_
for probs in pred_surv_probs:
plt.step(times, probs, where=&amp;quot;post&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, support for Python 3.7 has been dropped and the minimal required
version of the following dependencies are raised:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;numpy 1.17.3&lt;/li&gt;
&lt;li&gt;pandas 1.0.5&lt;/li&gt;
&lt;li&gt;scikit-learn 1.1.0&lt;/li&gt;
&lt;li&gt;scipy 1.3.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a full list of changes in scikit-survival 0.18.0, please see the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.18.0/release_notes.html#scikit-survival-0-18-0-2022-08-15&#34; target=&#34;_blank&#34;&gt;release notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;Pre-built conda packages are available for Linux, macOS (Intel), and Windows, either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c sebp scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>scikit-survival 0.17.2 released</title><link>https://k-d-w.org/blog/2022/04/scikit-survival-0.17.2-released/</link><pubDate>Mon, 25 Apr 2022 20:14:19 +0200</pubDate><guid>https://k-d-w.org/blog/2022/04/scikit-survival-0.17.2-released/</guid><description>&lt;p&gt;I&amp;rsquo;m pleased to announce the release of
&lt;a href=&#34;https://scikit-survival.readthedocs.io/&#34; target=&#34;_blank&#34;&gt;scikit-survival&lt;/a&gt; 0.17.2.
This release fixes several small issues with packaging scikit-survival
and the &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.2/&#34; target=&#34;_blank&#34;&gt;documentation&lt;/a&gt;.
For a full list of changes in scikit-survival 0.17.2, please see the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.2/release_notes.html#scikit-survival-0-17-2-2022-04-24&#34; target=&#34;_blank&#34;&gt;release notes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most notably, binary wheels are now available for Linux, Windows, and macOS (Intel).
This has been possible thanks to the &lt;a href=&#34;https://cibuildwheel.readthedocs.org/&#34; target=&#34;_blank&#34;&gt;cibuildwheel&lt;/a&gt;
build tool, which makes it incredible easy to use GitHub Actions for building
those wheels for multiple versions of Python.
Therefore, you can now use &lt;code&gt;pip&lt;/code&gt; without building everything from source by
simply running&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As before, pre-built conda packages are available too, by running&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c sebp scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;support-for-apple-silicon-m1&#34;&gt;Support for Apple Silicon M1&lt;/h2&gt;
&lt;p&gt;Currently, there are no pre-built packages for Mac with Apple Silicon M1
hardware (also known as &lt;code&gt;macos/arm64&lt;/code&gt;).
There are two main reasons for that. The biggest problem is the lack of
CI servers that run on Apple Silicon M1. This makes it difficult to
systematically test scikit-survival on such hardware.
Second, some of scikit-survival&amp;rsquo;s dependencies do not offer wheels
for &lt;code&gt;macos/arm64&lt;/code&gt; yet, namely &lt;a href=&#34;https://pypi.org/project/ecos/&#34; target=&#34;_blank&#34;&gt;ecos&lt;/a&gt;
and &lt;a href=&#34;https://pypi.org/project/osqp/&#34; target=&#34;_blank&#34;&gt;osqp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, &lt;a href=&#34;conda-forge.org&#34; target=&#34;_blank&#34;&gt;conda-forge&lt;/a&gt; figured out a way to
cross-compile packages, and do offer
&lt;a href=&#34;https://anaconda.org/conda-forge/scikit-survival&#34; target=&#34;_blank&#34;&gt;scikit-survival for macos/arm64&lt;/a&gt;.
I tried to adapt their conda recipe, but failed to achieve a working
cross-compilation process so far.
Cross-compiling with &lt;code&gt;cibuildwheel&lt;/code&gt; would be an alternative, but currently
doesn&amp;rsquo;t make much sense if run-time dependencies &lt;code&gt;ecos&lt;/code&gt; and &lt;code&gt;osqp&lt;/code&gt; do not
provide wheels for &lt;code&gt;macos/arm64&lt;/code&gt;.
I hope these issues can be resolved in the near future.&lt;/p&gt;</description></item><item><title>scikit-survival 0.17 released</title><link>https://k-d-w.org/blog/2022/01/scikit-survival-0.17-released/</link><pubDate>Sun, 09 Jan 2022 16:45:38 +0100</pubDate><guid>https://k-d-w.org/blog/2022/01/scikit-survival-0.17-released/</guid><description>&lt;p&gt;This release adds support for scikit-learn 1.0, which includes support for feature names. If you pass a pandas dataframe to &lt;code&gt;fit&lt;/code&gt;, the estimator will set a &lt;code&gt;feature_names_in_&lt;/code&gt; attribute containing the feature names. When a dataframe is passed to &lt;code&gt;predict&lt;/code&gt;, it is checked that the column names are consistent with those passed to &lt;code&gt;fit&lt;/code&gt;.
The
&lt;a href=&#34;https://k-d-w.org/blog/2022/01/scikit-survival-0.17-released/#feature-names-support&#34;&gt;
example below
&lt;/a&gt;
illustrates this feature.&lt;/p&gt;
&lt;p&gt;For a full list of changes in scikit-survival 0.17.0, please see the
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.0/release_notes.html#scikit-survival-0-17-0-2022-01-09&#34; target=&#34;_blank&#34;&gt;release notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Pre-built conda packages are available for Linux, macOS, and Windows via&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt; conda install -c sebp scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, scikit-survival can be installed from source following
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.0/install.html#from-source&#34; target=&#34;_blank&#34;&gt;these instructions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;feature-names-support&#34;&gt;Feature Names Support&lt;/h2&gt;
&lt;p&gt;Prior to scikit-survival 0.17, you could pass a pandas dataframe to estimators&amp;rsquo; &lt;code&gt;fit&lt;/code&gt;
and &lt;code&gt;predict&lt;/code&gt; methods, but the estimator was oblivious to the
feature names accessible via the dataframe&amp;rsquo;s &lt;code&gt;columns&lt;/code&gt; attribute.
With scikit-survival 0.17, and thanks to scikit-learn 1.0,
feature names will be considered when a dataframe is passed.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s illustrate feature names support using the Veteran&amp;rsquo;s Lung Cancer dataset.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.datasets import load_veterans_lung_cancer
X, y = load_veterans_lung_cancer()
X.head(3)
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;table-responsive&#34;&gt;
&lt;table class=&#34;table-dataframe&#34;&gt;
&lt;thead&gt;
&lt;tr style=&#34;text-align: right;&#34;&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Age_in_years&lt;/th&gt;
&lt;th&gt;Celltype&lt;/th&gt;
&lt;th&gt;Karnofsky_score&lt;/th&gt;
&lt;th&gt;Months_from_Diagnosis&lt;/th&gt;
&lt;th&gt;Prior_therapy&lt;/th&gt;
&lt;th&gt;Treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;69.0&lt;/td&gt;
&lt;td&gt;squamous&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;7.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;64.0&lt;/td&gt;
&lt;td&gt;squamous&lt;/td&gt;
&lt;td&gt;70.0&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;38.0&lt;/td&gt;
&lt;td&gt;squamous&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;standard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The original data has 6 features, three of which contain strings, which
we encode as numeric using &lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.0/api/generated/sksurv.preprocessing.OneHotEncoder.html&#34; target=&#34;_blank&#34;&gt;OneHotEncoder&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.preprocessing import OneHotEncoder
transform = OneHotEncoder()
Xt = transform.fit_transform(X)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transforms now have a &lt;code&gt;get_feature_names_out()&lt;/code&gt; method, which will
return the name of features after the transformation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;transform.get_feature_names_out()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;
array([&#39;Age_in_years&#39;, &#39;Celltype=large&#39;, &#39;Celltype=smallcell&#39;,
&#39;Celltype=squamous&#39;, &#39;Karnofsky_score&#39;, &#39;Months_from_Diagnosis&#39;,
&#39;Prior_therapy=yes&#39;, &#39;Treatment=test&#39;], dtype=object)
&lt;/pre&gt;
&lt;p&gt;The transformed data returned by &lt;code&gt;OneHotEncoder&lt;/code&gt; is again a dataframe,
which can be used to fit
&lt;a href=&#34;https://scikit-survival.readthedocs.io/en/v0.17.0/api/generated/sksurv.linear_model.CoxPHSurvivalAnalysis.html&#34; target=&#34;_blank&#34;&gt;Cox&amp;rsquo;s proportional hazards model&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from sksurv.linear_model import CoxPHSurvivalAnalysis
model = CoxPHSurvivalAnalysis().fit(Xt, y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we passed a dataframe, the &lt;code&gt;feature_names_in_&lt;/code&gt; attribute will contain
the names of the dataframe used when calling &lt;code&gt;fit&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;model.feature_names_in_
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;
array([&#39;Age_in_years&#39;, &#39;Celltype=large&#39;, &#39;Celltype=smallcell&#39;,
&#39;Celltype=squamous&#39;, &#39;Karnofsky_score&#39;, &#39;Months_from_Diagnosis&#39;,
&#39;Prior_therapy=yes&#39;, &#39;Treatment=test&#39;], dtype=object)
&lt;/pre&gt;
&lt;p&gt;This is used during prediction to check that the data matches the format
of the training data. For instance, when passing a raw numpy array instead
of a dataframe, a warning will be issued.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;pred = model.predict(Xt.values)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;
UserWarning: X does not have valid feature names, but CoxPHSurvivalAnalysis was fitted with feature names
&lt;/pre&gt;
&lt;p&gt;Moreover, it will also check that the order of columns matches.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;X_reordered = pd.concat(
(Xt.drop(&amp;quot;Age_in_years&amp;quot;, axis=1), Xt.loc[:, &amp;quot;Age_in_years&amp;quot;]),
axis=1
)
pred = model.predict(X_reordered)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;
FutureWarning: The feature names should match those that were passed during fit. Starting version 1.2, an error will be raised.
Feature names must be in the same order as they were in fit.
&lt;/pre&gt;
&lt;p&gt;For more details on feature names support, have a look at the
&lt;a href=&#34;https://scikit-learn.org/stable/auto_examples/release_highlights/plot_release_highlights_1_0_0.html#feature-names-support&#34; target=&#34;_blank&#34;&gt;scikit-learn release highlights&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>