1515"""
1616
1717import numpy as np
18+
1819from pyod .models .hbos import HBOS
1920
2021
@@ -26,55 +27,58 @@ def test_hbos_auto_bins_with_out_of_range_values():
2627 exceeded the maximum training value for any feature.
2728 """
2829 print ("Testing HBOS with n_bins='auto' and out-of-range test values..." )
29-
30+
3031 # Create training data with limited range
3132 np .random .seed (42 )
3233 n_train = 100
3334 n_features = 5
34-
35+
3536 # Training data ranges roughly from 0 to 10
3637 X_train = np .random .randn (n_train , n_features ) * 2 + 5
3738 X_train = np .clip (X_train , 0 , 10 )
38-
39+
3940 print (f"Training data shape: { X_train .shape } " )
4041 print (f"Training data range per feature:" )
4142 for i in range (n_features ):
42- print (f" Feature { i } : [{ X_train [:, i ].min ():.2f} , { X_train [:, i ].max ():.2f} ]" )
43-
43+ print (
44+ f" Feature { i } : [{ X_train [:, i ].min ():.2f} , { X_train [:, i ].max ():.2f} ]" )
45+
4446 # Initialize and fit HBOS with auto bins
4547 model = HBOS (n_bins = 'auto' , contamination = 0.1 )
4648 model .fit (X_train )
47-
49+
4850 print (f"\n Number of bins per feature after training:" )
4951 for i in range (n_features ):
5052 print (f" Feature { i } : { model .hist_ [i ].shape [0 ]} bins" )
51-
53+
5254 # Create test data with some values OUTSIDE the training range
5355 # This is the critical part that triggers the bug
5456 X_test = np .random .randn (10 , n_features ) * 3 + 5
5557 # Force some values to be higher than training max
56- X_test [0 , 2 ] = X_train [:, 2 ].max () + 5 # Way above training max for feature 2
58+ X_test [0 , 2 ] = X_train [:,
59+ 2 ].max () + 5 # Way above training max for feature 2
5760 X_test [1 , 3 ] = X_train [:, 3 ].max () + 3 # Above training max for feature 3
5861 X_test [2 , 0 ] = X_train [:, 0 ].min () - 2 # Below training min for feature 0
59-
62+
6063 print (f"\n Test data shape: { X_test .shape } " )
6164 print (f"Test data range per feature:" )
6265 for i in range (n_features ):
63- print (f" Feature { i } : [{ X_test [:, i ].min ():.2f} , { X_test [:, i ].max ():.2f} ]" )
64-
66+ print (
67+ f" Feature { i } : [{ X_test [:, i ].min ():.2f} , { X_test [:, i ].max ():.2f} ]" )
68+
6569 # This would fail with IndexError before the fix
6670 try :
6771 predictions = model .predict (X_test )
6872 scores = model .decision_function (X_test )
69-
73+
7074 print (f"\n ✓ SUCCESS: Predictions completed without error!" )
7175 print (f" Predictions shape: { predictions .shape } " )
7276 print (f" Scores shape: { scores .shape } " )
7377 print (f" Number of outliers detected: { predictions .sum ()} " )
7478 print (f" Outlier indices: { np .where (predictions == 1 )[0 ].tolist ()} " )
75-
79+
7680 return True
77-
81+
7882 except IndexError as e :
7983 print (f"\n ✗ FAILED: IndexError occurred!" )
8084 print (f" Error: { e } " )
@@ -86,26 +90,26 @@ def test_hbos_auto_bins_edge_cases():
8690 """
8791 Additional edge case tests for HBOS with auto bins.
8892 """
89- print ("\n " + "=" * 70 )
93+ print ("\n " + "=" * 70 )
9094 print ("Testing edge cases..." )
91- print ("=" * 70 )
92-
95+ print ("=" * 70 )
96+
9397 np .random .seed (123 )
94-
98+
9599 # Test 1: All test values above training range
96100 print ("\n Test 1: All test values above training range" )
97101 X_train = np .random .randn (50 , 3 ) * 1 + 5
98102 model = HBOS (n_bins = 'auto' , contamination = 0.1 )
99103 model .fit (X_train )
100-
104+
101105 X_test = X_train .max () + np .random .rand (5 , 3 ) * 3
102106 try :
103107 predictions = model .predict (X_test )
104108 print (f" ✓ Success: { predictions .sum ()} outliers detected" )
105109 except IndexError as e :
106110 print (f" ✗ Failed with IndexError: { e } " )
107111 return False
108-
112+
109113 # Test 2: All test values below training range
110114 print ("\n Test 2: All test values below training range" )
111115 X_test = X_train .min () - np .random .rand (5 , 3 ) * 3
@@ -115,7 +119,7 @@ def test_hbos_auto_bins_edge_cases():
115119 except IndexError as e :
116120 print (f" ✗ Failed with IndexError: { e } " )
117121 return False
118-
122+
119123 # Test 3: Mixed in-range and out-of-range values
120124 print ("\n Test 3: Mixed in-range and out-of-range values" )
121125 X_test = np .vstack ([
@@ -131,75 +135,77 @@ def test_hbos_auto_bins_edge_cases():
131135 except IndexError as e :
132136 print (f" ✗ Failed with IndexError: { e } " )
133137 return False
134-
138+
135139 return True
136140
137141
138142def test_hbos_static_bins_comparison ():
139143 """
140144 Compare behavior between auto and static bins to ensure consistency.
141145 """
142- print ("\n " + "=" * 70 )
146+ print ("\n " + "=" * 70 )
143147 print ("Comparing auto bins vs static bins behavior..." )
144- print ("=" * 70 )
145-
148+ print ("=" * 70 )
149+
146150 np .random .seed (456 )
147151 X_train = np .random .randn (100 , 4 ) * 2 + 5
148152 X_test = np .vstack ([
149153 X_train [:5 ],
150154 X_train .max (axis = 0 ) + 2 , # One sample above all training ranges
151155 ])
152-
156+
153157 # Test with auto bins
154158 model_auto = HBOS (n_bins = 'auto' , contamination = 0.1 )
155159 model_auto .fit (X_train )
156160 try :
157161 pred_auto = model_auto .predict (X_test )
158162 score_auto = model_auto .decision_function (X_test )
159163 print (f" Auto bins: ✓ Success" )
160- print (f" Outliers: { pred_auto .sum ()} , Score range: [{ score_auto .min ():.4f} , { score_auto .max ():.4f} ]" )
164+ print (
165+ f" Outliers: { pred_auto .sum ()} , Score range: [{ score_auto .min ():.4f} , { score_auto .max ():.4f} ]" )
161166 except Exception as e :
162167 print (f" Auto bins: ✗ Failed - { e } " )
163168 return False
164-
169+
165170 # Test with static bins
166171 model_static = HBOS (n_bins = 10 , contamination = 0.1 )
167172 model_static .fit (X_train )
168173 try :
169174 pred_static = model_static .predict (X_test )
170175 score_static = model_static .decision_function (X_test )
171176 print (f" Static bins: ✓ Success" )
172- print (f" Outliers: { pred_static .sum ()} , Score range: [{ score_static .min ():.4f} , { score_static .max ():.4f} ]" )
177+ print (
178+ f" Outliers: { pred_static .sum ()} , Score range: [{ score_static .min ():.4f} , { score_static .max ():.4f} ]" )
173179 except Exception as e :
174180 print (f" Static bins: ✗ Failed - { e } " )
175181 return False
176-
182+
177183 print (f"\n Both methods handled out-of-range values correctly!" )
178184 return True
179185
180186
181187if __name__ == "__main__" :
182- print ("=" * 70 )
188+ print ("=" * 70 )
183189 print ("HBOS IndexError Fix Test Suite" )
184190 print ("Issue: https://github.com/yzhao062/pyod/issues/476" )
185- print ("=" * 70 )
186-
191+ print ("=" * 70 )
192+
187193 all_passed = True
188-
194+
189195 # Run main test
190196 all_passed &= test_hbos_auto_bins_with_out_of_range_values ()
191-
197+
192198 # Run edge case tests
193199 all_passed &= test_hbos_auto_bins_edge_cases ()
194-
200+
195201 # Run comparison test
196202 all_passed &= test_hbos_static_bins_comparison ()
197-
198- print ("\n " + "=" * 70 )
203+
204+ print ("\n " + "=" * 70 )
199205 if all_passed :
200206 print ("✓ ALL TESTS PASSED!" )
201207 print ("The fix correctly handles out-of-range test values." )
202208 else :
203209 print ("✗ SOME TESTS FAILED!" )
204210 print ("The bug may not be fully fixed." )
205- print ("=" * 70 )
211+ print ("=" * 70 )
0 commit comments