@winm2m/inferential-stats-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Encode a UTF-8 string to Uint8Array
3
+ */
4
+ function encodeString(str) {
5
+ return new TextEncoder().encode(str);
6
+ }
7
+ /**
8
+ * Infer column type from values
9
+ */
10
+ function inferColumnType(values) {
11
+ for (const v of values) {
12
+ if (v === null || v === undefined)
13
+ continue;
14
+ if (typeof v === 'string')
15
+ return 'string';
16
+ if (typeof v === 'number') {
17
+ if (Number.isInteger(v))
18
+ continue;
19
+ return 'float64';
20
+ }
21
+ }
22
+ // Default numeric columns to float64 for compatibility
23
+ return 'float64';
24
+ }
25
+ /**
26
+ * Serialize an array of JSON objects into an ArrayBuffer for efficient Worker transfer.
27
+ * Uses columnar storage with dictionary encoding for strings.
28
+ *
29
+ * Format:
30
+ * [4 bytes: header length][header JSON bytes][column data bytes...]
31
+ *
32
+ * Column data layout per column:
33
+ * - float64/int32: raw typed array bytes
34
+ * - string: Int32Array of indices into stringTable in header
35
+ */
36
+ function serializeToBuffer(data) {
37
+ if (!data || data.length === 0) {
38
+ // Return minimal buffer for empty data
39
+ const header = { rowCount: 0, columns: [] };
40
+ const headerBytes = encodeString(JSON.stringify(header));
41
+ const buffer = new ArrayBuffer(4 + headerBytes.byteLength);
42
+ const view = new DataView(buffer);
43
+ view.setUint32(0, headerBytes.byteLength, true);
44
+ new Uint8Array(buffer, 4).set(headerBytes);
45
+ return buffer;
46
+ }
47
+ const rowCount = data.length;
48
+ const columnNames = Object.keys(data[0]);
49
+ const columns = [];
50
+ const columnBuffers = [];
51
+ for (const name of columnNames) {
52
+ const values = data.map(row => row[name]);
53
+ const dtype = inferColumnType(values);
54
+ if (dtype === 'string') {
55
+ // Dictionary encoding
56
+ const uniqueValues = [...new Set(values.map(v => String(v ?? '')))];
57
+ const lookupMap = new Map();
58
+ uniqueValues.forEach((v, i) => lookupMap.set(v, i));
59
+ const indices = new Int32Array(rowCount);
60
+ for (let i = 0; i < rowCount; i++) {
61
+ indices[i] = lookupMap.get(String(values[i] ?? '')) ?? 0;
62
+ }
63
+ columns.push({ name, dtype: 'string', stringTable: uniqueValues });
64
+ columnBuffers.push(indices.buffer.slice(0));
65
+ }
66
+ else {
67
+ // Numeric column
68
+ const arr = new Float64Array(rowCount);
69
+ for (let i = 0; i < rowCount; i++) {
70
+ const v = values[i];
71
+ arr[i] = (v === null || v === undefined) ? NaN : Number(v);
72
+ }
73
+ columns.push({ name, dtype: 'float64' });
74
+ columnBuffers.push(arr.buffer.slice(0));
75
+ }
76
+ }
77
+ const header = { rowCount, columns };
78
+ const headerBytes = encodeString(JSON.stringify(header));
79
+ // Calculate total size
80
+ let totalDataSize = 0;
81
+ for (const buf of columnBuffers) {
82
+ totalDataSize += buf.byteLength;
83
+ }
84
+ const totalSize = 4 + headerBytes.byteLength + totalDataSize;
85
+ const buffer = new ArrayBuffer(totalSize);
86
+ const view = new DataView(buffer);
87
+ // Write header length (4 bytes, little-endian)
88
+ view.setUint32(0, headerBytes.byteLength, true);
89
+ // Write header
90
+ new Uint8Array(buffer, 4, headerBytes.byteLength).set(headerBytes);
91
+ // Write column data
92
+ let offset = 4 + headerBytes.byteLength;
93
+ for (const colBuf of columnBuffers) {
94
+ new Uint8Array(buffer, offset, colBuf.byteLength).set(new Uint8Array(colBuf));
95
+ offset += colBuf.byteLength;
96
+ }
97
+ return buffer;
98
+ }
99
+ /**
100
+ * Get a list of transferable ArrayBuffers from the serialized buffer.
101
+ * Useful for postMessage with Transferable Objects.
102
+ */
103
+ function getTransferables(buffer) {
104
+ return [buffer];
105
+ }
106
+
107
+ /** Custom event name for progress updates */
108
+ const PROGRESS_EVENT_NAME = 'inferential-stats-progress';
109
+ /**
110
+ * InferentialStats - Main SDK class for browser-based statistical analysis.
111
+ *
112
+ * Uses Pyodide (WebAssembly) running in a Web Worker to perform
113
+ * SPSS-level statistical computations entirely client-side.
114
+ *
115
+ * Data is transferred to the worker using ArrayBuffer (Transferable Objects)
116
+ * for maximum efficiency with large datasets.
117
+ */
118
+ class InferentialStats {
119
+ constructor(config = {}) {
120
+ this.worker = null;
121
+ this.pendingRequests = new Map();
122
+ this.requestCounter = 0;
123
+ this.initialized = false;
124
+ this.initPromise = null;
125
+ this.config = {
126
+ workerUrl: config.workerUrl ?? '',
127
+ pyodideUrl: config.pyodideUrl ?? 'https://cdn.jsdelivr.net/pyodide/v0.27.5/full/',
128
+ eventTarget: config.eventTarget ?? (typeof globalThis !== 'undefined' ? globalThis : null),
129
+ };
130
+ }
131
+ /**
132
+ * Initialize the SDK - loads Pyodide and all required Python packages.
133
+ * Must be called before any analysis methods.
134
+ * Dispatches 'inferential-stats-progress' CustomEvents during loading.
135
+ */
136
+ async init() {
137
+ if (this.initialized)
138
+ return;
139
+ if (this.initPromise)
140
+ return this.initPromise;
141
+ this.initPromise = this._doInit();
142
+ return this.initPromise;
143
+ }
144
+ async _doInit() {
145
+ // Create worker
146
+ if (this.config.workerUrl) {
147
+ this.worker = new Worker(this.config.workerUrl);
148
+ }
149
+ else {
150
+ throw new Error('workerUrl is required. Point it to the stats-worker.js file from @winm2m/inferential-stats-js/worker');
151
+ }
152
+ // Set up message handler
153
+ this.worker.onmessage = (event) => {
154
+ this._handleWorkerMessage(event.data);
155
+ };
156
+ this.worker.onerror = (event) => {
157
+ console.error('[InferentialStats] Worker error:', event.message);
158
+ };
159
+ // Wait for worker ready signal
160
+ await new Promise((resolve) => {
161
+ const readyHandler = (event) => {
162
+ if (event.data.id === '__worker_ready__') {
163
+ resolve();
164
+ }
165
+ };
166
+ this.worker.addEventListener('message', readyHandler, { once: true });
167
+ });
168
+ // Initialize Pyodide in the worker
169
+ await this._sendRequest('init', undefined, {
170
+ pyodideUrl: this.config.pyodideUrl,
171
+ });
172
+ this.initialized = true;
173
+ }
174
+ /** Handle messages from the worker */
175
+ _handleWorkerMessage(response) {
176
+ if (response.type === 'progress' && response.progress) {
177
+ this._dispatchProgress(response.progress);
178
+ return; // Don't resolve the promise for progress messages
179
+ }
180
+ const pending = this.pendingRequests.get(response.id);
181
+ if (!pending)
182
+ return;
183
+ this.pendingRequests.delete(response.id);
184
+ if (response.type === 'error') {
185
+ pending.reject(new Error(response.error ?? 'Unknown worker error'));
186
+ }
187
+ else {
188
+ pending.resolve(response.data);
189
+ }
190
+ }
191
+ /** Dispatch a progress CustomEvent */
192
+ _dispatchProgress(detail) {
193
+ try {
194
+ if (this.config.eventTarget && typeof CustomEvent !== 'undefined') {
195
+ const event = new CustomEvent(PROGRESS_EVENT_NAME, { detail });
196
+ this.config.eventTarget.dispatchEvent(event);
197
+ }
198
+ }
199
+ catch {
200
+ // Silently ignore if CustomEvent is not available (e.g., Node.js test env)
201
+ }
202
+ }
203
+ /**
204
+ * Send a request to the worker and return a promise for the result.
205
+ * Optionally serializes data to ArrayBuffer for efficient transfer.
206
+ */
207
+ _sendRequest(type, data, params) {
208
+ if (!this.worker) {
209
+ return Promise.reject(new Error('Worker not initialized. Call init() first.'));
210
+ }
211
+ const id = `req_${++this.requestCounter}_${Date.now()}`;
212
+ return new Promise((resolve, reject) => {
213
+ this.pendingRequests.set(id, {
214
+ resolve: resolve,
215
+ reject,
216
+ });
217
+ const request = { id, type, params };
218
+ if (data && data.length > 0) {
219
+ // Serialize to ArrayBuffer for efficient transfer
220
+ const buffer = serializeToBuffer(data);
221
+ request.payload = buffer;
222
+ this.worker.postMessage(request, getTransferables(buffer));
223
+ }
224
+ else {
225
+ this.worker.postMessage(request);
226
+ }
227
+ });
228
+ }
229
+ /** Helper to wrap analysis calls with timing */
230
+ async _runAnalysis(type, input) {
231
+ if (!this.initialized) {
232
+ throw new Error('SDK not initialized. Call init() first.');
233
+ }
234
+ const startTime = performance.now();
235
+ try {
236
+ const { data, ...params } = input;
237
+ const result = await this._sendRequest(type, data, params);
238
+ return {
239
+ success: true,
240
+ data: result,
241
+ executionTimeMs: Math.round(performance.now() - startTime),
242
+ };
243
+ }
244
+ catch (err) {
245
+ return {
246
+ success: false,
247
+ data: null,
248
+ error: err instanceof Error ? err.message : String(err),
249
+ executionTimeMs: Math.round(performance.now() - startTime),
250
+ };
251
+ }
252
+ }
253
+ // ═══════════════════════════════════════
254
+ // ① Descriptive Statistics
255
+ // ═══════════════════════════════════════
256
+ /** Frequency analysis for categorical variables */
257
+ async frequencies(input) {
258
+ return this._runAnalysis('frequencies', input);
259
+ }
260
+ /** Descriptive statistics for continuous variables */
261
+ async descriptives(input) {
262
+ return this._runAnalysis('descriptives', input);
263
+ }
264
+ /** Cross-tabulation with Chi-square test */
265
+ async crosstabs(input) {
266
+ return this._runAnalysis('crosstabs', input);
267
+ }
268
+ // ═══════════════════════════════════════
269
+ // ② Compare Means
270
+ // ═══════════════════════════════════════
271
+ /** Independent-samples T-test with Levene's test */
272
+ async ttestIndependent(input) {
273
+ return this._runAnalysis('ttest_independent', input);
274
+ }
275
+ /** Paired-samples T-test */
276
+ async ttestPaired(input) {
277
+ return this._runAnalysis('ttest_paired', input);
278
+ }
279
+ /** One-Way ANOVA */
280
+ async anovaOneway(input) {
281
+ return this._runAnalysis('anova_oneway', input);
282
+ }
283
+ /** Post-hoc Tukey HSD test */
284
+ async posthocTukey(input) {
285
+ return this._runAnalysis('posthoc_tukey', input);
286
+ }
287
+ // ═══════════════════════════════════════
288
+ // ③ Regression
289
+ // ═══════════════════════════════════════
290
+ /** Linear regression (OLS) */
291
+ async linearRegression(input) {
292
+ return this._runAnalysis('linear_regression', input);
293
+ }
294
+ /** Binary logistic regression */
295
+ async logisticBinary(input) {
296
+ return this._runAnalysis('logistic_binary', input);
297
+ }
298
+ /** Multinomial logistic regression */
299
+ async logisticMultinomial(input) {
300
+ return this._runAnalysis('logistic_multinomial', input);
301
+ }
302
+ // ═══════════════════════════════════════
303
+ // ④ Classify
304
+ // ═══════════════════════════════════════
305
+ /** K-Means clustering */
306
+ async kmeans(input) {
307
+ return this._runAnalysis('kmeans', input);
308
+ }
309
+ /** Hierarchical (agglomerative) clustering */
310
+ async hierarchicalCluster(input) {
311
+ return this._runAnalysis('hierarchical_cluster', input);
312
+ }
313
+ // ═══════════════════════════════════════
314
+ // ⑤ Dimension Reduction
315
+ // ═══════════════════════════════════════
316
+ /** Exploratory Factor Analysis (EFA) with varimax rotation */
317
+ async efa(input) {
318
+ return this._runAnalysis('efa', input);
319
+ }
320
+ /** Principal Component Analysis (PCA) */
321
+ async pca(input) {
322
+ return this._runAnalysis('pca', input);
323
+ }
324
+ /** Multidimensional Scaling (MDS) */
325
+ async mds(input) {
326
+ return this._runAnalysis('mds', input);
327
+ }
328
+ // ═══════════════════════════════════════
329
+ // ⑥ Scale
330
+ // ═══════════════════════════════════════
331
+ /** Reliability analysis (Cronbach's Alpha) */
332
+ async cronbachAlpha(input) {
333
+ return this._runAnalysis('cronbach_alpha', input);
334
+ }
335
+ // ═══════════════════════════════════════
336
+ // Lifecycle
337
+ // ═══════════════════════════════════════
338
+ /** Check if the SDK is initialized */
339
+ isInitialized() {
340
+ return this.initialized;
341
+ }
342
+ /** Terminate the worker and clean up resources */
343
+ destroy() {
344
+ if (this.worker) {
345
+ this.worker.terminate();
346
+ this.worker = null;
347
+ }
348
+ this.initialized = false;
349
+ this.initPromise = null;
350
+ this.pendingRequests.clear();
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Decode UTF-8 bytes to string
356
+ */
357
+ function decodeString(bytes) {
358
+ return new TextDecoder().decode(bytes);
359
+ }
360
+ /**
361
+ * Deserialize an ArrayBuffer (created by serializeToBuffer) back to JSON objects.
362
+ */
363
+ function deserializeFromBuffer(buffer) {
364
+ const view = new DataView(buffer);
365
+ // Read header length
366
+ const headerLength = view.getUint32(0, true);
367
+ // Read header
368
+ const headerBytes = new Uint8Array(buffer, 4, headerLength);
369
+ const header = JSON.parse(decodeString(headerBytes));
370
+ if (header.rowCount === 0)
371
+ return [];
372
+ const { rowCount, columns } = header;
373
+ let offset = 4 + headerLength;
374
+ // Read column data
375
+ const columnData = new Map();
376
+ for (const col of columns) {
377
+ if (col.dtype === 'string') {
378
+ const byteLen = rowCount * 4; // Int32 = 4 bytes
379
+ const aligned = new Int32Array(new Uint8Array(buffer, offset, byteLen).slice().buffer);
380
+ const values = [];
381
+ for (let i = 0; i < rowCount; i++) {
382
+ values.push(col.stringTable[aligned[i]]);
383
+ }
384
+ columnData.set(col.name, values);
385
+ offset += byteLen;
386
+ }
387
+ else {
388
+ const byteLen = rowCount * 8; // Float64 = 8 bytes
389
+ const aligned = new Float64Array(new Uint8Array(buffer, offset, byteLen).slice().buffer);
390
+ const values = [];
391
+ for (let i = 0; i < rowCount; i++) {
392
+ values.push(aligned[i]);
393
+ }
394
+ columnData.set(col.name, values);
395
+ offset += byteLen;
396
+ }
397
+ }
398
+ // Reconstruct row-oriented JSON
399
+ const result = [];
400
+ for (let i = 0; i < rowCount; i++) {
401
+ const row = {};
402
+ for (const col of columns) {
403
+ row[col.name] = columnData.get(col.name)[i];
404
+ }
405
+ result.push(row);
406
+ }
407
+ return result;
408
+ }
409
+ /**
410
+ * Deserialize buffer and convert to column-oriented format.
411
+ * More efficient for statistical computations.
412
+ */
413
+ function deserializeToColumns(buffer) {
414
+ const view = new DataView(buffer);
415
+ const headerLength = view.getUint32(0, true);
416
+ const headerBytes = new Uint8Array(buffer, 4, headerLength);
417
+ const header = JSON.parse(decodeString(headerBytes));
418
+ if (header.rowCount === 0)
419
+ return { rowCount: 0, columns: {} };
420
+ const { rowCount, columns: colMeta } = header;
421
+ let offset = 4 + headerLength;
422
+ const columns = {};
423
+ for (const col of colMeta) {
424
+ if (col.dtype === 'string') {
425
+ const byteLen = rowCount * 4;
426
+ const indices = new Int32Array(new Uint8Array(buffer, offset, byteLen).slice().buffer);
427
+ const values = [];
428
+ for (let i = 0; i < rowCount; i++) {
429
+ values.push(col.stringTable[indices[i]]);
430
+ }
431
+ columns[col.name] = { dtype: 'string', values };
432
+ offset += byteLen;
433
+ }
434
+ else {
435
+ const byteLen = rowCount * 8;
436
+ const arr = new Float64Array(new Uint8Array(buffer, offset, byteLen).slice().buffer);
437
+ const values = Array.from(arr);
438
+ columns[col.name] = { dtype: 'float64', values };
439
+ offset += byteLen;
440
+ }
441
+ }
442
+ return { rowCount, columns };
443
+ }
444
+
445
+ export { InferentialStats, PROGRESS_EVENT_NAME, deserializeFromBuffer, deserializeToColumns, getTransferables, serializeToBuffer };
446
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/bridge/serializer.ts","../src/InferentialStats.ts","../src/bridge/deserializer.ts"],"sourcesContent":[null,null,null],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Python code for classification/clustering functions.
3
+ */
4
+ export declare const KMEANS_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom sklearn.cluster import KMeans\nfrom sklearn.preprocessing import StandardScaler\n\ndef run_kmeans(data_json, variables_json, k, max_iterations=300, random_state=42):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n X = df[variables].apply(pd.to_numeric, errors='coerce').dropna()\n \n scaler = StandardScaler()\n X_scaled = scaler.fit_transform(X)\n \n model = KMeans(n_clusters=k, max_iter=max_iterations, random_state=random_state, n_init=10)\n labels = model.fit_predict(X_scaled)\n \n # Transform centers back to original scale\n centers_original = scaler.inverse_transform(model.cluster_centers_)\n \n centers = []\n for i in range(k):\n center = {}\n for j, var in enumerate(variables):\n center[var] = round(float(centers_original[i, j]), 6)\n centers.append({'cluster': i, 'center': center})\n \n unique, counts = np.unique(labels, return_counts=True)\n cluster_sizes = {int(u): int(c) for u, c in zip(unique, counts)}\n \n return json.dumps({\n 'labels': [int(l) for l in labels],\n 'centers': centers,\n 'inertia': round(float(model.inertia_), 6),\n 'iterations': int(model.n_iter_),\n 'clusterSizes': cluster_sizes\n })\n";
5
+ export declare const HIERARCHICAL_CLUSTER_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom scipy.cluster.hierarchy import linkage, fcluster, dendrogram\nfrom sklearn.preprocessing import StandardScaler\n\ndef run_hierarchical_cluster(data_json, variables_json, method='ward', metric='euclidean', n_clusters=None, distance_threshold=None):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n X = df[variables].apply(pd.to_numeric, errors='coerce').dropna()\n \n scaler = StandardScaler()\n X_scaled = scaler.fit_transform(X)\n \n Z = linkage(X_scaled, method=method, metric=metric)\n \n if n_clusters is not None:\n labels = fcluster(Z, t=n_clusters, criterion='maxclust')\n elif distance_threshold is not None:\n labels = fcluster(Z, t=distance_threshold, criterion='distance')\n else:\n labels = fcluster(Z, t=3, criterion='maxclust')\n \n labels = labels - 1 # 0-indexed\n \n unique, counts = np.unique(labels, return_counts=True)\n cluster_sizes = {int(u): int(c) for u, c in zip(unique, counts)}\n \n # Dendrogram data (truncated for large datasets)\n trunc = min(30, len(X_scaled))\n dend = dendrogram(Z, truncate_mode='lastp', p=trunc, no_plot=True)\n \n return json.dumps({\n 'labels': [int(l) for l in labels],\n 'nClusters': len(unique),\n 'linkageMatrix': [[round(float(x), 6) for x in row] for row in Z.tolist()],\n 'clusterSizes': cluster_sizes,\n 'dendrogramData': {\n 'icoord': [[round(float(x), 4) for x in row] for row in dend['icoord']],\n 'dcoord': [[round(float(x), 4) for x in row] for row in dend['dcoord']],\n 'leaves': [int(x) for x in dend['leaves']]\n }\n })\n";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Python code for compare means functions.
3
+ */
4
+ export declare const TTEST_INDEPENDENT_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom scipy import stats\n\ndef run_ttest_independent(data_json, variable, group_variable, group1_value, group2_value):\n df = pd.DataFrame(json.loads(data_json))\n \n g1 = pd.to_numeric(df[df[group_variable] == group1_value][variable], errors='coerce').dropna()\n g2 = pd.to_numeric(df[df[group_variable] == group2_value][variable], errors='coerce').dropna()\n \n # Levene's test for equality of variances\n levene_stat, levene_p = stats.levene(g1, g2)\n equal_var = levene_p > 0.05\n \n # T-test with equal variance\n t_eq, p_eq = stats.ttest_ind(g1, g2, equal_var=True)\n # T-test with unequal variance (Welch's)\n t_uneq, p_uneq = stats.ttest_ind(g1, g2, equal_var=False)\n \n mean_diff = float(g1.mean() - g2.mean())\n \n # Degrees of freedom\n df_eq = len(g1) + len(g2) - 2\n \n # Welch df\n s1_sq = g1.var(ddof=1)\n s2_sq = g2.var(ddof=1)\n n1, n2 = len(g1), len(g2)\n num = (s1_sq/n1 + s2_sq/n2)**2\n denom = (s1_sq/n1)**2/(n1-1) + (s2_sq/n2)**2/(n2-1)\n df_welch = float(num/denom)\n \n # Confidence intervals\n se_eq = float(np.sqrt(((n1-1)*s1_sq + (n2-1)*s2_sq)/(n1+n2-2) * (1/n1 + 1/n2)))\n se_uneq = float(np.sqrt(s1_sq/n1 + s2_sq/n2))\n \n ci_eq = stats.t.interval(0.95, df_eq, loc=mean_diff, scale=se_eq)\n ci_uneq = stats.t.interval(0.95, df_welch, loc=mean_diff, scale=se_uneq)\n \n def make_result(t_stat, df_val, p_val, ci):\n return {\n 'tStatistic': round(float(t_stat), 6),\n 'degreesOfFreedom': round(float(df_val), 6),\n 'pValue': float(p_val),\n 'meanDifference': round(mean_diff, 6),\n 'confidenceInterval': [round(float(ci[0]), 6), round(float(ci[1]), 6)],\n 'group1Mean': round(float(g1.mean()), 6),\n 'group1Std': round(float(g1.std(ddof=1)), 6),\n 'group1N': n1,\n 'group2Mean': round(float(g2.mean()), 6),\n 'group2Std': round(float(g2.std(ddof=1)), 6),\n 'group2N': n2\n }\n \n return json.dumps({\n 'leveneTest': {\n 'statistic': round(float(levene_stat), 6),\n 'pValue': float(levene_p),\n 'equalVariance': bool(equal_var)\n },\n 'equalVariance': make_result(t_eq, df_eq, p_eq, ci_eq),\n 'unequalVariance': make_result(t_uneq, df_welch, p_uneq, ci_uneq)\n })\n";
5
+ export declare const TTEST_PAIRED_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom scipy import stats\n\ndef run_ttest_paired(data_json, variable1, variable2):\n df = pd.DataFrame(json.loads(data_json))\n \n v1 = pd.to_numeric(df[variable1], errors='coerce').dropna()\n v2 = pd.to_numeric(df[variable2], errors='coerce').dropna()\n \n # Align by index\n common = v1.index.intersection(v2.index)\n v1 = v1.loc[common]\n v2 = v2.loc[common]\n \n diff = v1 - v2\n n = len(diff)\n \n t_stat, p_val = stats.ttest_rel(v1, v2)\n \n mean_diff = float(diff.mean())\n std_diff = float(diff.std(ddof=1))\n se = std_diff / np.sqrt(n)\n ci = stats.t.interval(0.95, n-1, loc=mean_diff, scale=se)\n \n return json.dumps({\n 'tStatistic': round(float(t_stat), 6),\n 'degreesOfFreedom': n - 1,\n 'pValue': float(p_val),\n 'meanDifference': round(mean_diff, 6),\n 'stdDifference': round(std_diff, 6),\n 'confidenceInterval': [round(float(ci[0]), 6), round(float(ci[1]), 6)],\n 'mean1': round(float(v1.mean()), 6),\n 'mean2': round(float(v2.mean()), 6),\n 'n': n\n })\n";
6
+ export declare const ANOVA_ONEWAY_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom scipy import stats\n\ndef run_anova_oneway(data_json, variable, group_variable):\n df = pd.DataFrame(json.loads(data_json))\n \n groups = df.groupby(group_variable)[variable].apply(\n lambda x: pd.to_numeric(x, errors='coerce').dropna().tolist()\n )\n \n group_arrays = [np.array(g) for g in groups.values if len(g) > 0]\n group_names = [str(name) for name, g in zip(groups.index, groups.values) if len(g) > 0]\n \n f_stat, p_val = stats.f_oneway(*group_arrays)\n \n # Compute detailed ANOVA table\n grand_mean = np.concatenate(group_arrays).mean()\n n_total = sum(len(g) for g in group_arrays)\n k = len(group_arrays)\n \n ss_between = sum(len(g) * (g.mean() - grand_mean)**2 for g in group_arrays)\n ss_within = sum(((g - g.mean())**2).sum() for g in group_arrays)\n \n df_between = k - 1\n df_within = n_total - k\n \n ms_between = ss_between / df_between\n ms_within = ss_within / df_within\n \n ss_total = ss_between + ss_within\n eta_sq = ss_between / ss_total if ss_total > 0 else 0\n \n group_stats = []\n for name, arr in zip(group_names, group_arrays):\n group_stats.append({\n 'group': name,\n 'n': len(arr),\n 'mean': round(float(arr.mean()), 6),\n 'std': round(float(arr.std(ddof=1)), 6)\n })\n \n return json.dumps({\n 'fStatistic': round(float(f_stat), 6),\n 'pValue': float(p_val),\n 'degreesOfFreedomBetween': df_between,\n 'degreesOfFreedomWithin': df_within,\n 'sumOfSquaresBetween': round(float(ss_between), 6),\n 'sumOfSquaresWithin': round(float(ss_within), 6),\n 'meanSquareBetween': round(float(ms_between), 6),\n 'meanSquareWithin': round(float(ms_within), 6),\n 'groupStats': group_stats,\n 'etaSquared': round(float(eta_sq), 6)\n })\n";
7
+ export declare const POSTHOC_TUKEY_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom statsmodels.stats.multicomp import pairwise_tukeyhsd\n\ndef run_posthoc_tukey(data_json, variable, group_variable, alpha=0.05):\n df = pd.DataFrame(json.loads(data_json))\n \n df[variable] = pd.to_numeric(df[variable], errors='coerce')\n df = df.dropna(subset=[variable])\n \n result = pairwise_tukeyhsd(df[variable], df[group_variable], alpha=alpha)\n \n comparisons = []\n for i in range(len(result.summary().data) - 1):\n row = result.summary().data[i + 1]\n comparisons.append({\n 'group1': str(row[0]),\n 'group2': str(row[1]),\n 'meanDifference': round(float(row[2]), 6),\n 'pValue': round(float(row[3]), 6),\n 'lowerCI': round(float(row[4]), 6),\n 'upperCI': round(float(row[5]), 6),\n 'reject': bool(row[6])\n })\n \n return json.dumps({\n 'comparisons': comparisons,\n 'alpha': alpha\n })\n";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Python code for descriptive statistics functions.
3
+ * These are executed inside Pyodide in the Web Worker.
4
+ */
5
+ export declare const FREQUENCIES_PY = "\nimport json\nimport pandas as pd\n\ndef run_frequencies(data_json, variable):\n df = pd.DataFrame(json.loads(data_json))\n series = df[variable]\n total = len(series)\n \n counts = series.value_counts(dropna=False)\n pcts = series.value_counts(normalize=True, dropna=False) * 100\n \n freqs = []\n cum_pct = 0\n for val in counts.index:\n count = int(counts[val])\n pct = float(pcts[val])\n cum_pct += pct\n freqs.append({\n 'value': str(val) if not isinstance(val, (int, float)) else val,\n 'count': count,\n 'percentage': round(pct, 4),\n 'cumulativePercentage': round(cum_pct, 4)\n })\n \n return json.dumps({\n 'variable': variable,\n 'totalCount': total,\n 'frequencies': freqs\n })\n";
6
+ export declare const DESCRIPTIVES_PY = "\nimport json\nimport pandas as pd\nfrom scipy import stats as sp_stats\n\ndef run_descriptives(data_json, variables_json):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n results = []\n for var in variables:\n col = pd.to_numeric(df[var], errors='coerce').dropna()\n desc = col.describe()\n results.append({\n 'variable': var,\n 'count': int(desc['count']),\n 'mean': round(float(desc['mean']), 6),\n 'std': round(float(desc['std']), 6),\n 'min': round(float(desc['min']), 6),\n 'max': round(float(desc['max']), 6),\n 'q25': round(float(desc['25%']), 6),\n 'q50': round(float(desc['50%']), 6),\n 'q75': round(float(desc['75%']), 6),\n 'skewness': round(float(sp_stats.skew(col)), 6),\n 'kurtosis': round(float(sp_stats.kurtosis(col)), 6)\n })\n \n return json.dumps({'statistics': results})\n";
7
+ export declare const CROSSTABS_PY = "\nimport json\nimport pandas as pd\nfrom scipy.stats import chi2_contingency\nimport numpy as np\n\ndef run_crosstabs(data_json, row_variable, col_variable):\n df = pd.DataFrame(json.loads(data_json))\n \n ct = pd.crosstab(df[row_variable], df[col_variable])\n chi2, p, dof, expected = chi2_contingency(ct)\n \n n = ct.values.sum()\n k = min(ct.shape) - 1\n cramers_v = float(np.sqrt(chi2 / (n * k))) if k > 0 else 0\n \n row_labels = [str(x) for x in ct.index.tolist()]\n col_labels = [str(x) for x in ct.columns.tolist()]\n \n table = []\n row_sums = ct.sum(axis=1)\n col_sums = ct.sum(axis=0)\n total = ct.values.sum()\n \n for i, rl in enumerate(row_labels):\n for j, cl in enumerate(col_labels):\n obs = int(ct.iloc[i, j])\n exp = float(expected[i, j])\n table.append({\n 'row': rl,\n 'col': cl,\n 'observed': obs,\n 'expected': round(exp, 4),\n 'rowPercentage': round(obs / float(row_sums.iloc[i]) * 100, 4) if row_sums.iloc[i] > 0 else 0,\n 'colPercentage': round(obs / float(col_sums.iloc[j]) * 100, 4) if col_sums.iloc[j] > 0 else 0,\n 'totalPercentage': round(obs / float(total) * 100, 4) if total > 0 else 0\n })\n \n return json.dumps({\n 'rowVariable': row_variable,\n 'colVariable': col_variable,\n 'table': table,\n 'rowLabels': row_labels,\n 'colLabels': col_labels,\n 'chiSquare': round(float(chi2), 6),\n 'degreesOfFreedom': int(dof),\n 'pValue': float(p),\n 'cramersV': round(cramers_v, 6)\n })\n";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Python code for dimension reduction functions.
3
+ */
4
+ export declare const EFA_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom factor_analyzer import FactorAnalyzer\nfrom factor_analyzer.factor_analyzer import calculate_bartlett_sphericity, calculate_kmo\n\ndef run_efa(data_json, variables_json, n_factors, rotation='varimax', method='minres'):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n X = df[variables].apply(pd.to_numeric, errors='coerce').dropna()\n \n # KMO and Bartlett tests\n kmo_all, kmo_model = calculate_kmo(X)\n chi2, p_value = calculate_bartlett_sphericity(X)\n \n fa = FactorAnalyzer(n_factors=n_factors, rotation=rotation, method=method)\n fa.fit(X)\n \n loadings = fa.loadings_\n loadings_dict = {}\n for i, var in enumerate(variables):\n loadings_dict[var] = [round(float(x), 6) for x in loadings[i]]\n \n ev, v = fa.get_factor_variance()\n \n communalities = fa.get_communalities()\n uniquenesses = fa.get_uniquenesses()\n \n comm_dict = {}\n uniq_dict = {}\n for i, var in enumerate(variables):\n comm_dict[var] = round(float(communalities[i]), 6)\n uniq_dict[var] = round(float(uniquenesses[i]), 6)\n \n eigenvalues = fa.get_eigenvalues()[0]\n \n return json.dumps({\n 'loadings': loadings_dict,\n 'eigenvalues': [round(float(x), 6) for x in eigenvalues],\n 'variance': [round(float(x), 6) for x in ev],\n 'cumulativeVariance': [round(float(sum(v[:i+1])), 6) for i in range(len(v))],\n 'communalities': comm_dict,\n 'uniquenesses': uniq_dict,\n 'nFactors': n_factors,\n 'rotation': rotation,\n 'kmo': round(float(kmo_model), 6),\n 'bartlettChi2': round(float(chi2), 6),\n 'bartlettPValue': float(p_value)\n })\n";
5
+ export declare const PCA_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom sklearn.decomposition import PCA\nfrom sklearn.preprocessing import StandardScaler\n\ndef run_pca(data_json, variables_json, n_components=None, standardize=True):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n X = df[variables].apply(pd.to_numeric, errors='coerce').dropna()\n \n if standardize:\n scaler = StandardScaler()\n X_scaled = scaler.fit_transform(X)\n else:\n X_scaled = X.values\n \n if n_components is None:\n n_components = min(len(variables), len(X_scaled))\n \n pca = PCA(n_components=n_components)\n transformed = pca.fit_transform(X_scaled)\n \n loadings = {}\n for i, var in enumerate(variables):\n loadings[var] = [round(float(x), 6) for x in pca.components_[:, i]]\n \n cum_var = np.cumsum(pca.explained_variance_ratio_)\n \n return json.dumps({\n 'components': [[round(float(x), 6) for x in row] for row in transformed.tolist()],\n 'explainedVariance': [round(float(x), 6) for x in pca.explained_variance_],\n 'explainedVarianceRatio': [round(float(x), 6) for x in pca.explained_variance_ratio_],\n 'cumulativeVarianceRatio': [round(float(x), 6) for x in cum_var],\n 'loadings': loadings,\n 'singularValues': [round(float(x), 6) for x in pca.singular_values_],\n 'nComponents': n_components\n })\n";
6
+ export declare const MDS_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nfrom sklearn.manifold import MDS\nfrom sklearn.preprocessing import StandardScaler\n\ndef run_mds(data_json, variables_json, n_components=2, metric=True, max_iterations=300, random_state=42):\n df = pd.DataFrame(json.loads(data_json))\n variables = json.loads(variables_json)\n \n X = df[variables].apply(pd.to_numeric, errors='coerce').dropna()\n \n scaler = StandardScaler()\n X_scaled = scaler.fit_transform(X)\n \n mds = MDS(n_components=n_components, metric=metric, max_iter=max_iterations, random_state=random_state, normalized_stress='auto')\n coords = mds.fit_transform(X_scaled)\n \n return json.dumps({\n 'coordinates': [[round(float(x), 6) for x in row] for row in coords.tolist()],\n 'stress': round(float(mds.stress_), 6),\n 'nComponents': n_components\n })\n";
@@ -0,0 +1,6 @@
1
+ export { FREQUENCIES_PY, DESCRIPTIVES_PY, CROSSTABS_PY } from './descriptive';
2
+ export { TTEST_INDEPENDENT_PY, TTEST_PAIRED_PY, ANOVA_ONEWAY_PY, POSTHOC_TUKEY_PY } from './compare-means';
3
+ export { LINEAR_REGRESSION_PY, LOGISTIC_BINARY_PY, LOGISTIC_MULTINOMIAL_PY } from './regression';
4
+ export { KMEANS_PY, HIERARCHICAL_CLUSTER_PY } from './classify';
5
+ export { EFA_PY, PCA_PY, MDS_PY } from './dimension';
6
+ export { CRONBACH_ALPHA_PY } from './scale';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Python code for regression analysis functions.
3
+ */
4
+ export declare const LINEAR_REGRESSION_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nimport statsmodels.api as sm\n\ndef run_linear_regression(data_json, dependent, independents_json, add_constant=True):\n df = pd.DataFrame(json.loads(data_json))\n independents = json.loads(independents_json)\n \n y = pd.to_numeric(df[dependent], errors='coerce')\n X = df[independents].apply(pd.to_numeric, errors='coerce')\n \n mask = y.notna() & X.notna().all(axis=1)\n y = y[mask]\n X = X[mask]\n \n if add_constant:\n X = sm.add_constant(X)\n \n model = sm.OLS(y, X).fit()\n \n coefficients = []\n for i, name in enumerate(model.params.index):\n ci = model.conf_int().iloc[i]\n coefficients.append({\n 'variable': str(name),\n 'coefficient': round(float(model.params.iloc[i]), 6),\n 'stdError': round(float(model.bse.iloc[i]), 6),\n 'tStatistic': round(float(model.tvalues.iloc[i]), 6),\n 'pValue': float(model.pvalues.iloc[i]),\n 'confidenceInterval': [round(float(ci[0]), 6), round(float(ci[1]), 6)]\n })\n \n dw = float(sm.stats.stattools.durbin_watson(model.resid))\n \n return json.dumps({\n 'rSquared': round(float(model.rsquared), 6),\n 'adjustedRSquared': round(float(model.rsquared_adj), 6),\n 'fStatistic': round(float(model.fvalue), 6),\n 'fPValue': float(model.f_pvalue),\n 'coefficients': coefficients,\n 'residualStdError': round(float(np.sqrt(model.mse_resid)), 6),\n 'observations': int(model.nobs),\n 'degreesOfFreedom': int(model.df_resid),\n 'durbinWatson': round(dw, 6)\n })\n";
5
+ export declare const LOGISTIC_BINARY_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nimport statsmodels.api as sm\n\ndef run_logistic_binary(data_json, dependent, independents_json, add_constant=True):\n df = pd.DataFrame(json.loads(data_json))\n independents = json.loads(independents_json)\n \n y = pd.to_numeric(df[dependent], errors='coerce')\n X = df[independents].apply(pd.to_numeric, errors='coerce')\n \n mask = y.notna() & X.notna().all(axis=1)\n y = y[mask]\n X = X[mask]\n \n if add_constant:\n X = sm.add_constant(X)\n \n model = sm.Logit(y, X).fit(disp=0)\n \n coefficients = []\n ci = model.conf_int()\n for i, name in enumerate(model.params.index):\n coef = float(model.params.iloc[i])\n coefficients.append({\n 'variable': str(name),\n 'coefficient': round(coef, 6),\n 'stdError': round(float(model.bse.iloc[i]), 6),\n 'zStatistic': round(float(model.tvalues.iloc[i]), 6),\n 'pValue': float(model.pvalues.iloc[i]),\n 'oddsRatio': round(float(np.exp(coef)), 6),\n 'confidenceInterval': [round(float(ci.iloc[i, 0]), 6), round(float(ci.iloc[i, 1]), 6)]\n })\n \n return json.dumps({\n 'coefficients': coefficients,\n 'pseudoRSquared': round(float(model.prsquared), 6),\n 'logLikelihood': round(float(model.llf), 6),\n 'llrPValue': float(model.llr_pvalue),\n 'aic': round(float(model.aic), 6),\n 'bic': round(float(model.bic), 6),\n 'observations': int(model.nobs),\n 'convergence': bool(model.mle_retvals['converged'])\n })\n";
6
+ export declare const LOGISTIC_MULTINOMIAL_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\nimport statsmodels.api as sm\n\ndef run_logistic_multinomial(data_json, dependent, independents_json, reference_category=None):\n df = pd.DataFrame(json.loads(data_json))\n independents = json.loads(independents_json)\n \n X = df[independents].apply(pd.to_numeric, errors='coerce')\n y = df[dependent]\n \n mask = X.notna().all(axis=1) & y.notna()\n X = X[mask]\n y = y[mask]\n \n # Encode categories\n categories = sorted(y.unique().tolist(), key=str)\n if reference_category is not None:\n ref = str(reference_category)\n else:\n ref = str(categories[0])\n \n y_coded = pd.Categorical(y, categories=categories)\n y_dummies = pd.get_dummies(y_coded, drop_first=False)\n \n X_const = sm.add_constant(X)\n \n from sklearn.linear_model import LogisticRegression\n \n le_map = {str(c): i for i, c in enumerate(categories)}\n y_numeric = y.map(lambda x: le_map[str(x)])\n \n model = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=1000)\n model.fit(X, y_numeric)\n \n ref_idx = le_map[ref]\n non_ref_cats = [c for c in categories if str(c) != ref]\n \n coefficients = []\n for cat in non_ref_cats:\n cat_idx = le_map[str(cat)]\n for j, var_name in enumerate(independents):\n coef = float(model.coef_[cat_idx][j] - model.coef_[ref_idx][j])\n coefficients.append({\n 'category': str(cat),\n 'variable': var_name,\n 'coefficient': round(coef, 6),\n 'stdError': 0.0,\n 'zStatistic': 0.0,\n 'pValue': 0.0,\n 'oddsRatio': round(float(np.exp(coef)), 6),\n 'confidenceInterval': [0.0, 0.0]\n })\n \n # Log-likelihood\n proba = model.predict_proba(X)\n ll = float(np.sum(np.log(proba[np.arange(len(y_numeric)), y_numeric] + 1e-10)))\n n_params = len(non_ref_cats) * (len(independents) + 1)\n aic = -2 * ll + 2 * n_params\n bic_val = -2 * ll + n_params * np.log(len(y))\n \n return json.dumps({\n 'coefficients': coefficients,\n 'pseudoRSquared': round(float(model.score(X, y_numeric)), 6),\n 'logLikelihood': round(ll, 6),\n 'llrPValue': 0.0,\n 'aic': round(float(aic), 6),\n 'bic': round(float(bic_val), 6),\n 'categories': [str(c) for c in categories],\n 'referenceCategory': ref,\n 'observations': int(len(y))\n })\n";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Python code for scale/reliability functions.
3
+ */
4
+ export declare const CRONBACH_ALPHA_PY = "\nimport json\nimport pandas as pd\nimport numpy as np\n\ndef run_cronbach_alpha(data_json, items_json):\n df = pd.DataFrame(json.loads(data_json))\n items = json.loads(items_json)\n \n X = df[items].apply(pd.to_numeric, errors='coerce').dropna()\n \n n_items = len(items)\n n_obs = len(X)\n \n # Compute Cronbach's Alpha\n item_vars = X.var(ddof=1)\n total_var = X.sum(axis=1).var(ddof=1)\n alpha = (n_items / (n_items - 1)) * (1 - item_vars.sum() / total_var)\n \n # Standardized alpha (using correlation matrix)\n corr_matrix = X.corr()\n mean_r = (corr_matrix.sum().sum() - n_items) / (n_items * (n_items - 1))\n std_alpha = (n_items * mean_r) / (1 + (n_items - 1) * mean_r)\n \n # Item analysis\n item_analysis = []\n total_score = X.sum(axis=1)\n \n for item in items:\n item_col = X[item]\n other_items = [i for i in items if i != item]\n other_sum = X[other_items].sum(axis=1)\n \n # Corrected item-total correlation\n citc = float(item_col.corr(other_sum))\n \n # Alpha if item deleted\n if len(other_items) > 1:\n sub_X = X[other_items]\n sub_vars = sub_X.var(ddof=1)\n sub_total_var = sub_X.sum(axis=1).var(ddof=1)\n k = len(other_items)\n alpha_deleted = (k / (k - 1)) * (1 - sub_vars.sum() / sub_total_var)\n else:\n alpha_deleted = 0.0\n \n item_analysis.append({\n 'item': item,\n 'itemMean': round(float(item_col.mean()), 6),\n 'itemStd': round(float(item_col.std(ddof=1)), 6),\n 'correctedItemTotalCorrelation': round(citc, 6),\n 'alphaIfItemDeleted': round(float(alpha_deleted), 6)\n })\n \n return json.dumps({\n 'alpha': round(float(alpha), 6),\n 'standardizedAlpha': round(float(std_alpha), 6),\n 'nItems': n_items,\n 'nObservations': n_obs,\n 'itemAnalysis': item_analysis,\n 'interItemCorrelationMean': round(float(mean_r), 6)\n })\n";