Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
11 changes: 11 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
91 changes: 91 additions & 0 deletions playground/cat_accessor.html
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ <h2>1 · Categories and codes</h2>

console.log("nCategories:", size.cat.nCategories);
console.log("ordered:", size.cat.ordered);</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

size = pd.Series(
["M", "S", "L", "S", "M", "XL"],
name="size",
dtype="category",
)

# Sorted unique categories
print("categories:", size.cat.categories.tolist())

# Integer codes (position in categories array; -1 for null)
print("codes:", size.cat.codes.tolist())

print("nCategories:", len(size.cat.categories))
print("ordered:", size.cat.ordered)</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand All @@ -237,6 +253,13 @@ <h2>2 · Nulls are encoded as -1</h2>
console.log("categories:", [...s.cat.categories.values]);
console.log("codes:", s.cat.codes.toArray());
console.log("nCategories:", s.cat.nCategories);</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

s = pd.Series(["a", None, "b", None, "a"], dtype="category")

print("categories:", s.cat.categories.tolist())
print("codes:", s.cat.codes.tolist())
print("nCategories:", len(s.cat.categories))</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down Expand Up @@ -267,6 +290,18 @@ <h2>3 · Add and remove categories</h2>
const s3 = s.cat.removeCategories(["red"]);
console.log("after remove values:", s3.toArray());
console.log("after remove cats:", [...s3.cat.categories.values]);</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

s = pd.Series(["red", "blue", "red"], dtype="category")

# Add a new category (not yet in data)
s2 = s.cat.add_categories(["green"])
print("after add:", s2.cat.categories.tolist())

# Remove a category — matching values become null
s3 = s.cat.remove_categories(["red"])
print("after remove values:", s3.tolist())
print("after remove cats:", s3.cat.categories.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down Expand Up @@ -294,6 +329,16 @@ <h2>4 · Remove unused categories</h2>
const s3 = s2.cat.removeUnusedCategories();
console.log("after:", s3.cat.nCategories);
console.log("cats:", [...s3.cat.categories.values]);</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

s = pd.Series(["a", "b"], dtype="category")
# Manually add extra categories
s2 = s.cat.add_categories(["c", "d", "e"])
print("before:", len(s2.cat.categories))

s3 = s2.cat.remove_unused_categories()
print("after:", len(s3.cat.categories))
print("cats:", s3.cat.categories.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down Expand Up @@ -323,6 +368,18 @@ <h2>5 · Rename categories</h2>
// Array replacement (same order as categories)
const s3 = s.cat.renameCategories(["H", "L", "M"]);
console.log("array rename:", s3.toArray());</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

s = pd.Series(["low", "mid", "high", "mid"], dtype="category")

# Object mapping
s2 = s.cat.rename_categories({"low": "L", "mid": "M", "high": "H"})
print("renamed values:", s2.tolist())
print("renamed cats:", s2.cat.categories.tolist())

# Array replacement (same order as categories)
s3 = s.cat.rename_categories(["H", "L", "M"])
print("array rename:", s3.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down Expand Up @@ -354,6 +411,18 @@ <h2>6 · Set and reorder categories</h2>
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]);</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

s = pd.Series(["XS", "S", "M", "L", "XL"], dtype="category")

# set_categories: restrict to a subset
s2 = s.cat.set_categories(["S", "M", "L"])
print("set cats:", s2.tolist())

# reorder_categories: custom ordering (e.g., by size not alphabetically)
s3 = s.cat.reorder_categories(["XS", "S", "M", "L", "XL"], ordered=True)
print("ordered:", s3.cat.ordered)
print("custom order:", s3.cat.categories.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand All @@ -380,6 +449,14 @@ <h2>7 · Value counts per category</h2>
const counts = gradesFull.cat.valueCounts();
console.log("categories:", [...gradesFull.cat.categories.values]);
console.log("counts:", counts.toArray());</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

grades = pd.Series(["A", "B", "A", "C", "B", "A"], dtype="category")
grades_full = grades.cat.add_categories(["D", "F"])

counts = grades_full.value_counts()
print("categories:", grades_full.cat.categories.tolist())
print("counts:", counts.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down Expand Up @@ -411,6 +488,20 @@ <h2>8 · Ordered categories</h2>

// bad=0, good=1, excellent=2
console.log("codes:", ordered.cat.codes.toArray());</textarea>
<textarea class="playground-python" style="display:none">import pandas as pd

rating = pd.Series(["good", "bad", "excellent", "good"], dtype="category")

# Establish a meaningful order
ordered = rating.cat.reorder_categories(
["bad", "good", "excellent"], ordered=True
)

print("ordered:", ordered.cat.ordered)
print("categories:", ordered.cat.categories.tolist())

# bad=0, good=1, excellent=2
print("codes:", ordered.cat.codes.tolist())</textarea>
<div class="playground-output">Click ▶ Run to execute</div>
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
</div>
Expand Down
89 changes: 89 additions & 0 deletions playground/concat.html
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ <h2>1 · Stack Series vertically (axis=0)</h2>

const result = concat([s1, s2]);
console.log(result.toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

s1 = pd.Series([10, 20], index=["a", "b"])
s2 = pd.Series([30, 40], index=["c", "d"])

result = pd.concat([s1, s2])
print(result.to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand All @@ -242,6 +249,14 @@ <h2>2 · Stack DataFrames vertically (axis=0)</h2>
// join="outer" by default — fills missing columns with null
const result = concat([df1, df2]);
console.log(result.toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

df1 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
df2 = pd.DataFrame({"b": [5], "c": [6]})

# join="outer" by default — fills missing columns with NaN
result = pd.concat([df1, df2])
print(result.to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand Down Expand Up @@ -276,6 +291,21 @@ <h2>3 · Column-wise concat (axis=1)</h2>

console.log("\n=== DataFrame axis=1 ===");
console.log(concat([left, right], { axis: 1 }).toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

# Series → DataFrame (each Series becomes a column)
age = pd.Series([25, 30, 35], name="age")
score = pd.Series([88, 92, 79], name="score")

print("=== Series axis=1 ===")
print(pd.concat([age, score], axis=1).to_string())

# DataFrame side-by-side
left = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
right = pd.DataFrame({"c": [5, 6], "d": [7, 8]})

print("\n=== DataFrame axis=1 ===")
print(pd.concat([left, right], axis=1).to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand Down Expand Up @@ -317,6 +347,27 @@ <h2>4 · Join modes — outer vs inner</h2>

console.log("\n=== axis=1, join='inner' (only shared row 'b') ===");
console.log(concat([s1, s2], { axis: 1, join: "inner" }).toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

# axis=0: outer vs inner columns
df1 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
df2 = pd.DataFrame({"b": [5], "c": [6]})

print("=== axis=0, join='outer' (default) ===")
print(pd.concat([df1, df2]).to_string())

print("\n=== axis=0, join='inner' (only shared col 'b') ===")
print(pd.concat([df1, df2], join="inner").to_string())

# axis=1: outer vs inner row indexes
s1 = pd.Series([1, 2], index=["a", "b"], name="s1")
s2 = pd.Series([3, 4], index=["b", "c"], name="s2")

print("\n=== axis=1, join='outer' (union of row indexes) ===")
print(pd.concat([s1, s2], axis=1).to_string())

print("\n=== axis=1, join='inner' (only shared row 'b') ===")
print(pd.concat([s1, s2], axis=1, join="inner").to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand Down Expand Up @@ -349,6 +400,21 @@ <h2>5 · ignoreIndex — reset to RangeIndex</h2>

console.log("\n=== DataFrame ignoreIndex ===");
console.log(concat([df1, df2], { ignoreIndex: true }).toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

# Series with string indexes → reset to 0, 1, 2
a = pd.Series([1, 2], index=["x", "y"])
b = pd.Series([3], index=["z"])

print("=== Series ignore_index ===")
print(pd.concat([a, b], ignore_index=True).to_string())

# DataFrame ignore_index
df1 = pd.DataFrame({"v": [10, 20]})
df2 = pd.DataFrame({"v": [30, 40]})

print("\n=== DataFrame ignore_index ===")
print(pd.concat([df1, df2], ignore_index=True).to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand Down Expand Up @@ -390,6 +456,29 @@ <h2>🧪 Scratch Pad</h2>

console.log("\n=== Side-by-side columns ===");
console.log(concat([names, q1rev, q2rev], { axis: 1 }).toString());</pre>
<textarea class="playground-python" style="display:none">import pandas as pd

# Try it! Combine DataFrames in creative ways.
q1 = pd.DataFrame({
"product": ["Widget", "Gadget"],
"revenue": [1000, 1500],
})

q2 = pd.DataFrame({
"product": ["Widget", "Gadget"],
"revenue": [1200, 1800],
})

print("=== Q1 + Q2 stacked ===")
print(pd.concat([q1, q2], ignore_index=True).to_string())

# Side-by-side with axis=1
names = pd.Series(["Widget", "Gadget"], name="product")
q1_rev = pd.Series([1000, 1500], name="q1_rev")
q2_rev = pd.Series([1200, 1800], name="q2_rev")

print("\n=== Side-by-side columns ===")
print(pd.concat([names, q1_rev, q2_rev], axis=1).to_string())</textarea>
<pre class="playground-output">Click ▶ Run to execute</pre>
<div class="playground-hint">Ctrl+Enter to run</div>
</div>
Expand Down
Loading
Loading