1+ # Tests for numeric vs psw weight equivalence and backward compatibility
2+
3+ test_that(" backward compatibility: all functions work with numeric weights" , {
4+ # Create numeric weight vectors
5+ set.seed(123 )
6+ n <- 100
7+ x <- rnorm(n )
8+ g <- sample(c(0 , 1 ), n , replace = TRUE )
9+ numeric_weights <- runif(n , 0.5 , 2.0 )
10+
11+ # Test all balance functions with numeric weights
12+ expect_no_error(smd_result <- bal_smd(x , g , weights = numeric_weights ))
13+ expect_true(is.finite(smd_result ))
14+
15+ expect_no_error(vr_result <- bal_vr(x , g , weights = numeric_weights ))
16+ expect_true(is.finite(vr_result ) && vr_result > 0 )
17+
18+ expect_no_error(ks_result <- bal_ks(x , g , weights = numeric_weights ))
19+ expect_true(is.finite(ks_result ) && ks_result > = 0 && ks_result < = 1 )
20+
21+ # Test correlation with numeric weights
22+ y <- 2 * x + rnorm(n , sd = 0.5 )
23+ expect_no_error(corr_result <- bal_corr(x , y , weights = numeric_weights ))
24+ expect_true(is.finite(corr_result ) && corr_result > = - 1 && corr_result < = 1 )
25+
26+ # Test energy balance with numeric weights
27+ covariates <- data.frame (x1 = x , x2 = rnorm(n ))
28+ expect_no_error(energy_result <- bal_energy(covariates , g , weights = numeric_weights ))
29+ expect_true(is.finite(energy_result ) && energy_result > = 0 )
30+
31+ # Test check_balance with numeric weights
32+ test_data <- data.frame (
33+ x = x ,
34+ y = y ,
35+ g = g ,
36+ w = numeric_weights
37+ )
38+ expect_no_error(
39+ balance_results <- check_balance(test_data , c(x , y ), g , .wts = w , .metrics = " smd" )
40+ )
41+ expect_s3_class(balance_results , " data.frame" )
42+ expect_true(all(is.finite(balance_results $ estimate )))
43+ })
44+
45+ test_that(" numeric and psw weights produce identical results" , {
46+ skip_if_not_installed(" propensity" )
47+
48+ # Create test data
49+ set.seed(42 )
50+ n <- 100
51+ x <- rnorm(n )
52+ g <- sample(c(0 , 1 ), n , replace = TRUE )
53+
54+ # Create numeric weights
55+ numeric_weights <- runif(n , 0.5 , 2.0 )
56+
57+ # Create equivalent psw objects
58+ psw_ate <- propensity :: psw(numeric_weights , estimand = " ate" )
59+ psw_att <- propensity :: psw(numeric_weights , estimand = " att" )
60+ psw_ato <- propensity :: psw(numeric_weights , estimand = " ato" )
61+
62+ # Test bal_smd equivalence
63+ smd_numeric <- bal_smd(x , g , weights = numeric_weights )
64+ smd_psw_ate <- bal_smd(x , g , weights = psw_ate )
65+ smd_psw_att <- bal_smd(x , g , weights = psw_att )
66+ smd_psw_ato <- bal_smd(x , g , weights = psw_ato )
67+
68+ expect_identical(smd_numeric , smd_psw_ate )
69+ expect_identical(smd_numeric , smd_psw_att )
70+ expect_identical(smd_numeric , smd_psw_ato )
71+
72+ # Test bal_vr equivalence
73+ vr_numeric <- bal_vr(x , g , weights = numeric_weights )
74+ vr_psw_ate <- bal_vr(x , g , weights = psw_ate )
75+ vr_psw_att <- bal_vr(x , g , weights = psw_att )
76+ vr_psw_ato <- bal_vr(x , g , weights = psw_ato )
77+
78+ expect_identical(vr_numeric , vr_psw_ate )
79+ expect_identical(vr_numeric , vr_psw_att )
80+ expect_identical(vr_numeric , vr_psw_ato )
81+
82+ # Test bal_ks equivalence
83+ ks_numeric <- bal_ks(x , g , weights = numeric_weights )
84+ ks_psw_ate <- bal_ks(x , g , weights = psw_ate )
85+ ks_psw_att <- bal_ks(x , g , weights = psw_att )
86+ ks_psw_ato <- bal_ks(x , g , weights = psw_ato )
87+
88+ expect_identical(ks_numeric , ks_psw_ate )
89+ expect_identical(ks_numeric , ks_psw_att )
90+ expect_identical(ks_numeric , ks_psw_ato )
91+ })
92+
93+ test_that(" numeric and psw weights produce identical results for correlation" , {
94+ skip_if_not_installed(" propensity" )
95+
96+ # Create test data
97+ set.seed(42 )
98+ n <- 100
99+ x <- rnorm(n )
100+ y <- 2 * x + rnorm(n , sd = 0.5 )
101+
102+ # Create numeric weights
103+ numeric_weights <- runif(n , 0.5 , 2.0 )
104+
105+ # Create equivalent psw object
106+ psw_weights <- propensity :: psw(numeric_weights , estimand = " ate" )
107+
108+ # Test bal_corr equivalence
109+ corr_numeric <- bal_corr(x , y , weights = numeric_weights )
110+ corr_psw <- bal_corr(x , y , weights = psw_weights )
111+
112+ expect_identical(corr_numeric , corr_psw )
113+ })
114+
115+ test_that(" numeric and psw weights produce identical results for energy balance" , {
116+ skip_if_not_installed(" propensity" )
117+
118+ # Create test data
119+ set.seed(42 )
120+ n <- 100
121+ covariates <- data.frame (
122+ x1 = rnorm(n ),
123+ x2 = rnorm(n )
124+ )
125+ g <- sample(c(0 , 1 ), n , replace = TRUE )
126+
127+ # Create numeric weights
128+ numeric_weights <- runif(n , 0.5 , 2.0 )
129+
130+ # Create equivalent psw object
131+ psw_weights <- propensity :: psw(numeric_weights , estimand = " ate" )
132+
133+ # Test bal_energy equivalence
134+ energy_numeric <- bal_energy(covariates , g , weights = numeric_weights )
135+ energy_psw <- bal_energy(covariates , g , weights = psw_weights )
136+
137+ expect_identical(energy_numeric , energy_psw )
138+ })
139+
140+ test_that(" check_balance works equivalently with numeric and psw weights" , {
141+ skip_if_not_installed(" propensity" )
142+
143+ # Use nhefs_weights dataset
144+ data(nhefs_weights )
145+
146+ # Extract numeric data from existing psw weights for comparison
147+ w_ate_numeric <- vctrs :: vec_data(nhefs_weights $ w_ate )
148+ w_att_numeric <- vctrs :: vec_data(nhefs_weights $ w_att )
149+
150+ # Create new psw objects from the same numeric data
151+ w_ate_psw <- propensity :: psw(w_ate_numeric , estimand = " ate" )
152+ w_att_psw <- propensity :: psw(w_att_numeric , estimand = " att" )
153+
154+ # Add numeric weights to test data
155+ test_data <- nhefs_weights [1 : 100 , ]
156+ test_data $ w_ate_numeric <- w_ate_numeric [1 : 100 ]
157+ test_data $ w_att_numeric <- w_att_numeric [1 : 100 ]
158+ test_data $ w_ate_psw <- w_ate_psw [1 : 100 ]
159+ test_data $ w_att_psw <- w_att_psw [1 : 100 ]
160+
161+ # Test single weight comparison
162+ result_numeric <- check_balance(
163+ test_data ,
164+ c(age , wt71 ),
165+ qsmk ,
166+ .wts = w_ate_numeric ,
167+ .metrics = " smd" ,
168+ include_observed = FALSE
169+ )
170+
171+ result_psw <- check_balance(
172+ test_data ,
173+ c(age , wt71 ),
174+ qsmk ,
175+ .wts = w_ate_psw ,
176+ .metrics = " smd" ,
177+ include_observed = FALSE
178+ )
179+
180+ # Results should be identical except for method names
181+ expect_equal(result_numeric $ estimate , result_psw $ estimate )
182+ expect_equal(result_numeric $ variable , result_psw $ variable )
183+ # Method names will differ (w_ate_numeric vs w_ate_psw) but that's expected
184+ })
185+
186+ test_that(" mixed numeric and psw weights work in same function call" , {
187+ skip_if_not_installed(" propensity" )
188+
189+ # Use nhefs_weights dataset with mixed weight types
190+ data(nhefs_weights )
191+ test_data <- nhefs_weights [1 : 100 , ]
192+
193+ # Create a numeric version of one weight
194+ test_data $ w_ate_numeric <- vctrs :: vec_data(test_data $ w_ate )
195+
196+ # This should work without error - one numeric, one psw
197+ result <- check_balance(
198+ test_data ,
199+ c(age , wt71 ),
200+ qsmk ,
201+ .wts = c(w_ate_numeric , w_att ), # Mix numeric and psw
202+ .metrics = " smd" ,
203+ include_observed = FALSE
204+ )
205+
206+ expect_s3_class(result , " data.frame" )
207+ expect_true(nrow(result ) > 0 )
208+ expect_true(all(is.finite(result $ estimate )))
209+ expect_true(" w_ate_numeric" %in% result $ method )
210+ expect_true(" w_att" %in% result $ method )
211+ })
212+
213+ test_that(" edge cases work identically for numeric and psw weights" , {
214+ skip_if_not_installed(" propensity" )
215+
216+ # Test with extreme weights
217+ set.seed(42 )
218+ n <- 50
219+ x <- rnorm(n )
220+ g <- sample(c(0 , 1 ), n , replace = TRUE )
221+
222+ # Extreme numeric weights
223+ extreme_numeric <- c(rep(0.001 , n / 2 ), rep(100 , n / 2 ))
224+ extreme_psw <- propensity :: psw(extreme_numeric , estimand = " ate" )
225+
226+ # Both should handle extreme weights the same way
227+ smd_numeric <- bal_smd(x , g , weights = extreme_numeric )
228+ smd_psw <- bal_smd(x , g , weights = extreme_psw )
229+ expect_identical(smd_numeric , smd_psw )
230+
231+ # Test with uniform weights (should be close to unweighted)
232+ uniform_numeric <- rep(1 , n )
233+ uniform_psw <- propensity :: psw(uniform_numeric , estimand = " ate" )
234+
235+ smd_unweighted <- bal_smd(x , g )
236+ smd_uniform_numeric <- bal_smd(x , g , weights = uniform_numeric )
237+ smd_uniform_psw <- bal_smd(x , g , weights = uniform_psw )
238+
239+ expect_identical(smd_uniform_numeric , smd_uniform_psw )
240+ expect_equal(smd_unweighted , smd_uniform_numeric , tolerance = 1e-10 )
241+ })
242+
243+ test_that(" NA handling is identical for numeric and psw weights" , {
244+ skip_if_not_installed(" propensity" )
245+
246+ # Create test data with NAs
247+ set.seed(42 )
248+ n <- 100
249+ x <- rnorm(n )
250+ x [1 : 5 ] <- NA # Add some NAs
251+ g <- sample(c(0 , 1 ), n , replace = TRUE )
252+
253+ # Create weights
254+ numeric_weights <- runif(n , 0.5 , 2.0 )
255+ psw_weights <- propensity :: psw(numeric_weights , estimand = " ate" )
256+
257+ # Test na.rm = FALSE (should return NA)
258+ smd_numeric_na <- bal_smd(x , g , weights = numeric_weights , na.rm = FALSE )
259+ smd_psw_na <- bal_smd(x , g , weights = psw_weights , na.rm = FALSE )
260+
261+ expect_identical(smd_numeric_na , smd_psw_na )
262+ expect_true(is.na(smd_numeric_na ))
263+
264+ # Test na.rm = TRUE
265+ smd_numeric_narm <- bal_smd(x , g , weights = numeric_weights , na.rm = TRUE )
266+ smd_psw_narm <- bal_smd(x , g , weights = psw_weights , na.rm = TRUE )
267+
268+ expect_identical(smd_numeric_narm , smd_psw_narm )
269+ expect_true(is.finite(smd_numeric_narm ))
270+ })
271+
272+ test_that(" all helper functions work with both numeric and psw weights" , {
273+ skip_if_not_installed(" propensity" )
274+
275+ # Test weighted_quantile
276+ set.seed(42 )
277+ n <- 100
278+ x <- rnorm(n )
279+
280+ numeric_weights <- runif(n , 0.5 , 2.0 )
281+ psw_weights <- propensity :: psw(numeric_weights , estimand = " ate" )
282+
283+ q_numeric <- weighted_quantile(x , c(0.25 , 0.5 , 0.75 ), numeric_weights )
284+ q_psw <- weighted_quantile(x , c(0.25 , 0.5 , 0.75 ), psw_weights )
285+
286+ expect_identical(q_numeric , q_psw )
287+
288+ # Test ess function
289+ ess_numeric <- ess(numeric_weights )
290+ ess_psw <- ess(psw_weights )
291+
292+ expect_identical(ess_numeric , ess_psw )
293+ })
0 commit comments