Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
eeb817f
first commit
ohines Mar 27, 2026
2bbd2d3
update
ohines Mar 27, 2026
6e41a22
update example
ohines Mar 27, 2026
9204894
simplify example
ohines Mar 27, 2026
150e355
rename to survival
ohines Mar 27, 2026
0ec49da
fix test name
ohines Mar 27, 2026
106c97d
run pre-commits
ohines Mar 27, 2026
53711ad
remove divide by zero guards
ohines Mar 27, 2026
8b65dc0
fixes
ohines Mar 27, 2026
9789100
use real data in example and fix higher is better bug
ohines Mar 27, 2026
734e85c
simplify print
ohines Mar 27, 2026
fa1308f
Merge branch 'master' into oh-survival
ohines Mar 30, 2026
465b2d8
fix precommits
ohines Mar 30, 2026
15b3864
remove parse argument
ohines Apr 1, 2026
00e94f8
Merge branch 'master' into oh-survival
ohines Apr 1, 2026
ae39518
simplify indexing for max and sum operations
ohines Apr 1, 2026
d54662c
modify NeedAccuratePrediction
ohines Apr 1, 2026
eb083b1
use synthetic data in example
ohines Apr 1, 2026
0051861
update metrics.R
ohines Apr 2, 2026
03ad7aa
Update tests/python_package_test/test_engine.py
ohines Apr 2, 2026
e3059c3
Update tests/python_package_test/test_engine.py
ohines Apr 2, 2026
11ffcf9
rename load_survival to make_survival
ohines Apr 2, 2026
0b1dba1
update test criteria
ohines Apr 2, 2026
a0eb4db
remove survival alias
ohines Apr 2, 2026
2037974
update python tests
ohines Apr 2, 2026
f3ca82f
add R test
ohines Apr 2, 2026
314a5e8
Merge branch 'master' into oh-survival
ohines Apr 2, 2026
0628799
fix R lint issue
ohines Apr 2, 2026
d6133af
Merge branch 'master' into oh-survival
jameslamb Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/Parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Core Parameters

- **Note**: can be used only in CLI version; for language-specific packages you can use the correspondent functions

- ``objective`` :raw-html:`<a id="objective" title="Permalink to this parameter" href="#objective">&#x1F517;&#xFE0E;</a>`, default = ``regression``, type = enum, options: ``regression``, ``regression_l1``, ``huber``, ``fair``, ``poisson``, ``quantile``, ``mape``, ``gamma``, ``tweedie``, ``binary``, ``multiclass``, ``multiclassova``, ``cross_entropy``, ``cross_entropy_lambda``, ``lambdarank``, ``rank_xendcg``, aliases: ``objective_type``, ``app``, ``application``, ``loss``
- ``objective`` :raw-html:`<a id="objective" title="Permalink to this parameter" href="#objective">&#x1F517;&#xFE0E;</a>`, default = ``regression``, type = enum, options: ``regression``, ``regression_l1``, ``huber``, ``fair``, ``poisson``, ``quantile``, ``mape``, ``gamma``, ``tweedie``, ``binary``, ``multiclass``, ``multiclassova``, ``cross_entropy``, ``cross_entropy_lambda``, ``lambdarank``, ``rank_xendcg``, ``survival_cox``, aliases: ``objective_type``, ``app``, ``application``, ``loss``

- regression application

Expand Down Expand Up @@ -170,6 +170,12 @@ Core Parameters

- label should be ``int`` type, and larger number represents the higher relevance (e.g. 0:bad, 1:fair, 2:good, 3:perfect)

- survival analysis application

- ``survival_cox``, `Cox proportional hazards <https://en.wikipedia.org/wiki/Proportional_hazards_model>`__ partial likelihood with Breslow's method for ties, aliases: ``survival``, ``cox``, ``cox_ph``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these aliases used in other projects or research?

If not, let's please not use any aliases for this objective. Aliases add complexity and maintenance burden, and I'd especially like to avoid committing survival like this in case other survival objectives are added in the future.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about removing the name survival.

  • In XGBoost the relavant objective and metric aresurvival:cox and cox-nloglik.
  • In Scikit-survival the relevant function is CoxPHSurvivalAnalysis
  • In R Survival package the relevant function is coxph with ties=“breslow”
  • In the Lifelines package the relevant class is CoxPHFitter
  • In stats theory, the metric is sometimes referred to as a "partial likelihood"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for those links! That's exactly the type of thing I was looking for. Based on that, I'm happy with dropping survival but keeping cox and cox_ph.


- label encodes censoring via sign: positive value = event time, negative value = censored time

- custom objective function (gradients and hessians not computed directly by LightGBM)

- ``custom``
Expand Down Expand Up @@ -1279,6 +1285,10 @@ Metric Parameters

- ``kullback_leibler``, `Kullback-Leibler divergence <https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence>`__, aliases: ``kldiv``

- ``survival_cox_nll``, negative partial log-likelihood for `Cox proportional hazards <https://en.wikipedia.org/wiki/Proportional_hazards_model>`__ model, aliases: ``cox_nll``, ``survival_nll``

- ``concordance_index``, `Harrell's concordance index <https://doi.org/10.1002/(SICI)1097-0258(19960229)15:4<361::AID-SIM168>3.0.CO;2-4>`__ for survival models, aliases: ``c_index``

- support multiple metrics, separated by ``,``

- ``metric_freq`` :raw-html:`<a id="metric_freq" title="Permalink to this parameter" href="#metric_freq">&#x1F517;&#xFE0E;</a>`, default = ``1``, type = int, aliases: ``output_freq``, constraints: ``metric_freq > 0``
Expand Down
4 changes: 4 additions & 0 deletions examples/python-guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Examples include:
- Plot split value histogram
- Plot one specified tree
- Plot one specified tree with Graphviz
- [survival_example.py](https://github.com/lightgbm-org/LightGBM/blob/master/examples/python-guide/survival_example.py)
- Construct Dataset
- Use objective `survival_cox` for Cox proportional hazards survival analysis
- Evaluate with `survival_cox_nll` and `concordance_index` metrics
- [dataset_from_multi_hdf5.py](https://github.com/lightgbm-org/LightGBM/blob/master/examples/python-guide/dataset_from_multi_hdf5.py)
- Construct Dataset from multiple HDF5 files
- Avoid loading all data into memory
51 changes: 51 additions & 0 deletions examples/python-guide/survival_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# coding: utf-8
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split

import lightgbm as lgb

# Load FLCHAIN dataset (serum free light chain and mortality)
data = fetch_openml("flchain", version=1, as_frame=True, parser="auto")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AppVeyor builds are failing like this:

TypeError: fetch_openml() got an unexpected keyword argument 'parser'

https://ci.appveyor.com/project/guolinke/lightgbm/builds/53791302/job/oj3cfvbuifsjc7au?fullLog=true

Those jobs use a very old scikit-learn (1.0), which I guess must not have had that. Can you please figure out a more portable pattern? A different dataset, omitting the parser argument, something like that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. I wasn't sure how to debug the appveyor failing tests.
You are right - parser is not required so removed it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor fails again with

sklearn.datasets._openml.OpenMLError: Dataset flchain with version 1 not found

I will look into it. It seems that fetch_openml is marked as experimental in the scikit learn version 1.0 docs. Is there a particular reason that we test this version which was released in 2021?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we try to support a wide range of lightgbm's main dependencies, for the benefit of users who can't easily upgrade to newer versions (e.g. they're using managed environments like Databricks notebooks or constrained to older operating systems).

We'd prefer to have a compelling reason to bump a runtime floor, and "makes this example in documentation easier to test" isn't that compelling, in my opinion.

That said we do already have a Linux job testing an even older scikit-learn:

scikit-learn==0.24.2

So I wouldn't be opposed to updating the pin for Python 3.9 environments like the one on Appveyor. That could be done here:

scikit-learn=1.0.*

I'd support trying to bump that up to a newer scikit-learn if you'd like. But it'll probably require pinning more than just scikit-learn, so might take a bit of trial and error.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this on my local machine with python=3.10.20 and sklearn=1.0.2 and it works fine. Is it possible that the runner does not have access to open ml?

These are the api calls that are made (generated by adding a print statement here)

downloading data from https://openml.org/api/v1/json/data/list/data_name/flchain/limit/2/data_version/1
downloading data from https://openml.org/api/v1/json/data/46161
downloading data from https://openml.org/api/v1/json/data/features/46161
downloading data from https://openml.org/api/v1/json/data/qualities/46161
downloading data from https://openml.org/data/v1/download/22120605

In any case I can just replace the example to use synthetic data.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in eb083b1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this on my local machine with python=3.10.20 and sklearn=1.0.2 and it works fine. Is it possible that the runner does not have access to open ml?

That job uses Python 3.9, not 3.10. It's the standard Appveyor runner for open source projects and should have full access to the internet.

I suspect that maybe that "not found" error is actually from a broad try-catch and that something else in the environment (like some other dependency version) is causing it to fail.

The approach with synthetic data looks good to me!

Thanks for working through that.

df = data.data.copy()
time = data.target.values
event = df.pop("status").values

# Encode strings as integers
df["sex"] = (df["sex"] == "F").astype(int)
df["flc.grp"] = df["flc.grp"].astype(int)
df["mgus"] = df["mgus"].astype(int)

# Encode labels: positive = event (death), negative = censored
y = np.where(event == 1, time, -time)
X = df.values

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

lgb_train = lgb.Dataset(X_train, label=y_train)
lgb_val = lgb.Dataset(X_val, label=y_val, reference=lgb_train)

params = {
"objective": "survival_cox",
"metric": ["survival_cox_nll", "concordance_index"],
"num_leaves": 31,
"learning_rate": 0.05,
"verbose": 0,
}

evals_result = {}
gbm = lgb.train(
params,
lgb_train,
num_boost_round=200,
valid_sets=[lgb_val],
valid_names=["val"],
callbacks=[
lgb.early_stopping(stopping_rounds=20, first_metric_only=True),
lgb.record_evaluation(evals_result),
],
)

# Predictions are log-hazard ratios (higher = more risk)
preds = gbm.predict(X_val, num_iteration=gbm.best_iteration)
print(f"\nPrediction range: [{preds.min():.3f}, {preds.max():.3f}]")
13 changes: 12 additions & 1 deletion include/LightGBM/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct Config {
// [no-automatically-extract]
// [no-save]
// type = enum
// options = regression, regression_l1, huber, fair, poisson, quantile, mape, gamma, tweedie, binary, multiclass, multiclassova, cross_entropy, cross_entropy_lambda, lambdarank, rank_xendcg
// options = regression, regression_l1, huber, fair, poisson, quantile, mape, gamma, tweedie, binary, multiclass, multiclassova, cross_entropy, cross_entropy_lambda, lambdarank, rank_xendcg, survival_cox
// alias = objective_type, app, application, loss
// desc = regression application
// descl2 = ``regression``, L2 loss, aliases: ``regression_l2``, ``l2``, ``mean_squared_error``, ``mse``, ``l2_root``, ``root_mean_squared_error``, ``rmse``
Expand Down Expand Up @@ -160,6 +160,9 @@ struct Config {
// descl2 = ``rank_xendcg``, `XE_NDCG_MART <https://arxiv.org/abs/1911.09798>`__ ranking objective function, aliases: ``xendcg``, ``xe_ndcg``, ``xe_ndcg_mart``, ``xendcg_mart``
// descl2 = ``rank_xendcg`` is faster than and achieves the similar performance as ``lambdarank``
// descl2 = label should be ``int`` type, and larger number represents the higher relevance (e.g. 0:bad, 1:fair, 2:good, 3:perfect)
// desc = survival analysis application
// descl2 = ``survival_cox``, `Cox proportional hazards <https://en.wikipedia.org/wiki/Proportional_hazards_model>`__ partial likelihood with Breslow's method for ties, aliases: ``survival``, ``cox``, ``cox_ph``
// descl2 = label encodes censoring via sign: positive value = event time, negative value = censored time
// desc = custom objective function (gradients and hessians not computed directly by LightGBM)
// descl2 = ``custom``
// descl2 = must be passed through parameters explicitly in the C API
Expand Down Expand Up @@ -1039,6 +1042,8 @@ struct Config {
// descl2 = ``cross_entropy``, cross-entropy (with optional linear weights), aliases: ``xentropy``
// descl2 = ``cross_entropy_lambda``, "intensity-weighted" cross-entropy, aliases: ``xentlambda``
// descl2 = ``kullback_leibler``, `Kullback-Leibler divergence <https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence>`__, aliases: ``kldiv``
// descl2 = ``survival_cox_nll``, negative partial log-likelihood for `Cox proportional hazards <https://en.wikipedia.org/wiki/Proportional_hazards_model>`__ model, aliases: ``cox_nll``, ``survival_nll``
// descl2 = ``concordance_index``, `Harrell's concordance index <https://doi.org/10.1002/(SICI)1097-0258(19960229)15:4<361::AID-SIM168>3.0.CO;2-4>`__ for survival models, aliases: ``c_index``
// desc = support multiple metrics, separated by ``,``
std::vector<std::string> metric;

Expand Down Expand Up @@ -1293,6 +1298,8 @@ inline std::string ParseObjectiveAlias(const std::string& type) {
} else if (type == std::string("rank_xendcg") || type == std::string("xendcg") || type == std::string("xe_ndcg")
|| type == std::string("xe_ndcg_mart") || type == std::string("xendcg_mart")) {
return "rank_xendcg";
} else if (type == std::string("survival_cox") || type == std::string("survival") || type == std::string("cox") || type == std::string("cox_ph")) {
return "survival_cox";
} else if (type == std::string("none") || type == std::string("null") || type == std::string("custom") || type == std::string("na")) {
return "custom";
}
Expand Down Expand Up @@ -1323,6 +1330,10 @@ inline std::string ParseMetricAlias(const std::string& type) {
return "kullback_leibler";
} else if (type == std::string("mean_absolute_percentage_error") || type == std::string("mape")) {
return "mape";
} else if (type == std::string("survival_cox") || type == std::string("survival") || type == std::string("survival_cox_nll") || type == std::string("survival_nll") || type == std::string("cox") || type == std::string("cox_ph") || type == std::string("cox_nll")) {
return "survival_cox_nll";
} else if (type == std::string("c_index") || type == std::string("concordance_index")) {
return "concordance_index";
} else if (type == std::string("none") || type == std::string("null") || type == std::string("custom") || type == std::string("na")) {
return "custom";
}
Expand Down
3 changes: 2 additions & 1 deletion python-package/lightgbm/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5308,5 +5308,6 @@ def __get_eval_info(self) -> None:
)
self.__name_inner_eval = [string_buffers[i].value.decode("utf-8") for i in range(self.__num_inner_eval)]
self.__higher_better_inner_eval = [
name.startswith(("auc", "ndcg@", "map@", "average_precision")) for name in self.__name_inner_eval
name.startswith(("auc", "ndcg@", "map@", "average_precision", "concordance_index"))
for name in self.__name_inner_eval
]
188 changes: 188 additions & 0 deletions src/metric/cox_survival_metric.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*!
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
Comment on lines +2 to +3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
* Copyright (c) 2026 The LightGBM developers. All rights reserved.

(and XGBoost, if this has anything copied verbatim from there)

* Licensed under the MIT License. See LICENSE file in the project root for license information.
*/
#ifndef LIGHTGBM_SRC_METRIC_COX_SURVIVAL_METRIC_HPP_
#define LIGHTGBM_SRC_METRIC_COX_SURVIVAL_METRIC_HPP_

#include <LightGBM/metric.h>
#include <LightGBM/utils/log.h>

#include <string>
#include <algorithm>
#include <cmath>
#include <vector>

namespace LightGBM {

/*!
* \brief Negative partial log-likelihood metric for Cox PH models (Breslow's method).
*
* Labels encode censoring via sign: +t = event at time t, -t = censored at t.
* Lower is better.
*/
class CoxNLLMetric : public Metric {
public:
explicit CoxNLLMetric(const Config&) {}

~CoxNLLMetric() {}

void Init(const Metadata& metadata, data_size_t num_data) override {
name_.emplace_back("survival_cox_nll");
num_data_ = num_data;
label_ = metadata.label();

// Build sorted indices by ascending |label| (survival time)
sorted_indices_.resize(num_data_);
for (data_size_t i = 0; i < num_data_; ++i) {
sorted_indices_[i] = i;
}
std::stable_sort(sorted_indices_.begin(), sorted_indices_.end(),
[this](data_size_t a, data_size_t b) {
return std::fabs(label_[a]) < std::fabs(label_[b]);
});
}

const std::vector<std::string>& GetName() const override {
return name_;
}

double factor_to_bigger_better() const override {
return -1.0; // lower is better
}

std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
// Breslow forward-pass to compute negative partial log-likelihood
double max_p = score[sorted_indices_[0]];
for (data_size_t k = 1; k < num_data_; ++k) {
const data_size_t idx = sorted_indices_[k];
if (score[idx] > max_p) {
max_p = score[idx];
}
}

double exp_p_sum = 0.0;
for (data_size_t k = 0; k < num_data_; ++k) {
exp_p_sum += std::exp(score[sorted_indices_[k]] - max_p);
}

double last_exp_p = 0.0;
double last_abs_y = 0.0;
double accumulated_sum = 0.0;
double pll = 0.0;
int n_events = 0;

for (data_size_t k = 0; k < num_data_; ++k) {
const data_size_t idx = sorted_indices_[k];
const double p = score[idx];
const double exp_p = std::exp(p - max_p);
const double y = static_cast<double>(label_[idx]);
const double abs_y = std::fabs(y);

accumulated_sum += last_exp_p;
if (last_abs_y < abs_y) {
exp_p_sum -= accumulated_sum;
accumulated_sum = 0.0;
}

if (y > 0) {
// p - log(sum exp(p_k)) = (p - max_p) - log(sum exp(p_k - max_p))
pll += (p - max_p) - std::log(exp_p_sum);
n_events += 1;
}

last_abs_y = abs_y;
last_exp_p = exp_p;
}

double loss = -pll / std::max(n_events, 1);
return std::vector<double>(1, loss);
}

private:
data_size_t num_data_;
const label_t* label_;
std::vector<data_size_t> sorted_indices_;
std::vector<std::string> name_;
};

/*!
* \brief Harrell's concordance index metric for Cox PH models.
*
* Higher predictions = higher risk. A pair (i, j) is comparable if subject i
* had an event and T_i < |T_j|.
* Returns value in [0, 1] where 0.5 = random, 1.0 = perfect.
* Higher is better.
*/
class ConcordanceIndexMetric : public Metric {
public:
explicit ConcordanceIndexMetric(const Config&) {}

~ConcordanceIndexMetric() {}

void Init(const Metadata& metadata, data_size_t num_data) override {
name_.emplace_back("concordance_index");
num_data_ = num_data;
label_ = metadata.label();
}

const std::vector<std::string>& GetName() const override {
return name_;
}

double factor_to_bigger_better() const override {
return 1.0; // higher is better
}

std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
int64_t concordant = 0;
int64_t discordant = 0;
int64_t tied_risk = 0;

for (data_size_t i = 0; i < num_data_; ++i) {
const double y_i = static_cast<double>(label_[i]);
if (y_i <= 0) {
continue; // i must have an event
}
const double t_i = y_i;
for (data_size_t j = 0; j < num_data_; ++j) {
if (i == j) {
continue;
}
const double y_j = static_cast<double>(label_[j]);
const double t_j = std::fabs(y_j);
if (t_j < t_i) {
continue; // j's time is strictly earlier
}
if (t_j == t_i && y_j > 0) {
continue; // j also had event at the same time — not comparable
}
// comparable: j survived past t_i, or was censored at t_i
if (score[i] > score[j]) {
concordant += 1;
} else if (score[i] < score[j]) {
discordant += 1;
} else {
tied_risk += 1;
}
}
}

const int64_t total = concordant + discordant + tied_risk;
double c_index = 0.5;
if (total > 0) {
c_index = (static_cast<double>(concordant) + 0.5 * static_cast<double>(tied_risk))
/ static_cast<double>(total);
}
return std::vector<double>(1, c_index);
}

private:
data_size_t num_data_;
const label_t* label_;
std::vector<std::string> name_;
};

} // namespace LightGBM
#endif // LIGHTGBM_SRC_METRIC_COX_SURVIVAL_METRIC_HPP_
11 changes: 11 additions & 0 deletions src/metric/metric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <string>

#include "binary_metric.hpp"
#include "cox_survival_metric.hpp"
#include "map_metric.hpp"
#include "multiclass_metric.hpp"
#include "rank_metric.hpp"
Expand Down Expand Up @@ -81,6 +82,12 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) {
} else if (type == std::string("r2")) {
Log::Warning("Metric r2 is not implemented in cuda version. Fall back to evaluation on CPU.");
return new R2Metric(config);
} else if (type == std::string("survival_cox_nll")) {
Log::Warning("Metric survival_cox_nll is not implemented in cuda version. Fall back to evaluation on CPU.");
return new CoxNLLMetric(config);
} else if (type == std::string("concordance_index") || type == std::string("c_index")) {
Comment on lines +85 to +88

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update this mapping in the R package as well:

.METRICS_HIGHER_BETTER <- function() {

If you're comfortable writing R code we'd welcome new tests in the R package too, but at a minimum that mapping should be updated so the R package's early stopping behavior will be correct.

Log::Warning("Metric concordance_index is not implemented in cuda version. Fall back to evaluation on CPU.");
return new ConcordanceIndexMetric(config);
}
} else {
#endif // USE_CUDA
Expand Down Expand Up @@ -132,6 +139,10 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) {
return new TweedieMetric(config);
} else if (type == std::string("r2")) {
return new R2Metric(config);
} else if (type == std::string("survival_cox_nll")) {
return new CoxNLLMetric(config);
} else if (type == std::string("concordance_index") || type == std::string("c_index")) {
return new ConcordanceIndexMetric(config);
}
#ifdef USE_CUDA
}
Expand Down
Loading
Loading