--- title: "Introduction to stagelife: Two-Stage Life Testing" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to stagelife: Two-Stage Life Testing} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4 ) old_opts <- options(digits = 4) ``` ```{r setup} library(stagelife) ``` ## 1. The model This package implements the **stage life testing (SLT)** model of Laumen & Cramer (2019, *Naval Research Logistics* 66:632-647), which unifies progressive censoring (with fixed censoring times) and step-stress accelerated life testing. Units begin on a baseline stage and, at a fixed stage-change time `tau1`, `R*_1` of the units still surviving are randomly selected and moved to an elevated stage; the rest continue failing under the original stage conditions. [`rslt()`] is this package's *only* data-generating function, implementing the paper's own Procedure 2.3. `R*_1` is generated by one of two rules: * `design = "typeP"` transfers a fixed target *proportion* `pi1` of survivors: `R*_1 = floor(pi1 * (n - D1))`, where `D1` is the number of stage-0 failures observed before `tau1`. * `design = "typeM"` transfers a fixed target *count* `R0_1`, capped by the number of survivors actually available. Setting `pi1 = 1` (every survivor transferred) collapses the model to the classical "simple step-stress" design -- Remark 2.2(4) of the paper -- which is why no separate function is needed for that case: it is simply `rslt(..., design = "typeP", prop_transfer = 1)`. This is also the structure of a typical *published* step-stress data set (see Section 8), so it is the default `prop_transfer` used throughout this vignette's non-Section-5 examples for simplicity, even though the package's actual namesake model is the general random-withdrawal case. The two stages are linked by the **cumulative exposure model**: a unit that has survived to time `tau` under the baseline stage is treated, after switching, as if it had already accumulated an *equivalent age* \[ \nu = Q\big(F(\tau;\theta_0);\ \theta_1\big) \] under the elevated-stage distribution, where `F`/`Q` are the CDF/quantile function of a common two-parameter baseline family `\theta = (\lambda, \alpha)` (`lambda` a rate/scale-type parameter, `alpha` a shape parameter). [`slt_equiv_age()`] computes `\nu` directly, and this exactly reproduces equation (1.1) of the paper. [`dslt()`], [`pslt()`] and [`qslt()`] give the density/CDF/quantile function of a single unit's marginal lifetime under this model -- but note that these are only the correct *pooled* reference distribution for the combined `Y`/`Z` sample when `prop_transfer = 1`; see the note in [`slt_gof_ks()`] for why. **A parameterisation note for anyone comparing directly against the paper's tables**: the paper parameterises the Exponential/Weibull distributions by their **mean** `\theta` (`F(x) = 1 - exp(-x/\theta)`), whereas this package parameterises every family by a **rate-like** `lambda` (`F(x) = 1 - exp(-\lambda x)` for the alpha=1 case), so `lambda = 1/\theta`. Section 5.1 below reproduces the paper's own worked example numerically, using this correspondence. ### 1.1 Baseline distributions ```{r} slt_families() ``` ```{r} fam <- slt_dist("weibull") fam$p(1, lambda = 1, alpha = 2) fam$q(0.5, lambda = 1, alpha = 2) ``` | Family | `alpha = 1` reduces to | |---|---| | `weibull` | Exponential | | `expexponential` | Exponential | | `moexponential` | Exponential | | `gamma` | Exponential | | `powerlindley` | Lindley | | `explindley` | Lindley (same limit as `powerlindley`) | | `lomax` | (boundary member of the Lomax family) | | `expteissier` | baseline Teissier | | `powermuth` | baseline Teissier (same limit as `expteissier`) | | `loglogistic` | Log-Logistic, shape 1 | | `chen` | Chen, shape 1 | | `lognormal` | Log-normal, log-sd 1 | `case = "shape1"`/`"shape11"` (Section 1.2) fixes the elevated-stage shape parameter to exactly 1 -- as the table shows, this only makes the elevated stage *Exponential* for `weibull`, `expexponential`, `moexponential`, and `gamma`. `powerlindley`, `expexponential`, `lomax`, and `expteissier` are the paper's own baseline-stage extension points; the remaining seven families are this package's own additions, covering hazard shapes not available among the first four (a non-monotonic hazard for `loglogistic`; a bathtub-shaped hazard for `chen`; `gamma`/`lognormal`, two of the most standard lifetime distributions; `explindley`/`moexponential`, the "exponentiate the cdf" and "tilt parameter" siblings of `powerlindley`/`expexponential` respectively). See `?slt_dist` for the pdf/cdf and literature reference of each. ### 1.2 Parameter-sharing cases With `\theta_0 = (\lambda_0, \alpha_0)` at the baseline stage and `\theta_1 = (\lambda_1, \alpha_1)` at the elevated stage, four parameter-sharing structures are logically possible. This package supports three of them: ```{r} slt_cases() ``` The fourth, fully unrestricted structure (`\alpha_0` and `\alpha_1` both free, 4 parameters) is **not** offered: with a single change-point and no replicated stress levels, that structure is empirically non-identifiable -- the profile log-likelihood in `(\alpha_0, \alpha_1)` is extremely flat, producing unstable estimates and frequent optimiser non-convergence. `"shape1"` (baseline shape free, elevated shape fixed at 1) is the structure most useful as a primary working model; `"shape11"` (both shapes fixed at 1) is its natural nested null, used by [`slt_lrt()`]; `"common"` (a single shared shape, both scales free) is a well-identified alternative. [`slt_reduce_params()`] converts a full `(theta0, theta1)` pair into a case's free parameter vector -- mostly useful in simulation studies, to put a known "true" parameter set into the same format `fit_slt()` returns, for comparing against the fitted `par`: ```{r} slt_reduce_params(theta0 = c(0.2, 1.4), theta1 = c(0.5, 1.4), case = "common") ``` ## 2. Simulating data The classical simple-step-stress special case (`prop_transfer = 1`): ```{r} set.seed(2026) theta0 <- c(0.05, 1.5) # (lambda0, alpha0) theta1 <- c(0.20, 1) # (lambda1, alpha1 = 1) -- consistent with case = "shape1" tau1 <- slt_tau("weibull", theta0, p = 0.5) # change-point at the stage-0 median tau1 d <- rslt(n = 200, family = "weibull", theta0 = theta0, theta1 = theta1, tau1 = tau1, prop_transfer = 1) str(d) ``` The general random-withdrawal case -- the paper's actual SLT model -- uses the same function with a smaller `prop_transfer` (`"typeP"`, a target proportion) or `design = "typeM"` (a target count): ```{r} d_palt <- rslt(n = 200, family = "weibull", theta0 = theta0, theta1 = theta1, tau1 = tau1, design = "typeP", prop_transfer = 0.5) str(d_palt) d_palt_m <- rslt(n = 200, family = "weibull", theta0 = theta0, theta1 = theta1, tau1 = tau1, design = "typeM", R0_1 = 100) str(d_palt_m) ``` ## 3. Maximum likelihood estimation ```{r} fit <- fit_slt(d, family = "weibull", case = "shape1", tau1 = tau1) fit summary(fit) ``` `fit_slt()` runs a Nelder-Mead warm start followed by BFGS (with a numerical Hessian for inference) from several perturbed starting values, keeping the converged fit with the highest log-likelihood -- this guards against both local optima and the occasional optimiser failure. Standard S3 methods work as expected: ```{r} coef(fit) vcov(fit) logLik(fit) AIC(fit) BIC(fit) ``` One family-specific note: `lomax`'s shape parameter has a notoriously high-variance MLE at small-to-moderate sample sizes (a well known feature of the family, not a defect of the optimiser). If you plan to fit `family = "lomax"`, prefer larger samples and/or the bootstrap (rather than the asymptotic normal) confidence intervals below. ## 4. Confidence intervals Asymptotic (Wald) intervals: `type = "lognormal"` (the default) builds the interval on the log scale and exponentiates, guaranteeing a positive interval; `type = "normal"` applies the delta-method standard error directly on the natural scale and can occasionally produce a **negative lower bound** for a positive parameter, especially at small `n`: ```{r} confint(fit) # type = "lognormal" (default), always positive confint(fit, type = "normal") # can occasionally go negative for small n ``` Bootstrap alternatives -- non-parametric (case resampling) and parametric (simulate from the fitted model) -- each with **percentile**, **bootstrap-*t***, and **log-scale bootstrap-*t*** variants. The plain bootstrap-*t* interval has the same occasional-negative-bound issue as the natural-scale asymptotic interval above, for the same reason (it is an ordinary linear pivot); the log-scale version applies the identical pivot construction on `log(theta)` and exponentiates, guaranteeing positivity, directly analogous to why `type = "lognormal"` is preferred above. The percentile interval is already always positive (it is built from quantiles of the always-positive bootstrap estimates themselves). ```{r, eval = FALSE} b <- slt_bootstrap_ci(fit, B = 1000, method = "nonparametric") b$percentile # always positive b$boot_t # can occasionally go negative for small n, like type = "normal" above b$log_boot_t # always positive, like type = "lognormal" above slt_bootstrap_ci(fit, B = 1000, method = "parametric") # prop_transfer = 1 default slt_bootstrap_ci(fit, B = 1000, method = "parametric", prop_transfer = 0.5) # genuine partial withdrawal ``` ## 5. Design theory from the paper: reproducing Laumen & Cramer (2019) This section validates the package's fidelity to the paper directly, and documents where the package's inferential tools go beyond, or fall short of, the paper's own results. ### 5.1 Reproducing the paper's worked example exactly Table 2 of Laumen & Cramer (2019) gives a `"typeP"` SLT sample (`n=16`, `tau1=15`, true `theta0=40`, `theta1=20`, i.e. Exponential *means* -- see the parameterisation note in Section 1). Entering that data directly and fitting the pure-Exponential model (`case = "shape11"`) recovers the paper's own reported MLEs exactly: ```{r} Y <- c(2.55, 7.77, 9.01, 9.23, 12.34, 18.61, 19.39, 27.54, 39.67, 43.25, 91.46) Z <- c(24.51, 30.73, 36.88, 55.26, 65.39) fit_paper <- fit_slt(list(Y = Y, Z = Z), family = "weibull", case = "shape11", tau1 = 15) c(theta0_hat = 1 / fit_paper$par["lambda0"], theta1_hat = 1 / fit_paper$par["lambda1"]) # Paper reports: theta0_hat = 32.35, theta1_hat = 27.55 ``` ### 5.2 Planning a stage life test: `slt_prob_stage1()` (Lemma 4.1) The MLE of the elevated-stage parameter only exists when at least one unit is actually transferred and fails there (`R*_1 > 0`). `slt_prob_stage1()` implements the paper's Lemma 4.1 for this probability, and exactly reproduces the paper's own Table 5: ```{r} slt_prob_stage1(12, 0.5, c(1, 1), design = "typeP", pi1 = 0.25) # paper: n*=8, prob=0.9864756680 slt_prob_stage1(12, 0.5, c(1, 1), design = "typeM", R0_1 = 6) # paper: n*=11, prob=0.9999862301 ``` This is useful for planning: before running a real experiment, check that your intended `n`, `tau1`, and `pi1`/`R0_1` give a comfortably high `prob` under a plausible guess at `theta0`. ## 6. Goodness of fit and model comparison Because the reference distribution's parameters were estimated from the same data, the classical Kolmogorov-Smirnov p-value is anti-conservative (the well known Lilliefors problem). [`slt_gof_ks()`] reports both the naive value and a parametric-bootstrap-corrected one: ```{r, eval = FALSE} slt_gof_ks(fit, B = 1000) ``` `"shape11"` is nested in both `"shape1"` and `"common"` (fixing the remaining free shape(s) to 1), giving a likelihood-ratio test of `H0: shape = 1`: ```{r} fit0 <- fit_slt(d, "weibull", "shape11", tau1 = tau1) slt_lrt(fit, fit0) ``` For exploratory comparison across any set of family/case fits (not necessarily nested), use [`slt_model_comparison()`]: ```{r} fitc <- fit_slt(d, "weibull", "common", tau1 = tau1) slt_model_comparison(list("shape1" = fit, "shape11" = fit0, "common" = fitc)) ``` ## 7. Diagnostic plots ```{r, fig.show = "hold"} plot_cdf(fit) plot_survival(fit) plot_pp(fit) plot_qq(fit) ``` ## 8. Real-data example Four real step-stress data sets are bundled with the package (see `?slt_han_kundu_2014` and related help pages). Here we compare all twelve baseline distributions under `case = "common"` on the Han & Kundu (2014) data set (`case = "common"` estimates a single shared shape rather than fixing the elevated-stage shape at exactly 1, which suits an across-family comparison better -- see Section 1.2): ```{r} data(slt_han_kundu_2014) d_real <- list(Y = slt_han_kundu_2014$y0, Z = slt_han_kundu_2014$y1) tau1_real <- slt_han_kundu_2014$tau1 fits <- lapply(slt_families(), function(fam) { fit_slt(d_real, family = fam, case = "common", tau1 = tau1_real, n_starts = 5) }) names(fits) <- slt_families() slt_model_comparison(fits) ``` ```{r} best_fit <- fits[[slt_model_comparison(fits)$model[1]]] summary(best_fit) plot_cdf(best_fit) ``` Not every family need converge for every case/data set: a fixed elevated-stage shape of exactly 1 (`case = "shape1"`/`"shape11"`) can be a poor match for a family whose shape parameter's natural scale depends on the data (e.g. `chen`, where the shape is an exponent on the failure time feeding directly into an exponential), in which case [`slt_model_comparison()`] reports `converged = FALSE`/`NA` for that family rather than a misleading result; trying `case = "common"`, as above, is a reasonable first thing to check. ```{r, include = FALSE} options(old_opts) ```