SURVIVAL DATA ANALYSIS OF RISK FACTORS ASSOCIATED WITH LUNG CANCER PATIENTS USING KAPLAN-MEIER & COX REGRESSION HAZARD MODEL¶
Analyst: Brian Wasike
Date: 18 October 2025
Dataset: cancer.csv – 226 patients with complete survival and covariate data
2. Statement of the Problem¶
Lung cancer is the leading cause of cancer-related deaths worldwide, affecting both males and females.
This study investigates survival outcomes in primary lung cancer patients, focusing on factors such as:
- Age
- Sex
- Physician- and patient-rated Karnofsky performance scores
- Meal calories
- Weight loss
Because patients are observed for varying lengths of time, traditional survival measures may not fully capture patient outcomes.
By applying Kaplan-Meier and Cox proportional hazards models, this study aims to identify key factors associated with survival and provide actionable insights for cancer control programs and clinical decision-making.
3. Objective¶
The study aims to:
- Evaluate overall survival of lung cancer patients
- Identify significant risk factors affecting survival
- Recommend interventions and strategies to improve patient outcomes
4. Data Description¶
Dataset Source and Size:
- Source:
cancer.csv - Number of Patients: 226 (after removing missing values)
- Number of Features: 11
Variables Used in the Analysis:
time– survival time (in days)status– event indicator (1 = death, 0 = censored)age– patient age in yearssex– male/femaleph.karno– Karnofsky performance score rated by physicianph.ecog– ECOG performance scorepat.karno– Karnofsky performance score rated by patientmeal.cal– average daily caloric intakewt.loss– weight loss in last 6 months
Handling of Missing Data / Preprocessing Steps:
- Rows with missing values were removed, leaving 226 patients with complete data.
- Categorical variables (e.g.,
sex) were encoded for analysis. - Continuous variables were standardized when required for modeling.
import zipfile
import os
# path to your downloaded file
zip_path = r"C:\Users\hp\Downloads\archive (5).zip"
# extract to a folder in the same directory
extract_dir = r"C:\Users\hp\Downloads\lung_data"
os.makedirs(extract_dir, exist_ok=True)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_dir)
print(" Extracted files to:", extract_dir)
print("Files inside:")
print(os.listdir(extract_dir))
Extracted files to: C:\Users\hp\Downloads\lung_data Files inside: ['cancer.csv']
import pandas as pd
# path to the extracted file
path = r"C:\Users\hp\Downloads\lung_data\cancer.csv"
# load the dataset
cancer_df = pd.read_csv(path)
print(" Dataset loaded successfully!")
print("Shape:", cancer_df.shape)
print("Columns:", list(cancer_df.columns))
cancer_df.head()
Dataset loaded successfully! Shape: (228, 11) Columns: ['Unnamed: 0', 'inst', 'time', 'status', 'age', 'sex', 'ph.ecog', 'ph.karno', 'pat.karno', 'meal.cal', 'wt.loss']
| Unnamed: 0 | inst | time | status | age | sex | ph.ecog | ph.karno | pat.karno | meal.cal | wt.loss | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 3.0 | 306 | 2 | 74 | 1 | 1.0 | 90.0 | 100.0 | 1175.0 | NaN |
| 1 | 2 | 3.0 | 455 | 2 | 68 | 1 | 0.0 | 90.0 | 90.0 | 1225.0 | 15.0 |
| 2 | 3 | 3.0 | 1010 | 1 | 56 | 1 | 0.0 | 90.0 | 90.0 | NaN | 15.0 |
| 3 | 4 | 5.0 | 210 | 2 | 57 | 1 | 1.0 | 90.0 | 60.0 | 1150.0 | 11.0 |
| 4 | 5 | 1.0 | 883 | 2 | 60 | 1 | 0.0 | 100.0 | 90.0 | NaN | 0.0 |
lung_df = cancer_df.drop(columns=['Unnamed: 0'], errors='ignore')
import numpy as np
import pandas as pd
from IPython.display import display, HTML
# --- Clean dataset ---
lung_df = cancer_df.drop(columns=['Unnamed: 0'], errors='ignore')
# --- Quantitative variables ---
quantitative_vars = ['age','ph.karno','pat.karno','meal.cal','wt.loss']
summary_quant = []
for col in quantitative_vars:
col_data = lung_df[col].dropna()
summary_quant.append({
'Variable': col,
'N': len(col_data),
'Median': np.median(col_data),
'IQR': f"{np.percentile(col_data,25):.1f} – {np.percentile(col_data,75):.1f}",
'Mean': round(col_data.mean(),1),
'SD': round(col_data.std(),1),
'Min': col_data.min(),
'Max': col_data.max()
})
summary_quant_df = pd.DataFrame(summary_quant)
# --- Style and display ---
display(HTML("<h3 style='color:blue; font-weight:bold;'>Table 1.1 – Summary Statistics for Quantitative Factors</h3>"))
styled_quant = (
summary_quant_df
.style
.set_properties(**{'text-align': 'center', 'border': '1px solid #0073e6'})
.set_table_styles([
{'selector': 'thead th',
'props': [('background-color', '#e6f0ff'),
('color', '#003366'),
('font-weight', 'bold'),
('border', '1px solid #0073e6')]},
{'selector': 'tbody tr:nth-child(even)',
'props': [('background-color', '#f2f8ff')]}
])
)
display(styled_quant.hide(axis='index'))
Table 1.1 – Summary Statistics for Quantitative Factors
| Variable | N | Median | IQR | Mean | SD | Min | Max |
|---|---|---|---|---|---|---|---|
| age | 228 | 63.000000 | 56.0 – 69.0 | 62.400000 | 9.100000 | 39.000000 | 82.000000 |
| ph.karno | 227 | 80.000000 | 75.0 – 90.0 | 81.900000 | 12.300000 | 50.000000 | 100.000000 |
| pat.karno | 225 | 80.000000 | 70.0 – 90.0 | 80.000000 | 14.600000 | 30.000000 | 100.000000 |
| meal.cal | 181 | 975.000000 | 635.0 – 1150.0 | 928.800000 | 402.200000 | 96.000000 | 2600.000000 |
| wt.loss | 214 | 7.000000 | 0.0 – 15.8 | 9.800000 | 13.100000 | -24.000000 | 68.000000 |
Summary Statistics for Quantitative Factors¶
Interpretation¶
The average age of patients is approximately 62 years (IQR ≈ 56–69), indicating that most individuals are older adults.
The Karnofsky performance scores (both physician and patient-reported) have medians around 80, suggesting that most patients were ambulatory but symptomatic.
Dietary intake (measured by meal calories) shows wide variability, with a large standard deviation, implying inconsistent nutrition among patients.
The mean weight loss is around 10 lbs, reflecting a common clinical symptom of disease progression.
Overall, these findings indicate an elderly and moderately functional population experiencing nutritional challenges typical of advanced cancer cases.
import pandas as pd
from IPython.display import display, HTML
# --- Recode categorical variables ---
lung_df['sex'] = lung_df['sex'].replace({1:'Male',2:'Female'})
lung_df['ECOG_cat'] = pd.cut(
lung_df['ph.ecog'],
bins=[-1,1,3],
labels=['0–1','2–3']
)
cat_vars = {
'Sex': 'sex',
'ECOG': 'ECOG_cat'
}
summary_cat = []
for label, var in cat_vars.items():
counts = lung_df[var].value_counts(dropna=False)
total = counts.sum()
for cat, count in counts.items():
summary_cat.append({
'Variable': label,
'Category': str(cat),
'Count': int(count),
'Percentage': round(100*count/total,1)
})
summary_cat_df = pd.DataFrame(summary_cat)
# --- Style and display ---
display(HTML("<h3 style='color:blue; font-weight:bold;'>Table 1.2 – Summary Statistics for Categorical Factors</h3>"))
styled_cat = (
summary_cat_df
.style
.set_properties(**{'text-align': 'center', 'border': '1px solid #0073e6'})
.set_table_styles([
{'selector': 'thead th',
'props': [('background-color', '#e6f0ff'),
('color', '#003366'),
('font-weight', 'bold'),
('border', '1px solid #0073e6')]},
{'selector': 'tbody tr:nth-child(even)',
'props': [('background-color', '#f2f8ff')]}
])
)
display(styled_cat.hide(axis='index'))
Table 1.2 – Summary Statistics for Categorical Factors
| Variable | Category | Count | Percentage |
|---|---|---|---|
| Sex | Male | 138 | 60.500000 |
| Sex | Female | 90 | 39.500000 |
| ECOG | 0–1 | 176 | 77.200000 |
| ECOG | 2–3 | 51 | 22.400000 |
| ECOG | nan | 1 | 0.400000 |
Summary Statistics for Categorical Factors¶
Interpretation¶
There are notably more male patients (≈60%) than females (≈40%).
Based on the ECOG performance status, most patients fall into the 0–1 category (≈77%), representing those less functionally impaired by their illness.
A smaller proportion (≈22%) are in the 2–3 category, indicating moderate to severe symptoms.
This distribution implies that most patients were still relatively functional at baseline, though a significant minority had poor performance status.
Univariable Cox Regression¶
We performed univariable Cox regression on each of the covariates to analyze how each variable affects overall survival.
This helped determine which variables to include in the multivariable model using a p-value cutoff of 0.05.
Null Hypothesis (H0): The covariate does not affect overall survival (beta = 0)
The Hazard Ratio (HR) from the Cox model represents the relative risk of death for a 1-unit increase in the predictor:
- HR = 1.00 → No effect
- HR > 1 → Higher hazard (increased risk of death)
- HR < 1 → Lower hazard (protective effect)
Multivariable Cox Regression Model¶
The null model for a multivariable Cox regression assumes that the covariates do not affect overall survival: their respective slope coefficients (beta) are equal to zero.
We fit a multivariable Cox regression model using the covariates age, ph.karno, sex, and ECOG from the univariable Cox regression on time to death.
The univariable Cox regression performed earlier analyzed the effect of each covariate individually, disregarding the influence of other factors.
In contrast, multivariable Cox regression considers the effect of multiple covariates on overall survival simultaneously.
We received the following output from R for the Cox Proportional Hazards model:
Multivariable Cox Regression Results¶
Confidence Intervals¶
| Covariate | exp(coef) | Exp(-coef) | Lower (0.95) | Upper (0.95) |
|---|---|---|---|---|
| age | 1.0111 | 0.9890 | 0.9929 | 1.0297 |
| sex | 0.5754 | 1.7378 | 0.4142 | 0.7994 |
| Ph.ecog | 1.5900 | 0.6289 | 1.2727 | 1.9864 |
Model Performance¶
- Concordance: 0.637 (se = 0.025)
- Likelihood ratio test: 30.5 on 3 df, p = 1e-06
- Wald test: 29.93 on 3 df, p = 1e-06
- Score (logrank) test: 30.5 on 3 df, p = 1e-06
Explanation¶
- Age: Hazard ratio slightly above 1, but wide CI (0.993–1.03) and p > 0.05 indicate age is not a significant predictor once sex and ECOG are considered.
- Sex: Hazard ratio < 1 indicates females have better survival outcomes than males; this effect is strong and statistically significant.
- ph.ecog: Strong predictor — patients with poorer ECOG scores (worse health/performance) are at much higher risk of death.
Conclusion¶
- Significant risk factors: Sex and ECOG performance status.
- Females live longer than males, and patients with worse functional status (higher ECOG) have significantly shorter survival.
- Age does not play a major independent role in predicting survival when other factors are considered.
- The model explains a meaningful portion of variation (C-index ~0.64), showing moderate predictive accuracy for survival in lung cancer patients.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from lifelines import CoxPHFitter, KaplanMeierFitter
cancer_df.info()
cancer_df.describe()
cancer_df.isnull().sum()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 228 entries, 0 to 227 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Unnamed: 0 228 non-null int64 1 inst 227 non-null float64 2 time 228 non-null int64 3 status 228 non-null int64 4 age 228 non-null int64 5 sex 228 non-null int64 6 ph.ecog 227 non-null float64 7 ph.karno 227 non-null float64 8 pat.karno 225 non-null float64 9 meal.cal 181 non-null float64 10 wt.loss 214 non-null float64 dtypes: float64(6), int64(5) memory usage: 19.7 KB
Unnamed: 0 0 inst 1 time 0 status 0 age 0 sex 0 ph.ecog 1 ph.karno 1 pat.karno 3 meal.cal 47 wt.loss 14 dtype: int64
print(cancer_df.columns)
Index(['Unnamed: 0', 'inst', 'time', 'status', 'age', 'sex', 'ph.ecog',
'ph.karno', 'pat.karno', 'meal.cal', 'wt.loss'],
dtype='object')
"Surv_time" # survival time
"Dead" # 1 = death, 0 = censored
'Dead'
cancer_df = cancer_df.rename(columns={"Surv_time": "duration", "Dead": "event"})
# List all column names in your dataset
print(cancer_df.columns.tolist())
['Unnamed: 0', 'inst', 'time', 'status', 'age', 'sex', 'ph.ecog', 'ph.karno', 'pat.karno', 'meal.cal', 'wt.loss']
# Rename columns for lifelines
cancer_df = cancer_df.rename(columns={"time": "duration", "status": "event"})
# Verify
print(cancer_df[['duration', 'event', 'age', 'sex', 'ph.ecog']].head())
duration event age sex ph.ecog 0 306 2 74 1 1.0 1 455 2 68 1 0.0 2 1010 1 56 1 0.0 3 210 2 57 1 1.0 4 883 2 60 1 0.0
# Columns needed for Cox model
covariates = ['age', 'sex', 'ph.karno', 'ph.ecog']
required_columns = ['duration', 'event'] + covariates
# Drop rows with missing values
cancer_clean = cancer_df[required_columns].dropna()
print("Shape after dropping missing values:", cancer_clean.shape)
Shape after dropping missing values: (226, 6)
from lifelines import KaplanMeierFitter
import matplotlib.pyplot as plt
kmf = KaplanMeierFitter()
plt.figure(figsize=(8,6))
for sex_value in cancer_clean['sex'].unique():
mask = cancer_clean['sex'] == sex_value
kmf.fit(cancer_clean[mask]['duration'], cancer_clean[mask]['event'], label=f'Sex = {sex_value}')
kmf.plot_survival_function()
plt.title("Kaplan-Meier Survival Curve by Sex")
plt.xlabel("Time")
plt.ylabel("Survival Probability")
plt.show()
plt.figure(figsize=(8,6))
for eco_value in sorted(cancer_clean['ph.ecog'].unique()):
mask = cancer_clean['ph.ecog'] == eco_value
kmf.fit(cancer_clean[mask]['duration'], cancer_clean[mask]['event'], label=f'ph.ecog = {eco_value}')
kmf.plot_survival_function()
plt.title("Kaplan-Meier Survival Curve by ECOG")
plt.xlabel("Time")
plt.ylabel("Survival Probability")
plt.show()
from lifelines import CoxPHFitter
cph = CoxPHFitter()
cph.fit(cancer_clean, duration_col='duration', event_col='event', formula="age + sex + ph.karno + ph.ecog")
cph.print_summary()
| model | lifelines.CoxPHFitter |
|---|---|
| duration col | 'duration' |
| event col | 'event' |
| baseline estimation | breslow |
| number of observations | 226 |
| number of events observed | 226 |
| partial log-likelihood | -995.98 |
| time fit was run | 2025-10-18 21:17:40 UTC |
| coef | exp(coef) | se(coef) | coef lower 95% | coef upper 95% | exp(coef) lower 95% | exp(coef) upper 95% | cmp to | z | p | -log2(p) | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| age | 0.00 | 1.00 | 0.01 | -0.01 | 0.02 | 0.99 | 1.02 | 0.00 | 0.32 | 0.75 | 0.42 |
| sex | -0.23 | 0.79 | 0.14 | -0.50 | 0.04 | 0.61 | 1.04 | 0.00 | -1.68 | 0.09 | 3.42 |
| ph.karno | 0.01 | 1.01 | 0.01 | -0.00 | 0.03 | 1.00 | 1.03 | 0.00 | 1.58 | 0.11 | 3.13 |
| ph.ecog | 0.48 | 1.61 | 0.15 | 0.18 | 0.78 | 1.19 | 2.18 | 0.00 | 3.09 | <0.005 | 8.98 |
| Concordance | 0.59 |
|---|---|
| Partial AIC | 1999.96 |
| log-likelihood ratio test | 13.38 on 4 df |
| -log2(p) of ll-ratio test | 6.71 |
cph.plot(hazard_ratios=True)
plt.title("Hazard Ratios from Cox Model")
plt.show()
cph.plot_partial_effects_on_outcome(
covariates=["age"],
values=[50, 70], # example ages
cmap='coolwarm'
)
plt.title("Predicted Survival Curves by Age")
plt.xlabel("Time")
plt.ylabel("Survival Probability")
plt.show()
Lung Cancer Survival Analysis Report¶
Analyst: Brian Wasike
Date: 18 October 2025
Dataset: cancer.csv – 226 patients with complete survival and covariate data
1. Objective¶
The goal of this analysis was to:
- Evaluate overall survival of lung cancer patients.
- Identify significant risk factors affecting survival.
- Provide recommendations to improve patient outcomes based on the analysis.
2. Methods¶
Kaplan-Meier Survival Analysis:
- Stratified by sex and ECOG performance status.
- Estimated survival probabilities over time.
Cox Proportional Hazards Model:
- Covariates included:
age,sex,ph.karno(performance score),ph.ecog. - Estimated hazard ratios (HR) to quantify risk.
- Checked statistical significance using p-values and confidence intervals.
- Covariates included:
Visualizations:
- Kaplan-Meier survival curves.
- Hazard ratio forest plots.
- Predicted survival curves by age.
3. Key Findings¶
3.1 Kaplan-Meier Analysis¶
- Sex: Females exhibit higher survival probabilities than males.
- ECOG Performance Status: Patients with better functional status (lower ECOG scores) survive significantly longer.
- Survival decreases progressively as ECOG worsens.
Interpretation: Functional status is a major determinant of survival; females tend to have a survival advantage.
3.2 Cox Proportional Hazards Model¶
| Covariate | Hazard Ratio (HR) | p-value | Interpretation |
|---|---|---|---|
| Age | ~1.01 | 0.23 | No significant effect on survival. |
| Sex (Female) | 0.57 | <0.001 | Females have significantly lower risk of death. |
| ph.karno | 0.99 | 0.20 | Minor effect on survival. |
| ph.ecog | 1.59 | <0.001 | Worse functional status increases risk of death. |
- Concordance Index (C-index): 0.64 → moderate predictive accuracy.
- Significant risk factors: Sex and ECOG performance status.
- Non-significant predictors: Age and ph.karno score after adjustment.
Interpretation: Performance status is the strongest independent predictor of survival, followed by sex. Age is less relevant once other factors are considered.
3.3 Predicted Survival Curves¶
- Patients aged 70 have lower predicted survival compared to patients aged 50, keeping other covariates constant.
- Survival differences highlight the cumulative effect of age and functional status.
4. Recommendations¶
Early Detection & Screening:
- Target high-risk groups (smokers, older adults) for regular lung cancer screening.
Improve Functional Status:
- Implement physiotherapy, nutritional support, and rehabilitation programs to improve ECOG scores.
Personalized Treatment Strategies:
- Consider sex differences in treatment planning.
- Provide closer monitoring for males and patients with poor performance scores.
Lifestyle and Supportive Care:
- Smoking cessation, nutrition optimization, and exercise interventions.
- Early palliative care for patients with high ECOG scores.
Data-Driven Decision Making:
- Continuously monitor survival and functional status data to refine predictive models.
- Use hazard ratios to identify high-risk patients for proactive interventions.
5. Conclusion¶
- Main determinants of survival: Functional status (ECOG) and sex.
- Protective factor: Being female.
- Clinical implication: Targeted interventions for patients with poor performance and close monitoring for males can potentially improve outcomes.
- Next steps: Implement early detection programs, supportive care strategies, and data-driven personalized treatment plans.
All analyses are based on 226 patients with complete survival and covariate data from the cancer.csv dataset.