Skip to content

Commit 29f95d9

Browse files
Copilotmrjf
andauthored
Fix CI typecheck errors after merge conflict resolution
Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/2715e479-20de-4885-bb80-bc9bbae1f654 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent d87cb10 commit 29f95d9

20 files changed

Lines changed: 187 additions & 47 deletions

src/core/align.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function alignSeries<L extends Scalar, R extends Scalar>(
125125
left: Series<L>,
126126
right: Series<R>,
127127
options: AlignSeriesOptions = {},
128-
): [Series<L>, Series<R>] {
128+
): [Series<Scalar>, Series<Scalar>] {
129129
const { join = "outer", fillValue = null } = options;
130130
const targetIdx = resolveIndex(left.index, right.index, join);
131131

src/core/base-index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ export class Index<T extends Label = Label> {
4949
return this._values.length;
5050
}
5151

52+
/** Alias for `size` to preserve array-like ergonomics. */
53+
get length(): number {
54+
return this._values.length;
55+
}
56+
5257
/** Shape tuple (always 1-D). */
5358
get shape(): [number] {
5459
return [this._values.length];
@@ -65,8 +70,8 @@ export class Index<T extends Label = Label> {
6570
}
6671

6772
/** Snapshot of the underlying values as a plain array. */
68-
get values(): readonly T[] {
69-
return this._values;
73+
get values(): T[] {
74+
return [...this._values];
7075
}
7176

7277
/** True when every label appears exactly once. */

src/core/frame.ts

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,44 @@ export class DataFrame {
9696

9797
// ─── construction ─────────────────────────────────────────────────────────
9898

99+
constructor(columns: ReadonlyMap<string, Series<Scalar>>, index: Index<Label>);
100+
constructor(columns: Readonly<Record<string, Series<Scalar>>>, options?: DataFrameOptions);
99101
/**
100102
* Low-level constructor. Prefer the static factory methods for typical use.
101103
*
102-
* @param columns - Ordered map of column name → Series (all same length and index).
103-
* @param index - Row index (must match each Series' length).
104+
* @param columns - Ordered map of column name → Series (all same length and index),
105+
* or a plain record of column name → Series.
106+
* @param indexOrOptions - Row index for map input, or options for record input.
104107
*/
105-
constructor(columns: ReadonlyMap<string, Series<Scalar>>, index: Index<Label>) {
106-
this._columns = columns;
107-
this.index = index;
108-
this.columns = new Index<string>([...columns.keys()]);
108+
constructor(
109+
columns: ReadonlyMap<string, Series<Scalar>> | Readonly<Record<string, Series<Scalar>>>,
110+
indexOrOptions?: Index<Label> | DataFrameOptions,
111+
) {
112+
if (columns instanceof Map) {
113+
if (!(indexOrOptions instanceof Index)) {
114+
throw new TypeError("DataFrame constructor requires an Index when columns is a Map");
115+
}
116+
this._columns = columns;
117+
this.index = indexOrOptions;
118+
this.columns = new Index<string>([...columns.keys()]);
119+
return;
120+
}
121+
122+
const entries = Object.entries(columns);
123+
const colMap = new Map<string, Series<Scalar>>();
124+
for (const [name, series] of entries) {
125+
colMap.set(name, series);
126+
}
127+
const options = indexOrOptions instanceof Index ? { index: indexOrOptions } : indexOrOptions;
128+
const firstSeries = entries[0]?.[1];
129+
const inferredRows = firstSeries?.size ?? 0;
130+
const rowIndex =
131+
options?.index !== undefined
132+
? resolveRowIndex(inferredRows, options.index)
133+
: (firstSeries?.index ?? defaultRowIndex(0));
134+
this._columns = options?.index !== undefined ? reindexColumns(colMap, rowIndex) : colMap;
135+
this.index = rowIndex;
136+
this.columns = new Index<string>([...colMap.keys()]);
109137
}
110138

111139
/**
@@ -254,6 +282,18 @@ export class DataFrame {
254282
return this._columns.has(name);
255283
}
256284

285+
/** Alias for {@link has}. */
286+
hasColumn(name: string): boolean {
287+
return this.has(name);
288+
}
289+
290+
/** Iterate `(columnName, Series)` pairs in column order. */
291+
*[Symbol.iterator](): IterableIterator<[string, Series<Scalar>]> {
292+
for (const entry of this._columns) {
293+
yield entry;
294+
}
295+
}
296+
257297
// ─── slicing ──────────────────────────────────────────────────────────────
258298

259299
/** Return the first `n` rows (default 5). */
@@ -848,7 +888,32 @@ function reindexColumns(
848888
): Map<string, Series<Scalar>> {
849889
const result = new Map<string, Series<Scalar>>();
850890
for (const [name, series] of colMap) {
851-
result.set(name, new Series({ data: series.values as Scalar[], index: newIndex }));
891+
if (series.size === newIndex.size) {
892+
result.set(
893+
name,
894+
new Series({
895+
data: series.values,
896+
index: newIndex,
897+
dtype: series.dtype,
898+
...(series.name !== null ? { name: series.name } : {}),
899+
}),
900+
);
901+
continue;
902+
}
903+
904+
const resized: Scalar[] = new Array<Scalar>(newIndex.size);
905+
for (let i = 0; i < newIndex.size; i++) {
906+
resized[i] = series.values[i] ?? null;
907+
}
908+
result.set(
909+
name,
910+
new Series({
911+
data: resized,
912+
index: newIndex,
913+
dtype: series.dtype,
914+
...(series.name !== null ? { name: series.name } : {}),
915+
}),
916+
);
852917
}
853918
return result;
854919
}

src/core/reindex.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function applyNearest(values: Scalar[], present: readonly boolean[]): Scalar[] {
198198
out[i] = rightVal[i];
199199
} else if (rd === -1) {
200200
out[i] = leftVal[i];
201-
} else if (rd <= ld) {
201+
} else if (rd !== undefined && ld !== undefined && rd <= ld) {
202202
// prefer right on tie
203203
out[i] = rightVal[i];
204204
} else {
@@ -252,7 +252,7 @@ export function reindexSeries<T extends Scalar>(
252252
series: Series<T>,
253253
newIndex: readonly Label[] | Index<Label>,
254254
options: ReindexSeriesOptions = {},
255-
): Series<T> {
255+
): Series<Scalar> {
256256
const { fillValue = null, method, limit } = options;
257257

258258
const newIdx = toIndex(newIndex);
@@ -268,16 +268,19 @@ export function reindexSeries<T extends Scalar>(
268268
const key = String(newLabels[i]);
269269
const positions = labelMap.get(key);
270270
if (positions !== undefined && positions.length > 0) {
271-
resultValues[i] = series.values[positions[0]] as Scalar;
272-
present[i] = true;
271+
const pos = positions[0];
272+
if (pos !== undefined) {
273+
resultValues[i] = series.values[pos] ?? null;
274+
present[i] = true;
275+
}
273276
}
274277
}
275278

276279
const finalValues =
277280
method !== undefined ? applyFillMethod(resultValues, present, method, limit) : resultValues;
278281

279-
return new Series<T>({
280-
data: finalValues as T[],
282+
return new Series<Scalar>({
283+
data: finalValues,
281284
index: newIdx,
282285
name: series.name,
283286
});

src/core/timestamp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ function freqMs(freq: string): number {
331331
if (upper === "D") return MS_PER_DAY;
332332
// Try "Nunit" pattern (e.g. "2H", "15T").
333333
const m = /^(\d+)(.+)$/.exec(freq);
334-
if (m !== null) {
334+
if (m !== null && m[1] !== undefined && m[2] !== undefined) {
335335
return Number(m[1]) * freqMs(m[2]);
336336
}
337337
throw new Error(`Timestamp.floor/ceil/round: unsupported frequency "${freq}"`);

src/groupby/groupby.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Index } from "../core/index.ts";
2020
import { RangeIndex } from "../core/index.ts";
2121
import { DataFrame } from "../core/index.ts";
2222
import { Series } from "../core/index.ts";
23+
import { NamedAgg, type NamedAggSpec } from "./named_agg.ts";
2324
import type { Label, Scalar } from "../types.ts";
2425

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

308+
/**
309+
* Aggregate using pandas-style named aggregation.
310+
* Keys in `spec` become output column names.
311+
*/
312+
aggNamed(spec: NamedAggSpec, asIndex = true): DataFrame {
313+
const groupKeys = this._groups.map((g) => g.key);
314+
const resultCols: Record<string, Scalar[]> = {};
315+
316+
if (!asIndex) {
317+
if (this._by.length === 1) {
318+
const byCol = this._by[0];
319+
if (byCol !== undefined) {
320+
resultCols[byCol] = groupKeys.slice();
321+
}
322+
} else {
323+
for (const by of this._by) {
324+
resultCols[by] = [];
325+
}
326+
for (const g of this._groups) {
327+
const parts = String(g.key).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, named] of Object.entries(spec)) {
339+
if (!(named instanceof NamedAgg)) {
340+
throw new TypeError(`aggNamed: "${outCol}" must be a NamedAgg`);
341+
}
342+
const src = named.column;
343+
const fn = resolveAgg(named.aggfunc as AggName | AggFn);
344+
const srcSeries = this._df.col(src);
345+
const srcVals = srcSeries.values as readonly Scalar[];
346+
resultCols[outCol] = this._groups.map((g) => fn(g.positions.map((p) => srcVals[p] as Scalar)));
347+
}
348+
349+
const rowIdx: Index<Label> = asIndex ? new Index<Label>(groupKeys) : defaultIndex(groupKeys.length);
350+
return DataFrame.fromColumns(resultCols as Record<string, readonly Scalar[]>, { index: rowIdx });
351+
}
352+
307353
/** Shorthand for `agg("sum")` — numeric columns only, like pandas. */
308354
sum(): DataFrame {
309355
const cols = this._numericValueCols();

src/groupby/named_agg.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ export class NamedAgg {
4040
/** Source column to read from the DataFrame. */
4141
readonly column: string;
4242
/** Aggregation to apply — a built-in name or a custom function. */
43-
readonly aggfunc: AggName | AggFn;
43+
readonly aggfunc: AggName | AggFn | string;
4444

45-
constructor(column: string, aggfunc: AggName | AggFn) {
45+
constructor(column: string, aggfunc: AggName | AggFn | string) {
4646
this.column = column;
4747
this.aggfunc = aggfunc;
4848
}
@@ -59,7 +59,7 @@ export class NamedAgg {
5959
* });
6060
* ```
6161
*/
62-
export function namedAgg(column: string, aggfunc: AggName | AggFn): NamedAgg {
62+
export function namedAgg(column: string, aggfunc: AggName | AggFn | string): NamedAgg {
6363
return new NamedAgg(column, aggfunc);
6464
}
6565

src/io/json_normalize.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ export function jsonNormalize(
310310
let currentRecords: readonly JsonObject[] = records;
311311
for (let i = 0; i < pathList.length; i++) {
312312
const path = pathList[i];
313+
if (path === undefined) {
314+
continue;
315+
}
313316
const isFinal = i === pathList.length - 1;
314317
if (isFinal) {
315318
rows = normalizeWithPath(
@@ -343,7 +346,7 @@ export function jsonNormalize(
343346

344347
// ── Build column sets and DataFrame ───────────────────────────────────────
345348
if (rows.length === 0) {
346-
return new DataFrame({});
349+
return DataFrame.fromColumns({});
347350
}
348351

349352
// Union of all column names (preserve first-seen insertion order)
@@ -368,18 +371,14 @@ export function jsonNormalize(
368371
for (const [col, vals] of Object.entries(colData)) {
369372
let dtype: Dtype;
370373
if (vals.every((v) => v === null || typeof v === "number")) {
371-
dtype = vals.some((v) => v !== null && !Number.isInteger(v))
372-
? new Dtype("float64")
373-
: new Dtype("int64");
374+
dtype = vals.some((v) => v !== null && !Number.isInteger(v)) ? Dtype.float64 : Dtype.int64;
374375
} else if (vals.every((v) => v === null || typeof v === "boolean")) {
375-
dtype = new Dtype("bool");
376+
dtype = Dtype.bool;
376377
} else {
377-
dtype = new Dtype("object");
378+
dtype = Dtype.object;
378379
}
379-
seriesMap[col] = new Series(vals, { name: col, dtype });
380+
seriesMap[col] = new Series({ data: vals, name: col, dtype });
380381
}
381382

382-
return new DataFrame(seriesMap, {
383-
index: new RangeIndex(rows.length),
384-
});
383+
return new DataFrame(seriesMap, { index: new RangeIndex(rows.length) });
385384
}

src/stats/clip_with_bounds.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ import type { Label, Scalar } from "../types.ts";
2121
// ─── public types ─────────────────────────────────────────────────────────────
2222

2323
/** A scalar numeric bound, a positional array, or a Series aligned by label. */
24-
export type BoundArg = number | null | undefined | readonly (number | null)[] | Series<Scalar>;
24+
export type BoundArg =
25+
| number
26+
| null
27+
| undefined
28+
| readonly (number | null)[]
29+
| Series<Scalar>
30+
| DataFrame;
2531

2632
/** Options for {@link clipSeriesWithBounds}. */
2733
export interface SeriesClipBoundsOptions {

src/stats/crosstab.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function crosstab(
202202
const buckets: Array<Array<number[] | undefined>> | null =
203203
values !== undefined
204204
? Array.from({ length: nRows }, () =>
205-
Array.from<number[] | undefined>({ length: nCols }, () => undefined),
205+
Array.from({ length: nCols }, () => undefined as number[] | undefined),
206206
)
207207
: null;
208208

@@ -293,12 +293,12 @@ export function crosstab(
293293
const name = labelStr(colUniques[ci] as Scalar);
294294
const col = colData[name];
295295
if (col !== undefined) {
296-
col.push(col.reduce((s, v) => s + (v as number), 0));
296+
col.push(col.reduce((s: number, v) => s + (typeof v === "number" ? v : 0), 0));
297297
}
298298
}
299299
// Add an "All" column with row totals (and grand total in the last cell).
300300
const allCol: Scalar[] = cells.map((row) => row.reduce((s, v) => s + v, 0));
301-
allCol.push(allCol.reduce((s, v) => s + (v as number), 0));
301+
allCol.push(allCol.reduce((s: number, v) => s + (typeof v === "number" ? v : 0), 0));
302302
colData[marginsName] = allCol;
303303
finalRowLabels = [...rowLabels, marginsName as Label];
304304
}

0 commit comments

Comments
 (0)