Unreleased¶
- Add
ppc64learchitecture to Linux wheel builds. - Publish
abi3(stable-ABI,cp311-abi3) wheels alongside the per-version native wheels. pip/uv keep using the faster native wheel whenever one matches your interpreter; theabi3wheel is a fallback so River still installs as a wheel on CPython versions that don't yet have native builds (e.g. a brand-new release). - Moved
altairfrom the runtime dependencies to thedocs/devgroups; it was only used to draw plots in the docs, so installing River no longer pulls it in. - Publish Pyodide/WebAssembly wheels (CPython 3.13 and 3.14) so River can run in the browser, e.g. via JupyterLite or
micropip. The minimumnumpyandscipyversions were lowered to2.2.5and1.14.1to match Pyodide. - Display
compose.TransformerUnionelements vertically in HTML representations.
base¶
base.Transformerandbase.SupervisedTransformerare now properly abstract: theirtransform_oneabstract method is registered withabc, so a subclass that forgets to implement it raisesTypeErrorat instantiation instead of failing later. This also restores estimator-check coverage for all concrete transformers, which were unintentionally excluded from the automated test suite.
anomaly¶
- Added
api.anomaly.LODA, an online implementation of Pevný's Lightweight on-line detector of anomalies. It maintains an ensemble of one-dimensionalsketch.Histograms over sparse random projections and scores samples by their average negative log-likelihood. -
Rewrote
api.anomaly.LocalOutlierFactor. It now stores samples in a bounded sliding window via ariver.neighborssearch engine (LazySearchby default,SWINNfor approximate search) and computes the LOF of a sample against the window on demand.learn_oneis now constant-time and memory is bounded by the window size, andscore_oneno longer mutates the model. Scores match scikit-learn over the same window: an unseen point reproducesLocalOutlierFactor(novelty=True), and a stored point reproduces the in-samplenegative_outlier_factor_(a point is never its own neighbor).learn_manynow accepts any narwhals-supported eager dataframe (pandas, polars, pyarrow, ...). Behavior changes: scores now reflect the most recentwindow_sizesamples rather than the entire history, scoring an already-seen point returns its LOF instead of0.0, and thedistance_funcparameter is replaced by the engine's distance function. -
Made
anomaly.OneClassSVM.learn_manydataframe-agnostic via narwhals: it now accepts any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of only pandas. Outputs are unchanged.
cluster¶
- Gave the
CluStream,DenStream, andDBSTREAMmicro-cluster objects__slots__. These are created in large numbers on long streams, so dropping their per-instance__dict__trims memory (~40 bytes per micro-cluster). Behavior is unchanged. CluStreamMicroClusterno longer inherits frombase.Base; it is an internal data structure, not an estimator, so the estimator machinery (cloning, parameter introspection,repr) never applied to it. This matches theDenStream/DBSTREAMmicro-clusters and is what lets it use__slots__.
compose¶
- The mini-batch methods of
compose.Pipeline,compose.TransformerUnion,compose.Select, andcompose.TransformerProductnow accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only, so a whole pipeline can be mini-batched on a non-pandas backend. The input backend is preserved on output, including the pandas index, andpandasis no longer required unless the input is a pandas frame.TransformerProductkeeps the pandasSparse[uint8]fast path when crossing one-hot encoded features. compose.Pipelinenow forwards extra keyword arguments (such as the timestamptused byutils.TimeRolling, or a sample weightw) to each step whose method declares them, and drops them for steps that don't. This makesfeature_extraction.Agg/TargetAggbacked byutils.TimeRollingwork inside a pipeline viamodel.learn_one(x, y, t=t). Routing applies tolearn_oneand to the predict-time methods (predict_one,predict_proba_one,score_one,transform_one), so it also works undercompose.learn_during_predictwhere unsupervised steps learn duringpredict_one(x, t=t). Fixes #1600. The accepted arguments are determined once when the pipeline plan is built, so pipelines with no extra arguments keep their previous speed.- Fixed
compose.Pipeline.transform_manyfitting the final unsupervised transformer on the data being transformed whenlearn_during_predictis off (anotwas inverted relative to the single-instancetransform_oneand to the intermediate steps).transform_manyno longer mutates the model outsidelearn_during_predict, and it now agrees withtransform_oneinstead of double-learning the final step.
covariance¶
- Added
EwaCovariance,LedoitWolfCovariance,OASCovariance, andShrunkCovariance: online covariance estimators for non-stationary streams (exponentially weighted, recency-biased) and high-dimensional / few-sample regimes (shrinkage towards a well-conditioned target). They are dict-native likeEmpiricalCovarianceand support mini-batches viaupdate_manyon any narwhals-supported eager backend. - Added
EwaPrecision, an exponentially weighted precision (inverse covariance) matrix maintained online via a forgetting-factor Sherman-Morrison update. The recency-weighted counterpart ofEmpiricalPrecision, useful for tracking Mahalanobis distances and Gaussian likelihoods on non-stationary streams. EmpiricalCovariance.update_manyandEmpiricalPrecision.update_manynow accept any narwhals-supported eager dataframe (pandas, polars, pyarrow, ...) instead of pandas only. Outputs are unchanged for the pandas path.- Added weighted sample support to
EmpiricalCovariance.updateandEmpiricalCovariance.revertby accepting an optionalwparameter and propagating it to the underlyingstats.Covandstats.Varstatistics. - Sped up
EmpiricalCovariance.update/revert(~40% faster at 30 features) by caching the sorted feature list and pair iteration in the hot path. No semantic change. - Restructured
EmpiricalPrecisionaround NumPy-backed dense state, removing the per-update dict ↔ numpy marshalling. ~7× faster on 2000 × 20 sample streams. - Fixed an
EmpiricalPrecisionasymmetry where features introduced at different times left the stored matrix skewed (e.g.prec[a, b]≠prec[b, a]).
datasets¶
- Added
datasets.CriteoAds, a 100,000-row sample of the Criteo Display Advertising Challenge (binary click prediction with 13 integer and 26 high-cardinality categorical features). A natural fit for one-hot models such aslinear_model.AdPredictor. - Added
datasets.Shuttle, the UCI Statlog (Shuttle) dataset cast as a binary anomaly-detection task following the ODDS benchmark (49,097 observations, 9 numerical features, ~7% anomalies). Ships bundled with River. - Added
datasets.SP500Stocks, daily returns (1,257 trading days, 2013-2018) for ten large-cap S&P 500 stocks across diverse sectors. A natural fit for the online covariance estimators inriver.covariance.
facto¶
- Sped up
learn_onefor all factorization-machine models by vectorizing the per-factor latent updates with NumPy instead of looping in Python. On MovieLens 100K: ~1.4× faster forFFMRegressor/FFMClassifier, ~1.8× forFwFMRegressor/FwFMClassifierandHOFMRegressor/HOFMClassifier. Outputs are unchanged. - The factorization-machine models are now covered by the automated estimator checks (
utils.check_estimator).
feature_extraction¶
- Added proper mini-batch support to
feature_extraction.TFIDF:learn_manynow updates document frequencies, andtransform_manyreturns TF-IDF weights. Bothfeature_extraction.BagOfWords.transform_manyandTFIDF.transform_manynow accept any narwhals-supported dataframe backend (pandas, polars, pyarrow, ...), as either a series of documents or a dataframe with theonparameter, and return the same backend (a sparse dataframe for pandas).
linear_model¶
- Added
linear_model.AdPredictor, the Bayesian online probit-regression classifier Microsoft used for click-through-rate prediction in Bing's sponsored search (Graepel et al., 2010). It keeps a Gaussian belief over each feature weight and yields well-calibrated probabilities. - Restructured
BayesianLinearRegressionaround NumPy-backed storage. ~11× fasterlearn_oneat 20 features, ~24× at 50 features. Speeds upbandit.LinUCBtoo. BayesianLinearRegressionnow handles features arriving and disappearing after training begins (it passescheck_emerging_featuresandcheck_shuffle_features_no_impact, previously skipped).- Fixed
BayesianLinearRegressioncoefficients diverging toinf/nanunder emerging/disappearing features;learn_onenow updates the full state with a zero-paddedx. Behavior change: features absent fromxare treated as observed 0s (matching the other linear models) rather than skipped — identical to before when every call sees the same features. - Sped up the
LinearRegression/LogisticRegression.learn_manymini-batch gradient (~2-3×) by contracting the sample axis inside thenp.einsum. No semantic change. - Sped up
learn_onefor the linear models (LinearRegression,LogisticRegression,Perceptron, ...): updates now scale with the number of active features instead of the total number of features ever seen. Outputs are unchanged. - Stabilised
BayesianLinearRegressionacross BLAS implementations and sped it up (~10-20%) by accumulating an exact natural mean and recovering the posterior mean lazily, instead of propagating it through compounding rank-1 updates (which drifted ~0.6% between macOS Accelerate and Linux OpenBLAS). linear_model.LinearRegressionandlinear_model.LogisticRegressionmini-batch methods (learn_many,predict_many,predict_proba_many) now accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only. The input backend is preserved on output, including the pandas index. These methods no longer requirepandasto be installed.linear_model.BayesianLinearRegressionis now aMiniBatchRegressor: it gained alearn_manymethod, equivalent to loopinglearn_oneover the rows (exact without smoothing, and the matching closed-form geometric weighting with smoothing). Itslearn_many/predict_manyaccept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...), preserving the input backend and pandas index, and no longer requirepandas.
metrics¶
- Fixed
metrics.base.Metrics(a metrics collection, built viametric_a + metric_b) dropping the sample weightw:updatenow forwardswto each child metric, so weighted metrics report correct values inside a collection andupdate/revertcancel exactly. Previouslyrevertapplied the weight butupdateignored it. - Fixed
metrics.BalancedAccuracydeflating its score when a label appears in the predictions but never as a ground-truth label. Such a class has no support, so its recall is undefined and must be excluded from the per-class average; previously it was counted as0and inflated the denominator, disagreeing withsklearn.metrics.balanced_accuracy_score.BalancedAccuracyis now covered by the scikit-learn equivalence test.
multiclass¶
multiclass.OneVsRestClassifiermini-batch methods (learn_many,predict_many,predict_proba_many) now accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only. The input backend is preserved on output, including the pandas index, and these methods no longer requirepandasto be installed. Outputs are unchanged on the pandas path.
multioutput¶
- Added
multioutput.PerOutputClassifier, the streaming equivalent of scikit-learn'sMultiOutputClassifier. Trains one independent classifier per target output. - Added
multioutput.PerOutputRegressor, the streaming equivalent of scikit-learn'sMultiOutputRegressor. Trains one independent regressor per target output, with no inter-output dependencies.
naive_bayes¶
- Added mini-batch support to
GaussianNBvialearn_many,predict_many, andpredict_proba_many.
neighbors¶
- Fixed the Euclidean fast path of
neighbors.LazySearch, which returned the farthest candidates instead of the nearest because its search heap was keyed on the negated distance. This affectedKNNClassifier,KNNRegressor, andLocalOutlierFactorwhenever they ran over aLazySearchengine with the default Euclidean distance. - Gave the SWINN graph
Vertex__slots__and dropped itsbase.Baseinheritance (it is an internal graph node, not an estimator). One vertex is created per buffered sample, so this trims memory on largeneighbors.SWINNindexes; behavior is unchanged.
neural_net¶
- Removed the deprecated
river.neural_netmodule (and itsMLPRegressor), which had emitted aDeprecationWarningsince 0.25.0. Usedeep-riveror a dedicated deep-learning library such as PyTorch for neural networks.
optim¶
- Exposed
optim.Newton(Online Newton Step), which was implemented but never exported, and fixed an initialisation bug (the inverse Hessian started ateps * Iinstead of(1 / eps) * I) that crippled learning. Reworked around NumPy-backed dense state. - Fixed
optim.AdaBoundraisingTypeErrorafter being cloned (its base learning rate was captured as a scheduler instead of a number), which broke it insideevaluate, ensembles, model selection, and anywhere else estimators are cloned. - Fixed
optim.NesterovMomentumandoptim.FTRLProximalraising when used to optimise estimators whose weights are stored as NumPy arrays, such as the factorization machines (facto). - Added a test covering every optimizer against every estimator that accepts one, so optimizer/estimator incompatibilities are caught going forward.
- Fixed
optim.losses.Hinge.gradientreturning different values for single samples and numpy batches at the exact margin (y * p == threshold): the batch path used a strict<while the single-sample path used<=. Both now use<=(matching scikit-learn), so a point on the margin is treated as a violation andlearn_one/learn_manyagree. This only affects samples lying exactly on the margin.
preprocessing¶
- Added a
window_sizeparameter topreprocessing.StandardScaler,preprocessing.MinMaxScaler, andpreprocessing.MaxAbsScaler. When set, the scaler tracks its statistics over the lastwindow_sizeobservations instead of the whole stream. - Added a
_from_stateclassmethod topreprocessing.MinMaxScaler,preprocessing.MaxAbsScaler, andpreprocessing.StandardScalerso a scaler can be warm-started from precomputed statistics without replaying past observations. preprocessing.FeatureHashernow hashes with MurmurHash3 in Rust, making it much faster. It gains analternate_signparameter (defaultTrue, matching scikit-learn) and returns a plaindict. Hashed feature indices differ from previous versions.preprocessing.OneHotEncodermini-batch methods (learn_many,transform_many) now accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only, preserving the input backend (including the pandas index) on output. The pandas path keeps returningSparse[uint8]columns; other backends return dense integer columns, as they have no sparse-array equivalent.transform_manyonly requirespandaswhen the input is a pandas frame.preprocessing.OrdinalEncodermini-batch methods (learn_many,predict_many,predict_proba_many) now accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only. The input backend is preserved on output, including the pandas index. These methods no longer requirepandasto be installed.- Fixed
preprocessing.PreviousImputer.transform_onemutating the caller's dictionary in place: it now copies the features before filling missing values, likeStatImputerdoes. Transformers are supposed to be pure, and the mutation was visible to other branches of acompose.TransformerUnionfed the same dict. preprocessing.StandardScalermini-batch methods (learn_many,transform_many) now accept and return any narwhals-supported eager backend (pandas, polars, pyarrow, ...) instead of being pandas-only, preserving the input backend (including the pandas index) on output. Classic numpy-backed pandas keeps the historical fast path (and its float dtype, e.g.float32); other backends are scaled through afloat64path.transform_manyonly requirespandaswhen the input is a pandas frame. Outputs are unchanged on the pandas path.
proba¶
- Added weighted sample support to
MultivariateGaussian.updateandMultivariateGaussian.revertby accepting an optionalwparameter and propagating it to the underlyingEmpiricalCovarianceinstance. - Fixed
Beta.n_samplesreturning the negative of the observed-sample count (the two operands of the difference were transposed), so it now returns a non-negative count consistent with the otherprobadistributions.
reco¶
- Corrected the type annotations of the weight/latent
defaultdicts inBiasedMF,Baseline, andFunkMF, and dropped the bespokereco.base.IDalias in favour oftyping.Hashable. Typing-only; no behavioral change.
utils¶
- Added
utils.math.norm_cdfandutils.math.norm_pdf, the CDF and PDF of the standard normal distribution (used bylinear_model.AdPredictor). utils.Rollingandutils.TimeRollingnow accept a class plus constructor keyword arguments, e.g.utils.Rolling(stats.Mean, window_size=3). This avoids silently sharing state when they are used ascollections.defaultdict` factories. Passing a pre-built instance still works but is deprecated and will be removed in a future release.
sketch¶
- Sped up
sketch.Histogram.updateby roughly 2× on typical data by operating on the underlying list directly and inlining the bin search, instead of going throughcollections.UserList. Outputs are unchanged. - Fixed
sketch.Histogram.__add__: merging two histograms now conserves the total count (point bins were previously double-counted) and setsnon the result, socdfno longer raises on merged histograms. Merging with an empty histogram also works now. sketch.Histogram.cdfanditer_cdfnow return0.0on an empty histogram instead of raising.
rules¶
- Fixed
RecursionErrorinAMRuleson long streams: theEBSTSplitter,TEBSTSplitter, andExhaustiveSplitternow traverse and deep-copy their search trees iteratively, so deeply-skewed trees no longer blow Python's recursion limit. - Fixed an
AMRulesmemory leak whereHoeffdingRule.expandappended a redundantNumericLiteralwhen a new split shared a feature and direction with an existing literal without tightening the threshold. Literal(and itsNumericLiteral/NominalLiteralsubclasses) no longer inherits frombase.Base, so its existing__slots__now actually takes effect — previously every literal still carried a__dict__becausebase.Basedefines no slots. Literals are internal rule components, not estimators, so the estimator machinery never applied. Trims memory on rule sets with many literals; behavior is unchanged.
stats¶
- Added
stats.EWCov, an exponentially weighted covariance between two variables (the bivariate counterpart ofstats.EWVar). - Added
stats.ChiSquared, a streaming Chi-squared statistic between two categorical variables. Wrap it withutils.Rollingfor a rolling version.
tree¶
- Gave the binary-search-tree nodes of the numeric splitters (
EBSTSplitter/TEBSTSplitter,ExhaustiveSplitter,QOSplitter)__slots__. One node is created per distinct observed feature value, so on high-cardinality numeric streams these can number in the millions; dropping their per-instance__dict__trims memory (~40 bytes per node) with no change in behavior or throughput. - Slotted the
GradHessMeritsplit-candidate record used by the Stochastic Gradient Trees (tree.SGTClassifier/SGTRegressor) via@dataclass(slots=True), trimming its per-instance memory. Behavior is unchanged.
stream¶
- Added
stream.iter_frame, a dataframe-agnostic row iterator powered by Narwhals that works with any eager dataframe (pandas, polars, PyArrow, Modin, cuDF, ...). - Deprecated
stream.iter_pandasandstream.iter_polarsin favour ofstream.iter_frame. They now emit aDeprecationWarningand will be removed in a future release.