Skip to content

Commit 57a4688

Browse files
authored
Merge pull request #706 from Mohit-Ak/fix/generate-data-zero-offset-collapse
Fix generate_data producing zero-variance outliers for some seeds
2 parents 3d0169a + 0dfa1eb commit 57a4688

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

pyod/test/test_data.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,40 @@ def test_data_generate3(self):
8484
assert_allclose(y_train, y_train2)
8585
assert_allclose(y_test, y_test2)
8686

87+
def test_data_generate_outliers_have_spread(self):
88+
# Regression test for GH #141: for certain seeds the internal offset
89+
# was drawn as 0, which collapsed every outlier onto the origin
90+
# (uniform(-0, 0) == 0) and produced zero-variance outliers. Sweep a
91+
# range of seeds (41, 48 and 50 previously triggered the collapse)
92+
# and confirm outliers always keep a non-zero spread.
93+
for seed in range(60):
94+
X, y = generate_data(
95+
n_features=2,
96+
contamination=0.05,
97+
train_only=True,
98+
random_state=seed,
99+
)
100+
outliers = X[y == 1]
101+
assert outliers.var() > 0, \
102+
"outliers collapsed to zero variance for random_state=%d" % seed
103+
104+
def test_data_generate_reproducibility(self):
105+
# Golden values pinned from the pre-fix implementation for seeds whose
106+
# offset was already non-zero. Redrawing only when the offset comes out
107+
# as 0 leaves these untouched, so this guards against a future change
108+
# silently altering long-standing fixed-seed output.
109+
golden = {
110+
0: ([5.059894904, 5.061739412], [-0.263919547, 3.009107520]),
111+
1: ([3.401186423, 3.524852969], [0.181525489, 3.650202520]),
112+
42: ([6.433658544, 5.509168303], [-3.206743915, -4.912722786]),
113+
}
114+
for seed, (first, last) in golden.items():
115+
X, _ = generate_data(n_train=10, n_test=5, n_features=2,
116+
contamination=0.2, train_only=True,
117+
random_state=seed)
118+
assert_allclose(X[0], first, rtol=0, atol=1e-9)
119+
assert_allclose(X[-1], last, rtol=0, atol=1e-9)
120+
87121
def test_data_generate_cluster(self):
88122
X_train, X_test, y_train, y_test = \
89123
generate_data_clusters(n_train=self.n_train,

pyod/utils/data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,15 @@ def generate_data(n_train=1000, n_test=500, n_features=2, contamination=0.1,
176176

177177
# initialize a random state and seeds for the instance
178178
random_state = check_random_state(random_state)
179+
# randint(low=offset) samples from [0, offset), so it can return 0. A zero
180+
# offset collapses every outlier onto the origin (uniform(-0, 0) == 0) and
181+
# yields zero-variance outliers for those seeds (see GH #141). Only redraw
182+
# when that happens, so seeds that already produced a valid offset keep
183+
# their existing output.
179184
offset_ = random_state.randint(low=offset)
185+
if offset_ == 0:
186+
offset_ = (1 if offset == 1
187+
else random_state.randint(low=1, high=offset))
180188
coef_ = random_state.random_sample() + 0.001 # in case of underflow
181189

182190
if isinstance(contamination, (float, int)):

0 commit comments

Comments
 (0)