Skip to content

Commit 9a11f1f

Browse files
authored
Merge pull request #93 from githubnext/copilot/add-python-equivalent-tab
Add Python/pandas equivalent tab to all playground pages with CI validation
2 parents 3f36988 + ac04061 commit 9a11f1f

29 files changed

Lines changed: 2733 additions & 2 deletions

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,20 @@ jobs:
5959
with:
6060
name: dist
6161
path: dist/
62+
63+
validate-python-examples:
64+
name: Validate Python Examples
65+
runs-on: ubuntu-latest
66+
steps:
67+
- uses: actions/checkout@v4
68+
69+
- name: Setup Python
70+
uses: actions/setup-python@v5
71+
with:
72+
python-version: "3.12"
73+
74+
- name: Install Python dependencies
75+
run: pip install pandas numpy
76+
77+
- name: Validate Python playground examples
78+
run: python scripts/validate-python-examples.py playground/

.github/workflows/pages.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ jobs:
3636
- name: Bundle TypeScript compiler for offline playground
3737
run: cp node_modules/typescript/lib/typescript.js ./playground/dist/typescript.js
3838

39+
- name: Setup Python
40+
uses: actions/setup-python@v5
41+
with:
42+
python-version: "3.12"
43+
44+
- name: Install Python dependencies
45+
run: pip install pandas numpy
46+
47+
- name: Validate Python playground examples
48+
run: python scripts/validate-python-examples.py playground/
49+
3950
- name: Setup Pages
4051
uses: actions/configure-pages@v5
4152
with:

playground/cat_accessor.html

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ <h2>1 · Categories and codes</h2>
212212

213213
console.log("nCategories:", size.cat.nCategories);
214214
console.log("ordered:", size.cat.ordered);</textarea>
215+
<textarea class="playground-python" style="display:none">import pandas as pd
216+
217+
size = pd.Series(
218+
["M", "S", "L", "S", "M", "XL"],
219+
name="size",
220+
dtype="category",
221+
)
222+
223+
# Sorted unique categories
224+
print("categories:", size.cat.categories.tolist())
225+
226+
# Integer codes (position in categories array; -1 for null)
227+
print("codes:", size.cat.codes.tolist())
228+
229+
print("nCategories:", len(size.cat.categories))
230+
print("ordered:", size.cat.ordered)</textarea>
215231
<div class="playground-output">Click ▶ Run to execute</div>
216232
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
217233
</div>
@@ -237,6 +253,13 @@ <h2>2 · Nulls are encoded as -1</h2>
237253
console.log("categories:", [...s.cat.categories.values]);
238254
console.log("codes:", s.cat.codes.toArray());
239255
console.log("nCategories:", s.cat.nCategories);</textarea>
256+
<textarea class="playground-python" style="display:none">import pandas as pd
257+
258+
s = pd.Series(["a", None, "b", None, "a"], dtype="category")
259+
260+
print("categories:", s.cat.categories.tolist())
261+
print("codes:", s.cat.codes.tolist())
262+
print("nCategories:", len(s.cat.categories))</textarea>
240263
<div class="playground-output">Click ▶ Run to execute</div>
241264
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
242265
</div>
@@ -267,6 +290,18 @@ <h2>3 · Add and remove categories</h2>
267290
const s3 = s.cat.removeCategories(["red"]);
268291
console.log("after remove values:", s3.toArray());
269292
console.log("after remove cats:", [...s3.cat.categories.values]);</textarea>
293+
<textarea class="playground-python" style="display:none">import pandas as pd
294+
295+
s = pd.Series(["red", "blue", "red"], dtype="category")
296+
297+
# Add a new category (not yet in data)
298+
s2 = s.cat.add_categories(["green"])
299+
print("after add:", s2.cat.categories.tolist())
300+
301+
# Remove a category — matching values become null
302+
s3 = s.cat.remove_categories(["red"])
303+
print("after remove values:", s3.tolist())
304+
print("after remove cats:", s3.cat.categories.tolist())</textarea>
270305
<div class="playground-output">Click ▶ Run to execute</div>
271306
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
272307
</div>
@@ -294,6 +329,16 @@ <h2>4 · Remove unused categories</h2>
294329
const s3 = s2.cat.removeUnusedCategories();
295330
console.log("after:", s3.cat.nCategories);
296331
console.log("cats:", [...s3.cat.categories.values]);</textarea>
332+
<textarea class="playground-python" style="display:none">import pandas as pd
333+
334+
s = pd.Series(["a", "b"], dtype="category")
335+
# Manually add extra categories
336+
s2 = s.cat.add_categories(["c", "d", "e"])
337+
print("before:", len(s2.cat.categories))
338+
339+
s3 = s2.cat.remove_unused_categories()
340+
print("after:", len(s3.cat.categories))
341+
print("cats:", s3.cat.categories.tolist())</textarea>
297342
<div class="playground-output">Click ▶ Run to execute</div>
298343
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
299344
</div>
@@ -323,6 +368,18 @@ <h2>5 · Rename categories</h2>
323368
// Array replacement (same order as categories)
324369
const s3 = s.cat.renameCategories(["H", "L", "M"]);
325370
console.log("array rename:", s3.toArray());</textarea>
371+
<textarea class="playground-python" style="display:none">import pandas as pd
372+
373+
s = pd.Series(["low", "mid", "high", "mid"], dtype="category")
374+
375+
# Object mapping
376+
s2 = s.cat.rename_categories({"low": "L", "mid": "M", "high": "H"})
377+
print("renamed values:", s2.tolist())
378+
print("renamed cats:", s2.cat.categories.tolist())
379+
380+
# Array replacement (same order as categories)
381+
s3 = s.cat.rename_categories(["H", "L", "M"])
382+
print("array rename:", s3.tolist())</textarea>
326383
<div class="playground-output">Click ▶ Run to execute</div>
327384
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
328385
</div>
@@ -354,6 +411,18 @@ <h2>6 · Set and reorder categories</h2>
354411
const s3 = s.cat.reorderCategories(["XS", "S", "M", "L", "XL"], true);
355412
console.log("ordered:", s3.cat.ordered);
356413
console.log("custom order:", [...s3.cat.categories.values]);</textarea>
414+
<textarea class="playground-python" style="display:none">import pandas as pd
415+
416+
s = pd.Series(["XS", "S", "M", "L", "XL"], dtype="category")
417+
418+
# set_categories: restrict to a subset
419+
s2 = s.cat.set_categories(["S", "M", "L"])
420+
print("set cats:", s2.tolist())
421+
422+
# reorder_categories: custom ordering (e.g., by size not alphabetically)
423+
s3 = s.cat.reorder_categories(["XS", "S", "M", "L", "XL"], ordered=True)
424+
print("ordered:", s3.cat.ordered)
425+
print("custom order:", s3.cat.categories.tolist())</textarea>
357426
<div class="playground-output">Click ▶ Run to execute</div>
358427
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
359428
</div>
@@ -380,6 +449,14 @@ <h2>7 · Value counts per category</h2>
380449
const counts = gradesFull.cat.valueCounts();
381450
console.log("categories:", [...gradesFull.cat.categories.values]);
382451
console.log("counts:", counts.toArray());</textarea>
452+
<textarea class="playground-python" style="display:none">import pandas as pd
453+
454+
grades = pd.Series(["A", "B", "A", "C", "B", "A"], dtype="category")
455+
grades_full = grades.cat.add_categories(["D", "F"])
456+
457+
counts = grades_full.value_counts()
458+
print("categories:", grades_full.cat.categories.tolist())
459+
print("counts:", counts.tolist())</textarea>
383460
<div class="playground-output">Click ▶ Run to execute</div>
384461
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
385462
</div>
@@ -411,6 +488,20 @@ <h2>8 · Ordered categories</h2>
411488

412489
// bad=0, good=1, excellent=2
413490
console.log("codes:", ordered.cat.codes.toArray());</textarea>
491+
<textarea class="playground-python" style="display:none">import pandas as pd
492+
493+
rating = pd.Series(["good", "bad", "excellent", "good"], dtype="category")
494+
495+
# Establish a meaningful order
496+
ordered = rating.cat.reorder_categories(
497+
["bad", "good", "excellent"], ordered=True
498+
)
499+
500+
print("ordered:", ordered.cat.ordered)
501+
print("categories:", ordered.cat.categories.tolist())
502+
503+
# bad=0, good=1, excellent=2
504+
print("codes:", ordered.cat.codes.tolist())</textarea>
414505
<div class="playground-output">Click ▶ Run to execute</div>
415506
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
416507
</div>

playground/concat.html

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,13 @@ <h2>1 · Stack Series vertically (axis=0)</h2>
217217

218218
const result = concat([s1, s2]);
219219
console.log(result.toString());</pre>
220+
<textarea class="playground-python" style="display:none">import pandas as pd
221+
222+
s1 = pd.Series([10, 20], index=["a", "b"])
223+
s2 = pd.Series([30, 40], index=["c", "d"])
224+
225+
result = pd.concat([s1, s2])
226+
print(result.to_string())</textarea>
220227
<pre class="playground-output">Click ▶ Run to execute</pre>
221228
<div class="playground-hint">Ctrl+Enter to run</div>
222229
</div>
@@ -242,6 +249,14 @@ <h2>2 · Stack DataFrames vertically (axis=0)</h2>
242249
// join="outer" by default — fills missing columns with null
243250
const result = concat([df1, df2]);
244251
console.log(result.toString());</pre>
252+
<textarea class="playground-python" style="display:none">import pandas as pd
253+
254+
df1 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
255+
df2 = pd.DataFrame({"b": [5], "c": [6]})
256+
257+
# join="outer" by default — fills missing columns with NaN
258+
result = pd.concat([df1, df2])
259+
print(result.to_string())</textarea>
245260
<pre class="playground-output">Click ▶ Run to execute</pre>
246261
<div class="playground-hint">Ctrl+Enter to run</div>
247262
</div>
@@ -276,6 +291,21 @@ <h2>3 · Column-wise concat (axis=1)</h2>
276291

277292
console.log("\n=== DataFrame axis=1 ===");
278293
console.log(concat([left, right], { axis: 1 }).toString());</pre>
294+
<textarea class="playground-python" style="display:none">import pandas as pd
295+
296+
# Series → DataFrame (each Series becomes a column)
297+
age = pd.Series([25, 30, 35], name="age")
298+
score = pd.Series([88, 92, 79], name="score")
299+
300+
print("=== Series axis=1 ===")
301+
print(pd.concat([age, score], axis=1).to_string())
302+
303+
# DataFrame side-by-side
304+
left = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
305+
right = pd.DataFrame({"c": [5, 6], "d": [7, 8]})
306+
307+
print("\n=== DataFrame axis=1 ===")
308+
print(pd.concat([left, right], axis=1).to_string())</textarea>
279309
<pre class="playground-output">Click ▶ Run to execute</pre>
280310
<div class="playground-hint">Ctrl+Enter to run</div>
281311
</div>
@@ -317,6 +347,27 @@ <h2>4 · Join modes — outer vs inner</h2>
317347

318348
console.log("\n=== axis=1, join='inner' (only shared row 'b') ===");
319349
console.log(concat([s1, s2], { axis: 1, join: "inner" }).toString());</pre>
350+
<textarea class="playground-python" style="display:none">import pandas as pd
351+
352+
# axis=0: outer vs inner columns
353+
df1 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
354+
df2 = pd.DataFrame({"b": [5], "c": [6]})
355+
356+
print("=== axis=0, join='outer' (default) ===")
357+
print(pd.concat([df1, df2]).to_string())
358+
359+
print("\n=== axis=0, join='inner' (only shared col 'b') ===")
360+
print(pd.concat([df1, df2], join="inner").to_string())
361+
362+
# axis=1: outer vs inner row indexes
363+
s1 = pd.Series([1, 2], index=["a", "b"], name="s1")
364+
s2 = pd.Series([3, 4], index=["b", "c"], name="s2")
365+
366+
print("\n=== axis=1, join='outer' (union of row indexes) ===")
367+
print(pd.concat([s1, s2], axis=1).to_string())
368+
369+
print("\n=== axis=1, join='inner' (only shared row 'b') ===")
370+
print(pd.concat([s1, s2], axis=1, join="inner").to_string())</textarea>
320371
<pre class="playground-output">Click ▶ Run to execute</pre>
321372
<div class="playground-hint">Ctrl+Enter to run</div>
322373
</div>
@@ -349,6 +400,21 @@ <h2>5 · ignoreIndex — reset to RangeIndex</h2>
349400

350401
console.log("\n=== DataFrame ignoreIndex ===");
351402
console.log(concat([df1, df2], { ignoreIndex: true }).toString());</pre>
403+
<textarea class="playground-python" style="display:none">import pandas as pd
404+
405+
# Series with string indexes → reset to 0, 1, 2
406+
a = pd.Series([1, 2], index=["x", "y"])
407+
b = pd.Series([3], index=["z"])
408+
409+
print("=== Series ignore_index ===")
410+
print(pd.concat([a, b], ignore_index=True).to_string())
411+
412+
# DataFrame ignore_index
413+
df1 = pd.DataFrame({"v": [10, 20]})
414+
df2 = pd.DataFrame({"v": [30, 40]})
415+
416+
print("\n=== DataFrame ignore_index ===")
417+
print(pd.concat([df1, df2], ignore_index=True).to_string())</textarea>
352418
<pre class="playground-output">Click ▶ Run to execute</pre>
353419
<div class="playground-hint">Ctrl+Enter to run</div>
354420
</div>
@@ -390,6 +456,29 @@ <h2>🧪 Scratch Pad</h2>
390456

391457
console.log("\n=== Side-by-side columns ===");
392458
console.log(concat([names, q1rev, q2rev], { axis: 1 }).toString());</pre>
459+
<textarea class="playground-python" style="display:none">import pandas as pd
460+
461+
# Try it! Combine DataFrames in creative ways.
462+
q1 = pd.DataFrame({
463+
"product": ["Widget", "Gadget"],
464+
"revenue": [1000, 1500],
465+
})
466+
467+
q2 = pd.DataFrame({
468+
"product": ["Widget", "Gadget"],
469+
"revenue": [1200, 1800],
470+
})
471+
472+
print("=== Q1 + Q2 stacked ===")
473+
print(pd.concat([q1, q2], ignore_index=True).to_string())
474+
475+
# Side-by-side with axis=1
476+
names = pd.Series(["Widget", "Gadget"], name="product")
477+
q1_rev = pd.Series([1000, 1500], name="q1_rev")
478+
q2_rev = pd.Series([1200, 1800], name="q2_rev")
479+
480+
print("\n=== Side-by-side columns ===")
481+
print(pd.concat([names, q1_rev, q2_rev], axis=1).to_string())</textarea>
393482
<pre class="playground-output">Click ▶ Run to execute</pre>
394483
<div class="playground-hint">Ctrl+Enter to run</div>
395484
</div>

0 commit comments

Comments
 (0)