Skip to content

Commit 2a6f568

Browse files
vcarlclaude
andauthored
Euno user deets (#166)
- adds a reasonably generalized `helpers/statistics.ts` with a lot more functionality than we're currently using. Seems nice to have tho? - moves queries out of component files and into models (maybe too early to do this, since queries are probably going to evolve a lot? 🤷) - small formatting tweaks to the SH user details display --------- Co-authored-by: Claude <[email protected]>
1 parent 41041df commit 2a6f568

17 files changed

+1111
-447
lines changed

app/helpers/statistics.test.ts

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
descriptiveStats,
4+
correlation,
5+
linearRegression,
6+
zScore,
7+
percentile,
8+
outliers,
9+
movingAverage,
10+
histogram,
11+
confidence,
12+
tTest,
13+
} from "./statistics";
14+
15+
describe("statistics helpers", () => {
16+
const sampleData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
17+
const smallData = [1, 2, 3];
18+
19+
describe("descriptiveStats", () => {
20+
it("calculates basic statistics correctly", () => {
21+
const stats = descriptiveStats(sampleData);
22+
23+
expect(stats.mean).toBe(5.5);
24+
expect(stats.median).toBe(5.5);
25+
expect(stats.min).toBe(1);
26+
expect(stats.max).toBe(10);
27+
expect(stats.count).toBe(10);
28+
expect(stats.sum).toBe(55);
29+
expect(stats.range).toBe(9);
30+
});
31+
32+
it("throws error for empty data", () => {
33+
expect(() => descriptiveStats([])).toThrow(
34+
"Cannot calculate statistics for empty dataset",
35+
);
36+
});
37+
});
38+
39+
describe("correlation", () => {
40+
it("calculates correlation correctly", () => {
41+
const x = [1, 2, 3, 4, 5];
42+
const y = [2, 4, 6, 8, 10];
43+
44+
const result = correlation(x, y);
45+
46+
expect(result.coefficient).toBeCloseTo(1, 10);
47+
expect(result.direction).toBe("positive");
48+
expect(result.strength).toBe("very strong");
49+
});
50+
51+
it("throws error for mismatched array lengths", () => {
52+
expect(() => correlation([1, 2], [1, 2, 3])).toThrow(
53+
"Arrays must have the same length",
54+
);
55+
});
56+
});
57+
58+
describe("linearRegression", () => {
59+
it("calculates linear regression correctly", () => {
60+
const x = [1, 2, 3, 4, 5];
61+
const y = [2, 4, 6, 8, 10];
62+
63+
const result = linearRegression(x, y);
64+
65+
expect(result.slope).toBe(2);
66+
expect(result.intercept).toBe(0);
67+
expect(result.predict(6)).toBe(12);
68+
});
69+
70+
it("throws error for insufficient data points", () => {
71+
expect(() => linearRegression([1], [2])).toThrow(
72+
"Need at least 2 data points for regression",
73+
);
74+
});
75+
});
76+
77+
describe("zScore", () => {
78+
it("calculates z-score correctly", () => {
79+
const score = zScore(7, 5, 2);
80+
expect(score).toBe(1);
81+
});
82+
83+
it("throws error for zero standard deviation", () => {
84+
expect(() => zScore(5, 5, 0)).toThrow(
85+
"Standard deviation cannot be zero",
86+
);
87+
});
88+
});
89+
90+
describe("percentile", () => {
91+
it("calculates percentiles correctly", () => {
92+
const p50 = percentile(sampleData, 0.5);
93+
const p25 = percentile(sampleData, 0.25);
94+
95+
expect(p50).toBe(5.5);
96+
expect(p25).toBeCloseTo(3.25, 0);
97+
});
98+
99+
it("throws error for invalid percentile values", () => {
100+
expect(() => percentile(sampleData, 1.5)).toThrow(
101+
"Percentile must be between 0 and 1",
102+
);
103+
expect(() => percentile(sampleData, -0.1)).toThrow(
104+
"Percentile must be between 0 and 1",
105+
);
106+
});
107+
});
108+
109+
describe("outliers", () => {
110+
it("detects outliers using IQR method", () => {
111+
const dataWithOutliers = [1, 2, 3, 4, 5, 100];
112+
const result = outliers(dataWithOutliers, "iqr");
113+
114+
expect(result).toContain(100);
115+
});
116+
117+
it("detects outliers using z-score method", () => {
118+
const dataWithOutliers = [1, 2, 3, 4, 5, 100];
119+
const result = outliers(dataWithOutliers, "zscore");
120+
121+
expect(result).toContain(100);
122+
});
123+
124+
it("returns empty array for empty data", () => {
125+
expect(outliers([])).toEqual([]);
126+
});
127+
});
128+
129+
describe("movingAverage", () => {
130+
it("calculates moving average correctly", () => {
131+
const result = movingAverage([1, 2, 3, 4, 5], 3);
132+
133+
expect(result).toEqual([2, 3, 4]);
134+
});
135+
136+
it("throws error for invalid window size", () => {
137+
expect(() => movingAverage(sampleData, 0)).toThrow("Invalid window size");
138+
expect(() => movingAverage(sampleData, 15)).toThrow(
139+
"Invalid window size",
140+
);
141+
});
142+
});
143+
144+
describe("histogram", () => {
145+
it("creates histogram correctly", () => {
146+
const result = histogram(smallData, 2);
147+
148+
expect(result).toHaveLength(2);
149+
expect(result[0].count + result[1].count).toBe(3);
150+
});
151+
152+
it("returns empty array for empty data", () => {
153+
expect(histogram([])).toEqual([]);
154+
});
155+
});
156+
157+
describe("confidence", () => {
158+
it("calculates confidence interval correctly", () => {
159+
const result = confidence(sampleData, 0.95);
160+
161+
expect(result.mean).toBe(5.5);
162+
expect(result.lower).toBeLessThan(result.mean);
163+
expect(result.upper).toBeGreaterThan(result.mean);
164+
});
165+
166+
it("throws error for empty data", () => {
167+
expect(() => confidence([])).toThrow(
168+
"Cannot calculate confidence interval for empty dataset",
169+
);
170+
});
171+
172+
it("throws error for invalid confidence level", () => {
173+
expect(() => confidence(sampleData, 0)).toThrow(
174+
"Confidence level must be between 0 and 1",
175+
);
176+
expect(() => confidence(sampleData, 1)).toThrow(
177+
"Confidence level must be between 0 and 1",
178+
);
179+
});
180+
});
181+
182+
describe("tTest", () => {
183+
it("performs t-test correctly", () => {
184+
const sample1 = [1, 2, 3, 4, 5];
185+
const sample2 = [6, 7, 8, 9, 10];
186+
187+
const result = tTest(sample1, sample2);
188+
189+
expect(typeof result.tStatistic).toBe("number");
190+
expect(typeof result.pValue).toBe("number");
191+
expect(typeof result.significant).toBe("boolean");
192+
});
193+
194+
it("throws error for empty samples", () => {
195+
expect(() => tTest([], [1, 2, 3])).toThrow(
196+
"Cannot perform t-test on empty samples",
197+
);
198+
expect(() => tTest([1, 2, 3], [])).toThrow(
199+
"Cannot perform t-test on empty samples",
200+
);
201+
});
202+
});
203+
});

0 commit comments

Comments
 (0)