Skip to content

ipwpoint(): unstabilized weights fail for family = "multinomial" and "ordinal" (p.numerator vs w.numerator) #3

Description

@adcascone

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  

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions