Skip to main content

Fast O(N log N) concordance index (C-index) with Numba-accelerated bootstrapping

Project description

fast-cindex

Fast O(N log N) concordance index (C-index) for survival analysis, with parallel Numba-accelerated bootstrapping.

Installation

pip install fast-cindex

Usage

import numpy as np
from lifelines import CoxPHFitter
from lifelines.datasets import load_rossi
from sklearn.model_selection import train_test_split
from fast_cindex import cindex, bootstrap_cindex, compare_cindex

# Load dataset and fit a Cox model
rossi = load_rossi()
train, test = train_test_split(rossi, test_size=0.2, random_state=42)

cph = CoxPHFitter()
cph.fit(train, duration_col='week', event_col='arrest')

times  = test['week'].values
events = test['arrest'].values
scores = -cph.predict_partial_hazard(test).values  # higher = higher predicted risk

Single C-index

ci = cindex(times, scores, events)
print(f"C-index: {ci:.3f}")

Bootstrap confidence interval

boot = bootstrap_cindex(times, scores, events, n_bootstraps=1000, seed=42)
lo, hi = np.percentile(boot, [2.5, 97.5])
print(f"C-index: {boot.mean():.3f} (95% CI {lo:.3f}{hi:.3f})")

Compare two models

Bootstrap samples are shared across both models, enabling a valid paired test.

boot1, boot2 = compare_cindex(times, events, scores1, scores2, n_bootstraps=1000, seed=42)

diff = boot1 - boot2
p_value = np.mean(diff <= 0)   # one-sided: P(model1 ≤ model2)
print(f"Mean difference: {diff.mean():.4f}, p = {p_value:.4f}")

API

Function Returns Description
cindex(times, scores, observed) float Single C-index
bootstrap_cindex(times, scores, observed, n_bootstraps, seed) ndarray (B,) Per-bootstrap C-indices
compare_cindex(times, observed, scores1, scores2, n_bootstraps, seed) (ndarray, ndarray) Paired bootstrap for two models

Score convention: higher score = higher predicted risk (e.g. a predicted hazard or negative predicted survival time).

Benchmarks

Tested on the Rossi recidivism dataset (replicated to the target size), 1000 bootstrap iterations, Apple M-series CPU.

Single C-index

Size lifelines fast-cindex Speedup
100 0.0004s 2.9865s 0.0x
1,000 0.0032s 0.0001s 26.9x
5,000 0.0144s 0.0004s 39.6x
10,000 0.0280s 0.0007s 40.2x

Note: the 100-sample run is dominated by Numba JIT compilation on first call. Subsequent calls are fast.

Bootstrap (1000 iterations)

Size lifelines fast-cindex Speedup
100 0.2651s 1.3314s 0.2x
1,000 3.0063s 0.0209s 144.2x
5,000 14.0425s 0.1031s 136.2x
10,000 27.9935s 0.1900s 147.4x

To reproduce:

pip install lifelines scikit-learn
python benchmark.py
import time
import numpy as np
import pandas as pd
from lifelines.utils import concordance_index
from lifelines.datasets import load_rossi
from lifelines import CoxPHFitter
from sklearn.model_selection import train_test_split
from fast_cindex import cindex, bootstrap_cindex

SIZES = [100, 1000, 5000, 10000]
N_BOOTSTRAP = 1000

# Build a large base dataset to sample from
rossi = load_rossi()
while len(rossi) < max(SIZES):
    rossi = pd.concat([rossi, rossi], axis=0)
rossi = rossi.reset_index(drop=True).iloc[:max(SIZES)]

# Fit model once on the full dataset
cph = CoxPHFitter()
train, _ = train_test_split(rossi, test_size=0.2, random_state=42)
cph.fit(train, duration_col='week', event_col='arrest')

risk_scores = -cph.predict_partial_hazard(rossi).values
durations = rossi['week'].values
events = rossi['arrest'].values

print(f"{'Size':>8}  {'lifelines cindex':>18}  {'fast cindex':>14}  {'speedup':>10}")
print(f"{'':->8}  {'':->18}  {'':->14}  {'':->10}")

for n in SIZES:
    t = durations[:n]
    e = events[:n]
    r = risk_scores[:n]

    start = time.perf_counter()
    concordance_index(t, r, e.astype(bool))
    lifelines_time = time.perf_counter() - start

    start = time.perf_counter()
    cindex(t, r, e)
    fast_time = time.perf_counter() - start

    print(f"{n:>8}  {lifelines_time:>16.4f}s  {fast_time:>12.4f}s  {lifelines_time/fast_time:>9.1f}x")

print()
print(f"{'Size':>8}  {'lifelines bootstrap':>21}  {'fast bootstrap':>17}  {'speedup':>10}")
print(f"{'':->8}  {'':->21}  {'':->17}  {'':->10}")

for n in SIZES:
    t = durations[:n]
    e = events[:n]
    r = risk_scores[:n]

    rng = np.random.RandomState(42)
    start = time.perf_counter()
    for _ in range(N_BOOTSTRAP):
        idx = rng.choice(n, n, replace=True)
        concordance_index(t[idx], r[idx], e[idx].astype(bool))
    lifelines_time = time.perf_counter() - start

    start = time.perf_counter()
    bootstrap_cindex(t, r, e, n_bootstraps=N_BOOTSTRAP, seed=42)
    fast_time = time.perf_counter() - start

    print(f"{n:>8}  {lifelines_time:>19.4f}s  {fast_time:>15.4f}s  {lifelines_time/fast_time:>9.1f}x")

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast_cindex-0.1.0.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fast_cindex-0.1.0-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file fast_cindex-0.1.0.tar.gz.

File metadata

  • Download URL: fast_cindex-0.1.0.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for fast_cindex-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ce3804d4d32cca1af0862e5e9d92b1aadb5f4ac3d92c9804f1fcd7eae372f542
MD5 b0a12c5ee3d80dac29c6d58bc310bee6
BLAKE2b-256 95a0de574216ed351a8854c79bd13c7a04a6bc678f366518836d71d01627cde9

See more details on using hashes here.

File details

Details for the file fast_cindex-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fast_cindex-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for fast_cindex-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef0bff35bfff5ead2edd2bfedc2b7113f12ad158f2290c0a74d6f3e46ea4ccc6
MD5 ae6e2266af58e78803e620589a826bdb
BLAKE2b-256 d927162fc238b0f7067b97a8e7f4c37940be56379e05c5a3dabf141f362776f5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page