Skip to content

Commit 7f59634

Browse files
authored
Merge pull request #207 from githubnext/autoloop/build-tsb-pandas-typescript-migration-42dbd9e9
[Autoloop] [Autoloop: build-tsb-pandas-typescript-migration]
2 parents bd00bfb + 2477cb9 commit 7f59634

8 files changed

Lines changed: 2378 additions & 21 deletions

File tree

playground/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ <h3><a href="testing.html" style="color: var(--accent); text-decoration: none;">
449449
<p>assertSeriesEqual / assertFrameEqual / assertIndexEqual — rich assertion helpers for use in test suites. Numeric tolerance, checkLike column-order mode, dtype checks, AssertionError with detailed diff messages. Mirrors pandas.testing.</p>
450450
<div class="status done">✅ Complete</div>
451451
</div>
452+
<div class="feature-card">
453+
<h3><a href="style.html" style="color: var(--accent); text-decoration: none;">🎨 Styler — DataFrame Style API</a></h3>
454+
<p>dataFrameStyle(df) · highlightMax / highlightMin / highlightNull / highlightBetween · backgroundGradient / textGradient · barChart · format / formatIndex · apply / applymap / map · setCaption / setTableStyles / hide · toHtml / toLatex. Mirrors pandas.DataFrame.style (Styler).</p>
455+
<div class="status done">✅ Complete</div>
456+
</div>
452457
</section>
453458
<div class="features-grid">
454459
<div class="feature-card">

playground/style.html

Lines changed: 416 additions & 0 deletions
Large diffs are not rendered by default.

src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,17 @@ export type {
668668
AssertFrameEqualOptions,
669669
AssertIndexEqualOptions,
670670
} from "./testing/index.ts";
671+
export { Styler, dataFrameStyle } from "./stats/index.ts";
672+
export type {
673+
CellProps,
674+
TableStyle,
675+
StyleRecord,
676+
ValueFormatter,
677+
ColSubset,
678+
AxisStyleFn,
679+
ElementStyleFn,
680+
HighlightOptions,
681+
HighlightBetweenOptions,
682+
GradientOptions,
683+
BarOptions,
684+
} from "./stats/index.ts";

src/stats/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,17 @@ export type {
486486
ResampleAggFn,
487487
ResampleOptions,
488488
} from "./resample.ts";
489+
export { Styler, dataFrameStyle } from "./style.ts";
490+
export type {
491+
CellProps,
492+
TableStyle,
493+
StyleRecord,
494+
ValueFormatter,
495+
ColSubset,
496+
AxisStyleFn,
497+
ElementStyleFn,
498+
HighlightOptions,
499+
HighlightBetweenOptions,
500+
GradientOptions,
501+
BarOptions,
502+
} from "./style.ts";

src/stats/resample.ts

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ function isMissing(v: Scalar): boolean {
108108
// ─── helpers: date coercion ────────────────────────────────────────────────────
109109

110110
function toDate(v: Label): Date | null {
111-
if (v instanceof Date) return v;
111+
if (v instanceof Date) {
112+
return v;
113+
}
112114
if (typeof v === "string" || typeof v === "number") {
113115
const d = new Date(v as string | number);
114116
return Number.isNaN(d.getTime()) ? null : d;
@@ -246,15 +248,19 @@ function nextGroupKey(ts: number, freq: string): number {
246248
*/
247249
function keyToLabel(key: number, freq: string, label: ResampleLabel): number {
248250
const dflt = freqDefaultLabel(freq);
249-
if (label === dflt) return key;
251+
if (label === dflt) {
252+
return key;
253+
}
250254

251255
if (label === "right") {
252256
// User wants right label on a left-default freq → next bin start
253257
return nextGroupKey(key, freq);
254258
}
255259

256260
// User wants left label on a right-default freq (W*, ME, QE, YE/AE)
257-
if (freq.startsWith("W")) return key - 6 * MS_D; // anchor → Mon/+1
261+
if (freq.startsWith("W")) {
262+
return key - 6 * MS_D; // anchor → Mon/+1
263+
}
258264
if (freq === "ME") {
259265
const d = new Date(key);
260266
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
@@ -283,7 +289,9 @@ function buildGroups(index: Index<Label>, freq: string): Groups {
283289
for (let i = 0; i < index.size; i++) {
284290
const label = index.at(i) as Label;
285291
const d = toDate(label);
286-
if (d === null) continue;
292+
if (d === null) {
293+
continue;
294+
}
287295
const key = binGroupKey(d, freq);
288296
let arr = map.get(key);
289297
if (arr === undefined) {
@@ -317,25 +325,33 @@ function aggNums(vals: readonly Scalar[]): number[] {
317325

318326
function aggSum(vals: readonly Scalar[]): Scalar {
319327
const ns = aggNums(vals);
320-
if (ns.length === 0) return Number.NaN;
328+
if (ns.length === 0) {
329+
return Number.NaN;
330+
}
321331
return ns.reduce((a, b) => a + b, 0);
322332
}
323333

324334
function aggMean(vals: readonly Scalar[]): Scalar {
325335
const ns = aggNums(vals);
326-
if (ns.length === 0) return Number.NaN;
336+
if (ns.length === 0) {
337+
return Number.NaN;
338+
}
327339
return ns.reduce((a, b) => a + b, 0) / ns.length;
328340
}
329341

330342
function aggMin(vals: readonly Scalar[]): Scalar {
331343
const c = vals.filter((v): v is Exclude<Scalar, null | undefined> => !isMissing(v));
332-
if (c.length === 0) return Number.NaN;
344+
if (c.length === 0) {
345+
return Number.NaN;
346+
}
333347
return c.reduce((a, b) => (a < b ? a : b));
334348
}
335349

336350
function aggMax(vals: readonly Scalar[]): Scalar {
337351
const c = vals.filter((v): v is Exclude<Scalar, null | undefined> => !isMissing(v));
338-
if (c.length === 0) return Number.NaN;
352+
if (c.length === 0) {
353+
return Number.NaN;
354+
}
339355
return c.reduce((a, b) => (a > b ? a : b));
340356
}
341357

@@ -344,28 +360,38 @@ function aggCount(vals: readonly Scalar[]): Scalar {
344360
}
345361

346362
function aggFirst(vals: readonly Scalar[]): Scalar {
347-
for (const v of vals) if (!isMissing(v)) return v;
363+
for (const v of vals) {
364+
if (!isMissing(v)) {
365+
return v;
366+
}
367+
}
348368
return Number.NaN;
349369
}
350370

351371
function aggLast(vals: readonly Scalar[]): Scalar {
352372
for (let i = vals.length - 1; i >= 0; i--) {
353373
const v = vals[i]!;
354-
if (!isMissing(v)) return v;
374+
if (!isMissing(v)) {
375+
return v;
376+
}
355377
}
356378
return Number.NaN;
357379
}
358380

359381
function aggStd(vals: readonly Scalar[]): Scalar {
360382
const ns = aggNums(vals);
361-
if (ns.length < 2) return Number.NaN;
383+
if (ns.length < 2) {
384+
return Number.NaN;
385+
}
362386
const m = ns.reduce((a, b) => a + b, 0) / ns.length;
363387
return Math.sqrt(ns.reduce((s, v) => s + (v - m) ** 2, 0) / (ns.length - 1));
364388
}
365389

366390
function aggVar(vals: readonly Scalar[]): Scalar {
367391
const ns = aggNums(vals);
368-
if (ns.length < 2) return Number.NaN;
392+
if (ns.length < 2) {
393+
return Number.NaN;
394+
}
369395
const m = ns.reduce((a, b) => a + b, 0) / ns.length;
370396
return ns.reduce((s, v) => s + (v - m) ** 2, 0) / (ns.length - 1);
371397
}
@@ -384,9 +410,13 @@ const BUILTIN: Readonly<Record<ResampleAggName, AggFn>> = {
384410
};
385411

386412
function resolveAgg(spec: ResampleAggName | ResampleAggFn): AggFn {
387-
if (typeof spec === "function") return spec;
413+
if (typeof spec === "function") {
414+
return spec;
415+
}
388416
const fn = BUILTIN[spec];
389-
if (!fn) throw new Error(`Unknown resample aggregation: "${spec}"`);
417+
if (!fn) {
418+
throw new Error(`Unknown resample aggregation: "${spec}"`);
419+
}
390420
return fn;
391421
}
392422

@@ -447,7 +477,7 @@ export class SeriesResampler {
447477
return new Series<Scalar>({ data: [], index: new Index<Label>([]), name: this._s.name });
448478
}
449479
const vals = this._s.values;
450-
const binKeys = allKeys(keys[0]!, keys[keys.length - 1]!, this._freq);
480+
const binKeys = allKeys(keys[0]!, keys.at(-1)!, this._freq);
451481
const data: Scalar[] = binKeys.map((k) => {
452482
const positions = map.get(k) ?? [];
453483
return fn(positions.map((p) => vals[p] as Scalar));
@@ -521,7 +551,7 @@ export class SeriesResampler {
521551
return DataFrame.fromColumns({ open: [], high: [], low: [], close: [] });
522552
}
523553
const vals = this._s.values;
524-
const binKeys = allKeys(keys[0]!, keys[keys.length - 1]!, this._freq);
554+
const binKeys = allKeys(keys[0]!, keys.at(-1)!, this._freq);
525555

526556
const open: Scalar[] = [];
527557
const high: Scalar[] = [];
@@ -596,11 +626,13 @@ export class DataFrameResampler {
596626

597627
if (keys.length === 0) {
598628
const emptyCols: Record<string, readonly Scalar[]> = {};
599-
for (const c of colNames) emptyCols[c] = [];
629+
for (const c of colNames) {
630+
emptyCols[c] = [];
631+
}
600632
return DataFrame.fromColumns(emptyCols);
601633
}
602634

603-
const binKeys = allKeys(keys[0]!, keys[keys.length - 1]!, this._freq);
635+
const binKeys = allKeys(keys[0]!, keys.at(-1)!, this._freq);
604636
const idx = buildDateIndex(binKeys, this._freq, this._label);
605637
const colData: Record<string, Scalar[]> = {};
606638

@@ -676,7 +708,7 @@ export class DataFrameResampler {
676708
if (keys.length === 0) {
677709
return new Series<Scalar>({ data: [], index: new Index<Label>([]) });
678710
}
679-
const binKeys = allKeys(keys[0]!, keys[keys.length - 1]!, this._freq);
711+
const binKeys = allKeys(keys[0]!, keys.at(-1)!, this._freq);
680712
const data: Scalar[] = binKeys.map((k) => (map.get(k) ?? []).length);
681713
return new Series<Scalar>({ data, index: buildDateIndex(binKeys, this._freq, this._label) });
682714
}

0 commit comments

Comments
 (0)