Skip to content

Commit 7d642a4

Browse files
authored
[Tests] fix unstable tests for desparsified Lasso and knockoffs (#474)
* fix unstable tests * add tolerance to test * use confidence as threshold * remove seed + check fdp and power * same for group desparsified * test interval coverage good 70% of the time * update docstring * typo
1 parent ae16933 commit 7d642a4

File tree

2 files changed

+95
-61
lines changed

2 files changed

+95
-61
lines changed

test/test_desparsified_lasso.py

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,23 @@
1616

1717

1818
def test_desparsified_lasso():
19-
"""Testing the procedure on a simulation with no structure and
20-
a support of size 1. Computing 99% confidence bounds and checking
21-
that they contains the true parameter vector."""
22-
23-
n_samples, n_features = 52, 50
24-
support_size = 1
25-
signal_noise_ratio = 50
19+
"""
20+
Test desparsified lasso on a simple simulation with no structure and
21+
a support of size 5.
22+
- Test that the confidence intervals contain the true beta 70% of the time. This
23+
threshold is arbitrary.
24+
- Test that the empirical false discovery proportion is below the target FDR
25+
Although this is not guaranteed (control is only in expectation), the scenario
26+
is simple enough for the test to pass
27+
- Test that the true discovery proportion is above 80%, this threshold is arbitrary
28+
"""
29+
30+
n_samples, n_features = 400, 40
31+
support_size = 5
32+
signal_noise_ratio = 32
2633
rho = 0.0
34+
confidence = 0.9
35+
alpha = 1 - confidence
2736

2837
X, y, beta, noise = multivariate_simulation(
2938
n_samples=n_samples,
@@ -32,78 +41,93 @@ def test_desparsified_lasso():
3241
signal_noise_ratio=signal_noise_ratio,
3342
rho=rho,
3443
shuffle=False,
35-
seed=10,
3644
)
37-
expected_pval_corr = np.ones_like(beta) * 0.5
38-
expected_pval_corr[beta != 0] = 0.0
3945

4046
beta_hat, sigma_hat, precision_diag = desparsified_lasso(X, y)
41-
pval, pval_corr, one_minus_pval, one_minus_pval_corr, cb_min, cb_max = (
42-
desparsified_lasso_pvalue(
43-
X.shape[0], beta_hat, sigma_hat, precision_diag, confidence=0.99
44-
)
47+
_, pval_corr, _, _, cb_min, cb_max = desparsified_lasso_pvalue(
48+
X.shape[0], beta_hat, sigma_hat, precision_diag, confidence=confidence
4549
)
46-
assert_almost_equal(beta_hat, beta, decimal=1)
47-
assert_equal(cb_min < beta, True)
48-
assert_equal(cb_max > beta, True)
49-
assert_almost_equal(pval_corr, expected_pval_corr, decimal=1)
50+
# Check that beta is within the confidence intervals
51+
correct_interval = np.sum((beta >= cb_min) & (beta <= cb_max))
52+
assert correct_interval >= int(0.7 * n_features)
53+
54+
# Check p-values for important and non-important features
55+
important = beta != 0
56+
non_important = beta == 0
57+
tp = np.sum(pval_corr[important] < alpha)
58+
fp = np.sum(pval_corr[non_important] < alpha)
59+
assert fp / np.sum(non_important) <= alpha
60+
assert tp / np.sum(important) >= 0.8
5061

5162
beta_hat, sigma_hat, precision_diag = desparsified_lasso(X, y, dof_ajdustement=True)
52-
pval, pval_corr, one_minus_pval, one_minus_pval_corr, cb_min, cb_max = (
53-
desparsified_lasso_pvalue(
54-
X.shape[0], beta_hat, sigma_hat, precision_diag, confidence=0.99
55-
)
63+
_, pval_corr, _, _, cb_min, cb_max = desparsified_lasso_pvalue(
64+
X.shape[0], beta_hat, sigma_hat, precision_diag, confidence=confidence
5665
)
57-
assert_almost_equal(beta_hat, beta, decimal=1)
58-
assert_equal(cb_min < beta, True)
59-
assert_equal(cb_max > beta, True)
60-
assert_almost_equal(pval_corr, expected_pval_corr, decimal=1)
66+
# Check that beta is within the confidence intervals
67+
correct_interval = np.sum((beta >= cb_min) & (beta <= cb_max))
68+
assert correct_interval >= int(0.7 * n_features)
6169

70+
# Check p-values for important and non-important features
71+
tp = np.sum(pval_corr[important] < alpha)
72+
fp = np.sum(pval_corr[non_important] < alpha)
73+
assert fp / np.sum(non_important) <= alpha
74+
assert tp / np.sum(important) >= 0.8
6275

63-
def test_desparsified_group_lasso():
64-
"""Testing the procedure on a simulation with no structure and
65-
a support of size 2. Computing one-sided p-values, we want
66-
low p-values for the features of the support and p-values
67-
close to 0.5 for the others."""
6876

69-
n_samples = 50
70-
n_features = 100
77+
def test_desparsified_group_lasso():
78+
"""
79+
Testing the procedure on a simulation with no structure and a support of size 2.
80+
- Test that the empirical false discovery proportion is below the target FDR
81+
Although this is not guaranteed (control is only in expectation), the scenario
82+
is simple enough for the test to pass.
83+
- Test that the true discovery proportion is above 80%, this threshold is arbitrary
84+
"""
85+
86+
n_samples = 400
87+
n_features = 40
7188
n_target = 10
72-
support_size = 2
73-
signal_noise_ratio = 5000
89+
support_size = 5
90+
signal_noise_ratio = 32
7491
rho_serial = 0.9
92+
alpha = 0.1
93+
7594
corr = toeplitz(np.geomspace(1, rho_serial ** (n_target - 1), n_target))
7695

77-
X, Y, beta, noise = multivariate_simulation(
96+
X, Y, beta, _ = multivariate_simulation(
7897
n_samples=n_samples,
7998
n_features=n_features,
8099
n_targets=n_target,
81100
support_size=support_size,
82101
rho_serial=rho_serial,
83102
signal_noise_ratio=signal_noise_ratio,
84-
seed=10,
85103
)
86104

87105
beta_hat, theta_hat, precision_diag = desparsified_lasso(
88106
X, Y, multioutput=True, covariance=corr
89107
)
90-
pval, pval_corr, one_minus_pval, one_minus_pval_corr = (
91-
desparsified_group_lasso_pvalue(beta_hat, theta_hat, precision_diag)
108+
_, pval_corr, _, _ = desparsified_group_lasso_pvalue(
109+
beta_hat, theta_hat, precision_diag
92110
)
93111

94-
expected_pval_corr = np.ones_like(beta[:, 0]) * 0.5
95-
expected_pval_corr[beta[:, 0] != 0] = 0.0
112+
important = beta[:, 0] != 0
113+
non_important = beta[:, 0] == 0
96114

97115
assert_almost_equal(beta_hat, beta, decimal=1)
98-
assert_almost_equal(pval_corr, expected_pval_corr, decimal=1)
116+
tp = np.sum(pval_corr[important] < alpha)
117+
fp = np.sum(pval_corr[non_important] < alpha)
118+
assert fp / np.sum(non_important) <= alpha
119+
assert tp / np.sum(important) >= 0.8
99120

100121
beta_hat, theta_hat, precision_diag = desparsified_lasso(X, Y, multioutput=True)
101-
pval, pval_corr, one_minus_pval, one_minus_pval_corr = (
102-
desparsified_group_lasso_pvalue(beta_hat, theta_hat, precision_diag, test="F")
122+
_, pval_corr, _, _ = desparsified_group_lasso_pvalue(
123+
beta_hat, theta_hat, precision_diag
103124
)
104125

105126
assert_almost_equal(beta_hat, beta, decimal=1)
106-
assert_almost_equal(pval_corr, expected_pval_corr, decimal=1)
127+
tp = np.sum(pval_corr[important] < alpha)
128+
fp = np.sum(pval_corr[non_important] < alpha)
129+
assert fp / np.sum(non_important) <= alpha
130+
assert tp / np.sum(important) >= 0.8
107131

108132
# Testing error is raised when the covariance matrix has wrong shape
109133
bad_cov = np.delete(corr, 0, axis=1)

test/test_knockoff.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,38 +205,48 @@ def test_model_x_knockoff_exception():
205205
def test_estimate_distribution():
206206
"""
207207
test different estimation of the covariance
208-
TODO: This test is unstable, testing for perfect recovery of the support with
209-
n=100 and p=50 is too ambitious. It currently passes thanks to a lucky draw.
208+
- Test that the empirical false discovery proportion is below the target FDR
209+
Although this is not guaranteed (control is only in expectation), the scenario
210+
is simple enough for the test to pass.
211+
- Test that the true discovery proportion is above 80%, this threshold is arbitrary
210212
"""
211-
seed = 3
212-
fdr = 0.1
213-
n = 100
214-
p = 50
215-
X, y, beta, noise = multivariate_simulation(n, p, seed=seed)
213+
fdr = 0.2
214+
n = 400
215+
p = 100
216+
signal_noise_ratio = 32
217+
support_size = 5
218+
219+
X, y, beta, noise = multivariate_simulation(
220+
n, p, support_size=support_size, signal_noise_ratio=signal_noise_ratio
221+
)
216222
non_zero = np.where(beta)[0]
217-
selected, test_scores, threshold, X_tildes = model_x_knockoff(
223+
selected, _, _, _ = model_x_knockoff(
218224
X,
219225
y,
220226
cov_estimator=LedoitWolf(assume_centered=True),
221227
n_bootstraps=1,
222-
random_state=seed,
223228
fdr=fdr,
224229
)
225-
for i in selected:
226-
assert np.any(i == non_zero)
227-
selected, test_scores, threshold, X_tildes = model_x_knockoff(
230+
tp = len(set(selected) & set(non_zero))
231+
fp = len(set(selected) - set(non_zero))
232+
assert fp / (p - len(non_zero)) <= fdr
233+
assert tp / len(non_zero) >= 0.8
234+
235+
selected, _, _, _ = model_x_knockoff(
228236
X,
229237
y,
230238
cov_estimator=GraphicalLassoCV(
231239
alphas=[1e-3, 1e-2, 1e-1, 1],
232-
cv=KFold(n_splits=5, shuffle=True, random_state=seed + 2),
240+
cv=KFold(n_splits=5, shuffle=True),
233241
),
234242
n_bootstraps=1,
235-
random_state=seed,
236243
fdr=fdr,
237244
)
238-
for i in selected:
239-
assert np.any(i == non_zero)
245+
246+
tp = len(set(selected) & set(non_zero))
247+
fp = len(set(selected) - set(non_zero))
248+
assert fp / (p - len(non_zero)) <= fdr
249+
assert tp / len(non_zero) >= 0.8
240250

241251

242252
def test_gaussian_knockoff_equi():

0 commit comments

Comments
 (0)