Hello,
Thanks for creating and maintaining this package!
Summary
In ipwpoint(), computing unstabilized weights throws an error when family = "multinomial" and family = "ordinal". The exposure model fits and converges, but assembling the weights fails with:
Error in `$<-.data.frame`(`*tmp*`, "ipw.weights", value = numeric(0)) :
replacement has 0 rows, data has 200
I've realized that family = "binomial" is unaffected and since family = "gaussian" has no unstabilized path by design, that's unaffected too. As a result, unstabilized weights cannot currently be produced for categorical or ordinal exposures.
Minimal reproducible example
library(ipw)
set.seed(1)
n <- 200
d <- data.frame(l = rnorm(n))
# 3-level categorical exposure that depends on the confounder l
eta2 <- -0.5 + d$l
eta3 <- -0.5 + 2 * d$l
den <- 1 + exp(eta2) + exp(eta3)
p1 <- 1/den; p2 <- exp(eta2)/den
u <- runif(n)
d$a <- factor(ifelse(u < p1, 1, ifelse(u < p1 + p2, 2, 3)))
# Unstabilized weights: numerator omitted --> ERROR
ipwpoint(exposure = a, family = "multinomial", denominator = ~ l, data = d)
# Same failure for ordinal:
d$ao <- ordered(d$a)
ipwpoint(exposure = ao, family = "ordinal", link = "logit", denominator = ~ l, data = d)
Both calls converge the model and then error at the ipw.weights assignment.
Expected behavior
A list containing ipw.weights (length n) equal to the unstabilized weights W = 1 / f(A | L), where f(A | L) is the predicted probability of the observed exposure category. As a cross-check, the mean of these weights should be ≈ the number of categories (≈ 3 here). The equivalent stabilized call (numerator = ~ 1) works correctly.
Root cause
I dug into the source code to try to diagnose this issue. In the unstabilized approach, the multinomial and ordinal cases assign the constant 1 to p.numerator, but the final weight computation reads w.numerator (which is only created in the numerator-specified else block). So w.numerator is NULL, NULL / w.denominator is numeric(0), and the data-frame assignment fails.
In R/ipwpoint.R:
Binomial (correct), line 136:
if (is.null(tempcall$numerator)) tempdat$w.numerator <- 1
Multinomial (bug), line 167:
if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1 # should be w.numerator
Ordinal (bug), line 198:
if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1 # should be w.numerator
All three then finish with tempdat$ipw.weights <- tempdat$w.numerator / tempdat$w.denominator (lines 163 / 190 / 225).
Proposed fix
Change p.numerator to w.numerator on lines 167 and 198:
if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1
if (is.null(tempcall$numerator)) tempdat$w.numerator <- 1
This isn't an issue in the ipwtm() function, since the appropriate w.numerator parameter is being called in the analogous lines of that function!
Thanks again,
Arianna D. Cascone, PhD
> sessionInfo()
R version 4.6.0 (2026-04-24)
Platform: x86_64-pc-linux-gnu
Running under: Rocky Linux 8.10 (Green Obsidian)
Matrix products: default
BLAS: /opt/R/4.6.0/lib64/R/lib/libRblas.so
LAPACK: /opt/R/4.6.0/lib64/R/lib/libRlapack.so; LAPACK version 3.12.1
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8
[4] LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Etc/UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ipw_1.3.0
loaded via a namespace (and not attached):
[1] vctrs_0.7.3 cli_3.6.6 rlang_1.2.0 geepack_1.3.13
[5] purrr_1.2.2 generics_0.1.4 glue_1.8.1 backports_1.5.1
[9] nnet_7.3-20 grid_4.6.0 tibble_3.3.1 MASS_7.3-65
[13] lifecycle_1.0.5 compiler_4.6.0 dplyr_1.2.1 pkgconfig_2.0.3
[17] tidyr_1.3.2 rstudioapi_0.18.0 lattice_0.22-9 R6_2.6.1
[21] tidyselect_1.2.1 pillar_1.11.1 splines_4.6.0 magrittr_2.0.5
[25] Matrix_1.7-5 tools_4.6.0 broom_1.0.13 survival_3.8-6
Hello,
Thanks for creating and maintaining this package!
Summary
In
ipwpoint(), computing unstabilized weights throws an error whenfamily = "multinomial"andfamily = "ordinal". The exposure model fits and converges, but assembling the weights fails with:I've realized that
family = "binomial"is unaffected and sincefamily = "gaussian"has no unstabilized path by design, that's unaffected too. As a result, unstabilized weights cannot currently be produced for categorical or ordinal exposures.Minimal reproducible example
Both calls converge the model and then error at the ipw.weights assignment.
Expected behavior
A list containing ipw.weights (length
n) equal to the unstabilized weightsW = 1 / f(A | L), wheref(A | L)is the predicted probability of the observed exposure category. As a cross-check, the mean of these weights should be ≈ the number of categories (≈ 3 here). The equivalent stabilized call (numerator = ~ 1) works correctly.Root cause
I dug into the source code to try to diagnose this issue. In the unstabilized approach, the multinomial and ordinal cases assign the constant 1 to
p.numerator, but the final weight computation readsw.numerator(which is only created in the numerator-specified else block). Sow.numeratorisNULL,NULL / w.denominatorisnumeric(0), and the data-frame assignment fails.In R/ipwpoint.R:
Binomial (correct), line 136:
if (is.null(tempcall$numerator)) tempdat$w.numerator <- 1Multinomial (bug), line 167:
if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1# should be w.numeratorOrdinal (bug), line 198:
if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1# should be w.numeratorAll three then finish with
tempdat$ipw.weights <- tempdat$w.numerator / tempdat$w.denominator(lines 163 / 190 / 225).Proposed fix
Change
p.numeratortow.numeratoron lines 167 and 198:if (is.null(tempcall$numerator)) tempdat$p.numerator <- 1if (is.null(tempcall$numerator)) tempdat$w.numerator <- 1This isn't an issue in the
ipwtm()function, since the appropriatew.numeratorparameter is being called in the analogous lines of that function!Thanks again,
Arianna D. Cascone, PhD