Add preprocessing.GapEncoder - #3
Open
Fazel94 wants to merge 21 commits into
Open
Conversation
A `metrics.base.Metrics` collection (built via `metric_a + metric_b`) accepts a sample weight `w` in `update`, but forwarded to its child metrics without it, so weights were silently treated as 1. The sibling `revert` already passed `w`, which made the two asymmetric: a weighted metric inside a collection reported the wrong value, and an `update(w)` / `revert(w)` pair no longer cancelled (it could raise "Cannot go below 0" from the underlying running statistic). Forward `w` in the three `update` call sites, mirroring `revert`. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps [mistune](https://github.com/lepture/mistune) from 3.2.1 to 3.3.0. - [Release notes](https://github.com/lepture/mistune/releases) - [Changelog](https://github.com/lepture/mistune/blob/main/docs/changes.rst) - [Commits](lepture/mistune@v3.2.1...v3.3.0) --- updated-dependencies: - dependency-name: mistune dependency-version: 3.3.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [soupsieve](https://github.com/facelessuser/soupsieve) from 2.8.3 to 2.8.4. - [Release notes](https://github.com/facelessuser/soupsieve/releases) - [Commits](facelessuser/soupsieve@2.8.3...2.8.4) --- updated-dependencies: - dependency-name: soupsieve dependency-version: 2.8.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1940) - Updated `Cargo.lock` to include new dependencies: `anyhow`, `approx`, `bitflags`, `cc`, `cfg_aliases`, `colored`, `find-msvc-tools`, `getrandom`, `glob`, `nix`, `r-efi`, and `statrs`. - Replaced `criterion` with `codspeed-criterion-compat` in `Cargo.toml` for benchmarking. - Added CodSpeed benchmarking setup in GitHub Actions with workflows for both Python and Rust benchmarks. - Introduced new benchmark tests for linear models and statistics in `benchmarks/codspeed`. - Updated documentation to include instructions for running benchmarks locally and integrating with CodSpeed.
* Update dependencies and add CodSpeed benchmarking support - Updated `Cargo.lock` to include new dependencies: `anyhow`, `approx`, `bitflags`, `cc`, `cfg_aliases`, `colored`, `find-msvc-tools`, `getrandom`, `glob`, `nix`, `r-efi`, and `statrs`. - Replaced `criterion` with `codspeed-criterion-compat` in `Cargo.toml` for benchmarking. - Added CodSpeed benchmarking setup in GitHub Actions with workflows for both Python and Rust benchmarks. - Introduced new benchmark tests for linear models and statistics in `benchmarks/codspeed`. - Updated documentation to include instructions for running benchmarks locally and integrating with CodSpeed. * Add benchmarks for various statistical and machine learning functions - Introduced new benchmark files for `adwin_bench`, `rolling_metrics_bench`, `sorted_window_bench`, `covariance_bench`, and `expected_mutual_info_bench` in the Rust benchmarks directory. - Updated `Cargo.toml` to include new benchmark configurations. - Added Python benchmark tests for various algorithms in the CodSpeed benchmarking framework, including tests for active learning, anomaly detection, and ensemble methods. - Configured per-file ignores for specific benchmarks in `pyproject.toml` to streamline linting. - Enhanced the `workloads.py` file to support new benchmark scenarios with deterministic data streams. * Update benchmarks and documentation for Python and Rust - Added new benchmark tests for various algorithms in the CodSpeed framework, including active learning, anomaly detection, and ensemble methods. - Introduced Rust benchmarks for adaptive windowing, covariance, expected mutual information, and rolling metrics. - Updated `Cargo.toml` to include new benchmark paths and configurations. - Revised `CONTRIBUTING.md` to reflect changes in benchmark directory structure. - Adjusted `pyproject.toml` to ignore specific benchmark files for linting. - Enhanced `benchmarks/README.md` with instructions for running benchmarks locally and integrating with CodSpeed. - Added new Python workload functions to support deterministic data streams for benchmarks. * Enhance CodSpeed benchmarking with heavy shard support - Added a new marker for heavy benchmarks in `pyproject.toml` to categorize tests for the heavy CI shard. - Updated GitHub Actions workflow to implement a matrix strategy for Python benchmarks, splitting them into "heavy" and "rest" shards based on execution time. - Revised benchmark tests to utilize the new `heavy` marker, ensuring that the most resource-intensive benchmarks are appropriately categorized. - Improved documentation in `benchmarks/README.md` to explain the new sharding strategy and its impact on CI performance. --------- Co-authored-by: Max Halford <maxhalford25@gmail.com>
…ml#1923) * feat(covariance): add online EWA and shrinkage covariance/precision estimators Add a family of online covariance estimators reimplemented from the `precise` package, following River conventions (dict-native, Narwhals mini-batches) and excluding any lazy invert-on-read methods: - covariance.EwaCovariance: exponentially weighted covariance (RiskMetrics style); diagonal matches stats.EWVar, off-diagonals match stats.EWCov. - covariance.LedoitWolfCovariance / OASCovariance: data-driven shrinkage towards a scaled identity for high-dimensional / few-sample regimes. - covariance.ShrunkCovariance: fixed-intensity shrinkage with a constant-correlation (finance) or identity target. - covariance.EwaPrecision: exponentially weighted precision via a forgetting-factor Sherman-Morrison update; genuinely online, never inverts explicitly. Recency-weighted counterpart of EmpiricalPrecision. - stats.EWCov: exponentially weighted covariance primitive (bivariate counterpart of stats.EWVar). - datasets.SP500Stocks: daily returns of ten large-cap S&P 500 stocks (2013-2018), used in the docstring examples. Internals are array-backed with a feature->index map (like EmpiricalPrecision) behind a dict-native interface. Also guards SymmetricMatrix.__repr__ against empty (unfitted) matrices. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(covariance): use independent references for covariance tests Replace the circular EWCov test (which re-implemented the estimator's own E[xy]-E[x]E[y] recursion) with a comparison against pandas' ewm().cov(), and add a test comparing EmpiricalCovariance against sklearn's batch estimator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(covariance): simplify new covariance tests Drop pytest.importorskip in favour of a plain inline sklearn import (matching the existing sklearn test), extract the _dense value helper to module level, and trim the EWCov comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(covariance): make EmpiricalCovariance/EmpiricalPrecision update_many narwhals-native Migrate the empirical estimators' `update_many` off the hard-coded pandas path (`.values`/`.columns`) to the `utils.dataframe` narwhals boundary helpers, matching the new EWA/shrinkage estimators and the rest of the online-ml#1919 migration. Any narwhals-supported eager dataframe (pandas, polars, pyarrow, ...) now flows through; the pandas path is byte-for-byte unchanged. Adds multi-backend tests via the `frame_backend` fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tector (online-ml#1929) Replace the incremental-LOF bookkeeping (nine threaded dicts + free functions) with a small class that delegates storage and neighbor search to a `river.neighbors` engine (LazySearch by default, SWINN for approximate search). - `learn_one` is now O(1): it appends to a bounded sliding window, so memory no longer grows with the stream (was super-linear before). - `score_one` computes the LOF against the current window on demand and no longer mutates the model. A point is never its own neighbor, so an unseen point reproduces scikit-learn's `LocalOutlierFactor(novelty=True)` and a stored point reproduces the in-sample `negative_outlier_factor_` (matched to ~1e-15). Per-score memoization keeps it ~6x faster. - `learn_many` accepts any narwhals-supported eager dataframe (pandas, polars, pyarrow, ...) instead of pandas only. Addresses the anomaly/lof.py item of online-ml#1919. Along the way, fix a pre-existing bug in the Rust Euclidean fast path of `neighbors.LazySearch`: its search heap was keyed on the negated distance, so it returned the *farthest* k candidates instead of the nearest. This affected KNNClassifier/KNNRegressor/LOF on a LazySearch engine with the default distance. Add a regression test (test_lazy.py). Switch the shared `check_roc_auc` anomaly check to score-then-learn so it no longer leaks the label by scoring an already-learned point. KNNRegressor now skips check_shuffle_features_no_impact (its weighted average is sensitive to float summation order under feature reordering, like the forest models); LOF joins the automated estimator checks. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* update pyproject - mypy should take rust bindings into account * add _river_rust.stats stubs * add _river_rust.draft stubs * add _river_rust.tree stubs * add _river_rust.vectordict stubs * add _river_rust.feature_hashing stubs * add __init__.pyi for package detection * unit test for keeping stubs and runtime in sync - v0, only stats for now * solve remaining mypy errors * format * add missing instance check * test stub-runtime sync for all stubs * revert changes in Optimizer + add type: ignore comment * format$
…-ml#1954) `proba.Beta.n_samples` computed `self._alpha - self.alpha + self._beta - self.beta`. `_alpha`/`_beta` are the frozen initial parameters while `alpha`/`beta` grow by one on each `update()`, so the expression equals `-(#successes + #failures)` — the negative of the number of observed samples. A fresh `Beta().update(True)` five times reports `n_samples == -5`. `n_samples` is the abstract `proba.base.Distribution` contract ("The number of observed samples") and every sibling upholds it as a non-negative count: `Gaussian.n_samples` returns `.mean.n`, `Multinomial.n_samples` returns `sum(counts)`. Swap the two operands so Beta counts its observed updates too (priors excluded, matching the initial value of 0). Verified: no in-tree consumer reads `Beta.n_samples`, so this only corrects the public property's sign. Added an `n_samples` line to the existing Beta doctest (it fails with `-300` before, passes with `300` after). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nline-ml#1953) BalancedAccuracy.get divided the recall sum by len(self.cm.classes), which is the union of true and predicted labels. A class that only ever appears in the predictions has no support: its recall is undefined (0/0 -> ZeroDivisionError, skipped in the sum) but it was still counted in the denominator, so the score was deflated. Count only the classes whose recall is defined, matching sklearn.metrics.balanced_accuracy_score, which excludes such classes. BalancedAccuracy was never registered in the scikit-learn equivalence test suite, which is why this went unnoticed; it is now added there alongside a targeted regression test. Signed-off-by: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ose mini-batching via narwhals (online-ml#1932) * feat(preprocessing,compose): make StandardScaler & compose mini-batching dataframe-agnostic via narwhals Route StandardScaler's and the composition primitives' mini-batch methods through the same narwhals boundary as the GLM/OneHotEncoder paths, so a whole pipeline can be mini-batched on any narwhals-supported eager backend (pandas, polars, pyarrow, nullable/arrow-backed pandas, ...). The numpy compute cores are untouched and the input backend (including the pandas index) is rebuilt on output. preprocessing/scale.py — StandardScaler: - learn_many wraps via into_frame and drops to a float64 numpy matrix; the windowed branch iterates rows backend-agnostically. - transform_many keeps a verbatim classic-pandas fast path (in-place divide, no-copy frame, float-dtype preservation) and adds an agnostic float64 path for every other backend. Pandas output is byte-for-byte unchanged. compose: - Pipeline: type hints only — the orchestration was already backend-agnostic. - TransformerUnion: pd.concat(axis=1) -> nw.concat(how="horizontal"). - Select: X.loc[...].copy() -> narwhals select (still pure). - TransformerProduct: keeps the pandas Sparse[uint8] fast path, adds an agnostic elementwise-product path for other backends. Adds cross-backend tests (mixed-dtype TrumpApproval, chunked learning, emerging/ disappearing/reordered features, native-backend round-trip) using the frame_backend fixture. Pandas remains the oracle. Refs online-ml#1919. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(utils): update transform_many pandas-requirement test for narwhals StandardScaler `StandardScaler.transform_many` no longer imports pandas unconditionally: it only needs pandas on the classic-pandas fast path. The old test passed `object()` (which now fails at the narwhals boundary, and isn't a valid `IntoDataFrameT`). Split it into two: a pandas input still raises ImportError when pandas is missing, while a polars input goes through the agnostic path and works without pandas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * perf(utils, preprocessing): Build native frames directly from 2D numpy, simplify `StandardScaler` (online-ml#1935) * Allow to create dataframe directly from 2d numpy array * fixup square array issue * simplify StandardScaler --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Francesco Bruzzesi <42817048+FBruzzesi@users.noreply.github.com>
…online-ml#1955) `Pipeline.transform_many` gated the learn-during-predict step on `not last_step._supervised and not self._LEARN_UNSUPERVISED_DURING_PREDICT`. The second `not` is inverted relative to both the single-instance twin `transform_one` (which uses `... and self._LEARN_UNSUPERVISED_DURING_PREDICT`) and to `transform_many`'s own intermediate-step loop. As a result, with `learn_during_predict` off (the default) `transform_many` fits the final unsupervised transformer on the very data it is transforming: - a bare `transform_many` mutates fitted state (a fresh scaler ends up with counts == len(X)), while `transform_one` leaves it untouched; - the normal `learn_many` -> `transform_many` loop double-learns the final step; - `transform_one` and `transform_many`, documented as the same operation, disagree. Drop the erroneous `not` so the batch path matches the single-instance path and the intermediate steps. `test_product.py::test_issue_1253` relied on the bug: its model was never trained, so the only thing that ever fitted the nested `StandardScaler` was the fit-during-transform. Adding an explicit `model.learn_many(X, y)` before the transform reproduces the exact same expected output (fitting the same rows twice left mean/variance unchanged), so the expected block is untouched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne-ml#1952) * Stop PreviousImputer.transform_one from mutating the input dict PreviousImputer.transform_one filled missing values directly on the caller's dict (`x[i] = ...`), so it mutated its input and returned an alias. Transformers are supposed to be pure -- the sibling StatImputer already copies (`x = x.copy()`) with that exact comment. The estimator purity check (check_predict_one_pure) never caught it because its dataset has no missing values, so the None branch was never exercised. The mutation is observable: compose.TransformerUnion feeds the same dict to every branch, so an imputer running first changes what later branches see. Copy the features first, mirroring StatImputer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> * add PreviousImputer to global tests --------- Signed-off-by: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Max Halford <maxhalford25@gmail.com>
Online Gamma-Poisson factorization of character n-gram counts, for encoding messy string categories (hand-typed city names, and the like). It's the one-at-a-time version of skrub's GapEncoder: the n-gram vocabulary and the topics both grow as strings arrive, topic updates use A/B accumulators with a rho forgetting factor, and transform_one is read-only. Passes check_estimator. Closes online-ml#1439
…ties from French ones
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Online GapEncoder for online-ml#1439 (streaming Gamma-Poisson over character n-grams). Opening this against my own fork's main first, just to run CI before sending it upstream.