
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@winm2m/inferential-stats-js
Advanced tools
A headless JavaScript SDK for advanced statistical analysis in the browser using WebAssembly (Pyodide). Performs SPSS-level inferential statistics entirely client-side with no backend required.
A headless JavaScript SDK for advanced statistical analysis in the browser using WebAssembly (Pyodide). Performs SPSS-level inferential statistics entirely client-side with no backend required.
@winm2m/inferential-stats-js runs entirely in the browser — no backend server, no API calls, no data ever leaves the client.
┌─────────────────────────────────────────────────────────┐
│ Main Thread │
│ ┌───────────────────────┐ postMessage() │
│ │ InferentialStats SDK │ ──── ArrayBuffer ──────┐ │
│ │ (ESM / CJS) │ (Transferable) │ │
│ └───────────────────────┘ ▼ │
│ ┌─────────────────────┐ │
│ │ Web Worker │ │
│ │ ┌────────────────┐ │ │
│ │ │ Pyodide WASM │ │ │
│ │ │ ┌───────────┐ │ │ │
│ │ │ │ Python │ │ │ │
│ │ │ │ Runtime │ │ │ │
│ │ │ └───────────┘ │ │ │
│ │ └────────────────┘ │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────┘
| Principle | Description |
|---|---|
| 100 % Client-Side | Statistical computation runs entirely in-browser via WebAssembly. No network requests to any analytics server. |
| Web Worker Isolation | All heavy computation is offloaded to a dedicated Web Worker, keeping the main thread responsive and the UI jank-free. |
| ArrayBuffer / TypedArray Transfer | Data is serialized into a columnar binary format (Float64Array, Int32Array, dictionary-encoded strings) and transferred to the worker using the Transferable Objects API for near-zero-copy performance. |
| Pyodide WASM Runtime | The worker loads Pyodide — a full CPython interpreter compiled to WebAssembly — along with pandas, SciPy, statsmodels, scikit-learn, and factor_analyzer. |
| Progress Events | Initialization and computation stages emit CustomEvent progress events on a configurable EventTarget, enabling real-time progress bars. |
| Dual Module Format | Ships as both ESM (dist/index.js) and CommonJS (dist/index.cjs) with full TypeScript declarations. |
This section documents the mathematical foundations and internal Python implementations of all 16 analyses.
Note on math rendering: Equations are rendered as images so they display correctly on npm.
Computes a frequency distribution for a categorical variable, including absolute counts, relative percentages, and cumulative percentages.
Python implementation: pandas.Series.value_counts(normalize=True)
Relative frequency:
where is the count of category
and
is the total number of observations. Cumulative percentage is the running sum of
.
Produces summary statistics for one or more numeric variables: count, mean, standard deviation, min, max, quartiles (Q1, Q2, Q3), skewness, and kurtosis.
Python implementation: pandas.DataFrame.describe(), scipy.stats.skew, scipy.stats.kurtosis
Arithmetic mean:
Sample standard deviation (Bessel-corrected):
Skewness (Fisher):
Excess kurtosis (Fisher):
Cross-tabulates two categorical variables and tests for independence using Pearson's Chi-square test. Reports observed and expected counts, row/column/total percentages, and Cramér's V as an effect-size measure.
Python implementation: pandas.crosstab, scipy.stats.chi2_contingency
Pearson's Chi-square statistic:
where is the observed frequency in cell (
) and
is the expected frequency under independence.
Cramér's V:
where .
Compares the means of a numeric variable between two independent groups. Automatically reports results for both equal-variance and unequal-variance (Welch's) assumptions. Includes Levene's test for equality of variances.
Python implementation: scipy.stats.ttest_ind, scipy.stats.levene
T-statistic (equal variance assumed):
Pooled standard deviation:
Degrees of freedom:
When Levene's test is significant (), Welch's t-test is recommended, which uses the Welch–Satterthwaite approximation for degrees of freedom.
Tests whether the mean difference between two paired measurements is significantly different from zero.
Python implementation: scipy.stats.ttest_rel
T-statistic:
where is the mean difference and
is the standard deviation of the differences.
Degrees of freedom:
Tests whether the means of a numeric variable differ significantly across three or more groups.
Python implementation: scipy.stats.f_oneway
F-statistic:
Sum of Squares Between Groups:
Sum of Squares Within Groups:
Mean Squares:
Effect size (Eta-squared):
Performs pairwise comparisons of group means following a significant ANOVA result using the Studentized Range distribution.
Python implementation: statsmodels.stats.multicomp.pairwise_tukeyhsd
Studentized range statistic:
where is the within-group mean square from the ANOVA and
is the harmonic mean of group sizes. The critical
value is obtained from the Studentized Range distribution with
groups and
degrees of freedom.
Fits an Ordinary Least Squares regression model with one or more independent variables. Reports regression coefficients, standard errors, t-statistics, p-values, confidence intervals, , adjusted
, F-test, and the Durbin-Watson statistic for autocorrelation detection.
Python implementation: statsmodels.api.OLS
Model:
where .
OLS estimator:
Coefficient of determination:
where and
.
Models the probability of a binary outcome as a function of one or more independent variables. Reports coefficients (log-odds), odds ratios, z-statistics, p-values, pseudo-, AIC, and BIC.
Python implementation: statsmodels.discrete.discrete_model.Logit
Logit link function:
Predicted probability:
Coefficients are estimated by Maximum Likelihood Estimation (MLE). The odds ratio for predictor j is .
Extends binary logistic regression to outcomes with more than two unordered categories. One category is designated as the reference; the model estimates log-odds of each other category relative to the reference.
Python implementation: sklearn.linear_model.LogisticRegression(multi_class='multinomial')
Log-odds relative to reference category :
for each category .
Predicted probability via softmax:
Partitions observations into clusters by iteratively assigning points to the nearest centroid and updating centroids until convergence.
Python implementation: sklearn.cluster.KMeans
Objective function (inertia):
where is the set of observations in cluster j and
is the centroid. The algorithm minimizes J using Lloyd's algorithm (Expectation-Maximization style).
Builds a hierarchy of clusters using a bottom-up approach. Supports Ward, complete, average, and single linkage methods. Returns a full linkage matrix and dendrogram data for visualization.
Python implementation: scipy.cluster.hierarchy.linkage, scipy.cluster.hierarchy.fcluster
Ward's minimum variance method (default):
At each step, the pair of clusters (A, B) that produces the smallest increase in total within-cluster variance is merged. Ward's method tends to produce compact, equally sized clusters.
Discovers latent factors underlying a set of observed variables. Supports varimax, promax, oblimin, and no rotation. Reports factor loadings, communalities, eigenvalues, KMO measure of sampling adequacy, and Bartlett's test of sphericity.
Python implementation: factor_analyzer.FactorAnalyzer(rotation='varimax') — installed at runtime via micropip
Factor model:
where is the observed variable vector,
is the matrix of factor loadings,
is the vector of latent factors, and
is the unique variance.
Kaiser-Meyer-Olkin (KMO) measure:
where are elements of the correlation matrix and
are elements of the partial correlation matrix. KMO values above 0.6 are generally considered acceptable for factor analysis.
Finds orthogonal components that maximize variance in the data. Reports component loadings, explained variance, cumulative variance ratios, and singular values. Optionally standardizes the input.
Python implementation: sklearn.decomposition.PCA
Objective: Find the weight vector that maximizes projected variance:
This is equivalent to finding the eigenvectors of the covariance matrix . The eigenvalues
represent the variance explained by each component.
Explained variance ratio:
Projects high-dimensional data into a lower-dimensional space (typically 2D) while preserving pairwise distances. Supports both metric and non-metric MDS.
Python implementation: sklearn.manifold.MDS
Stress function (Kruskal's Stress-1):
where is the distance in the reduced space and
is the original distance (or a monotonic transformation for non-metric MDS). A stress value below 0.1 is generally considered a good fit.
Measures the internal consistency (reliability) of a set of scale items. Reports raw alpha, standardized alpha, item-total correlations, and alpha-if-item-deleted for diagnostic purposes.
Python implementation: Custom implementation using pandas covariance matrix operations
Cronbach's alpha (raw):
where is the number of items,
is the variance of item i, and
is the variance of the total score.
Standardized alpha (based on mean inter-item correlation):
where is the mean of all pairwise Pearson correlations among items.
| Alpha Range | Interpretation |
|---|---|
| ≥ 0.9 | Excellent |
| 0.8 – 0.9 | Good |
| 0.7 – 0.8 | Acceptable |
| 0.6 – 0.7 | Questionable |
| < 0.6 | Poor |
npm install @winm2m/inferential-stats-js
Peer dependency (optional): If you want explicit control over the Pyodide version, install
pyodide(>= 0.26.0) as a peer dependency. Otherwise the SDK loads Pyodide from the jsDelivr CDN automatically.
import { InferentialStats, PROGRESS_EVENT_NAME } from '@winm2m/inferential-stats-js';
// 1. Listen for initialization progress
window.addEventListener(PROGRESS_EVENT_NAME, (e: Event) => {
const { stage, progress, message } = (e as CustomEvent).detail;
console.log(`[${stage}] ${progress}% — ${message}`);
});
// 2. Create an instance (pass the URL to the bundled worker)
const stats = new InferentialStats({
workerUrl: new URL('@winm2m/inferential-stats-js/worker', import.meta.url).href,
});
// 3. Initialize (loads Pyodide + Python packages inside the worker)
await stats.init();
// 4. Prepare your data
const data = [
{ group: 'A', score: 85 },
{ group: 'A', score: 90 },
{ group: 'B', score: 78 },
{ group: 'B', score: 82 },
// ... more rows
];
// 5. Run an analysis
const result = await stats.anovaOneway({
data,
variable: 'score',
groupVariable: 'group',
});
console.log(result);
// {
// success: true,
// data: { fStatistic: ..., pValue: ..., groupStats: [...], ... },
// executionTimeMs: 42
// }
// 6. Clean up when done
stats.destroy();
You can use the SDK directly in a browser or CodePen with no build step. The full demo code is identical to the local page below (except for CDN import paths).
src/dev/demo.htmlAll analysis methods are async and return Promise<AnalysisResult<T>>:
interface AnalysisResult<T> {
success: boolean;
data: T;
error?: string;
executionTimeMs: number;
}
| Method | Description |
|---|---|
new InferentialStats(config) | Create an instance. config.workerUrl is required. Optional: config.pyodideUrl, config.eventTarget. |
init(): Promise<void> | Load Pyodide and install Python packages inside the Web Worker. |
isInitialized(): boolean | Returns true if the worker is ready. |
destroy(): void | Terminate the Web Worker and release resources. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 1 | frequencies(input) | FrequenciesInput → FrequenciesOutput | Frequency distribution and relative percentages for a categorical variable. |
| 2 | descriptives(input) | DescriptivesInput → DescriptivesOutput | Summary statistics (mean, std, min, max, quartiles, skewness, kurtosis) for numeric variables. |
| 3 | crosstabs(input) | CrosstabsInput → CrosstabsOutput | Cross-tabulation with observed/expected counts, Chi-square test, and Cramér's V. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 4 | ttestIndependent(input) | TTestIndependentInput → TTestIndependentOutput | Independent-samples t-test with Levene's equality-of-variances test. |
| 5 | ttestPaired(input) | TTestPairedInput → TTestPairedOutput | Paired-samples t-test for dependent observations. |
| 6 | anovaOneway(input) | AnovaInput → AnovaOutput | One-way ANOVA with group descriptives and eta-squared effect size. |
| 7 | posthocTukey(input) | PostHocInput → PostHocOutput | Post-hoc Tukey HSD pairwise comparisons following ANOVA. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 8 | linearRegression(input) | LinearRegressionInput → LinearRegressionOutput | OLS linear regression with coefficients, R², F-test, and Durbin-Watson statistic. |
| 9 | logisticBinary(input) | LogisticBinaryInput → LogisticBinaryOutput | Binary logistic regression with odds ratios, pseudo-R², and model fit statistics. |
| 10 | logisticMultinomial(input) | MultinomialLogisticInput → MultinomialLogisticOutput | Multinomial logistic regression with per-category coefficients and odds ratios. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 11 | kmeans(input) | KMeansInput → KMeansOutput | K-Means clustering with cluster centers, labels, and inertia. |
| 12 | hierarchicalCluster(input) | HierarchicalClusterInput → HierarchicalClusterOutput | Agglomerative hierarchical clustering with linkage matrix and dendrogram data. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 13 | efa(input) | EFAInput → EFAOutput | Exploratory Factor Analysis with rotation, KMO, and Bartlett's test. |
| 14 | pca(input) | PCAInput → PCAOutput | Principal Component Analysis with loadings and explained variance. |
| 15 | mds(input) | MDSInput → MDSOutput | Multidimensional Scaling with stress value and coordinate output. |
| # | Method | Input → Output | Description |
|---|---|---|---|
| 16 | cronbachAlpha(input) | CronbachAlphaInput → CronbachAlphaOutput | Reliability analysis with Cronbach's alpha, item-total correlations, and alpha-if-deleted. |
The repository includes a ready-to-use sample dataset at docs/sample-survey-data.json, also hosted on GitHub Pages at:
https://winm2m.github.io/inferential-stats-js/sample-survey-data.json
This dataset contains 2,000 rows of simulated survey data generated with a seeded pseudo-random number generator for full reproducibility.
| Column | Type | Description |
|---|---|---|
id | integer | Unique respondent ID (1–2000) |
gender | string | "Male", "Female", or "Other" |
age_group | string | "20s", "30s", "40s", "50s", "60s" |
nationality | string | One of several country labels |
favorite_music | string | Preferred music genre |
favorite_movie | string | Preferred movie genre |
favorite_art | string | Preferred art form |
music_satisfaction | integer (1–5) | Satisfaction with music offerings (Likert scale) |
movie_satisfaction | integer (1–5) | Satisfaction with movie offerings (Likert scale) |
art_satisfaction | integer (1–5) | Satisfaction with art offerings (Likert scale) |
weekly_hours_music | float | Weekly hours spent on music |
weekly_hours_movie | float | Weekly hours spent on movies |
monthly_art_visits | integer | Number of art gallery visits per month |
This dataset is suitable for exercising every analysis method in the SDK.
During init(), the SDK dispatches CustomEvents to report progress through multiple stages (loading Pyodide, installing Python packages, etc.). You can use these events to drive a progress bar or loading indicator.
The event name is exported as the constant PROGRESS_EVENT_NAME (value: 'inferential-stats-progress').
interface ProgressDetail {
stage: string; // Current stage identifier (e.g. "pyodide", "packages")
progress: number; // Percentage complete (0–100)
message: string; // Human-readable status message
}
import { InferentialStats, PROGRESS_EVENT_NAME } from '@winm2m/inferential-stats-js';
// You can target any EventTarget — window, document, or a custom one.
const eventTarget = window;
const stats = new InferentialStats({
workerUrl: '/dist/stats-worker.js',
eventTarget, // Progress events will be dispatched here
});
// Register the listener BEFORE calling init()
eventTarget.addEventListener(PROGRESS_EVENT_NAME, ((event: CustomEvent) => {
const { stage, progress, message } = event.detail as {
stage: string;
progress: number;
message: string;
};
// Update a progress bar
const progressBar = document.getElementById('progress-bar') as HTMLProgressElement;
progressBar.value = progress;
progressBar.max = 100;
// Update a status label
const statusLabel = document.getElementById('status');
if (statusLabel) {
statusLabel.textContent = `[${stage}] ${message} (${progress}%)`;
}
console.log(`[${stage}] ${progress}% — ${message}`);
}) as EventListener);
// Start initialization — progress events will fire throughout
await stats.init();
console.log('Ready!');
| Stage | Progress | Message |
|---|---|---|
pyodide | 0 | Loading Pyodide runtime… |
pyodide | 30 | Pyodide runtime loaded |
packages | 40 | Installing pandas… |
packages | 55 | Installing scipy… |
packages | 70 | Installing statsmodels… |
packages | 80 | Installing scikit-learn… |
packages | 90 | Installing factor_analyzer… |
ready | 100 | All packages installed. Ready. |
MIT © 2026 WinM2M
FAQs
A headless JavaScript SDK for advanced statistical analysis in the browser using WebAssembly (Pyodide). Performs SPSS-level inferential statistics entirely client-side with no backend required.
We found that @winm2m/inferential-stats-js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.