This function wraps the custom approach for fitting distributions to doubly censored data using fitdistrplus and primarycensored. It handles primary censoring (when the primary event time is not known exactly), secondary censoring (when the secondary event time is interval-censored), and truncation (when events are only observed within a delay range [L, D]).
Usage
fitdistdoublecens(
censdata,
distr,
left = "left",
right = "right",
pwindow = "pwindow",
L = "L",
D = "D",
dprimary = dunif,
primary_args = NULL,
pprimary = NULL,
dprimary_args = NULL,
truncation_check_multiplier = 2,
prior = NULL,
hazard_model = c("rw", "re"),
check = TRUE,
...
)Arguments
- censdata
A data frame with columns 'left' and 'right' representing the lower and upper bounds of the censored observations. Unlike
fitdistrplus::fitdistcens()NAis not supported for either the upper or lower bounds. Useleft == rightfor an exactly observed secondary event.- distr
A character string naming the distribution to be fitted. Special values
"discretestep"and"discretehazard"select the non-parametric step-distribution fitting; see Details.- left
Column name for lower bound of observed values (default: "left").
- right
Column name for upper bound of observed values (default: "right").
- pwindow
Column name for primary window (default: "pwindow"). Use a primary window of 0 for an exactly observed primary event.
- L
Column name for minimum delay (lower truncation point). For any finite L the distribution is left-truncated at L; use
L = -Inffor no left truncation. This is useful for modelling generation intervals where day 0 is excluded, particularly when used in renewal models. (default: "L"). If the column is not present in censdata, L = -Inf is assumed.- D
Column name for maximum delay (upper truncation point). If finite, the distribution is truncated at D. If set to Inf, no upper truncation is applied. (default: "D"). Observations whose secondary censoring interval straddles
D(left < D <= right) are accepted: the upper endpoint is internally clipped toDand the likelihood becomesP(X in [left, min(right, D)] | L <= X <= D). This is a no-op for the standard parametric case whereright <= D. Observations withleft >= Dare rejected because under truncation atDno event with latent value>= Dis observable.- dprimary
Function to generate the probability density function (PDF) of primary event times. This function should take a value
xand apwindowparameter, and return a probability density. It should be normalized to integrate to 1 over [0, pwindow]. Defaults to a uniform distribution over [0, pwindow]. Users can provide custom functions or use helper functions likedexpgrowthfor an exponential growth distribution. Seepcd_primary_distributions()for examples. The package can identify base R distributions for potential analytical solutions. For non-base R functions, users can applyadd_name_attribute()to yield properly tagged functions if they wish to leverage analytical solutions.- primary_args
List of additional arguments to be passed to dprimary (and the matching primary CDF). For example, when using
dexpgrowth, you would passlist(min = 0, max = pwindow, r = 0.2)to set the minimum, maximum, and rate parameters. Replaces the deprecateddprimary_args; defaults toNULL.- pprimary
Optional CDF for the primary event distribution. May be a function or a character string naming a primary distribution in
pcd_primary_distributions. Defaults toNULL, in which case the primary CDF is looked up automatically from the registry using the"name"attribute ofdprimary. When bothdprimaryandpprimarycarry a"name"attribute (or are base R functions whose name can be inferred), the two names must agree on everything other than the leadingd/pprefix; mismatches such asdunif+pexpgrowthraise an error. Supplyingpprimaryexplicitly is mainly useful when using a custom primary distribution whose CDF is not in the registry.- dprimary_args
[Deprecated] Use
primary_argsinstead.- truncation_check_multiplier
Numeric multiplier to use for checking if the truncation time D is appropriate relative to the maximum delay. Set to NULL to skip the check. Default is 2.
- prior
Optional list of prior settings used by the dist function's
fit_penaltyattribute (currently only"discretehazard"). Each element is itself a list withmeanandsdentries. Defaults are used for any component not supplied. Seepdiscretehazard()for the default values.- hazard_model
One of
"rw"(default) or"re". Only consulted whendistr = "discretehazard"."rw"selects the random-walk transformlogit(h_i) = alpha + sigma * cumsum(eps);"re"selects the IID logit random-effect transformlogit(h_i) = alpha + sigma * eps_i. See Details.- check
Logical; if
TRUE(the default)pdistis validated withcheck_pdist()anddprimarywithcheck_dprimary(). Neither changes across a fit, so validation runs on the first likelihood evaluation only rather than on every one. Set toFALSEto skip it entirely. For non-parametric distributions,startis required and determines the number of bins; passboundarieshere to override the default0:Kunit-width bins.- ...
Additional arguments to be passed to
fitdistrplus::fitdist().
Details
How distribution functions are resolved
The distr argument names a distribution. The function looks up the
density and CDF functions by prepending d and p to the name (e.g.
distr = "gamma" resolves to dgamma() and pgamma()). Custom
distributions can be used as long as the corresponding d<distr>() and
p<distr>() functions are defined.
Parametric distributions are fitted in the parameterisation named by
start, and the returned estimates and covariance matrix use the same
names. For example, gamma can be fitted with either
start = list(shape = , rate = ) or start = list(shape = , scale = ).
Parameters can be held fixed by passing fix.arg to
fitdistrplus::fitdist() through ..., either as a named list or as a
function of the delays returning one.
Non-parametric distributions
Two non-parametric distributions are supported. They share a common
fitting machinery: the dist function carries a vector_param attribute
("pmf" for pdiscretestep()/ddiscretestep(), "hazards" for
pdiscretehazard()/ddiscretehazard()) that drives this function to
build a closure mapping flat scalar parameters into the underlying
vector argument.
distr = "discretestep": free parametersp1, ..., p_{K-1}(in[0, 1]); the last bin probability is1 - sum(p1, ..., p_{K-1}). Seepdiscretestep()for parameterisation details and the soft simplex penalty applied when probabilities are infeasible.distr = "discretehazard": free parametersalpha,log_sigma,eps_1, ..., eps_{K-1}. The hazard form parameterises the same family of step distributions as"discretestep", but its free parameters drive either a Gaussian random walk on the logit hazard (hazard_model = "rw", the default,logit(h_i) = alpha + sigma * cumsum(eps)) or an IID logit random-effect transform (hazard_model = "re",logit(h_i) = alpha + sigma * eps_iwitheps_i ~ N(0, 1)). The smoothing of the random walk regularises the recovered PMF against over-fitting in sparse data and replaces the simplex constraint with an unconstrained optimisation; the random-effect variant models hazards as independent draws aroundalpharather than a smoothed trajectory. Seepdiscretehazard()for full parameterisation details and the MAP-equivalent prior penalty applied during fitting; passpriorto override the default prior settings.
For non-parametric distributions K is implied by length(start):
K = length(start) + 1 for "discretestep" and
K = length(start) - 1 for "discretehazard". start is therefore
required.
Exact observations
Rows with pwindow = 0 have an exactly known primary event time. Rows
with left == right have an exactly known secondary event time and
contribute a density rather than a probability to the likelihood (see
dprimarycensored()). Rows of different types can be mixed in one fit,
as for the exact, single interval censored and doubly interval censored
observations of coarseDataTools::dic.fit(). Data with the primary
event in [EL, ER] and the secondary event in [SL, SR] map
to left = SL - EL, right = SR - EL and pwindow = ER - EL.
See also
pdiscretestep() pdiscretehazard()
Modelling wrappers for external fitting packages
pcd_as_stan_data(),
pcd_cmdstan_model()
Examples
# Example with normal distribution
set.seed(123)
n <- 1000
true_mean <- 5
true_sd <- 2
pwindow <- 2
swindow <- 2
D <- 10
samples <- rprimarycensored(
n, rnorm,
mean = true_mean, sd = true_sd,
pwindow = pwindow, swindow = swindow, D = D
)
delay_data <- data.frame(
left = samples,
right = samples + swindow,
pwindow = rep(pwindow, n),
D = rep(D, n)
)
fit_norm <- fitdistdoublecens(
delay_data,
distr = "norm",
start = list(mean = 0, sd = 1)
)
summary(fit_norm)
#> Fitting of the distribution ' pcens_dist ' by maximum likelihood
#> Parameters :
#> estimate Std. Error
#> mean 5.003695 0.07759293
#> sd 1.997836 0.06707002
#> Loglikelihood: -1401.056 AIC: 2806.112 BIC: 2815.927
#> Correlation matrix:
#> mean sd
#> mean 1.0000000 0.3174891
#> sd 0.3174891 1.0000000
#>
# \donttest{
# Example with discretestep (non-parametric PMF) distribution
set.seed(42)
true_pmf <- c(0.1, 0.3, 0.4, 0.15, 0.05)
step_samples <- rprimarycensored(
500, rdiscretestep,
boundaries = 0:5, pmf = true_pmf,
pwindow = 1, swindow = 1, D = 6
)
step_data <- data.frame(
left = step_samples,
right = step_samples + 1,
pwindow = rep(1, 500),
D = rep(6, 500)
)
fit_step <- fitdistdoublecens(
step_data,
distr = "discretestep",
boundaries = 0:5,
start = as.list(setNames(rep(0.2, 4), paste0("p", 1:4)))
)
# Example with discretehazard (logit-hazard random walk) distribution
fit_haz <- fitdistdoublecens(
step_data,
distr = "discretehazard",
boundaries = 0:5,
start = c(
list(alpha = -2, log_sigma = log(0.5)),
as.list(setNames(rep(0, 4), paste0("eps_", 1:4)))
)
)
#> Warning: diag(V) had non-positive or NA entries; the non-finite result may be dubious
#> Warning: NaNs produced
# }
