A lighting company tests a new lamp. Lamps were switched on at different times, and the test stopped before all of them had failed. For each lamp you know hours[i], how long it was watched, and failed[i]: True if it failed after exactly hours[i] hours, False if it was still burning when the test stopped (so its lifetime is longer than hours[i], by an unknown amount).
Model the lifetimes as exponential with failure rate rate: a lamp fails at time t with density rate * exp(-rate * t), and survives beyond time t with probability exp(-rate * t).
Write lamp_rate(hours, failed) that returns a dict with four entries:
"rate": the failure rate that maximises the likelihood of everything observed,"mean_life": the mean lifetime1 / rateunder that fit (Noneif no lamp failed),"loglik": the log-likelihood at that rate (use0 * log(0) = 0, so it is0.0when no lamp failed),"naive_mean": the plain average of the hours of the lamps that failed (Noneif none did), to show what ignoring the burning lamps would give.
The setup provides lamp_test(n, mean_life, stop, seed), which returns (hours, failed) for n simulated lamps with the given true mean lifetime, switched on at random times and watched until hour stop.
Examples
Input: hours = [120, 300, 450, 500, 500], failed = [True, True, True, False, False]
Output: {"rate": 0.0016042780748663102, "mean_life": 623.3333333333334,
"loglik": -22.305244263541567, "naive_mean": 290.0}
Explanation: three lamps failed after 120, 300 and 450 hours; two were still burning
at 500 hours. The fitted mean lifetime is 623 hours, far above the naive 290.
Input: hours = [80, 80], failed = [False, False]
Output: {"rate": 0.0, "mean_life": None, "loglik": 0.0, "naive_mean": None}
Constraints
1 <= len(hours) == len(failed) <= 10**5, everyhours[i] > 0- floats are compared with a tolerance of
1e-6
Goals
- Build a likelihood that mixes exact observations with 'at least this long' observations
- Maximise it in closed form for an exponential lifetime model
- See how ignoring unfinished units biases an estimate