Fuzzy survival analysis for data with vague event times and vague failure indicators.
Survival records are often imprecise: a failure is known to have
happened “during the second quarter”, between two follow-up visits, or
not at all - a subject lost to follow-up may or may not have failed.
fuzzySurvival models that imprecision directly instead of
discarding it or forcing an arbitrary choice, and reports the whole
consequence on the estimation rather than a single number.
The package implements the alpha-cut extension of the Kaplan-Meier estimator, of the Cox proportional hazards model and of the log-rank test, together with the type-reduction (defuzzification) rules needed to turn a fuzzy result into a single interpretable quantity.
# from a local checkout
install.packages(".", repos = NULL, type = "source")Survival analysis with fuzzy numbers is not covered by any CRAN
package. FuzzyNumbers and friends provide the fuzzy-set
machinery, but nothing connects it to censoring and to the partial
likelihood; survival covers crisp data only. The two
ingredients are trivially combined once the algebra is right, but the
result has to satisfy properties that ordinary code breaks:
survival.Both properties are covered by the test suite.
Vague event times. The membership function of the event time says how plausible each value is. A failure observed between two examinations gives a flat fuzzy number on the interval between them (the value is entirely plausible anywhere in it); a rounded recollection gives a trapezoid.
library(fuzzySurvival)
# "the failure occurred in the second quarter" (months 4 to 6)
fuzzy_number_interval(4, 6)
#> <fuzzy_number> with 1 element
#> [1] "I[4, 6]"
# certainly between 3 and 9, plausibly concentrated in 5-7
fuzzy_number_trapezoid(3, 5, 7, 9)Vague failure indicators. A status strictly between 0 and 1 is read as the degree of belief that the subject failed. Such a subject contributes a fractional number of events, and stays in the risk set until they are censored:
fuzzy_surv(c(2, 5, 7, 9), c(1, 0.5, 1, 1))At level alpha the data compatible with the information
are the box of times between the alpha-cuts. Replacing each time by the
lower or the upper end of its cut makes the failures happen as early or
as late as possible, so the two Kaplan-Meier curves bound the survival
function:
data(fuzzy_dfs)
fit <- fkm(fuzzy_surv(ft, fs) ~ 1, data = fuzzy_dfs)
fit
#> Fuzzy Kaplan-Meier estimate
#> n = 240, events = 81, uncertain = 40, alpha levels = 21
#> mean width of the alpha-cuts at level 0: 0.675
summary(fit, times = c(6, 12, 24))[, c("time", "n.risk", "lower", "surv", "upper")]
#> time n.risk lower surv upper
#> 1 6 212 0.00000 0.9781 0.9781
#> 2 12 187 0.9360 0.9410 0.9460
#> 3 24 129 0.7668 0.7784 0.7900lower and upper are the bounds at the
requested level, surv the defuzzified curve. On precise
data the two bounds coincide and the estimator reproduces
survival::survfit() exactly.
The same construction is applied to the partial likelihood: the alpha-cut of a coefficient spans every value obtained at a level at or below it, which is what makes the intervals nested. Hazard ratios keep their membership structure because the exponential is monotone.
fcox(fuzzy_surv(ft, fs) ~ age + arm, data = fuzzy_dfs)
#> Fuzzy Cox proportional hazards model
#>
#> coef p fuzzy.lower fuzzy.upper
#> age -0.0465 7.914e-05 -0.0696 -0.0234
#> arm 0.5211 0.005565 0.1527 0.8895flogrank(fuzzy_surv(ft, fs) ~ arm, data = fuzzy_dfs)
#> Fuzzy log-rank test
#> n = 240, groups: Control vs Treated
#> observed / expected events in group 1: 49 / 65.66
#> crisp chi-square (level 1): 9.3273
#> crisp p-value: 0.002258On crisp data this reproduces survival::survdiff().
A fuzzy result carries a whole family of alpha-cuts; a single number
is obtained by a type-reduction rule. Nine classical rules are available
- midrange, centroid, gmirl,
sgmrl, ms, msm,
yager, min, max:
defuzz(fuzzy_number_trapezoid(3, 5, 7, 9), "centroid")
#> [1] 6
# the fuzzy survival probability itself, reduced at three levels
defuzz(fkm_fuzzy_surv(fit, times = c(6, 12, 24)), "midrange", level = 0.5)Every rule of a symmetric fuzzy number returns its centre, which makes the rules easy to check:
defuzz(fuzzy_number_triangle(2, 5, 8), c("centroid", "gmirl", "sgmrl"))
#> centroid gmirl sgmrl
#> [1,] 5 5 5sim_fuzzy_surv() generates exact event times and then
coarsens them the way a record does, so that a study can be validated
against a known truth:
set.seed(1)
d <- sim_fuzzy_surv(n = 200, beta = c(age = 0.4, arm = -0.5))
head(d[, c("arm", "fs")])
fit <- fkm(fuzzy_surv(ft, fs) ~ arm, data = d)
plot(fit, type = "diff")vignette("fuzzy-survival", package = "fuzzySurvival")
walks through the construction, the estimation, and the caveats.
?fkm, ?fcox, ?flogrank and
?fuzzy_number give the reference.
alpha. Use a
trapezoid when the plausibility of the values should be graded.MIT