diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9fd424d4..b5590d66a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,3 +59,20 @@ jobs: with: name: dist path: dist/ + + validate-python-examples: + name: Validate Python Examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: pip install pandas numpy + + - name: Validate Python playground examples + run: python scripts/validate-python-examples.py playground/ diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 6df604a6d..a7ede9cdf 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -36,6 +36,17 @@ jobs: - name: Bundle TypeScript compiler for offline playground run: cp node_modules/typescript/lib/typescript.js ./playground/dist/typescript.js + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: pip install pandas numpy + + - name: Validate Python playground examples + run: python scripts/validate-python-examples.py playground/ + - name: Setup Pages uses: actions/configure-pages@v5 with: diff --git a/playground/cat_accessor.html b/playground/cat_accessor.html index d354c4ae5..51f559f0e 100644 --- a/playground/cat_accessor.html +++ b/playground/cat_accessor.html @@ -212,6 +212,22 @@

1 · Categories and codes

console.log("nCategories:", size.cat.nCategories); console.log("ordered:", size.cat.ordered); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -237,6 +253,13 @@

2 · Nulls are encoded as -1

console.log("categories:", [...s.cat.categories.values]); console.log("codes:", s.cat.codes.toArray()); console.log("nCategories:", s.cat.nCategories); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -267,6 +290,18 @@

3 · Add and remove categories

const s3 = s.cat.removeCategories(["red"]); console.log("after remove values:", s3.toArray()); console.log("after remove cats:", [...s3.cat.categories.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -294,6 +329,16 @@

4 · Remove unused categories

const s3 = s2.cat.removeUnusedCategories(); console.log("after:", s3.cat.nCategories); console.log("cats:", [...s3.cat.categories.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -323,6 +368,18 @@

5 · Rename categories

// Array replacement (same order as categories) const s3 = s.cat.renameCategories(["H", "L", "M"]); console.log("array rename:", s3.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -354,6 +411,18 @@

6 · Set and reorder categories

const s3 = s.cat.reorderCategories(["XS", "S", "M", "L", "XL"], true); console.log("ordered:", s3.cat.ordered); console.log("custom order:", [...s3.cat.categories.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -380,6 +449,14 @@

7 · Value counts per category

const counts = gradesFull.cat.valueCounts(); console.log("categories:", [...gradesFull.cat.categories.values]); console.log("counts:", counts.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -411,6 +488,20 @@

8 · Ordered categories

// bad=0, good=1, excellent=2 console.log("codes:", ordered.cat.codes.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/concat.html b/playground/concat.html index fd19e7739..52f09e027 100644 --- a/playground/concat.html +++ b/playground/concat.html @@ -217,6 +217,13 @@

1 · Stack Series vertically (axis=0)

const result = concat([s1, s2]); console.log(result.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -242,6 +249,14 @@

2 · Stack DataFrames vertically (axis=0)

// join="outer" by default — fills missing columns with null const result = concat([df1, df2]); console.log(result.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -276,6 +291,21 @@

3 · Column-wise concat (axis=1)

console.log("\n=== DataFrame axis=1 ==="); console.log(concat([left, right], { axis: 1 }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -317,6 +347,27 @@

4 · Join modes — outer vs inner

console.log("\n=== axis=1, join='inner' (only shared row 'b') ==="); console.log(concat([s1, s2], { axis: 1, join: "inner" }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -349,6 +400,21 @@

5 · ignoreIndex — reset to RangeIndex

console.log("\n=== DataFrame ignoreIndex ==="); console.log(concat([df1, df2], { ignoreIndex: true }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -390,6 +456,29 @@

🧪 Scratch Pad

console.log("\n=== Side-by-side columns ==="); console.log(concat([names, q1rev, q2rev], { axis: 1 }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
diff --git a/playground/corr.html b/playground/corr.html index e5c918cf9..13fe13133 100644 --- a/playground/corr.html +++ b/playground/corr.html @@ -236,6 +236,25 @@

1 · Series pearsonCorr

// Require at least 5 valid pairs — returns NaN when fewer exist console.log("r (minPeriods=5):", pearsonCorr(c, d, { minPeriods: 5 })); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -277,6 +296,25 @@

2 · DataFrame corr matrix (dataFrameCorr)

console.log("height–weight r:", rHW.toFixed(4)); console.log("height–age r: ", rHA.toFixed(4)); console.log("diagonal:", [r.col("height").iat(0), r.col("weight").iat(1), r.col("age").iat(2)]); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -318,6 +356,25 @@

3 · DataFrame cov matrix (dataFrameCov)

const varPop = dataFrameCov(returns, { ddof: 0 }).col("AAPL").iat(0); console.log("\nAAPL sample variance:", varSample.toFixed(6)); console.log("AAPL population variance:", varPop.toFixed(6)); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -362,6 +419,31 @@

🧪 Scratch Pad

const vals = [...dataFrameCov(df).col(col).values].map(v => (v).toFixed(2)); console.log(col + ":", vals.join(" ")); } +
Click ▶ Run to execute
Ctrl+Enter to run
diff --git a/playground/csv.html b/playground/csv.html index e25df347c..898ae9cbd 100644 --- a/playground/csv.html +++ b/playground/csv.html @@ -215,6 +215,22 @@

1 · Parse a CSV string

console.log("score dtype:", df.col("score").dtype.name); console.log("names:", [...df.col("name").values]); console.log("ages :", [...df.col("age").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -246,6 +262,19 @@

2 · Missing values (NA)

const df = readCsv(csv, { naValues: ["?"] }); console.log("x:", [...df.col("x").values]); console.log("y:", [...df.col("y").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -275,6 +304,18 @@

3 · Quoted fields & custom separator

console.log("name[0]:", df.col("name").values[0]); console.log("note[1]:", df.col("note").values[1]); console.log("name[2]:", df.col("name").values[2]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -304,6 +345,18 @@

4 · Index column

console.log("index:", [...df.index.values]); console.log("columns:", [...df.columns.values]); console.log("city at A:", df.col("city").at("A")); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -336,6 +389,21 @@

5 · Limiting rows

const skip2 = readCsv(csv, { skipRows: 2 }); console.log("skipRows=2:", [...skip2.col("val").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -373,6 +441,25 @@

6 · Serialize with toCsv

const df2 = DataFrame.fromColumns({ x: [1, null, 3] }); console.log("--- semicolons + NA rep ---"); console.log(toCsv(df2, { sep: ";", index: false, naRep: "MISSING" })); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -407,6 +494,23 @@

7 · Round-trip

console.log("restored y:", [...restored.col("y").values]); console.log("restored label:", [...restored.col("label").values]); console.log("shapes match:", JSON.stringify(original.shape) === JSON.stringify(restored.shape)); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/cum_ops.html b/playground/cum_ops.html index b9f5e7c59..1390c9320 100644 --- a/playground/cum_ops.html +++ b/playground/cum_ops.html @@ -204,6 +204,11 @@

1 · cumsum: running total

const s = new Series({ data: [1, 2, 3, 4, 5] }); const cs = cumsum(s); console.log("cumsum:", [...cs.values]); // [1, 3, 6, 10, 15] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -228,6 +233,11 @@

2 · cumprod: running product

const s = new Series({ data: [1, 2, 3, 4, 5] }); const cp = cumprod(s); console.log("cumprod:", [...cp.values]); // [1, 2, 6, 24, 120] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -252,6 +262,11 @@

3 · cummax and cummin

const s = new Series({ data: [3, 1, 4, 1, 5, 9, 2, 6] }); console.log("cummax:", [...cummax(s).values]); // [3, 3, 4, 4, 5, 9, 9, 9] console.log("cummin:", [...cummin(s).values]); // [3, 1, 1, 1, 1, 1, 1, 1] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -283,6 +298,18 @@

4 · Handling missing values (skipna)

// skipna: false — NaN at position 1 poisons everything after const skipFalse = cumsum(s, { skipna: false }); console.log("skipna=false:", [...skipFalse.values]); // [1, NaN, NaN, NaN] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -314,6 +341,19 @@

5 · DataFrame: axis=0 (column-wise)

const cmDf = dataFrameCummax(df); console.log("cummax revenue:", [...cmDf.col("revenue").values]); // [100, 150, 200, 200] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -346,6 +386,19 @@

6 · DataFrame: axis=1 (row-wise)

console.log("q1 (unchanged):", [...ytd.col("q1").values]); // [10, 20, 30] console.log("q2 (q1+q2): ", [...ytd.col("q2").values]); // [25, 45, 65] console.log("q3 (q1+q2+q3):", [...ytd.col("q3").values]); // [37, 67, 97] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -383,6 +436,20 @@

7 · Real-world example: portfolio tracking

console.log("Equity curve: ", [...equity.values]); console.log("All-time high:", [...peak.values]); console.log("Drawdown: ", [...drawdown.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -408,6 +475,13 @@

8 · String series: lexicographic cummax / cummin

// ["banana", "banana", "cherry", "cherry", "cherry"] console.log("cummin:", [...cummin(words).values]); // ["banana", "apple", "apple", "apple", "apple"] +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/dataframe.html b/playground/dataframe.html index e0b506976..5a7b5bdd1 100644 --- a/playground/dataframe.html +++ b/playground/dataframe.html @@ -238,6 +238,28 @@

Construction

["a", "b"], ); console.log("\nfrom2D col 'a' values:", df3.col("a").values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -269,6 +291,20 @@

Properties

console.log("empty:", df.empty); // false console.log("index:", df.index.toString()); console.log("columns:", df.columns.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -308,6 +344,27 @@

Column Access

// Chain into Series methods console.log("mean age:", df.col("age").mean()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -349,6 +406,29 @@

Slicing

); console.log("\nloc(['a', 'c']):"); console.log(labeled.loc(["a", "c"]).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -395,6 +475,33 @@

Column Mutations

const renamed = df.rename({ age: "years" }); console.log("\nrename({ age: 'years' }):"); console.log(renamed.columns.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -432,6 +539,26 @@

Missing Values

// fillna — replace nulls with a value console.log("\nfillna(0):"); console.log(df.fillna(0).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -466,6 +593,21 @@

Aggregations

console.log("\ndescribe():"); console.log(df.describe().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -506,6 +648,28 @@

Sorting

); console.log("\nsortIndex():"); console.log(labeled.sortIndex().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -548,6 +712,28 @@

Apply & Iteration

for (const [label, row] of df.iterrows()) { console.log(` row ${label}:`, row.values); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -590,6 +776,29 @@

Conversion

console.log("\nresetIndex():"); console.log(reset.toString()); console.log("columns:", reset.columns.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -622,6 +831,20 @@

🧪 Try It Yourself

console.log("Avg price:", sales.col("price").mean()); console.log("\nSorted by price (desc):"); console.log(sales.sortValues("price", false).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/datetime_accessor.html b/playground/datetime_accessor.html index 110997cac..83aaaa4fd 100644 --- a/playground/datetime_accessor.html +++ b/playground/datetime_accessor.html @@ -215,6 +215,20 @@

1 · Calendar Components

console.log("hour :", dates.dt.hour().toArray()); console.log("minute:", dates.dt.minute().toArray()); console.log("second:", dates.dt.second().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -247,6 +261,18 @@

2 · Day of Week & Quarter

console.log("dayofweek:", dates.dt.dayofweek().toArray()); console.log("quarter :", dates.dt.quarter().toArray()); console.log("dayofyear:", dates.dt.dayofyear().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -282,6 +308,22 @@

3 · Boolean Properties

console.log("is_year_end :", dates.dt.is_year_end().toArray()); console.log("is_leap_year :", dates.dt.is_leap_year().toArray()); console.log("days_in_month :", dates.dt.days_in_month().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -313,6 +355,18 @@

4 · strftime Formatting

console.log("Datetime :", dates.dt.strftime("%Y-%m-%d %H:%M:%S").toArray()); console.log("Friendly :", dates.dt.strftime("%A, %B %d %Y").toArray()); console.log("Short month:", dates.dt.strftime("%b %d, %Y").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -347,6 +401,15 @@

5 · Normalization & Rounding

console.log("floor(H) :", ts.dt.floor("H").toArray().map(fmt)); console.log("ceil(H) :", ts.dt.ceil("H").toArray().map(fmt)); console.log("round(T) :", ts.dt.round("T").toArray().map(fmt)); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -373,6 +436,17 @@

6 · Null Propagation

console.log("year :", mixed.dt.year().toArray()); console.log("month :", mixed.dt.month().toArray()); console.log("fmt :", mixed.dt.strftime("%Y-%m-%d").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -399,6 +473,13 @@

7 · total_seconds & date()

console.log("total_seconds:", dates.dt.total_seconds().toArray()); console.log("date :", dates.dt.date().toArray().map(d => d.toISOString())); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -441,6 +522,29 @@

8 · Combining dt with Other Operations

// Month names via strftime const monthNames = dates.dt.strftime("%B"); console.log("Month names :", monthNames.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/describe.html b/playground/describe.html index 30b4d1a9b..617ef8e44 100644 --- a/playground/describe.html +++ b/playground/describe.html @@ -215,6 +215,19 @@

1 · Describe a numeric Series

console.log("50% :", stats.at("50%")); console.log("75% :", stats.at("75%")); console.log("max :", stats.at("max")); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -248,6 +261,20 @@

2 · Custom percentiles

for (const label of stats.index.values) { console.log(`${label.toString().padEnd(5)}: ${Number(stats.at(label)).toFixed(2)}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -279,6 +306,18 @@

3 · Describe a categorical Series

console.log("unique:", stats.at("unique")); console.log("top :", stats.at("top")); console.log("freq :", stats.at("freq")); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -315,6 +354,22 @@

4 · Describe a DataFrame

.join(" "); console.log(`${String(label).padEnd(8)} ${row}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -358,6 +413,30 @@

5 · include="all" for mixed DataFrames

const v = summary.col("score").at(lbl); if (v !== null) console.log(`${String(lbl).padEnd(8)}: ${Number(v).toFixed(2)}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -391,6 +470,20 @@

6 · Series.quantile()

console.log("\nmedian() :", s.median()); console.log("quantile(0.5):", s.quantile(0.5)); console.log("Equal? :", s.quantile(0.5) === s.median()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -433,6 +526,30 @@

7 · Standalone quantile() utility

if (v < lower || v > upper) console.log(` OUTLIER: ${v}`); } console.log("No outliers found in this dataset."); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/dtype.html b/playground/dtype.html index 5381c603e..566212571 100644 --- a/playground/dtype.html +++ b/playground/dtype.html @@ -231,6 +231,27 @@

Creating Dtypes

]; console.log("All dtypes:", all.map(d => d.name).join(", ")); console.log("Total count:", all.length); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -264,6 +285,28 @@

Kind Classification

for (const d of checks) { console.log(`${d.name}: isBool=${d.isBool}, isString=${d.isString}, isDatetime=${d.isDatetime}, isTimedelta=${d.isTimedelta}, isCategory=${d.isCategory}, isObject=${d.isObject}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -298,6 +341,20 @@

Item Sizes

const pad = d.name.padEnd(13); console.log(`${pad}${d.itemsize}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -337,6 +394,29 @@

Type Casting

// Cross-family: int → float console.log("int32 → float64:", Dtype.int32.canCastTo(Dtype.float64)); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -374,6 +454,26 @@

Common Type Resolution

// Incompatible → object console.log("string + int32:", Dtype.commonType(Dtype.string, Dtype.int32).name); console.log("datetime + float64:", Dtype.commonType(Dtype.datetime, Dtype.float64).name); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -418,6 +518,36 @@

Type Inference

// Nulls are ignored during inference console.log("with nulls:", Dtype.inferFrom([null, 42, null]).name); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -452,6 +582,24 @@

🧪 Try It Yourself

const row = numericTypes.map(to => (from.canCastTo(to) ? "✓" : "✗").padEnd(8)).join(""); console.log(`${from.name.padEnd(10)}${row}`); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/elem_ops.html b/playground/elem_ops.html index 1ff9d3f80..2c1aedaae 100644 --- a/playground/elem_ops.html +++ b/playground/elem_ops.html @@ -209,6 +209,15 @@

1 · clip: bound values to a range

// One-sided: only lower bound const pos = clip(s, { lower: 0 }); console.log("lower only:", [...pos.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -236,6 +245,15 @@

2 · clip on a DataFrame

const safe = dataFrameClip(df, { lower: 0, upper: 100 }); console.log("temperature:", [...safe.col("temperature").values]); console.log("humidity:", [...safe.col("humidity").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -260,6 +278,11 @@

3 · seriesAbs: absolute value

const returns = new Series({ data: [-0.05, 0.12, -0.08, 0.03] }); const magnitude = seriesAbs(returns); console.log("magnitude:", [...magnitude.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -287,6 +310,15 @@

4 · dataFrameAbs: absolute values for all columns

const result = dataFrameAbs(df); console.log("x:", [...result.col("x").values]); console.log("y:", [...result.col("y").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -316,6 +348,15 @@

5 · seriesRound: round to N decimal places

// Round to nearest 10 const big = new Series({ data: [14, 25, 36] }); console.log("nearest 10:", [...seriesRound(big, { decimals: -1 }).values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -343,6 +384,15 @@

6 · dataFrameRound: round all columns

const r2 = dataFrameRound(df, { decimals: 2 }); console.log("lat:", [...r2.col("lat").values]); console.log("lon:", [...r2.col("lon").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -368,6 +418,14 @@

7 · Missing values pass through

console.log("clip:", [...clip(s, { lower: 0, upper: 5 }).values]); console.log("abs:", [...seriesAbs(s).values]); console.log("round:", [...seriesRound(s, { decimals: 0 }).values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/ewm.html b/playground/ewm.html index 8e2c5d98a..a8a2a7d92 100644 --- a/playground/ewm.html +++ b/playground/ewm.html @@ -231,6 +231,20 @@

1 · Decay Parameters

console.log("com=1: ", b.values); console.log("halflife=1:", c.values); console.log("alpha=0.5: ", d.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -262,6 +276,13 @@

2 · EWM Mean

// EWM mean with span=3 (alpha=0.5) const ewmMean = s.ewm({ span: 3 }).mean(); console.log("ewm mean:", ewmMean.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -288,6 +309,13 @@

3 · adjust=false: Simple IIR Filter

// Simple exponential smoothing (alpha=0.3) const smooth = s.ewm({ alpha: 0.3, adjust: false }).mean(); console.log("smoothed:", smooth.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -317,6 +345,17 @@

4 · EWM Variance and Standard Deviation

// Biased (population) variance const ewmVarBiased = prices.ewm({ span: 5 }).var(true); console.log("var (biased):", ewmVarBiased.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -343,6 +382,14 @@

5 · EWM Covariance

// x goes up, y goes down → negative covariance const ewmCov = x.ewm({ alpha: 0.4 }).cov(y); console.log("cov(x, y):", ewmCov.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -368,6 +415,13 @@

6 · EWM Correlation

const ewmCorr = s1.ewm({ span: 3 }).corr(s2); console.log("corr(s1, s2):", ewmCorr.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -399,6 +453,18 @@

7 · Missing Values (ignoreNa)

// ignoreNa=true: missing values completely skipped const r2 = s.ewm({ alpha: 0.5, ignoreNa: true }).mean(); console.log("ignoreNa=true: ", r2.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -432,6 +498,22 @@

8 · DataFrame EWM

const dfStd = df.ewm({ span: 3 }).std(); console.log("\nEWM std:"); dfStd.print(); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -459,6 +541,14 @@

9 · Custom apply

vals.reduce((acc, v, i) => acc + v * weights[i], 0) ); console.log("weighted sum:", ewmWeightedSum.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/groupby.html b/playground/groupby.html index 4a8b5e80d..017e97e16 100644 --- a/playground/groupby.html +++ b/playground/groupby.html @@ -221,6 +221,16 @@

1 · Basic groupby + sum()

const result = df.groupby("dept").sum(); console.log(result.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -254,6 +264,22 @@

2 · mean(), min(), max()

console.log("\n=== max() ==="); console.log(df.groupby("team").max().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -281,6 +307,16 @@

3 · count()

const counts = df.groupby("dept").count(); console.log(counts.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -308,6 +344,16 @@

4 · std()

const result = df.groupby("group").std(); console.log(result.toString()); // Group C has only 1 row → std is NaN +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -338,6 +384,20 @@

5 · first() / last()

console.log("\n=== last() ==="); console.log(df.groupby("dept").last().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -369,6 +429,20 @@

6 · size(), ngroups, groupKeys

console.log("\nsize():"); console.log(gb.size().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -410,6 +484,28 @@

7 · agg() with named specs

return Math.max(...nums) - Math.min(...nums); }); console.log(range.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -446,6 +542,20 @@

8 · transform()

return vals.map((v) => (typeof v === "number" ? v - mean : v)); }); console.log(demeaned.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -476,6 +586,19 @@

9 · apply()

sub.sortValues("sales", false).head(1), ); console.log(topRows.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -505,6 +628,18 @@

10 · filter()

const big = df.groupby("dept").filter((sub) => sub.shape[0] > 1); console.log("Groups with > 1 row (C dropped):"); console.log(big.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -540,6 +675,23 @@

🧪 Scratch Pad

console.log("\nGroup sizes:"); console.log(sales.groupby("region").size().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
diff --git a/playground/index-playground.html b/playground/index-playground.html index 308e694b7..9f773b421 100644 --- a/playground/index-playground.html +++ b/playground/index-playground.html @@ -228,6 +228,23 @@

Creating an Index

const stepped = new RangeIndex(0, 10, 2); console.log(stepped.toString()); console.log("Values:", stepped.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -257,6 +274,18 @@

Properties

console.log("isUnique:", idx.isUnique); console.log("hasDuplicates:", idx.hasDuplicates); console.log("isMonotonicIncreasing:", idx.isMonotonicIncreasing); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -282,6 +311,14 @@

Label Look-up

console.log("getLoc('a'):", idx.getLoc("a")); // duplicated → array console.log("contains('c'):", idx.contains("c")); console.log("isin(['a', 'c']):", idx.isin(["a", "c"])); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -308,6 +345,15 @@

Set Operations

console.log("intersection:", a.intersection(b).toString()); console.log("difference: ", a.difference(b).toString()); console.log("symm. diff.: ", a.symmetricDifference(b).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -335,6 +381,16 @@

Sorting & Aggregation

console.log("max:", idx.max()); console.log("argmin:", idx.argmin()); console.log("argmax:", idx.argmax()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -361,6 +417,15 @@

Manipulation (immutable — always returns new Index)

console.log("delete: ", idx.delete(0).toString()); console.log("drop: ", idx.drop(["b"]).toString()); console.log("rename: ", idx.rename("new_name").toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -386,6 +451,15 @@

Missing Values

console.log("notna:", idx.notna()); console.log("dropna:", idx.dropna().toString()); console.log("fillna(0):", idx.fillna(0).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -417,6 +491,20 @@

RangeIndex — Memory Efficient

// Slicing preserves RangeIndex type const sliced = r.slice(10, 20); console.log("sliced:", sliced.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -448,6 +536,19 @@

🧪 Try It Yourself

console.log("\nMean:", temps.mean()); console.log("Max:", temps.max()); console.log("Min:", temps.min()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/json.html b/playground/json.html index ee6ab1276..61139bf8a 100644 --- a/playground/json.html +++ b/playground/json.html @@ -215,6 +215,16 @@

1 · Parse records JSON (default)

console.log("columns:", [...df.columns.values]); console.log("names:", [...df.col("name").values]); console.log("ages:", [...df.col("age").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -248,6 +258,22 @@

2 · Split orient

console.log("index:", [...df.index.values]); console.log("x:", [...df.col("x").values]); console.log("y:", [...df.col("y").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -279,6 +305,21 @@

3 · Index orient

console.log("index:", [...df.index.values]); console.log("a:", [...df.col("a").values]); console.log("b:", [...df.col("b").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -310,6 +351,20 @@

4 · Columns orient

console.log("index:", [...df.index.values]); console.log("x:", [...df.col("x").values]); console.log("y:", [...df.col("y").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -337,6 +392,15 @@

5 · Values orient

console.log("shape:", df.shape); console.log("columns:", [...df.columns.values]); console.log("col 0:", [...df.col("0").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -369,6 +433,19 @@

6 · Serialize with toJson()

console.log("--- split (indented) ---"); console.log(toJson(df, { orient: "split", indent: 2 })); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -401,6 +478,21 @@

7 · Round-trip

console.log("x restored:", [...restored.col("x").values]); console.log("label restored:", [...restored.col("label").values]); console.log("shapes match:", JSON.stringify(original.shape) === JSON.stringify(restored.shape)); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/melt.html b/playground/melt.html index 64f6a3991..6ecd76dae 100644 --- a/playground/melt.html +++ b/playground/melt.html @@ -207,6 +207,16 @@

1 · Basic melt

const long = melt(df); console.log(long.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -240,6 +250,21 @@

2 · Preserve identifier columns

value_name: "sales", }); console.log(long.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -273,6 +298,22 @@

3 · Selective value columns

value_name: "score", }); console.log(long.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -305,6 +346,21 @@

4 · Multiple id columns

value_name: "revenue", }); console.log(long.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/merge.html b/playground/merge.html index f99707a31..99f75ec82 100644 --- a/playground/merge.html +++ b/playground/merge.html @@ -226,6 +226,21 @@

1 · Basic inner merge

// Default how="inner" — customers 30 and 40 have no match → excluded const result = merge(orders, customers, { on: "customerId" }); console.log(result.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -260,6 +275,19 @@

2 · Left, Right, and Outer joins

console.log("\n=== OUTER JOIN ==="); console.log(merge(left, right, { on: "k", how: "outer" }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -298,6 +326,23 @@

3 · Merge on different column names

right_on: "id", }); console.log(result.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -334,6 +379,21 @@

4 · Custom suffixes for overlapping columns

on: "id", suffixes: ["_pre", "_post"], }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
@@ -375,6 +435,29 @@

🧪 Scratch Pad

on: "productId", how: "left", }).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run
diff --git a/playground/multi_index.html b/playground/multi_index.html index 861bb3262..6e8a939f8 100644 --- a/playground/multi_index.html +++ b/playground/multi_index.html @@ -211,6 +211,20 @@

1 · Create from tuples

console.log("names:", mi.names); console.log("at(0):", mi.at(0)); console.log("at(-1):", mi.at(-1)); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -240,6 +254,17 @@

2 · Create from arrays

); console.log("toArray:", mi.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -269,6 +294,16 @@

3 · Create from Cartesian product

for (const t of mi) { console.log(t.join(" / ")); } +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -299,6 +334,18 @@

4 · Look-up by label

// Duplicate tuples return an array of positions const dup = MultiIndex.fromTuples([["a", 1], ["b", 2], ["a", 1]]); console.log("dup getLoc(['a', 1]):", dup.getLoc(["a", 1])); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -335,6 +382,25 @@

5 · Level operations: droplevel & swaplevel

// Drop to a single level → plain Index const idx = mi.droplevel([0, 1]); console.log("droplevel([0,1]) type:", idx.constructor.name); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -361,6 +427,14 @@

6 · Set operations

console.log("union:", mi1.union(mi2).toArray()); console.log("intersection:", mi1.intersection(mi2).toArray()); console.log("difference:", mi1.difference(mi2).toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -387,6 +461,15 @@

7 · Sorting and deduplication

console.log("sortValues:", mi.sortValues().toArray()); console.log("dropDuplicates:", mi.dropDuplicates().toArray()); console.log("duplicated('first'):", mi.duplicated("first")); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -418,6 +501,20 @@

8 · Missing values

const clean = mi.dropna(); console.log("after dropna size:", clean.size); console.log("after dropna toArray:", clean.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/nlargest.html b/playground/nlargest.html index ee3638926..ed7b25d9a 100644 --- a/playground/nlargest.html +++ b/playground/nlargest.html @@ -206,6 +206,14 @@

1 · Series.nlargest basics

const top3 = nlargestSeries(s, 3); console.log("values:", [...top3.values]); console.log("indices:", [...top3.index.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -231,6 +239,13 @@

2 · Series.nsmallest basics

const bottom3 = nsmallestSeries(s, 3); console.log("values:", [...bottom3.values]); console.log("indices:", [...bottom3.index.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -272,6 +287,27 @@

3 · The keep parameter

const all = nlargestSeries(s, 2, { keep: "all" }); console.log("all values:", [...all.values]); console.log("all index:", [...all.index.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -303,6 +339,20 @@

4 · Labeled index preservation

const cheapest = nsmallestSeries(prices, 2); console.log("cheapest values:", [...cheapest.values]); console.log("cheapest labels:", [...cheapest.index.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -327,6 +377,14 @@

5 · NaN / null handling

console.log("values:", [...top3.values]); console.log("size:", top3.size); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -362,6 +420,22 @@

6 · DataFrame.nlargest

// Break ties on "score" using "age" (secondary sort descending) const top2ByAge = nlargestDataFrame(df, 2, { columns: ["score", "age"] }); console.log("tie-break names:", [...top2ByAge.col("name").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -392,6 +466,18 @@

7 · DataFrame.nsmallest

const cheapest2 = nsmallestDataFrame(df, 2, { columns: "price" }); console.log("products:", [...cheapest2.col("product").values]); console.log("prices:", [...cheapest2.col("price").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -428,6 +514,25 @@

8 · Edge cases

const words = new Series({ data: ["banana", "apple", "cherry"] }); console.log("largest strings:", [...nlargestSeries(words, 2).values]); console.log("smallest strings:", [...nsmallestSeries(words, 2).values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/pivot.html b/playground/pivot.html index 1fc7e3343..bd07df288 100644 --- a/playground/pivot.html +++ b/playground/pivot.html @@ -208,6 +208,17 @@

1 · pivot: basic reshape

const wide = pivot(df, { index: "date", columns: "city", values: "temp" }); console.log("records:", wide.toRecords()); console.log("index:", [...wide.index.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -238,6 +249,18 @@

2 · pivot: multiple value columns

const wide = pivot(df, { index: "row", columns: "col" }); console.log("columns:", wide.columns.values); console.log("records:", wide.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -271,6 +294,21 @@

3 · pivotTable: mean aggregation

aggfunc: "mean", }); console.log("records:", table.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -305,6 +343,22 @@

4 · pivotTable: sum with fill_value

fill_value: 0, }); console.log("records:", table.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -337,6 +391,21 @@

5 · pivotTable: count

aggfunc: "count", }); console.log("records:", counts.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/playground-runtime.js b/playground/playground-runtime.js index 6cba3e499..8a6afbd50 100644 --- a/playground/playground-runtime.js +++ b/playground/playground-runtime.js @@ -8,10 +8,43 @@ * 2. Loads the TypeScript compiler (local bundle first, CDN fallback) * 3. Converts playground blocks into editable editors with Run/Reset buttons * 4. Transforms imports, transpiles TS → JS, and executes with output capture + * 5. Shows equivalent Python/pandas code in a switchable tab (read-only) + * 6. Reports execution timing for each code run * * No WASM needed — the TypeScript compiler runs natively in JavaScript. */ +// ── Inject tab-related CSS ───────────────────────────────────────── + +(function injectTabStyles() { + var style = document.createElement("style"); + style.textContent = [ + ".playground-tabs { display: flex; gap: 0; margin-bottom: -1px; position: relative; z-index: 1; }", + ".playground-tab {", + " padding: 0.35rem 0.9rem; font-size: 0.8rem; font-weight: 500;", + " border: 1px solid #30363d; border-bottom: none;", + " border-radius: 0.4rem 0.4rem 0 0; cursor: pointer;", + " background: #0d1117; color: #8b949e; transition: background 0.15s, color 0.15s;", + "}", + ".playground-tab:hover { background: #161b22; color: #e6edf3; }", + ".playground-tab.active { background: #1c2128; color: #58a6ff; border-bottom-color: #1c2128; }", + ".playground-tab-python.active { color: #3572A5; }", + ".playground-python-view {", + " display: none; width: 100%; background: #0d1117; color: #e6edf3;", + " border: 1px solid #30363d; border-top: none; border-bottom: none;", + " padding: 1rem; font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;", + " font-size: 0.875rem; line-height: 1.55; white-space: pre; overflow-x: auto;", + " tab-size: 4;", + "}", + ".playground-python-view.active { display: block; }", + ".playground-timing {", + " font-size: 0.75rem; color: #8b949e; margin-left: auto; font-family: system-ui, sans-serif;", + "}", + ".playground-timing .timing-value { color: #3fb950; font-weight: 600; }", + ].join("\n"); + document.head.appendChild(style); +})(); + // ── Load TypeScript compiler (local bundle → CDN fallback) ───────── function loadScriptWithTimeout(src, timeoutMs) { @@ -176,11 +209,68 @@ function setupBlock(block, ts) { var runBtn = block.querySelector(".playground-run"); var resetBtn = block.querySelector(".playground-reset"); var output = block.querySelector(".playground-output"); + var pythonSource = block.querySelector(".playground-python"); if (!editor || !runBtn || !output) return; var originalCode = getEditorCode(editor); - // Auto-resize textarea to fit content + // ── Python tab setup ─────────────────────────────── + if (pythonSource) { + var pythonCode = getEditorCode(pythonSource); + pythonSource.style.display = "none"; // hide the source textarea + + // Create tab bar + var header = block.querySelector(".playground-header"); + var tabBar = document.createElement("div"); + tabBar.className = "playground-tabs"; + + var tsTab = document.createElement("div"); + tsTab.className = "playground-tab playground-tab-ts active"; + tsTab.textContent = "TypeScript"; + + var pyTab = document.createElement("div"); + pyTab.className = "playground-tab playground-tab-python"; + pyTab.textContent = "Python"; + + tabBar.appendChild(tsTab); + tabBar.appendChild(pyTab); + + // Insert tab bar before the header + block.insertBefore(tabBar, header); + + // Hide the label in the header since tabs show the language + var label = header.querySelector(".playground-label"); + if (label) label.style.display = "none"; + + // Create Python read-only view + var pythonView = document.createElement("pre"); + pythonView.className = "playground-python-view"; + pythonView.textContent = pythonCode; + + // Insert Python view after editor + editor.parentNode.insertBefore(pythonView, editor.nextSibling); + + // Tab switching + tsTab.addEventListener("click", function () { + tsTab.classList.add("active"); + pyTab.classList.remove("active"); + editor.style.display = ""; + pythonView.classList.remove("active"); + runBtn.style.display = ""; + if (resetBtn) resetBtn.style.display = ""; + }); + + pyTab.addEventListener("click", function () { + pyTab.classList.add("active"); + tsTab.classList.remove("active"); + editor.style.display = "none"; + pythonView.classList.add("active"); + runBtn.style.display = "none"; + if (resetBtn) resetBtn.style.display = "none"; + }); + } + + // ── Auto-resize textarea to fit content ──────────── function autoResize() { if (!isTextarea(editor)) return; editor.style.height = "auto"; @@ -212,16 +302,31 @@ function setupBlock(block, ts) { } }); - // Run button handler + // Create timing display element + var timingEl = document.createElement("span"); + timingEl.className = "playground-timing"; + var actionsEl = block.querySelector(".playground-actions"); + if (actionsEl) { + actionsEl.parentNode.insertBefore(timingEl, actionsEl); + } + + // Run button handler (with timing) runBtn.addEventListener("click", function () { output.classList.remove("error"); output.classList.add("active"); + timingEl.innerHTML = ""; try { var code = getEditorCode(editor); + var startTime = performance.now(); var js = transformCode(code, ts); var result = executeCode(js); + var elapsed = performance.now() - startTime; output.textContent = result || "(no output \u2014 add console.log() to see results)"; + timingEl.innerHTML = + "\u23f1 " + + elapsed.toFixed(1) + + "ms"; } catch (err) { output.textContent = "\u274c " + err.message; output.classList.add("error"); @@ -234,6 +339,7 @@ function setupBlock(block, ts) { setEditorCode(editor, originalCode); output.textContent = ""; output.classList.remove("error", "active"); + timingEl.innerHTML = ""; autoResize(); }); } diff --git a/playground/rank.html b/playground/rank.html index 9ac4f5d92..14c2943f6 100644 --- a/playground/rank.html +++ b/playground/rank.html @@ -205,6 +205,12 @@

1 · Basic ranking

const r = rankSeries(s); console.log("ranks:", [...r.values]); // 1 appears twice → average rank (1+2)/2 = 1.5 +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -233,6 +239,15 @@

2 · Tie-breaking methods

console.log("max: ", [...rankSeries(s, { method: "max" }).values]); console.log("first: ", [...rankSeries(s, { method: "first" }).values]); console.log("dense: ", [...rankSeries(s, { method: "dense" }).values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -259,6 +274,15 @@

3 · Descending rank

const rank = rankSeries(scores, { ascending: false }); console.log("ranks:", [...rank.values]); // Dana (95) → rank 1, Bob (92) → rank 2, etc. +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -290,6 +314,18 @@

4 · Handling NaN/null values

console.log("keep: ", [...keep.values]); console.log("top: ", [...top.values]); console.log("bottom:", [...bottom.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -312,6 +348,11 @@

5 · Percentage rank (pct)

const s = new Series({ data: [10, 20, 30, 40, 50] }); const pct = rankSeries(s, { pct: true }); console.log("pct ranks:", [...pct.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -339,6 +380,16 @@

6 · DataFrame rank by column (axis=0)

const ranked = rankDataFrame(df); console.log("score ranks:", [...ranked.col("score").values]); console.log("time ranks: ", [...ranked.col("time").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -369,6 +420,19 @@

7 · DataFrame rank by row (axis=1)

console.log("math ranks: ", [...ranked.col("math").values]); console.log("english ranks:", [...ranked.col("english").values]); console.log("science ranks:", [...ranked.col("science").values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -397,6 +461,16 @@

8 · Dense rank for competition rankings

const denseRank = rankSeries(points, { method: "dense", ascending: false }); console.log("dense ranks:", [...denseRank.values]); // Tied players share the same position; no position is skipped +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/rolling.html b/playground/rolling.html index 24a8bb367..3cd41875b 100644 --- a/playground/rolling.html +++ b/playground/rolling.html @@ -208,6 +208,13 @@

1 · Basic rolling mean

// 3-day rolling mean — first two positions are null (not enough data) const ma3 = prices.rolling(3).mean(); console.log(ma3.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -233,6 +240,13 @@

2 · minPeriods — allow partial windows

// window=3 but minPeriods=1 → start computing from index 0 const result = s.rolling(3, { minPeriods: 1 }).mean(); console.log(result.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -260,6 +274,15 @@

3 · Rolling sum, std, min, max

v === null ? null : Number(v.toFixed(4)))); console.log("min :", w.min().toArray()); console.log("max :", w.max().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -288,6 +311,17 @@

4 · Rolling count (handles nulls)

// mean() with minPeriods=1 skips null-only windows console.log(s.rolling(2, { minPeriods: 1 }).mean().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -314,6 +348,15 @@

5 · Rolling median

console.log("mean :", meanW.map(v => v === null ? null : +v.toFixed(2))); console.log("median:", medW); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -345,6 +388,17 @@

6 · Custom aggregation with apply()

// Rolling product const prod = s.rolling(2).apply(vals => vals.reduce((a, b) => a * b, 1)); console.log("prod :", prod.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -374,6 +428,17 @@

7 · Centered window

console.log("trailing:", trailing); console.log("centered:", centered); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -402,6 +467,16 @@

8 · DataFrame.rolling()

const rolled = df.rolling(3).mean(); console.log(rolled.col("open").toArray()); console.log(rolled.col("close").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/series.html b/playground/series.html index 0bce6feed..55bab4319 100644 --- a/playground/series.html +++ b/playground/series.html @@ -228,6 +228,19 @@

Creating a Series

// From an object (keys become index labels) const s3 = Series.fromObject({ x: 1, y: 2, z: 3 }); console.log(s3.toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -261,6 +274,18 @@

Properties

console.log("values:", s.values); console.log("dtype: ", s.dtype.toString()); console.log("name: ", s.name); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -300,6 +325,24 @@

Element Access

// Multiple positions → sub-Series console.log("\niloc([1, 3]):"); console.log(s.iloc([1, 3]).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -328,6 +371,17 @@

Arithmetic

console.log("div(b):", a.div(b).values); console.log("mod(7):", a.mod(7).values); console.log("pow(2):", a.pow(2).values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -355,6 +409,16 @@

Comparison

console.log("le(3):", s.le(3).values); console.log("gt(3):", s.gt(3).values); console.log("ge(3):", s.ge(3).values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -388,6 +452,18 @@

Filtering & Boolean Masking

// Filter with a comparison result (boolean Series) console.log("\nValues > 12:"); console.log(s.filter(s.gt(12)).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -417,6 +493,19 @@

Missing Values

console.log("\nfillna(0):"); console.log(s.fillna(0).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -451,6 +540,23 @@

Statistics

console.log("\nvalueCounts():"); console.log(s.valueCounts().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -483,6 +589,19 @@

Sorting

console.log("\nsortIndex:"); console.log(s.sortIndex().toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -514,6 +633,19 @@

🧪 Try It Yourself

console.log("\nMean:", temps.mean()); console.log("Hot days (>= 75):"); console.log(temps.filter(temps.ge(75)).toString()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/stack_unstack.html b/playground/stack_unstack.html index affbeaf1c..7f9cdf2ec 100644 --- a/playground/stack_unstack.html +++ b/playground/stack_unstack.html @@ -208,6 +208,16 @@

1 · Basic stack

const s = stack(df, { dropna: false }); console.log("index:", [...s.index.values]); console.log("values:", [...s.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -243,6 +253,23 @@

2 · stack drops null by default

const sFull = stack(df, { dropna: false }); console.log("dropna=false index:", [...sFull.index.values]); console.log("dropna=false values:", [...sFull.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -274,6 +301,19 @@

3 · unstack: recover the original DataFrame

console.log("index:", [...recovered.index.values]); console.log("columns:", [...recovered.columns.values]); console.log("records:", recovered.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -307,6 +347,20 @@

4 · unstack fills missing cells

// unstack with fill_value=0 const recovered = unstack(s, { fill_value: 0 }); console.log("records:", recovered.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -338,6 +392,20 @@

5 · Custom separator

const recovered = unstack(s, { sep: "::" }); console.log("index:", [...recovered.index.values]); console.log("columns:", [...recovered.columns.values]); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -374,6 +442,26 @@

6 · Reshape workflow: stack → filter → unstack

console.log("index:", [...result.index.values]); console.log("records:", result.toRecords()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/string_accessor.html b/playground/string_accessor.html index 0fc19aae4..c65928008 100644 --- a/playground/string_accessor.html +++ b/playground/string_accessor.html @@ -144,6 +144,15 @@

Case Operations

console.log("title :", s.str.title().toArray()); console.log("capitalize:", s.str.capitalize().toArray()); console.log("swapcase :", s.str.swapcase().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -169,6 +178,15 @@

Length & Slicing

console.log("get(0) :", s.str.get(0).toArray()); console.log("get(-1) :", s.str.get(-1).toArray()); console.log("slice step :", s.str.slice(0, undefined, 2).toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -196,6 +214,17 @@

Strip & Pad

console.log("center(10) :", s.str.center(10).toArray()); console.log("zfill(5) :", new Series({ data: ["42", "-7", "3"] }).str.zfill(5).toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -222,6 +251,16 @@

Search & Match

console.log("endswith('3') :", s.str.endswith("3").toArray()); console.log("match('\\d+') :", s.str.match("\\d+").toArray()); console.log("fullmatch('\\w+') :", s.str.fullmatch("\\w+").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -247,6 +286,15 @@

Count, Find & Replace

console.log("rfind('a') :", s.str.rfind("a").toArray()); console.log("replace('a','X'):", s.str.replace("a", "X").toArray()); console.log("replace n=1 :", s.str.replace("a", "X", 1).toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -269,6 +317,12 @@

Extract

const prices = s.str.extract(":\\s*([\\d.]+)"); console.log("extracted:", prices.toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -298,6 +352,19 @@

Split & Join

const a = new Series({ data: ["foo", "bar"] }); const b = new Series({ data: ["1", "2"] }); console.log("cat :", a.str.cat([b.toArray()], "-").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -324,6 +391,16 @@

Predicates

console.log("isupper :", s.str.isupper().toArray()); console.log("istitle :", s.str.istitle().toArray()); console.log("isspace :", s.str.isspace().toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -347,6 +424,13 @@

Null Propagation

console.log("upper:", s.str.upper().toArray()); console.log("len :", s.str.len().toArray()); console.log("contains:", s.str.contains("o").toArray()); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/playground/value_counts.html b/playground/value_counts.html index 2f9e5a7bd..887a1c5a7 100644 --- a/playground/value_counts.html +++ b/playground/value_counts.html @@ -206,6 +206,13 @@

1 · Basic usage (Series)

console.log("index:", vc.index.values); console.log("values:", vc.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -231,6 +238,13 @@

2 · Normalize — return proportions

console.log("index:", pct.index.values); console.log("proportions:", pct.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -262,6 +276,19 @@

3 · Sort order

const ins = valueCounts(s, { sort: false }); console.log("unsorted index:", ins.index.values); console.log("unsorted values:", ins.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -293,6 +320,19 @@

4 · Missing-value handling

const keepNull = valueCounts(s, { dropna: false }); console.log("dropna=false index:", keepNull.index.values); console.log("dropna=false values:", keepNull.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -323,6 +363,18 @@

5 · DataFrame value_counts

const vc = dataFrameValueCounts(df); console.log("index:", vc.index.values); console.log("counts:", vc.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
@@ -352,6 +404,18 @@

6 · DataFrame subset

const vc = dataFrameValueCounts(df, { subset: ["city"] }); console.log("index:", vc.index.values); console.log("counts:", vc.values); +
Click ▶ Run to execute
Ctrl+Enter to run · Tab to indent
diff --git a/scripts/validate-python-examples.py b/scripts/validate-python-examples.py new file mode 100644 index 000000000..39da0b4e7 --- /dev/null +++ b/scripts/validate-python-examples.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +Validate Python/pandas examples from playground HTML pages. + +Extracts all ', + re.DOTALL, + ) + + # Also find section headings to label blocks + section_pattern = re.compile(r"

(.*?)

", re.DOTALL) + sections = section_pattern.findall(content) + + for i, match in enumerate(pattern.finditer(content)): + code = html.unescape(match.group(1)) + # Try to find the closest preceding section heading + label = sections[i] if i < len(sections) else f"block_{i}" + # Strip HTML tags from label + label = re.sub(r"<[^>]+>", "", label).strip() + blocks.append((label, code)) + + return blocks + + +def run_python_block(code: str, label: str, html_file: str) -> tuple[bool, str, float]: + """Run a Python code block and return (success, output, elapsed_ms).""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".py", delete=False, encoding="utf-8" + ) as f: + f.write(code) + tmp_path = f.name + + try: + start = time.perf_counter() + result = subprocess.run( + [sys.executable, tmp_path], + capture_output=True, + text=True, + timeout=30, + ) + elapsed_ms = (time.perf_counter() - start) * 1000 + + if result.returncode != 0: + return ( + False, + f"STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}", + elapsed_ms, + ) + return True, result.stdout, elapsed_ms + except subprocess.TimeoutExpired: + return False, "TIMEOUT (30s)", 0.0 + finally: + os.unlink(tmp_path) + + +def main() -> int: + playground_dir = sys.argv[1] if len(sys.argv) > 1 else "playground" + + if not os.path.isdir(playground_dir): + print(f"Error: directory '{playground_dir}' not found", file=sys.stderr) + return 1 + + html_files = sorted( + f + for f in os.listdir(playground_dir) + if f.endswith(".html") and f != "index.html" + ) + + total = 0 + passed = 0 + failed = 0 + failures: list[str] = [] + + for html_file in html_files: + filepath = os.path.join(playground_dir, html_file) + blocks = extract_python_blocks(filepath) + + if not blocks: + continue + + print(f"\n{'='*60}") + print(f" {html_file} ({len(blocks)} Python blocks)") + print(f"{'='*60}") + + for i, (label, code) in enumerate(blocks): + total += 1 + success, output, elapsed_ms = run_python_block( + code, label, html_file + ) + + if success: + passed += 1 + print(f" ✅ [{i+1}] {label} ({elapsed_ms:.1f}ms)") + else: + failed += 1 + fail_msg = f" ❌ [{i+1}] {label} in {html_file}" + print(fail_msg) + print(f" {output[:200]}") + failures.append(f"{html_file} [{i+1}] {label}") + + print(f"\n{'='*60}") + print(f" Results: {passed}/{total} passed, {failed} failed") + print(f"{'='*60}") + + if failures: + print("\nFailed examples:") + for f in failures: + print(f" - {f}") + return 1 + + print("\n✅ All Python examples validated successfully!") + return 0 + + +if __name__ == "__main__": + sys.exit(main())