A statistical test that always says “not significant” is worse than no test at all — it fails silently, with the full confidence of a passing test suite behind it.
Today we released PandRS 0.4.1 — a correctness-and-hardening release built on a second, independent, more exhaustive audit of the codebase. It closes a chi-squared p-value bug that survived 0.4.0’s own correctness pass, rewrites the typed GroupBy path from O(n²) to O(n), replaces fabricated GPU and Python-binding results with real computations, and fixes a data-loss bug in the columnar core — on top of a build that is, for the first time, genuinely warning-free.
No pandas. No scikit-learn or SciPy C-extensions. No Cython. No Python GIL.
No conda environments, no platform-specific wheels, no native segfaults hiding under a friendly API.
Just a memory-safe, high-performance DataFrame that compiles to a single static binary (or WASM) and computes the same answer everywhere — laptop, server, edge, browser.
Why PandRS 0.4.1 is a game changer
0.4.0 replaced a wave of fabricated ML and statistics stubs with real algorithms — including a native chi2_sf implementation to power real χ²-distributed p-values. What a second, independent, more exhaustive audit found in the months since is that fixing a stub is not the same as fixing it correctly:
chi2_sf’s continued fraction usedan = i*(i-a)instead of-i*(i-a), plus an inverted Lentz seed — so it returned ≈1.0 for essentially every input. Ljung-Box, Box-Pierce, Breusch-Godfrey, Friedman, Kruskal-Wallis, and χ² independence/goodness-of-fit tests were all silently under-reporting significance.- The Student-t CDF could return values greater than 1 — an impossible probability — corrupting t-test and correlation-test p-values.
- The F-distribution CDF saturated to 1.0 (collapsing every ANOVA p-value toward 0), and its
inverse_cdfreturned a constant1.0regardless of input. Column::from_anysilently returned an emptyInt64Columnfor every input, soclone_column()quietly lost data and mis-typed columns.- Non-CUDA GPU paths returned
Array2::zeros, and the Python GPU bindings (gpu_corr,gpu_pca,gpu_kmeans,gpu_linear_regression) returned fabricated placeholders — while a hardcoded “2.5x speedup” metric reported success regardless of what actually ran. - The typed GroupBy aggregation path was O(n²), and a crate-wide
#![allow(clippy::all)]was quietly suppressing whatever clippy would otherwise have flagged.
PandRS 0.4.1 ends all of that.
- Real hypothesis-test p-values. χ², Student-t, F, and normal CDF/survival/quantile functions are now exact (Lanczos
ln_gamma, Numerical-Recipes incomplete gamma/beta), unit-tested against published critical values, and centralized in onestats::specialmodule shared bystatsandtime_series— no more duplicated, independently-buggy copies. - A GroupBy path that scales. The typed/columnar
group_by(...)aggregation is now O(n) instead of O(n²) — independently re-measured at ~1,040x faster wall-clock on the measured case (n = 4,000, one row per group; the exact ratio tracks group cardinality) — and both the typed and base aggregation paths, includinghierarchical_groupby, now skip NaN the same way pandas’skipna=Truedoes. - GPU results you can trust, CUDA or not. Non-CUDA fallback paths compute real CPU results instead of zero matrices, the Python GPU bindings compute genuine PCA/k-means/regression/correlation, and fabricated speedup and device-capability claims are gone.
- A warning-free build.
unused_variables/unused_imports/dead_codemoved fromallowtowarn, with ~667 warning sites fixed at the root cause instead of silenced; the crate-wide#![allow(clippy::all)]is gone andclippy::correctnessis now denied. - Security hardening. A cyclic ReBAC graph no longer crashes the process; a path-traversal guard actually enforces
canonical_basein the local connector; JWT verification, credential AEAD (now with AAD), and secret comparisons are real end-to-end;object_store0.14.1 closes RUSTSEC-2026-0194 and RUSTSEC-2026-0195.
Technical Deep Dive: Four Layers That Got Re-Audited
1. The shared statistical core (stats::special).
This is the architectural fix behind the p-value bugs above: a single, numerically-correct special-functions module — Lanczos ln_gamma; Numerical-Recipes regularized incomplete gamma (gser/gcf) and incomplete beta (betacf); exact CDF/survival/quantile for the normal, Student-t, chi-squared, and F distributions — now shared across stats and time_series, which previously carried their own, independently-buggy copies of the same math. TDistribution::ln_gamma’s one-term Stirling approximation (which put lnΓ(1) at −0.081 instead of 0, corrupting every t/χ²/F PDF built on it) is gone, replaced by the accurate Lanczos form. OLS regression p-values now use the Student-t distribution at the residual degrees of freedom instead of an anti-conservative normal approximation, and Shapiro-Wilk uses the real Royston (1992) AS R94 coefficients — W now matches published reference values (n=5 → W≈0.984, versus the former 0.943 stub).
2. The columnar data-integrity layer.
Column::from_any’s downcast now correctly recovers the real column type instead of silently returning an empty Int64Column. DataFrame::to_arrow() converts real column data with correct Arrow types instead of fabricating values from the row index or column name; CSV/JSON/Parquet (de)serialization read and write actual data instead of hardcoded ["Alice","Bob","Charlie"], "{}", or silent no-ops; joins perform a correct hash join instead of self-recursing or returning empty, and the right_join side-inversion bug is fixed. OptimizedDataFrame ↔ DataFrame conversion now preserves NA by upcasting per type — a NULL-free Int64Column still converts natively, but a column with a real NULL widens (Int64Column → Series<f64> with NaN, BooleanColumn → Series<String>) rather than defaulting to 0/false. On the performance side, apply(Axis::Row), duplicated, and drop_duplicates went from O(N²·C) to O(N·C) by extracting each column once instead of per row, and .iat random-position access is now O(1) instead of O(n) per call.
3. GPU honesty, CPU fallback and Python bindings.
The #[cfg(not(cuda_available))] matrix-multiply, elementwise, and reduce paths in src/gpu/cuda.rs now compute real ndarray CPU results instead of Array2::zeros; cuda_qr/svd/eigen/matrix_inverse and GPU DataFrame extensions return Error::NotImplemented instead of the unchanged input or an identity matrix standing in as if it were a real answer; stats::gpu/ml::gpu linear regression solves the real normal equations (was 0.1·i). Fabricated speedup numbers, a benchmark “speedup” column, hardcoded p-values, and fabricated P2P/synchronize_all device claims are gone, replaced by real cudarc-0.19 device queries. The Python GPU bindings (py_bindings/src/py_gpu.rs) — gpu_corr, gpu_pca, gpu_kmeans, gpu_linear_regression — now compute genuine Pearson correlation, PCA fit+transform, k-means clustering, and OLS instead of returning fabricated zeros.
4. Security, lints, and release engineering.
A cyclic ReBAC relationship graph previously crashed the process with SIGABRT; resolution now uses a visited-set with a bounded MAX_RESOLUTION_DEPTH and returns Ok(false). API keys are hashed at rest, JWT verification is real end-to-end, secret comparisons are constant-time, and credential encryption now carries AAD. object_store moved 0.13.2 → 0.14.1, closing RUSTSEC-2026-0194 and RUSTSEC-2026-0195, and a new deny.toml makes cargo deny check bans part of the release gate. On the hygiene side, the crate-wide #![allow(clippy::all)] is gone (clippy::correctness denied, clippy::suspicious/clippy::perf warned and clean), RUSTDOCFLAGS="-D warnings" cargo doc is clean on both the all-safe and docs.rs feature sets (~47 broken intra-doc links fixed), and package.exclude trims the published crate to what a library consumer actually needs.
Getting Started
cargo add pandrs
use pandrs::column::{Column, Float64Column};
use pandrs::OptimizedDataFrame;
fn main() -> pandrs::error::Result<()> {
let mut df = OptimizedDataFrame::new();
df.add_column(
"x".to_string(),
Column::Float64(Float64Column::new(vec![1.0, 2.0, 3.0, 4.0])),
)?;
df.add_column(
"y".to_string(),
Column::Float64(Float64Column::new(vec![3.0, 5.0, 7.0, 9.0])),
)?;
// New in 0.4.1: OLS directly on a columnar OptimizedDataFrame — no
// conversion to the row-oriented DataFrame required first.
let model = df.linear_regression("y", &["x"])?;
println!(
"intercept: {:.4}, slope: {:.4}, R²: {:.4}",
model.intercept, model.coefficients[0], model.r_squared
);
Ok(())
}
What’s New in 0.4.1
New APIs
stats::special— the shared special-functions module behind the correctness fixes below.OptimizedDataFrame::linear_regression(y_column, x_columns)— OLS directly on a columnar frame, complete-case row selection, sameLinearRegressionResultasstats::linear_regression.DataFramePlotExtforOptimizedDataFrame— all 9 plotting methods (line/bar/scatter/histogram/box/heatmap/area/pie/plot) now work without a manual conversion, previously TODO’d.- Distributed schema validator’s
validate_windownow validates 18 SQL window functions against the real schema, instead of being a#[allow(dead_code)]stub. DistributedConfigoptimizer rules wired through to DataFusion 53.1’s typedSessionConfig.
Statistical correctness
chi2_sfcontinued-fraction sign bug fixed — every χ²-distributed test was silently under-reporting significance.- Student-t CDF, F-distribution CDF/quantile, and chi-squared CDF/quantile all corrected to exact incomplete-beta/gamma forms.
- OLS regression p-values now use the Student-t distribution at residual degrees of freedom.
- Shapiro-Wilk now uses real Royston (1992) AS R94 coefficients.
Performance
- Typed/columnar GroupBy: O(n²) → O(n).
apply(Axis::Row)/duplicated/drop_duplicates: O(N²·C) → O(N·C)..iatrandom-position access: O(n) → O(1).column_names()returns&[String]instead ofVec<String>, removing a heap allocation per call.
DataFrame / IO / Arrow correctness
to_arrow(), CSV/JSON/Parquet (de)serialization, joins, MultiIndex.xs()/.select()/.sort_index(), andgroupby(...).agg()/.apply()all fixed from fabricated or no-op behavior to real data.Column::from_anydata-loss/mis-typing bug fixed.melt/stack/unstackno longer fabricate placeholder rows.OptimizedDataFrame↔DataFrameconversion now preserves NA correctly in both directions.- Path-traversal guard enforced in the local connector.
GPU
- Non-CUDA fallbacks and Python GPU bindings compute real results instead of zeros/placeholders.
- Fabricated speedup and device-capability claims removed; real
cudarc-0.19 device queries.
Time series
- ARIMA (Levinson-Durbin), Augmented Dickey-Fuller (real OLS t-stat), and PACF (Durbin-Levinson) are now genuine, alongside Savitzky-Golay, cubic-spline interpolation, seasonal fill, and isolation-forest outlier detection.
- STL/X-13 decomposition and Kalman/Hodrick-Prescott/LOWESS smoothing now return
NotImplementedinstead of silently substituting a different algorithm. - Spectral/periodogram analysis is now routed through OxiFFT.
Security
- ReBAC cyclic-graph crash fixed; JWT/API-key/audit-log/credential hardening;
object_store0.14.1 closes RUSTSEC-2026-0194/0195.
API hygiene
DataFrame::groupby(&str)renamed togroupby_pivot;df.groupby(&["col"])in method syntax now correctly resolves toGroupByExt::groupby.StatisticalAnalyzer::test_columnsrenamed tocolumns_ttest.pandrs::error/PandRSErrorare a stable, non-deprecated alias again (it was erroneously marked#[deprecated]).Legacy*aliases (LegacyDataFrame,LegacyJoinType,LegacyAxis, …) remain exported and deprecated, scheduled for removal in 0.5.0.
Dependencies
scirs2-core/scirs2-stats/scirs2-linalg0.5.0 → 0.6.5;oxiarc-lz4/oxiarc-zstd/oxiarc-archive→ 0.4.1;pyo3/numpy→ 0.29.0;craneliftfamily → 0.133.1;quick-xml→oxixml-quickxml-compat;aes-gcm→ 0.11.0.
Tips
- Trust the hypothesis tests again — but know what changed. χ², Student-t, and F-distribution p-values (Ljung-Box, Box-Pierce, Breusch-Godfrey, Friedman, Kruskal-Wallis, ANOVA, correlation and OLS t-tests) were silently wrong before 0.4.1. Re-run any analysis whose “not significant” result you took at face value.
df.groupby(&["col"])now means what it looks like it means. The old inherentDataFrame::groupby(&str)— a single-key pivot method — was renamed togroupby_pivot. If you relied on the previous shadowing behavior, switch that call site togroupby_pivotexplicitly.- Reach for
OptimizedDataFrame::linear_regressionwhen your data is already columnar. It skips the conversion to the row-orientedDataFrameentirely — see Getting Started above. - Re-measure before you keep a GroupBy workaround. The typed/columnar aggregation path is now O(n) instead of O(n²); chunking or pre-aggregating to dodge GroupBy slowness may no longer be necessary.
- Match your toolchain to your feature flags. The default feature set and
distributedneed Rust 1.88;cloud-storage,all-safe, andstableneed 1.89 (pulled in byobject_store’s AWS backend viacrc-fast). Legacy*aliases still work, but plan the move.LegacyDataFrame,LegacyJoinType,LegacyAxis, and the rest of the ~20Legacy*aliases are deprecated and scheduled for removal in 0.5.0.
This is the foundation
PandRS is the DataFrame layer of the mature COOLJAPAN scientific stack, and 0.4.1 tightens the connections 0.4.0 first wired in:
- NumRS2 — the NumPy-class N-dimensional array core.
- SciRS2 / SkleaRS — SciPy- and scikit-learn-class scientific computing and ML; PandRS now sits on SciRS2 0.6.5 for stats and linalg, with
scirs2-corealways-on. - OxiFFT — Pure Rust FFT now powers PandRS’s time-series periodogram and spectral analysis directly, in place of a former placeholder.
- OxiARC — Pure Rust compression (
oxiarc-lz4/oxiarc-zstd/oxiarc-archive0.4.1) underneath the I/O layer — and, as of 0.4.1, genuinely compresses, with ratios measured from real byte counts instead of a fabricated table. - OptiRS — optimizers for the training and tuning loops feeding off your frames.
Together they form a broad, mature, sovereign analytics stack: load a DataFrame, get a p-value you can trust, fit a real model on GPU or CPU, and serialize through Pure Rust compression — all in one static binary, with no Python interpreter and no native dependency chain to fight.
Repository: https://github.com/cool-japan/pandrs
Star the repo if you want a DataFrame whose hypothesis tests, GroupBy, and GPU paths all compute what they claim to compute.
The era of a p-value you can compute but can’t trust is over.
Pure Rust data analytics is here — fast, safe, correct, and sovereign.
— KitaSan at COOLJAPAN OÜ August 24, 2026