Benchmarks and verification
OLS verification in R, statsmodels and PHP
The executable verification notebook shows the same observations, equivalent calls, coefficients, inference and model statistics across all three implementations. ols-results.json contains the saved complete numerical results, inputs, versions, sample metadata, PHP warnings and differences from R. These are generated results, not hand-entered examples. The math notebook derives the calculations.
Reproduction
Follow notebook setup to install development tools and build the PHP extension, then run:
.context/notebooks-venv/bin/python docs/verifications/compare_ols.pyThe script fits every case afresh using Rscript, statsmodels and PHP, checks all statistical fields against R with absolute tolerance 1e-9 and relative tolerance 1e-7, and writes ols-results.json. It consumes the ordinary deterministic cases from tests/fixtures/ols-r.json plus an explicit missing-data case. R and Python are optional development tools; cargo test and tests/ols.php still run without them. See verification policy for the tolerance’s limits.
Equivalent specifications
For the shared simulated housing dataset in examples/ols.php, run from the repository root:
observations <- read.csv("docs/data/housing.csv")
fit <- lm(monthly_rent ~ area_m2, data = observations, na.action = na.omit)
summary(fit)
confint(fit, level = 0.95)
logLik(fit)
# A second model uses correlated predictors:
summary(lm(monthly_rent ~ area_m2 + bedrooms, data = observations))import pandas as pd
import statsmodels.api as sm
observations = pd.read_csv("docs/data/housing.csv")
X = sm.add_constant(observations[["area_m2"]])
y = observations["monthly_rent"]
fit = sm.OLS(y, X, missing='drop', hasconst=True).fit(use_t=True)
print(fit.params, fit.bse, fit.tvalues, fit.pvalues, fit.conf_int(), fit.llf)// The verification runner prepares a complete design from the shared housing CSV.
// Each row of $x is [1.0, area_m2]; $y contains monthly_rent.
$result = Phit\fit_ols(x: $x, y: $y, intercept: false);These are synthetic apartments, not empirical housing records. The math notebook explains the generating process and reports sample correlations. The fixtures retain small hand-checkable numerical cases alongside these teaching examples.
These use an intercept, conventional homoskedastic covariance, residual variance RSS/(n-p), Student t tests, and 95% intervals. Likelihood uses ML variance RSS/n. The general R runner receives the explicit design columns with formula y ~ . - 1, then calculates R-squared using the same ones-column convention as the extension; this avoids a duplicate intercept. R’s coefficients, covariance, standard errors, tests, intervals, residuals and log-likelihood come from lm(). The notebook displays both the user-facing calls and this runner convention.
Sources: R lm, statsmodels OLS and statsmodels missing data.
Verified cases
| Case | Input / retained n | Model |
|---|---|---|
| Simulated housing | 8 / 8 | Intercept and floor area |
| Simulated housing, multiple | 8 / 8 | Intercept, area and correlated bedrooms |
| Simple | 5 / 5 | Intercept and one predictor |
| Multiple | 10 / 10 | Intercept and two predictors |
| No intercept | 10 / 10 | Two predictors, uncentered R-squared |
| Correlated | 10 / 10 | Intercept and two correlated full-rank predictors |
| Listwise missing | 6 / 4 | One predictor-null row and one outcome-null row omitted |
All fields pass the stated tolerance for all seven cases. The executable notebook displays the housing coefficients and inference from all three implementations. The saved report records the actual environment: R 4.6.1, statsmodels 0.15.0, NumPy 2.5.4, Python 3.14, and PHP 8.5.10 on the development Mac. This is not evidence of testing Linux or other PHP builds.
For the missing-data case, original positions 1 and 3 are omitted; positions 0, 2, 4 and 5 are retained. PHP emits one E_WARNING with the dropped/original counts and returns sample metadata. The runner checks those positions and counts, and exposes the captured warning in both the saved report and notebook.
Perfect/zero fits and invalid/singular inputs are covered by the Rust/PHP tests, not finite three-way comparisons. Decomposition-dependent tiny residuals make perfect-fit t/p and likelihood comparisons unstable. References may also accept singular designs by aliasing coefficients while our implementation rejects them.
From phpstats/phit@76b22f0.