The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
In quantile regression the conditional quantile function of a
response \(y\) given covariates \(x\) is \(Q(p \mid
x) = x^\top\beta(p)\), \(0 < p <
1\). Standard quantile regression estimates one quantile at a
time; quantile regression coefficients modeling (QRCM, Frumento and
Bottai, 2016) instead describes the coefficients as parametric functions
of \(p\), \[\beta(p \mid \theta) = \theta\, b(p),\]
where \(b(p) = (b_1(p), \dots,
b_k(p))^\top\) are known functions and \(\theta\) is a \(q
\times k\) matrix of parameters. All quantiles are estimated
simultaneously by minimizing the integrated loss \[L(\theta) = \int_0^1 \sum_{i=1}^n \rho_p\{y_i -
x_i^\top\theta\, b(p)\}\,dp,\] which is a smooth convex function
of \(\theta\) whose gradient and
Hessian are available in closed form. The package ‘qrcm’ implements this
estimator (qrcm::iqr()); ‘qrcmNP’ adds penalized estimation
for covariate selection (piqr()) and nonlinear parametric
models (niqr()).
piqr()Sottile et al. (2020) proposed to select covariates by minimizing \[L(\theta) + \lambda\, \mathrm{pen}(\theta)\] with standardized \(x\) and \(b(p)\). Since \(\beta_j(p \mid \theta) \equiv 0\) if and only if \(\theta_{j1} = \dots = \theta_{jk} = 0\), selection uses the information on all quantiles at once. Two penalties are available:
penalty = "lasso" (default): \(\lambda\sum_{j,h} w_j|\theta_{jh}|\), the
penalty of Sottile et al. (2020, eq. 3.4);penalty = "group": \(\lambda\sum_j
w_j\sqrt{k_j}\,\|\theta_{j\cdot}\|_2\), a group lasso that keeps
or removes each covariate as a whole.The weights \(w_j\) are set by
penalty.factor (default 1; 0 leaves a covariate
unpenalized). The intercept is never penalized.
We simulate data from the model \(\beta_0(p) = 1 + \Phi^{-1}(p)\), \(\beta_1(p) = 1 + 2p\), \(\beta_2(p) = 1\), with three irrelevant covariates.
set.seed(1234)
n <- 400
d <- data.frame(x1 = rexp(n), x2 = runif(n, 0, 5), x3 = rnorm(n),
x4 = runif(n), x5 = rbinom(n, 1, 0.5))
u <- runif(n)
d$y <- 1 + qnorm(u) + (1 + 2 * u) * d$x1 + d$x2The coefficient functions are modelled by shifted Legendre
polynomials of degree 2 (slp(p, 2), plus a constant).
piqr() computes the whole path of solutions, starting from
\(\lambda_{max}\) (only the intercept
in the model):
fit <- piqr(y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~ slp(p, 2), data = d,
nlambda = 40)
fit
#>
#> Call:
#> piqr(formula = y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~slp(p,
#> 2), data = d, nlambda = 40)
#>
#> Penalty: lasso n. of lambda values: 40
#>
#> Df Npar ObjFunction Lambda
#> lambda1 1 3 334.6 80.490000
#> lambda2 3 6 259.0 63.560000
#> lambda3 3 6 225.7 50.190000
#> lambda4 3 7 210.7 39.630000
#> lambda5 3 7 196.1 31.300000
#> lambda6 3 7 188.5 24.710000
#> lambda7 3 8 183.6 19.520000
#> lambda8 3 8 180.1 15.410000
#> lambda9 4 10 177.8 12.170000
#> lambda10 4 10 176.3 9.609000
#> lambda11 4 10 175.4 7.588000
#> lambda12 4 9 174.9 5.992000
#> lambda13 5 10 174.5 4.731000
#> lambda14 5 11 174.3 3.736000
#> lambda15 5 11 174.1 2.950000
#> lambda16 5 11 174.1 2.330000
#> lambda17 5 11 174.0 1.840000
#> lambda18 5 11 174.0 1.453000
#> lambda19 5 11 173.9 1.147000
#> lambda20 6 13 173.9 0.905800
#> lambda21 6 13 173.8 0.715300
#> lambda22 6 15 173.8 0.564800
#> lambda23 6 14 173.8 0.446000
#> lambda24 6 15 173.7 0.352200
#> lambda25 6 17 173.7 0.278100
#> lambda26 6 17 173.7 0.219600
#> lambda27 6 17 173.7 0.173400
#> lambda28 6 17 173.7 0.136900
#> lambda29 6 17 173.7 0.108100
#> lambda30 6 17 173.7 0.085390
#> lambda31 6 17 173.7 0.067430
#> lambda32 6 17 173.7 0.053240
#> lambda33 6 17 173.6 0.042040
#> lambda34 6 17 173.6 0.033200
#> lambda35 6 17 173.6 0.026220
#> lambda36 6 18 173.6 0.020700
#> lambda37 6 18 173.6 0.016350
#> lambda38 6 18 173.6 0.012910
#> lambda39 6 18 173.6 0.010190
#> lambda40 6 18 173.6 0.008049gof.piqr() computes the AIC and BIC of Sottile et
al. (2020, Section 3.1), \(\log
L(\hat\theta_\lambda) + c_n\,df(\lambda)/n\), with \(c_n = 2\) (AIC) or \(c_n = \log n\) (BIC):
best
#>
#> Selection of lambda by BIC
#>
#> Selected lambda: 5.992 (position 12 )
#> Degrees of freedom: 9
#>
#> Coefficients:
#> (Intercept) slp1 slp2
#> (Intercept) -0.36617 1.65368 0.10827
#> x1 1.01840 0.83515 -0.13759
#> x2 0.94377 NA NA
#> x3 NA NA NA
#> x4 NA NA NA
#> x5 NA 0.09813 -0.10019Cross-validation of the integrated loss is also available:
cv <- cv_piqr(y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~ slp(p, 2), data = d,
nfolds = 5, nlambda = 40)
cv
#>
#> Cross-validated integrated loss (5 folds)
#>
#> Lambda Index Measure SE Df
#> min 4.731 13 0.4451 0.01506 5
#> 1se 15.410 8 0.4557 0.01121 3
plot(cv)fitg <- piqr(y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~ slp(p, 2), data = d,
nlambda = 40, penalty = "group")
bestg <- gof.piqr(fitg, plot = FALSE)
bestg
#>
#> Selection of lambda by BIC
#>
#> Selected lambda: 14.37 (position 8 )
#> Degrees of freedom: 9
#>
#> Coefficients:
#> (Intercept) slp1 slp2
#> (Intercept) 1.1980 1.5147 0.9307
#> x1 0.5802 0.7119 -0.4972
#> x2 0.3155 0.1943 -0.2793
#> x3 NA NA NA
#> x4 NA NA NA
#> x5 NA NA NAAs suggested by Sottile et al. (2020, Section 3.2), the penalized
estimator is used to select the covariates, and the selected model is
re-estimated without penalty. refit_piqr() does this with
qrcm::iqr(), which provides standard errors, Wald tests and
goodness-of-fit diagnostics (the standard errors do not account for the
selection step):
m <- refit_piqr(fit, pos.lambda = best$posMinLambda)
summary(m)
#>
#> Call: qrcm::iqr(formula = y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~slp(p,
#> 2), data = d, s = structure(c(1, 1, 1, 0, 0, 1, 1, 1, 1,
#> 0, 0, 1, 1, 1, 1, 0, 0, 1), dim = c(6L, 3L)))
#>
#> converged: TRUE
#> n. of iterations: 10
#> n. of free parameters: 12
#> n. of observations: 400
#>
#> ######################
#> ######################
#>
#> Coefficients:
#> (Intercept) slp1 slp2
#> (Intercept) -0.99415 1.84750 -0.01593
#> x1 1.21977 0.80738 -0.08405
#> x2 1.12352 -0.08471 0.03807
#> x3 NA NA NA
#> x4 NA NA NA
#> x5 0.08917 0.12613 -0.12671
#>
#> Standard errors:
#> (Intercept) slp1 slp2
#> (Intercept) 0.38322 0.21992 0.20119
#> x1 0.17633 0.09931 0.14888
#> x2 0.10656 0.06213 0.05731
#> x3 NA NA NA
#> x4 NA NA NA
#> x5 0.33200 0.19311 0.16572
#>
#> ######################
#> ######################
#>
#> Wald test for x:
#> chi-square df P(> chi)
#> (Intercept) 126.241 3 <2e-16 ***
#> x1 890.682 3 <2e-16 ***
#> x2 432.139 3 <2e-16 ***
#> x3 NA 0 NA
#> x4 NA 0 NA
#> x5 6.509 3 0.0893 .
#>
#>
#> Wald test for b(p):
#> chi-square df P(> chi)
#> (Intercept) 616.893 4 <2e-16 ***
#> slp1 784.854 4 <2e-16 ***
#> slp2 1.582 4 0.812
#>
#> Minimized loss function: 173.9093
summary(m, p = c(0.1, 0.5, 0.9))
#>
#> Call: qrcm::iqr(formula = y ~ x1 + x2 + x3 + x4 + x5, formula.p = ~slp(p,
#> 2), data = d, s = structure(c(1, 1, 1, 0, 0, 1, 1, 1, 1,
#> 0, 0, 1, 1, 1, 1, 0, 0, 1), dim = c(6L, 3L)))
#>
#> p = 0.1
#>
#> Coefficients:
#> Estimate std.err z value p(>|z|))
#> (Intercept) -0.61605 0.28968 -2.127 0.0334 *
#> x1 1.42664 0.12511 11.403 <2e-16 ***
#> x2 1.08602 0.08075 13.449 <2e-16 ***
#> x3 NA NA NA NA
#> x4 NA NA NA NA
#> x5 0.18281 0.25353 0.721 0.4709
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#>
#>
#> p = 0.5
#>
#> Coefficients:
#> Estimate std.err z value p(>|z|))
#> (Intercept) 0.87724 0.22157 3.959 7.52e-05 ***
#> x1 2.15323 0.16310 13.202 < 2e-16 ***
#> x2 0.98170 0.05992 16.385 < 2e-16 ***
#> x3 NA NA NA NA
#> x4 NA NA NA NA
#> x5 0.40536 0.17269 2.347 0.0189 *
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#>
#>
#> p = 0.9
#>
#> Coefficients:
#> Estimate std.err z value p(>|z|))
#> (Intercept) 2.33995 0.21604 10.831 <2e-16 ***
#> x1 2.71845 0.09903 27.451 <2e-16 ***
#> x2 0.95048 0.06296 15.097 <2e-16 ***
#> x3 NA NA NA NA
#> x4 NA NA NA NA
#> x5 0.38462 0.19612 1.961 0.0499 *
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1Right-censored and left-truncated responses are specified with
survival::Surv(), as in qrcm::iqr():
cens <- rexp(n, 0.05)
d$time <- pmin(d$y + 5, cens)
d$status <- as.numeric(d$y + 5 <= cens)
fitc <- piqr(survival::Surv(time, status) ~ x1 + x2 + x3 + x4 + x5,
formula.p = ~ slp(p, 2), data = d, nlambda = 20)
gof.piqr(fitc, plot = FALSE)
#>
#> Selection of lambda by BIC
#>
#> Selected lambda: 2.723 (position 7 )
#> Degrees of freedom: 8
#>
#> Coefficients:
#> (Intercept) slp1 slp2
#> (Intercept) 4.223977 1.798755 0.079512
#> x1 1.069666 0.848069 NA
#> x2 1.016954 NA -0.002393
#> x3 NA NA NA
#> x4 NA NA NA
#> x5 NA NA -0.189861For each value of \(\lambda\),
piqr() uses a proximal Newton method: the
integrated loss is replaced by its second-order expansion (its gradient
and Hessian are available in closed form), and the resulting penalized
quadratic problem is solved by coordinate descent (soft-thresholding for
the lasso, group soft-thresholding for the group lasso) followed by an
exact “polishing” step on the selected coefficients; a backtracking line
search guarantees a decrease of the penalized objective. Along the path
the algorithm uses warm starts, the sequential strong rules of
Tibshirani et al. (2012) and a final check of the Karush-Kuhn-Tucker
conditions. The conditional CDF of each observation is found by
bisection on a grid of 1023 values of \(p\), denser in the tails, on which \(b(p)\) and its integrals are pre-computed
(as in ‘qrcm’). The algorithm of ‘qrcmNP’ 0.2.x is available with
method = "legacy".
niqr()Bottai and Cilluffo (2020) considered parametric models \(Q(p \mid x, \theta)\) of any
functional form, estimated by minimizing \[L_n(\theta) = \frac1n\sum_{i=1}^n\int_0^1
\rho_p\{y_i - Q(p \mid x_i, \theta)\}\,dp.\] In
niqr(), the model is specified by two functions:
fun(theta, p) returns the matrix of coefficient
functions \(\beta(p \mid \theta)\) (one
row for each value of p);fun2(beta, X) (optional) combines them with the
covariates, row by row; by default \(Q(p \mid
x) = x^\top\beta(p \mid \theta)\).Following Bottai and Cilluffo (2020, Section 6), the intercept can be modelled as a transformation of a normal quantile function, \(z = \Phi^{-1}(p)\), with polynomial effects of the covariates. Here the data have heavier tails than the normal distribution:
set.seed(1)
n <- 400
x <- runif(n)
z <- qnorm(runif(n))
y <- 2 + 0.5 * z + 0.1 * z^3 + (1 + pnorm(z)) * x
dn <- data.frame(y, x)
# Model 1: normal intercept
m1 <- niqr(y ~ x, data = dn, x0 = c(2, 0, 1, 0),
fun = function(theta, p) cbind(theta[1] + exp(theta[2]) * qnorm(p),
theta[3] + theta[4] * p))
# Model 2: a cubic term accounts for the kurtosis (exp() keeps it positive,
# so that the quantile function is increasing in p)
m2 <- niqr(y ~ x, data = dn, x0 = c(2, 0, -2, 1, 0),
fun = function(theta, p) {
z <- qnorm(p)
cbind(theta[1] + exp(theta[2]) * z + exp(theta[3]) * z^3, theta[4] + theta[5] * p)
})
gof.niqr(m1, m2)
#> npar loss AIC BIC AIC.log BIC.log
#> m1 4 0.2537794 109.5117 125.4776 -1.361290 -1.341333
#> m2 5 0.2528031 111.1212 131.0785 -1.362644 -1.337698
summary(m2)
#>
#> Call: niqr(formula = y ~ x, data = dn, fun = function(theta, p) {
#> z <- qnorm(p)
#> cbind(theta[1] + exp(theta[2]) * z + exp(theta[3]) * z^3,
#> theta[4] + theta[5] * p)
#> }, x0 = c(2, 0, -2, 1, 0))
#>
#> n. of observations: 400 n. of iterations: 4
#> Standard errors: sandwich
#>
#> Estimate Std. Error z value Pr(>|z|)
#> theta0 2.03442 0.07919 25.690 < 2e-16 ***
#> theta1 -0.59066 0.16849 -3.506 0.000456 ***
#> theta2 -2.22999 0.21187 -10.525 < 2e-16 ***
#> theta3 1.23175 0.26375 4.670 3.01e-06 ***
#> theta4 0.55099 0.42081 1.309 0.190417
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Minimized loss function: 0.2528031
#> AIC = 111.121 BIC = 131.079 AIC* = -1.36264 BIC* = -1.3377The log-criteria \(\mathrm{AIC}^* = \log
L_n + k/n\) and \(\mathrm{BIC}^* = \log
L_n + k\log(n)/(2n)\) (Bottai and Cilluffo, 2020, Section 3) are
reported as AIC.log and BIC.log; here AIC*
prefers the second model, while BIC* slightly prefers the simpler one.
The goodness-of-fit test below is more conclusive. The estimated
coefficient functions are plotted with pointwise 95% confidence
bands:
Under a correctly specified model the fitted values of the
conditional CDF are uniformly distributed; test.fit()
computes the Kolmogorov-Smirnov and Cramer-von Mises statistics, with
bootstrap p-values. The normal model is rejected, the model with heavy
tails is not:
The Hill quantile function (Bottai and Cilluffo, 2020, eq. 2) \[Q(p \mid \text{dose}) = \beta_0(p) + \frac{\beta_1(p) - \beta_0(p)}{1 + 10^{\beta_3(p) - \log_{10}\text{dose}}}\] is nonlinear in both the covariate and the parameters. Here the minimal response \(\beta_0(p)\) is normal, and the maximal response and the (log) dose at half the maximal effect are constant:
set.seed(2)
dose <- rep(10^seq(-2, 2, length.out = 20), each = 15)
fun.h <- function(theta, p) cbind(theta[1] + exp(theta[2]) * qnorm(p), theta[3], theta[4])
fun2.h <- function(beta, X) beta[, 1] + (beta[, 2] - beta[, 1]) / (1 + 10^(beta[, 3] - log10(X[, 2])))
yh <- fun2.h(fun.h(c(1, log(0.5), 10, 0), runif(length(dose))), cbind(1, dose))
mh <- niqr(yh ~ dose, fun = fun.h, fun2 = fun2.h, x0 = c(0, 0, 5, 0.5))
summary(mh)
#>
#> Call: niqr(formula = yh ~ dose, fun = fun.h, fun2 = fun2.h, x0 = c(0,
#> 0, 5, 0.5))
#>
#> n. of observations: 300 n. of iterations: 16
#> Standard errors: sandwich
#>
#> Estimate Std. Error z value Pr(>|z|)
#> theta0 1.003353 0.056302 17.821 <2e-16 ***
#> theta1 -0.632827 0.054445 -11.623 <2e-16 ***
#> theta2 10.001469 0.002756 3629.444 <2e-16 ***
#> theta3 0.003041 0.006897 0.441 0.659
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Minimized loss function: 0.073441
#> AIC = 30.0323 BIC = 44.8474 AIC* = -2.59794 BIC* = -2.57325dd <- data.frame(dose = 10^seq(-2, 2, length.out = 100))
qf <- predict(mh, type = "QF", p = c(0.05, 0.5, 0.95), newdata = dd, se = FALSE)
plot(dose, yh, log = "x", pch = 20, col = "gray", xlab = "dose", ylab = "response")
matlines(dd$dose, qf, lty = c(2, 1, 2), col = 1)The covariance matrix of \(\hat\theta\) is estimated by the sandwich
formula \(H^{-1}\Omega H^{-1}/n\)
(Bottai and Cilluffo, 2020, Theorem 3), where \(H\) is the Hessian of the expected loss,
\(H = n^{-1}\sum_i \partial_\theta
Q(p_i^*)\,\partial_\theta Q(p_i^*)^\top/Q'(p_i^*)\), \(p_i^* = F(y_i \mid x_i, \hat\theta)\). With
repeated measures, the argument cluster gives
cluster-robust standard errors; the (cluster) bootstrap used in the
paper is available with
control = niqr.control(se = "boot").
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.