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