Real Data Case Studies
Kaplan-Meier Survival and the Log-Rank Test on Real Failure Times
The ANOVA lesson in this chapter found a real, statistically significant
difference between lines. This lesson uses the same 3-line dataset's
time-to-failure records and finds the opposite: a descriptive difference in
median survival that does not clear the log-rank test's significance bar.
Both outcomes are real lmda computations on real data — a "not significant"
result is not a failed lesson, it's the correct honest answer for this
sample.
The real dataset
data/time_to_failure.csv records one row per machine (25 machines per
line, 75 rows total): a line, a time-to-failure or time-to-censoring in
days, and an event flag (1 = failed and observed, 0 = still running when
the 180-day observation window ended — right-censored, not "survived
forever").
| line | machine | time | event |
|---|---|---|---|
| A | 1 | 180.0 | 0 |
| A | 2 | 138.95 | 1 |
| A | 3 | 73.93 | 1 |
| B | 1 | 6.37 | 1 |
| C | 1 | 1.23 | 1 |
Censored rows (event=0) still carry information — "this machine ran for at
least time days" — which is exactly what Kaplan-Meier is built to use
without discarding or mis-treating them as failures.
Kaplan-Meier survival curves per line
lmda kaplanmeier data/time_to_failure.csv --time time --event event --group line --format json
{
"censored": 26,
"confidence": 0.95,
"curves": [
{
"censored": 6,
"events": 19,
"group": "A",
"median_survival": 127.67,
"n": 25,
"points": [
{
"at_risk": 25,
"censored": 0,
"ci_lower": 0.883185413178315,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.03919183588453085,
"survival": 0.96,
"time": 19.87
},
{
"at_risk": 24,
"censored": 0,
"ci_lower": 0.8136550200144667,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.054258639865002144,
"survival": 0.92,
"time": 34.72
},
{
"at_risk": 23,
"censored": 0,
"ci_lower": 0.7526174185431462,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.06499230723708768,
"survival": 0.88,
"time": 35.02
},
{
"at_risk": 22,
"censored": 0,
"ci_lower": 0.6962930669033272,
"ci_upper": 0.9837069330966728,
"events": 1,
"standard_error": 0.07332121111929343,
"survival": 0.84,
"time": 44.63
},
{
"at_risk": 21,
"censored": 0,
"ci_lower": 0.6432028812367957,
"ci_upper": 0.9567971187632044,
"events": 1,
"standard_error": 0.08000000000000002,
"survival": 0.8,
"time": 56.3
},
{
"at_risk": 20,
"censored": 0,
"ci_lower": 0.5925864893272221,
"ci_upper": 0.9274135106727779,
"events": 1,
"standard_error": 0.08541662601625048,
"survival": 0.76,
"time": 73.93
},
{
"at_risk": 19,
"censored": 0,
"ci_lower": 0.5439956707064356,
"ci_upper": 0.8960043292935643,
"events": 1,
"standard_error": 0.0897997772825746,
"survival": 0.72,
"time": 75.19
},
{
"at_risk": 18,
"censored": 0,
"ci_lower": 0.4971447086482821,
"ci_upper": 0.862855291351718,
"events": 1,
"standard_error": 0.0932952303175248,
"survival": 0.68,
"time": 78.79
},
{
"at_risk": 17,
"censored": 0,
"ci_lower": 0.45184345748415483,
"ci_upper": 0.8281565425158453,
"events": 1,
"standard_error": 0.096,
"survival": 0.64,
"time": 89.0
},
{
"at_risk": 16,
"censored": 0,
"ci_lower": 0.40796353294578763,
"ci_upper": 0.7920364670542124,
"events": 1,
"standard_error": 0.09797958971132711,
"survival": 0.6,
"time": 93.46
},
{
"at_risk": 15,
"censored": 0,
"ci_lower": 0.3654198927516965,
"ci_upper": 0.7545801072483036,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.56,
"time": 104.73
},
{
"at_risk": 14,
"censored": 0,
"ci_lower": 0.3241604614338306,
"ci_upper": 0.7158395385661694,
"events": 1,
"standard_error": 0.09991996797437439,
"survival": 0.52,
"time": 115.33
},
{
"at_risk": 13,
"censored": 0,
"ci_lower": 0.2841604614338306,
"ci_upper": 0.6758395385661693,
"events": 1,
"standard_error": 0.09991996797437437,
"survival": 0.48,
"time": 127.67
},
{
"at_risk": 12,
"censored": 0,
"ci_lower": 0.24541989275169648,
"ci_upper": 0.6345801072483035,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.44,
"time": 138.95
},
{
"at_risk": 11,
"censored": 0,
"ci_lower": 0.20796353294578765,
"ci_upper": 0.5920364670542124,
"events": 1,
"standard_error": 0.09797958971132713,
"survival": 0.4,
"time": 149.86
},
{
"at_risk": 10,
"censored": 0,
"ci_lower": 0.1718434574841548,
"ci_upper": 0.5481565425158452,
"events": 1,
"standard_error": 0.096,
"survival": 0.36,
"time": 152.92
},
{
"at_risk": 9,
"censored": 0,
"ci_lower": 0.13714470864828204,
"ci_upper": 0.5028552913517179,
"events": 1,
"standard_error": 0.09329523031752482,
"survival": 0.32,
"time": 156.8
},
{
"at_risk": 8,
"censored": 0,
"ci_lower": 0.1039956707064357,
"ci_upper": 0.4560043292935644,
"events": 1,
"standard_error": 0.0897997772825746,
"survival": 0.28,
"time": 174.42
},
{
"at_risk": 7,
"censored": 0,
"ci_lower": 0.07258648932722206,
"ci_upper": 0.4074135106727779,
"events": 1,
"standard_error": 0.08541662601625048,
"survival": 0.24,
"time": 179.18
},
{
"at_risk": 6,
"censored": 6,
"ci_lower": 0.07258648932722206,
"ci_upper": 0.4074135106727779,
"events": 0,
"standard_error": 0.08541662601625048,
"survival": 0.24,
"time": 180.0
}
]
},
{
"censored": 9,
"events": 16,
"group": "B",
"median_survival": 99.72,
"n": 25,
"points": [
{
"at_risk": 25,
"censored": 0,
"ci_lower": 0.883185413178315,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.03919183588453085,
"survival": 0.96,
"time": 6.37
},
{
"at_risk": 24,
"censored": 0,
"ci_lower": 0.8136550200144667,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.054258639865002144,
"survival": 0.92,
"time": 17.91
},
{
"at_risk": 23,
"censored": 0,
"ci_lower": 0.7526174185431462,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.06499230723708768,
"survival": 0.88,
"time": 19.62
},
{
"at_risk": 22,
"censored": 0,
"ci_lower": 0.6962930669033272,
"ci_upper": 0.9837069330966728,
"events": 1,
"standard_error": 0.07332121111929343,
"survival": 0.84,
"time": 22.5
},
{
"at_risk": 21,
"censored": 0,
"ci_lower": 0.6432028812367957,
"ci_upper": 0.9567971187632044,
"events": 1,
"standard_error": 0.08000000000000002,
"survival": 0.8,
"time": 35.77
},
{
"at_risk": 20,
"censored": 0,
"ci_lower": 0.5925864893272221,
"ci_upper": 0.9274135106727779,
"events": 1,
"standard_error": 0.08541662601625048,
"survival": 0.76,
"time": 37.04
},
{
"at_risk": 19,
"censored": 0,
"ci_lower": 0.5439956707064356,
"ci_upper": 0.8960043292935643,
"events": 1,
"standard_error": 0.0897997772825746,
"survival": 0.72,
"time": 56.75
},
{
"at_risk": 18,
"censored": 0,
"ci_lower": 0.4971447086482821,
"ci_upper": 0.862855291351718,
"events": 1,
"standard_error": 0.0932952303175248,
"survival": 0.68,
"time": 57.37
},
{
"at_risk": 17,
"censored": 0,
"ci_lower": 0.45184345748415483,
"ci_upper": 0.8281565425158453,
"events": 1,
"standard_error": 0.096,
"survival": 0.64,
"time": 60.53
},
{
"at_risk": 16,
"censored": 0,
"ci_lower": 0.40796353294578763,
"ci_upper": 0.7920364670542124,
"events": 1,
"standard_error": 0.09797958971132711,
"survival": 0.6,
"time": 64.88
},
{
"at_risk": 15,
"censored": 0,
"ci_lower": 0.3654198927516965,
"ci_upper": 0.7545801072483036,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.56,
"time": 66.7
},
{
"at_risk": 14,
"censored": 0,
"ci_lower": 0.3241604614338306,
"ci_upper": 0.7158395385661694,
"events": 1,
"standard_error": 0.09991996797437439,
"survival": 0.52,
"time": 83.51
},
{
"at_risk": 13,
"censored": 0,
"ci_lower": 0.2841604614338306,
"ci_upper": 0.6758395385661693,
"events": 1,
"standard_error": 0.09991996797437437,
"survival": 0.48,
"time": 99.72
},
{
"at_risk": 12,
"censored": 0,
"ci_lower": 0.24541989275169648,
"ci_upper": 0.6345801072483035,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.44,
"time": 107.89
},
{
"at_risk": 11,
"censored": 0,
"ci_lower": 0.20796353294578765,
"ci_upper": 0.5920364670542124,
"events": 1,
"standard_error": 0.09797958971132713,
"survival": 0.4,
"time": 162.8
},
{
"at_risk": 10,
"censored": 0,
"ci_lower": 0.1718434574841548,
"ci_upper": 0.5481565425158452,
"events": 1,
"standard_error": 0.096,
"survival": 0.36,
"time": 166.95
},
{
"at_risk": 9,
"censored": 9,
"ci_lower": 0.1718434574841548,
"ci_upper": 0.5481565425158452,
"events": 0,
"standard_error": 0.096,
"survival": 0.36,
"time": 180.0
}
]
},
{
"censored": 11,
"events": 14,
"group": "C",
"median_survival": 172.72,
"n": 25,
"points": [
{
"at_risk": 25,
"censored": 0,
"ci_lower": 0.883185413178315,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.03919183588453085,
"survival": 0.96,
"time": 1.23
},
{
"at_risk": 24,
"censored": 0,
"ci_lower": 0.8136550200144667,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.054258639865002144,
"survival": 0.92,
"time": 6.9
},
{
"at_risk": 23,
"censored": 0,
"ci_lower": 0.7526174185431462,
"ci_upper": 1.0,
"events": 1,
"standard_error": 0.06499230723708768,
"survival": 0.88,
"time": 12.4
},
{
"at_risk": 22,
"censored": 0,
"ci_lower": 0.6962930669033272,
"ci_upper": 0.9837069330966728,
"events": 1,
"standard_error": 0.07332121111929343,
"survival": 0.84,
"time": 24.59
},
{
"at_risk": 21,
"censored": 0,
"ci_lower": 0.6432028812367957,
"ci_upper": 0.9567971187632044,
"events": 1,
"standard_error": 0.08000000000000002,
"survival": 0.8,
"time": 29.52
},
{
"at_risk": 20,
"censored": 0,
"ci_lower": 0.5925864893272221,
"ci_upper": 0.9274135106727779,
"events": 1,
"standard_error": 0.08541662601625048,
"survival": 0.76,
"time": 29.66
},
{
"at_risk": 19,
"censored": 0,
"ci_lower": 0.5439956707064356,
"ci_upper": 0.8960043292935643,
"events": 1,
"standard_error": 0.0897997772825746,
"survival": 0.72,
"time": 35.3
},
{
"at_risk": 18,
"censored": 0,
"ci_lower": 0.4971447086482821,
"ci_upper": 0.862855291351718,
"events": 1,
"standard_error": 0.0932952303175248,
"survival": 0.68,
"time": 45.27
},
{
"at_risk": 17,
"censored": 0,
"ci_lower": 0.45184345748415483,
"ci_upper": 0.8281565425158453,
"events": 1,
"standard_error": 0.096,
"survival": 0.64,
"time": 121.92
},
{
"at_risk": 16,
"censored": 0,
"ci_lower": 0.40796353294578763,
"ci_upper": 0.7920364670542124,
"events": 1,
"standard_error": 0.09797958971132711,
"survival": 0.6,
"time": 135.22
},
{
"at_risk": 15,
"censored": 0,
"ci_lower": 0.3654198927516965,
"ci_upper": 0.7545801072483036,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.56,
"time": 141.12
},
{
"at_risk": 14,
"censored": 0,
"ci_lower": 0.3241604614338306,
"ci_upper": 0.7158395385661694,
"events": 1,
"standard_error": 0.09991996797437439,
"survival": 0.52,
"time": 146.85
},
{
"at_risk": 13,
"censored": 0,
"ci_lower": 0.2841604614338306,
"ci_upper": 0.6758395385661693,
"events": 1,
"standard_error": 0.09991996797437437,
"survival": 0.48,
"time": 172.72
},
{
"at_risk": 12,
"censored": 0,
"ci_lower": 0.24541989275169648,
"ci_upper": 0.6345801072483035,
"events": 1,
"standard_error": 0.09927738916792686,
"survival": 0.44,
"time": 176.91
},
{
"at_risk": 11,
"censored": 11,
"ci_lower": 0.24541989275169648,
"ci_upper": 0.6345801072483035,
"events": 0,
"standard_error": 0.09927738916792686,
"survival": 0.44,
"time": 180.0
}
]
}
],
"dropped": 0,
"event": "event",
"events": 49,
"group": "line",
"n": 75,
"time": "time"
}
Each line's curve steps down at every observed failure time and holds flat
across censored gaps. Reading median_survival per group: line A ≈ 127.7
days, line B ≈ 99.7 days, line C ≈ 172.7 days — line C's machines are
lasting descriptively longest in this sample, line B shortest.
Is the difference real? The log-rank test
A descriptive gap between 3 median-survival numbers could still be sampling noise with only 25 machines per line. The log-rank test compares observed vs. expected failure counts per group across every event time to test whether the underlying survival curves are the same:
lmda logrank data/time_to_failure.csv --time time --event event --group line --format json
{
"censored": 26,
"chi_square": 1.0396746340021665,
"degrees_of_freedom": 2,
"dropped": 0,
"event": "event",
"events": 49,
"group": "line",
"groups": [
{
"expected_events": 16.500932890480293,
"group": "A",
"n": 25,
"observed_events": 19
},
{
"expected_events": 15.243313685455735,
"group": "B",
"n": 25,
"observed_events": 16
},
{
"expected_events": 17.25575342406397,
"group": "C",
"n": 25,
"observed_events": 14
}
],
"n": 75,
"p_value": 0.5946172742235007,
"time": "time"
}
p_value is well above 0.05: at this sample size, the observed spread in
median survival is not distinguishable from what 3 curves with the same
underlying survival function could produce by chance.
Honesty
This is the real, computed result for this sample — not a rounding artifact and not a weaker version of the ANOVA finding. The descriptive numbers (median survival 127.7 / 99.7 / 172.7 days) are true statements about these 75 machines; the log-rank test says that with only 25 machines per line, that descriptive spread does not clear the bar for "the 3 lines have genuinely different failure-time distributions." Reporting only the descriptive medians and skipping the log-rank test would overstate the evidence — a real analysis has to say both halves. As with the ANOVA lesson, this dataset is a realistic simulated dataset (lmda's own case-study-1 worked example), not observed production data, so treat "line C's machines last longest" as a property of this sample, not a manufacturing claim.
Implementation notes
- Right-censoring (
event=0) is not the same as "no failure" — a censored machine contributes "at risk, no observed failure" information up to its censoring time and then leaves the risk set; Kaplan-Meier's step function only drops at observed events, not at censoring times. - Both commands above are re-run against the committed CSV on every
validate_source.pypass and must byte-for-byte match the checked-inoutputs/*.json. - Cross-reference:
anova-defect-rate-by-line(this chapter) for a significant result on the same 3 lines' defect-rate data — the pairing is deliberate: real data does not always cross the significance line, and knowing which is which matters as much as running the test.