Skip to content

Commit 3de7f1e

Browse files
Merge pull request #46 from r-causal/numerics
add extra tests
2 parents 41ec02b + 5e0f6bb commit 3de7f1e

File tree

2 files changed

+384
-0
lines changed

2 files changed

+384
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Tests for extract_weight_data() utility function
2+
3+
test_that("extract_weight_data handles NULL input", {
4+
result <- extract_weight_data(NULL)
5+
expect_null(result)
6+
})
7+
8+
test_that("extract_weight_data passes through numeric vectors unchanged", {
9+
# Test simple numeric vector
10+
numeric_weights <- c(1, 2, 3, 4, 5)
11+
result <- extract_weight_data(numeric_weights)
12+
expect_identical(result, numeric_weights)
13+
expect_type(result, "double")
14+
15+
# Test with different numeric values
16+
uniform_weights <- runif(10, 0.5, 1.5)
17+
result2 <- extract_weight_data(uniform_weights)
18+
expect_identical(result2, uniform_weights)
19+
20+
# Test with extreme values
21+
extreme_weights <- c(0.001, 100, 0.5, 2.5)
22+
result3 <- extract_weight_data(extreme_weights)
23+
expect_identical(result3, extreme_weights)
24+
})
25+
26+
test_that("extract_weight_data extracts data from psw objects", {
27+
skip_if_not_installed("propensity")
28+
29+
# Create psw object from numeric data
30+
numeric_data <- c(1.2, 0.8, 1.5, 0.9, 1.1)
31+
psw_obj <- propensity::psw(numeric_data, estimand = "ate")
32+
33+
# Extract should return the underlying numeric data
34+
result <- extract_weight_data(psw_obj)
35+
expect_equal(result, numeric_data)
36+
expect_type(result, "double")
37+
38+
# Should not have psw class anymore
39+
expect_false(inherits(result, "psw"))
40+
})
41+
42+
test_that("extract_weight_data preserves values from different psw estimands", {
43+
skip_if_not_installed("propensity")
44+
45+
numeric_data <- c(2.1, 1.3, 0.7, 1.8, 1.0)
46+
47+
# Test different estimands produce same numeric extraction
48+
psw_ate <- propensity::psw(numeric_data, estimand = "ate")
49+
psw_att <- propensity::psw(numeric_data, estimand = "att")
50+
psw_ato <- propensity::psw(numeric_data, estimand = "ato")
51+
52+
result_ate <- extract_weight_data(psw_ate)
53+
result_att <- extract_weight_data(psw_att)
54+
result_ato <- extract_weight_data(psw_ato)
55+
56+
expect_equal(result_ate, numeric_data)
57+
expect_equal(result_att, numeric_data)
58+
expect_equal(result_ato, numeric_data)
59+
60+
# All should be identical
61+
expect_identical(result_ate, result_att)
62+
expect_identical(result_ate, result_ato)
63+
})
64+
65+
test_that("extract_weight_data works with integer input", {
66+
# Test with integer vector (passes through unchanged)
67+
int_weights <- c(1L, 2L, 3L, 4L)
68+
result <- extract_weight_data(int_weights)
69+
expect_equal(result, c(1L, 2L, 3L, 4L))
70+
expect_type(result, "integer")
71+
})
72+
73+
test_that("extract_weight_data preserves NA values", {
74+
# Test with NA values in numeric vector
75+
numeric_with_na <- c(1.2, NA, 0.8, NA, 1.5)
76+
result <- extract_weight_data(numeric_with_na)
77+
expect_identical(result, numeric_with_na)
78+
expect_equal(sum(is.na(result)), 2)
79+
})
80+
81+
test_that("extract_weight_data works with edge case values", {
82+
# Test with zeros, very small, and very large values
83+
edge_weights <- c(0, 1e-10, 1e10, Inf, -Inf)
84+
result <- extract_weight_data(edge_weights)
85+
expect_identical(result, edge_weights)
86+
87+
# Test with single value
88+
single_weight <- 1.5
89+
result_single <- extract_weight_data(single_weight)
90+
expect_identical(result_single, single_weight)
91+
})
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
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

Comments
 (0)