onlinerake
Real-time survey weighting for streaming data.
The Problem
You're collecting survey responses or observational data one record at a time. Your sample doesn't match population demographics—too many young respondents, too few from certain regions. Traditional weighting methods (raking/IPF) require reprocessing the entire dataset whenever a new response arrives.
onlinerake updates weights as observations arrive, reducing squared error between weighted margins and population targets. Each update revisits all retained observations: memory is O(n × d), work per arrival is O(n × d × n_sgd_steps), and total work grows quadratically with stream length for fixed feature count. Check the remaining margin errors; a finite number of updates need not balance all targets.
When to Use This
- Online surveys where responses arrive continuously
- A/B tests that need demographic balance during collection
- Passive data collection (app usage, sensor data) requiring real-time calibration
- Moderate streams where updating retained weights after every arrival is affordable
Quick Start
pip install onlinerake
from onlinerake import OnlineRakingSGD, Targets
# Define population targets (proportion with indicator = 1)
targets = Targets(
female=0.51, # 51% female in population
college=0.32, # 32% college educated
age_65_plus=0.17, # 17% age 65+
)
# Create raker
raker = OnlineRakingSGD(targets, learning_rate=5.0)
survey_stream = [
{"female": 1, "college": 0, "age_65_plus": 0},
{"female": 0, "college": 1, "age_65_plus": 1},
{"female": 1, "college": 1, "age_65_plus": 0},
{"female": 0, "college": 0, "age_65_plus": 0},
]
# Replace this list with your incoming responses.
for response in survey_stream:
raker.partial_fit(response)
# Check current state anytime
print(f"Weighted margins: {raker.margins}")
print(f"Effective sample size: {raker.effective_sample_size:.0f}")
# Get final weights
weights = raker.weights
Which Algorithm?
| Update | Algorithm | Default learning rate |
|---|---|---|
| Additive weight adjustments | OnlineRakingSGD |
5.0 |
| Multiplicative weight adjustments | OnlineRakingMWU |
1.0 |
Both methods accept records one at a time and initialize each new weight at one.
Unequal starting weights are not supported. Each method takes a fixed number of
steps toward the target means, so always inspect the remaining error. The
multiplicative method need not choose the same weights as a method that fits the
whole dataset at once. BatchIPF provides that comparison for binary targets.
Learning rates need evaluation on the intended data, particularly when continuous features use different units. Better target balance can require more unequal weights, reducing effective sample size.
Performance
Run python examples/streaming_mwu.py for a reproducible evaluation with 10
seeds and 500 arrivals per stream. With the default MWU settings, mean squared
margin error over arrivals 100–500 falls by 83% in the stationary binary case,
75% after a sampling shift, and 83% with binary and scaled continuous features.
Mean final effective sample sizes are 292, 383, and 155 out of 500, respectively.
These simulations measure calibration on the accumulated sample; they do not
establish improved outcome estimates or a general performance guarantee.
Features
Continuous Covariates
Target means instead of proportions:
continuous_targets = Targets(
age=(42.0, "mean"), # Target mean age = 42
income=(55000, "mean"), # Target mean income = $55,000
female=0.51, # Binary: 51% female
)
Learning Rate Schedules
To inspect learning-rate summability (not a guarantee of calibration):
from onlinerake import OnlineRakingSGD, Targets, PolynomialDecayLR
from onlinerake.convergence import verify_robbins_monro
schedule = PolynomialDecayLR(initial_lr=10.0, power=0.6)
scheduled_raker = OnlineRakingSGD(targets, learning_rate=schedule)
# Verify Robbins-Monro conditions (analytical for known schedules)
result = verify_robbins_monro(schedule)
print(result.condition_1_satisfied) # True: Σ η_t = ∞
print(result.condition_2_satisfied) # True: Σ η_t² < ∞
verify_robbins_monro() checks the two learning-rate series for known schedule
types. A positive min_lr floor fails square summability. These checks do not
establish convergence for a changing, clipped calibration problem.
.converged reports whether current squared moment loss is at most 1e-6 after
convergence_window observations; later data can reset it. For a different loss
threshold, call .check_convergence(tolerance=...). Batch IPF instead checks the
maximum absolute margin error against its tolerance. A stalled or infeasible
fit does not count as converged.
Unsupported numerical regret/loss-bound helpers have been removed. The learning
rate helper is now suggest_mwu_learning_rate, explicitly an empirical tuning
heuristic.
Diagnostics
from onlinerake import check_target_feasibility, compute_design_effect
# Screen for missing support; this does not prove joint feasibility
feasibility = check_target_feasibility(raker)
print(f"Passes support screen: {feasibility.is_feasible}")
# Measure weighting efficiency
deff = compute_design_effect(raker)
print(f"Design effect: {deff:.2f}")
Batch Comparison
Compare streaming results against traditional IPF:
from onlinerake import BatchIPF
batch_raker = BatchIPF(targets)
batch_raker.fit(survey_stream)
print(f"Online loss: {raker.loss:.6f}")
print(f"Batch loss: {batch_raker.loss:.6f}")
API Reference
Core Classes
Targets(**features) - Define population margins
- Binary features:
female=0.51(proportion = 1) - Continuous features:
age=(42.0, "mean")(target mean)
OnlineRakingSGD(targets, learning_rate=5.0) - SGD-based streaming raker
.partial_fit(obs)- Process one observation.margins- Current weighted margins (dict).loss- Current squared-error loss.weights- Copy of the active weight array.effective_sample_size- ESS accounting for weight variation.converged- Whether loss is below tolerance
OnlineRakingMWU(targets, learning_rate=1.0) - Multiplicative weights raker
- Same API as
OnlineRakingSGD
Key Parameters
| Parameter | Default | Description |
|---|---|---|
learning_rate |
5.0 (SGD), 1.0 (MWU) | Step size for updates |
min_weight |
0.001 | Minimum allowed weight |
max_weight |
100.0 | Maximum allowed weight |
n_sgd_steps |
3 | Gradient steps per observation |
convergence_window |
20 | Minimum observations before reporting convergence |
Upgrading from 2.0.0
See the 3.0.0 upgrade notes for removed bound helpers, the renamed learning-rate heuristic, changed decay floors, and corrected convergence reporting.
Installation
pip install onlinerake
Development install:
git clone https://github.com/finite-sample/onlinerake.git
cd onlinerake
uv sync --all-groups
Testing
pytest tests/ -v
Examples
See examples/ for complete worked examples:
streaming_mwu.py- Online multiplicative weighting, drift, and the ESS tradeoffreal_survey_example.py- Basic survey weightingab_test_calibration.py- Balancing treatment/control groupsad_targeting_calibration.py- Real-time ad delivery calibrationrecommendation_balancing.py- Content recommendation fairness
Interactive notebooks in docs/notebooks/:
01_getting_started.ipynb- Visual introduction02_performance_comparison.ipynb- Algorithm benchmarking03_advanced_diagnostics.ipynb- Convergence and diagnostics
Citation
If you use this package in research, please cite:
@software{onlinerake,
author = {Sood, Gaurav},
title = {onlinerake: Streaming Survey Raking},
url = {https://github.com/finite-sample/onlinerake},
year = {2026}
}
License
MIT
Implementation audit
See the software audit for reproduced defects, regression
checks, and the relationship to the retired adaptive-eb prototype. The historical
manuscript in ms/ describes an update that changes only the newest row and reports
O(Kp) cost; it is not documentation of this implementation and its performance and
convergence claims should not be used for this package.
Metadata
Release files for onlinerake 3.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| onlinerake-3.0.0.tar.gz | 70.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| onlinerake-3.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 147.5 kB
Release files / onlinerake-3.0.0.tar.gz
| Download URL | onlinerake-3.0.0.tar.gz |
|---|---|
| Size | 70.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a717ba4add91f91237676994e7c1f7230fb5998cae7bcd216eaf081fb7a5d92
|
|
BLAKE2b-256 checksum How to use checksums |
15392f6f0d098ccb7e8c3f4a9abe24deb155fa1ab7355cd37d216ac6f7a48ce7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / onlinerake-3.0.0-py3-none-any.whl
| Download URL | onlinerake-3.0.0-py3-none-any.whl |
|---|---|
| Size | 77.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0555aebc589971508c54f4b942266a01148800918d62ab9023cfba267c7025c6
|
|
BLAKE2b-256 checksum How to use checksums |
39b40073e97879d847a7729da56e0c79f250d5ab958364b9df1bf613509d3ba6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log