Skip to content

Commit b62f28d

Browse files
Copilotmrjf
andauthored
fix: restore failing assign, named agg, memory usage, and combine-first tests
Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/78d95042-4c45-4c08-be04-02e8afe42b80 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent a08658f commit b62f28d

6 files changed

Lines changed: 95 additions & 26 deletions

File tree

src/core/frame.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,16 +302,27 @@ export class DataFrame {
302302
* const df2 = df.assign({ c: [7, 8, 9] });
303303
* ```
304304
*/
305-
assign(newCols: Readonly<Record<string, readonly Scalar[] | Series<Scalar>>>): DataFrame {
306-
const colMap = new Map<string, Series<Scalar>>(this._columns);
307-
for (const [name, val] of Object.entries(newCols)) {
308-
if (val instanceof Series) {
309-
colMap.set(name, val);
310-
} else {
311-
colMap.set(name, new Series({ data: val, index: this.index }));
312-
}
305+
assign(
306+
newCols: Readonly<
307+
Record<
308+
string,
309+
readonly Scalar[] | Series<Scalar> | ((df: DataFrame) => readonly Scalar[] | Series<Scalar>)
310+
>
311+
>,
312+
): DataFrame {
313+
let working: DataFrame = this;
314+
for (const [name, spec] of Object.entries(newCols)) {
315+
const resolved: readonly Scalar[] | Series<Scalar> =
316+
typeof spec === "function" ? spec(working) : spec;
317+
const series: Series<Scalar> =
318+
resolved instanceof Series
319+
? resolved
320+
: new Series({ data: resolved, index: working.index });
321+
const colMap = new Map<string, Series<Scalar>>(working._columns);
322+
colMap.set(name, series);
323+
working = new DataFrame(colMap, working.index);
313324
}
314-
return new DataFrame(colMap, this.index);
325+
return working;
315326
}
316327

317328
/** Drop one or more columns by name. Returns a new DataFrame. */

src/groupby/groupby.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { RangeIndex } from "../core/index.ts";
2121
import { DataFrame } from "../core/index.ts";
2222
import { Series } from "../core/index.ts";
2323
import type { Label, Scalar } from "../types.ts";
24+
import type { NamedAggSpec } from "./named_agg.ts";
2425

2526
// ─── types ────────────────────────────────────────────────────────────────────
2627

@@ -304,6 +305,53 @@ export class DataFrameGroupBy {
304305
return this._runAgg(colSpecs, asIndex);
305306
}
306307

308+
/**
309+
* Aggregate each group using named aggregation specs.
310+
*
311+
* Each key in `spec` becomes the output column name; the `NamedAgg` value
312+
* specifies which source column to aggregate and how.
313+
*/
314+
aggNamed(spec: NamedAggSpec, asIndex = true): DataFrame {
315+
const groupKeys = this._groups.map((g) => g.key);
316+
const resultCols: Record<string, Scalar[]> = {};
317+
318+
if (!asIndex) {
319+
if (this._by.length === 1) {
320+
const byCol = this._by[0] as string;
321+
resultCols[byCol] = groupKeys.slice();
322+
} else {
323+
for (const by of this._by) {
324+
resultCols[by] = [];
325+
}
326+
for (const g of this._groups) {
327+
const parts = (g.key as string).split("__SEP__");
328+
this._by.forEach((by, idx) => {
329+
const byArr = resultCols[by];
330+
if (byArr !== undefined) {
331+
byArr.push(parts[idx] ?? null);
332+
}
333+
});
334+
}
335+
}
336+
}
337+
338+
for (const [outCol, namedSpec] of Object.entries(spec)) {
339+
const fn = resolveAgg(namedSpec.aggfunc);
340+
const srcVals = this._df.col(namedSpec.column).values as readonly Scalar[];
341+
resultCols[outCol] = this._groups.map((g) =>
342+
fn(g.positions.map((p) => srcVals[p] as Scalar)),
343+
);
344+
}
345+
346+
const rowIdx: Index<Label> = asIndex
347+
? new Index<Label>(groupKeys)
348+
: defaultIndex(groupKeys.length);
349+
350+
return DataFrame.fromColumns(resultCols as Record<string, readonly Scalar[]>, {
351+
index: rowIdx,
352+
});
353+
}
354+
307355
/** Shorthand for `agg("sum")` — numeric columns only, like pandas. */
308356
sum(): DataFrame {
309357
const cols = this._numericValueCols();

src/stats/combine_first.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ export function combineFirstDataFrame(self: DataFrame, other: DataFrame): DataFr
149149
const resultColMap = new Map<string, Series<Scalar>>();
150150

151151
for (const colName of unionCols) {
152-
const selfHasCol = self.hasColumn(colName);
153-
const otherHasCol = other.hasColumn(colName);
152+
const selfHasCol = self.has(colName);
153+
const otherHasCol = other.has(colName);
154154

155155
const data: Scalar[] = [];
156156

src/stats/memory_usage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export function dataFrameMemoryUsage(
176176
values.push(indexMemoryBytes(df.index, deep));
177177
}
178178

179-
for (const [colName, col] of df) {
179+
for (const [colName, col] of df.items()) {
180180
names.push(colName);
181181
let mem: number;
182182
if (deep) {

tests/core/assign.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ describe("dataFrameAssign — property tests", () => {
207207
}
208208

209209
const df2 = dataFrameAssign(df, spec);
210-
expect(df2.columns.length).toBe(1 + nNew);
210+
expect(df2.columns.size).toBe(1 + nNew);
211211
expect(df2.shape[0]).toBe(nRows);
212212
},
213213
),
@@ -256,10 +256,10 @@ describe("dataFrameAssign — property tests", () => {
256256
fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 8 }),
257257
(arr) => {
258258
const df = DataFrame.fromColumns({ x: arr });
259-
const origColCount = df.columns.length;
259+
const origColCount = df.columns.size;
260260
dataFrameAssign(df, { y: arr, z: arr });
261261

262-
expect(df.columns.length).toBe(origColCount);
262+
expect(df.columns.size).toBe(origColCount);
263263
},
264264
),
265265
);

tests/stats/memory_usage.test.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ import {
1414
} from "../../src/index.ts";
1515
import type { Scalar } from "../../src/index.ts";
1616

17+
function dfFromSeries(columns: Record<string, Series<Scalar>>): DataFrame {
18+
const colMap = new Map<string, Series<Scalar>>();
19+
for (const [name, series] of Object.entries(columns)) {
20+
colMap.set(name, series);
21+
}
22+
const firstCol = Object.values(columns)[0];
23+
const index = new RangeIndex(firstCol?.size ?? 0);
24+
return new DataFrame(colMap, index);
25+
}
26+
1727
// ─── seriesMemoryUsage ────────────────────────────────────────────────────────
1828

1929
describe("seriesMemoryUsage", () => {
@@ -155,7 +165,7 @@ describe("seriesMemoryUsage", () => {
155165

156166
describe("dataFrameMemoryUsage", () => {
157167
it("returns Series indexed by column names with Index entry", () => {
158-
const df = new DataFrame({
168+
const df = dfFromSeries({
159169
a: new Series<number>({ data: [1, 2, 3], dtype: Dtype.int32 }),
160170
b: new Series<number>({ data: [4, 5, 6], dtype: Dtype.float64 }),
161171
});
@@ -167,15 +177,15 @@ describe("dataFrameMemoryUsage", () => {
167177
});
168178

169179
it("Index row = 24 bytes (RangeIndex)", () => {
170-
const df = new DataFrame({
180+
const df = dfFromSeries({
171181
x: new Series<number>({ data: [1, 2], dtype: Dtype.int64 }),
172182
});
173183
const mu = dataFrameMemoryUsage(df);
174184
expect(mu.at("Index")).toBe(24);
175185
});
176186

177187
it("column bytes = n × itemsize for fixed-width", () => {
178-
const df = new DataFrame({
188+
const df = dfFromSeries({
179189
a: new Series<number>({ data: [10, 20, 30], dtype: Dtype.int32 }),
180190
b: new Series<number>({ data: [1.0, 2.0, 3.0], dtype: Dtype.float64 }),
181191
});
@@ -185,15 +195,15 @@ describe("dataFrameMemoryUsage", () => {
185195
});
186196

187197
it("string column = n × 8 bytes (pointers) when shallow", () => {
188-
const df = new DataFrame({
198+
const df = dfFromSeries({
189199
s: new Series<Scalar>({ data: ["hello", "world"], dtype: Dtype.string }),
190200
});
191201
const mu = dataFrameMemoryUsage(df);
192202
expect(mu.at("s")).toBe(2 * 8);
193203
});
194204

195205
it("index=false excludes 'Index' row", () => {
196-
const df = new DataFrame({
206+
const df = dfFromSeries({
197207
a: new Series<number>({ data: [1, 2], dtype: Dtype.int32 }),
198208
});
199209
const mu = dataFrameMemoryUsage(df, { index: false });
@@ -203,7 +213,7 @@ describe("dataFrameMemoryUsage", () => {
203213
});
204214

205215
it("deep=true string column reflects actual string sizes", () => {
206-
const df = new DataFrame({
216+
const df = dfFromSeries({
207217
s: new Series<Scalar>({ data: ["hi", "hello"], dtype: Dtype.string }),
208218
});
209219
const mu = dataFrameMemoryUsage(df, { deep: true, index: false });
@@ -212,12 +222,12 @@ describe("dataFrameMemoryUsage", () => {
212222
});
213223

214224
it("result Series name is 'memory_usage'", () => {
215-
const df = new DataFrame({ x: new Series<number>({ data: [1], dtype: Dtype.int64 }) });
225+
const df = dfFromSeries({ x: new Series<number>({ data: [1], dtype: Dtype.int64 }) });
216226
expect(dataFrameMemoryUsage(df).name).toBe("memory_usage");
217227
});
218228

219229
it("total() matches sum of all column bytes (no index)", () => {
220-
const df = new DataFrame({
230+
const df = dfFromSeries({
221231
a: new Series<number>({ data: [1, 2, 3, 4], dtype: Dtype.int32 }),
222232
b: new Series<number>({ data: [1.0, 2.0, 3.0, 4.0], dtype: Dtype.float64 }),
223233
});
@@ -227,7 +237,7 @@ describe("dataFrameMemoryUsage", () => {
227237
});
228238

229239
it("empty DataFrame returns only Index row", () => {
230-
const df = new DataFrame({});
240+
const df = dfFromSeries({});
231241
const mu = dataFrameMemoryUsage(df);
232242
expect(mu.index.size).toBe(1);
233243
expect(mu.at("Index")).toBe(24);
@@ -245,7 +255,7 @@ describe("dataFrameMemoryUsage", () => {
245255
dtype: Dtype.int32,
246256
});
247257
});
248-
const df = new DataFrame(cols);
258+
const df = dfFromSeries(cols);
249259
const mu = dataFrameMemoryUsage(df);
250260
return mu.values.every((v) => v >= 0);
251261
},
@@ -265,7 +275,7 @@ describe("dataFrameMemoryUsage", () => {
265275
dtype: Dtype.int32,
266276
});
267277
});
268-
const df = new DataFrame(cols);
278+
const df = dfFromSeries(cols);
269279
const mu = dataFrameMemoryUsage(df, { index: false });
270280
const expected = colSizes.reduce((s, n) => s + n * 4, 0);
271281
return mu.sum() === expected;

0 commit comments

Comments
 (0)