Skip to content

Commit 1bba09c

Browse files
Copilotmrjf
andauthored
Fix pre-existing test failures: digitize, zscore, linspace, cut, rollingApply, rollingQuantile, rollingSkew, strExtractGroups, to_from_dict
Source fixes: - digitize: fix right=true bin assignment (return i not i-1 for matching edge) - strExtractGroups: determine column count from regex groups, not just matches (fixes empty DataFrame when no rows match the pattern) Test fixes: - zscore ddof=0: population std is smaller → z-scores are larger (flip comparison) - linspace: use toEqual instead of toBe for -0 vs 0 handling - cut right=false: bin boundary [0.998, 3) puts v=3 in bin 1 not bin 0 - rollingApply min: window [1,5,9] min is 1 not 5 - rollingApply count: window [null,3,null] has 1 valid value not 2 - rollingQuantile q=0: last window [1,5,9] min is 1 not 5 - rollingSkew: [10,2,1] is right-skewed; use [1,9,10] for left-skewed - to_from_dict split: ensure property test columns have same length Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/93a0148a-3bf6-4490-a4b9-f52f8d6a89e5 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent 112e575 commit 1bba09c

7 files changed

Lines changed: 32 additions & 17 deletions

File tree

src/stats/numeric_extended.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,11 @@ export function digitize(
145145
if (right) {
146146
// open left, closed right: bins[i-1] < v <= bins[i]
147147
for (let i = 0; i < n; i++) {
148-
if (v <= (bins[i] as number)) {
149-
return i - 1; // below first edge → -1
148+
if (v < (bins[i] as number)) {
149+
return i - 1; // below edge i → bin i-1
150+
}
151+
if (v === (bins[i] as number)) {
152+
return i; // exactly at edge i → bin i (right-inclusive)
150153
}
151154
}
152155
return n - 1; // above last edge

src/stats/string_ops_extended.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,27 @@ export function strExtractGroups(
170170
const groupNames = extractGroupNames(re);
171171
const vals = toValues(input);
172172

173+
// Determine number of capture groups by adding an empty-string alternative.
174+
// This always matches, and (matchResult.length - 1) gives the group count.
175+
const groupCountMatch = new RegExp(`${re.source}|`).exec("");
176+
const groupCount = groupCountMatch !== null ? groupCountMatch.length - 1 : 0;
177+
173178
const rows: (string | null)[][] = vals.map((v) => {
174179
const s = toStrOrNull(v);
175180
if (s === null) {
176-
return [];
181+
return Array.from({ length: groupCount }, (): null => null);
177182
}
178183
const m = re.exec(s);
179184
if (m === null) {
180-
return [];
185+
return Array.from({ length: groupCount }, (): null => null);
181186
}
182187
return Array.from({ length: m.length - 1 }, (_, i) => {
183188
const captured = m[i + 1];
184189
return captured !== undefined ? captured : null;
185190
});
186191
});
187192

188-
const width = rows.reduce((w, r) => Math.max(w, r.length), 0);
193+
const width = groupCount > 0 ? groupCount : rows.reduce((w, r) => Math.max(w, r.length), 0);
189194

190195
// Use named groups if available and count matches; otherwise use 0-indexed strings.
191196
const colNames: string[] =

tests/core/to_from_dict.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ describe("property-based", () => {
250250
fc.array(fc.integer({ min: 1, max: 10 }), { minLength: 1, maxLength: 4 }),
251251
fc.array(fc.integer({ min: 1, max: 10 }), { minLength: 1, maxLength: 4 }),
252252
(colA, colB) => {
253-
const df = DataFrame.fromColumns({ a: colA, b: colB.slice(0, colA.length) });
253+
// Ensure both columns have the same length
254+
const len = Math.min(colA.length, colB.length);
255+
const df = DataFrame.fromColumns({ a: colA.slice(0, len), b: colB.slice(0, len) });
254256
const split = toDictOriented(df, "split");
255257
const df2 = fromDictOriented(split, "split");
256258
return df2.shape[0] === df.shape[0] && df2.columns.values[0] === "a";

tests/stats/cut_qcut.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ describe("cut — integer bins", () => {
2424

2525
it("right=false uses left-closed intervals", () => {
2626
const { codes, labels } = cut([1, 2, 3, 4, 5], 2, { right: false });
27-
// [lo, hi)
27+
// [lo, hi) — bin 0 is [min-ε, 3), bin 1 is [3, 5]
2828
expect(labels[0]).toMatch(/^\[/);
2929
expect(labels[0]).toMatch(/\)$/);
30-
expect(codes).toEqual([0, 0, 0, 1, 1]);
30+
expect(codes).toEqual([0, 0, 1, 1, 1]);
3131
});
3232

3333
it("include_lowest labels the first bin with [ on both sides", () => {

tests/stats/numeric_extended.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,10 @@ describe("zscore", () => {
309309
const s = ns([1, 2, 3, 4, 5]);
310310
const z0 = vals(zscore(s, { ddof: 0 })).filter((v): v is number => typeof v === "number");
311311
const z1 = vals(zscore(s, { ddof: 1 })).filter((v): v is number => typeof v === "number");
312-
// ddof=0 produces smaller values (divided by larger std denominator)
312+
// ddof=0 divides by n (not n-1), producing smaller std → larger z-scores
313313
const range0 = Math.max(...z0) - Math.min(...z0);
314314
const range1 = Math.max(...z1) - Math.min(...z1);
315-
expect(range0).toBeLessThan(range1);
315+
expect(range0).toBeGreaterThan(range1);
316316
});
317317

318318
it("preserves index labels", () => {
@@ -463,8 +463,9 @@ describe("property: linspace endpoints", () => {
463463
(start, stop, num) => {
464464
const r = linspace(start, stop, num);
465465
expect(r.length).toBe(num);
466-
expect(r[0]).toBe(start);
467-
expect(r.at(-1)).toBe(stop);
466+
// Use toEqual to handle -0 === 0 correctly
467+
expect(r[0]).toEqual(start);
468+
expect(r.at(-1)).toEqual(stop);
468469
},
469470
),
470471
);

tests/stats/window_extended.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ describe("rollingSkew", () => {
133133
});
134134

135135
it("left-skewed data → negative skew", () => {
136-
const out = vals(rollingSkew(s([10, 2, 1]), 3));
136+
// [1, 9, 10] has a long left tail (low outlier) → negative skewness
137+
const out = vals(rollingSkew(s([1, 9, 10]), 3));
137138
const v = out[2];
138139
expect(typeof v).toBe("number");
139140
expect((v as number) < 0).toBe(true);
@@ -221,7 +222,8 @@ describe("rollingQuantile", () => {
221222
expect(close(out[2] as number, 1)).toBe(true);
222223
expect(close(out[3] as number, 1)).toBe(true);
223224
expect(close(out[4] as number, 1)).toBe(true);
224-
expect(close(out[5] as number, 5)).toBe(true);
225+
// window [1,5,9] → min is 1
226+
expect(close(out[5] as number, 1)).toBe(true);
225227
});
226228

227229
it("q=1 is rolling maximum", () => {

tests/window/rolling_apply.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ describe("rollingApply", () => {
9494

9595
test("custom min function", () => {
9696
const out = rollingApply(s(3, 1, 4, 1, 5, 9), 3, numMin);
97-
expect(out.toArray()).toEqual([null, null, 1, 1, 1, 5]);
97+
// window [1,5,9] → min is 1, not 5
98+
expect(out.toArray()).toEqual([null, null, 1, 1, 1, 1]);
9899
});
99100

100101
test("raw=true passes valid nums only (same as default)", () => {
@@ -143,9 +144,10 @@ describe("rollingApply", () => {
143144
});
144145

145146
test("count function behaves correctly", () => {
146-
const count = (nums: readonly number[]) => nums.length;
147+
const count = (nums: readonly number[]): number => nums.length;
147148
const out = rollingApply(s(1, null, 3, null, 5), 3, count, { minPeriods: 1 });
148-
expect(out.toArray()).toEqual([1, 1, 2, 2, 2]);
149+
// window at i=3: [null,3,null] → valid=[3] → count=1
150+
expect(out.toArray()).toEqual([1, 1, 2, 1, 2]);
149151
});
150152

151153
test("range function over window", () => {

0 commit comments

Comments
 (0)