35 std::vector<double>
se;
55 const std::vector<double>& times,
56 const std::vector<bool>& events)
58 if (times.size() != events.size()) {
59 throw std::invalid_argument(
"statcpp::kaplan_meier: times and events must have same length");
62 throw std::invalid_argument(
"statcpp::kaplan_meier: empty data");
65 std::size_t n = times.size();
68 std::vector<std::size_t> indices(n);
69 std::iota(indices.begin(), indices.end(), 0);
70 std::sort(indices.begin(), indices.end(),
71 [×](std::size_t i, std::size_t j) {
72 return times[i] < times[j];
76 result.
times.push_back(0.0);
78 result.
se.push_back(0.0);
85 std::size_t n_risk = n;
89 double t = times[indices[i]];
95 while (i < n && times[indices[i]] == t) {
96 if (events[indices[i]]) {
106 double q =
static_cast<double>(d) /
static_cast<double>(n_risk);
111 var_sum +=
static_cast<double>(d) /
112 (
static_cast<double>(n_risk) *
static_cast<double>(n_risk - d));
115 double se = S * std::sqrt(var_sum);
119 double ci_lower, ci_upper;
120 if (S > 0 && S < 1) {
121 double log_S = std::log(S);
122 double se_log = se / S;
123 ci_lower = std::exp(log_S - z * se_log);
124 ci_upper = std::exp(log_S + z * se_log);
125 ci_lower = std::max(0.0, ci_lower);
126 ci_upper = std::min(1.0, ci_upper);
132 result.
times.push_back(t);
134 result.
se.push_back(se);
135 result.
ci_lower.push_back(ci_lower);
136 result.
ci_upper.push_back(ci_upper);
185 const std::vector<double>& times1,
186 const std::vector<bool>& events1,
187 const std::vector<double>& times2,
188 const std::vector<bool>& events2)
190 if (times1.size() != events1.size() || times2.size() != events2.size()) {
191 throw std::invalid_argument(
"statcpp::logrank_test: times and events must have same length");
193 if (times1.empty() || times2.empty()) {
194 throw std::invalid_argument(
"statcpp::logrank_test: empty data");
198 std::vector<double> all_times;
199 all_times.reserve(times1.size() + times2.size());
200 for (
double t : times1) all_times.push_back(t);
201 for (
double t : times2) all_times.push_back(t);
202 std::sort(all_times.begin(), all_times.end());
203 all_times.erase(std::unique(all_times.begin(), all_times.end()), all_times.end());
212 for (
double t : all_times) {
214 std::size_t n1_risk = 0;
215 std::size_t n2_risk = 0;
219 for (std::size_t i = 0; i < times1.size(); ++i) {
220 if (times1[i] >= t) {
223 if (times1[i] == t && events1[i]) {
228 for (std::size_t i = 0; i < times2.size(); ++i) {
229 if (times2[i] >= t) {
232 if (times2[i] == t && events2[i]) {
237 std::size_t d = d1 + d2;
238 std::size_t n_risk = n1_risk + n2_risk;
240 if (d > 0 && n_risk > 0) {
244 double e1 =
static_cast<double>(n1_risk) *
static_cast<double>(d) /
245 static_cast<double>(n_risk);
250 var +=
static_cast<double>(n1_risk) *
static_cast<double>(n2_risk) *
251 static_cast<double>(d) *
static_cast<double>(n_risk - d) /
252 (
static_cast<double>(n_risk) *
static_cast<double>(n_risk) *
253 static_cast<double>(n_risk - 1));
258 double E2 =
static_cast<double>(O1 + O2) - E1;
263 double diff =
static_cast<double>(O1) - E1;
270 return {stat, p_value, 1, E1, E2, O1, O2};
289 for (std::size_t i = 0; i < km.
survival.size(); ++i) {
295 return std::numeric_limits<double>::quiet_NaN();
338 const std::vector<double>& times,
339 const std::vector<bool>& events)
341 if (times.size() != events.size()) {
342 throw std::invalid_argument(
"statcpp::nelson_aalen: times and events must have same length");
345 throw std::invalid_argument(
"statcpp::nelson_aalen: empty data");
348 std::size_t n = times.size();
351 std::vector<std::size_t> indices(n);
352 std::iota(indices.begin(), indices.end(), 0);
353 std::sort(indices.begin(), indices.end(),
354 [×](std::size_t i, std::size_t j) {
355 return times[i] < times[j];
359 result.
times.push_back(0.0);
360 result.
hazard.push_back(0.0);
364 std::size_t n_risk = n;
368 double t = times[indices[i]];
372 while (i < n && times[indices[i]] == t) {
373 if (events[indices[i]]) {
381 if (d > 0 && n_risk > 0) {
382 double h =
static_cast<double>(d) /
static_cast<double>(n_risk);
385 result.
times.push_back(t);
386 result.
hazard.push_back(h);
Continuous distribution functions.
double median_survival_time(const kaplan_meier_result &km)
Calculate median survival time.
double chisq_cdf(double x, double df)
Chi-square distribution cumulative distribution function (CDF)
double var(Iterator first, Iterator last, std::size_t ddof=0)
Variance (ddof = Delta Degrees of Freedom)
logrank_result logrank_test(const std::vector< double > ×1, const std::vector< bool > &events1, const std::vector< double > ×2, const std::vector< bool > &events2)
Log-rank test (comparison of two survival curves)
kaplan_meier_result kaplan_meier(const std::vector< double > ×, const std::vector< bool > &events)
Estimate Kaplan-Meier survival curve.
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
hazard_rate_result nelson_aalen(const std::vector< double > ×, const std::vector< bool > &events)
Nelson-Aalen cumulative hazard estimation.
std::vector< double > times
Interval start times.
std::vector< double > cumulative_hazard
Cumulative hazard.
std::vector< double > hazard
Hazard rates.
Kaplan-Meier estimation result.
std::vector< double > times
Event times.
std::vector< double > ci_upper
95% confidence interval upper bound
std::vector< double > ci_lower
95% confidence interval lower bound
std::vector< double > survival
Survival probabilities.
std::vector< std::size_t > n_censored
Number of censored at each time point.
std::vector< std::size_t > n_at_risk
Risk set size.
std::vector< std::size_t > n_events
Number of events at each time point.
std::vector< double > se
Standard errors (Greenwood's formula)
double expected2
Expected number of events in group 2.
double statistic
Test statistic (chi-square)
std::size_t df
Degrees of freedom.
std::size_t observed2
Observed number of events in group 2.
double expected1
Expected number of events in group 1.
std::size_t observed1
Observed number of events in group 1.