Skip to content

v3.6.5

Latest

Choose a tag to compare

@yzhao062 yzhao062 released this 17 Aug 04:13
b9c0388

Three contributor bug fixes, plus maintainer follow-ups from a two-panel review and two rounds of /implement-review.

Fixes

generate_data produced anomaly-free benchmarks for about one seed in ten (#706 by @Mohit-Ak, closes #141). The internal offset was drawn with randint(low=offset), which returns 0 roughly one time in ten for the default offset=10. Every labelled outlier then landed exactly on the origin. Over seeds 0 to 500, 46 of 501 integer random_state values produced an outlier block with exactly zero per-feature variance. KNN scored roc_auc of exactly 0.0 on seeds 41, 48, 50 and 85. The offset is now redrawn only when it comes out as 0.

Data from any seed that was not degenerate is unchanged. That was verified by an RNG call-sequence differential over 11,280 offset and seed combinations. It compared both the generated arrays and the final RandomState, and found zero regressions. The comparison is between the parent and patched code on one machine; generated values have never agreed bit-for-bit across platforms, since randn goes through a log that libm implementations round differently in the last ulp. Data from the 46 degenerate seeds necessarily changes, since its previous form was unusable.

generate_data now also accepts a float offset in the open interval (1, 2), which previously raised ValueError. An offset below 1 still raises, now as an explicit ValueError. The inlier spread does not scale with offset, so a smaller box would sit inside the inlier cloud and leave the labels anti-correlated with outlierness.

CBLOF.n_jobs was accepted but never stored (#719 by @bishtashish708, closes #713). get_params() reported None and sklearn.base.clone() dropped the value. It is now stored unmodified, restoring the estimator contract.

fit() now emits a FutureWarning when a non-default n_jobs is supplied, announcing removal in v4.0.0. scikit-learn deprecated KMeans.n_jobs in 0.23, where it already had no effect after the move to OpenMP, and removed it in 1.0. The value is no longer forwarded to KMeans under any scikit-learn version. Use OMP_NUM_THREADS, threadpoolctl, or a configured custom clustering_estimator instead. Detector output is unaffected.

ROD returned nan with a RuntimeWarning when a sample sat on the geometric median (#722 by @bishtashish708, closes #523). Such a sample has a zero-length displacement vector, so its rotation angle is undefined. The undefined angle is now excluded from the MinMaxScaler fit and the row is assigned its limiting cost of 0 directly.

A second case is now handled explicitly. When the geometric median falls on the coordinate origin, no rotation angle is defined for any row. That subspace emits a RuntimeWarning, and its costs are driven only by the distance from the geometric median.

Behavior changes

  • Generated data changes for the 46 degenerate random_state values listed above. All other seeds are bit-identical.
  • ROD decision_scores_ and labels_ change for any input containing a point at the geometric median. Scores on data with no such point are bit-identical.
  • CBLOF emits a new FutureWarning for a non-default n_jobs. It repeats on each fit call rather than once per process, because scikit-learn's internal use of catch_warnings invalidates the caller's warning registry.
  • generate_data no longer accepts offsets that were never scalars but which NumPy used to coerce, such as '2', b'2', and one-element sequences or arrays. Those forms were outside the documented contract in every released version.

Test infrastructure

pyod/test/conftest.py guarded its torch import with except ImportError. A broken rather than absent install raises OSError instead, which aborted collection of the whole suite rather than skipping the torch-dependent modules. The guard now distinguishes the two: only ModuleNotFoundError for torch counts as absence. A broken install skips those 25 modules with a warning locally, and re-raises under CI. A silent skip there would let a job that promised full torch coverage pass while running none of it.

Still open

DevNet has no constructor docstring and exposes unused arguments (#714). pyod info infers Claude Code from a directory that pyod install skill itself creates (#715). A separate question about ROD's rotation reference direction is tracked in #726.

Full changelog: v3.6.4...v3.6.5