Skip to content

Commit 56a5489

Browse files
Iteration 135: Add insertColumn/popColumn — DataFrame column insertion and removal
Add `src/core/insert_pop.ts` implementing four column manipulation utilities that mirror pandas' DataFrame.insert() and DataFrame.pop(): - `insertColumn(df, loc, col, values)` — inserts a new column at integer position `loc`, rebuilding the ordered column Map; raises RangeError on duplicate names (unless allowDuplicates=true), out-of-range loc, or wrong value length - `popColumn(df, col)` — removes and returns `{ series, df }` (immutable style) - `reorderColumns(df, order)` — reorders/subsets columns (mirrors df[order]) - `moveColumn(df, col, newLoc)` — convenience wrapper: pop then re-insert All operations are non-mutating (return new DataFrames). 40+ unit + 3 property-based tests. Interactive playground page: insert_pop.html. Run: https://github.com/githubnext/tsessebe/actions/runs/24165728899 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a27a995 commit 56a5489

6 files changed

Lines changed: 682 additions & 0 deletions

File tree

playground/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ <h3><a href="multi_index.html" style="color: var(--accent); text-decoration: non
264264
<p>Hierarchical indexing. MultiIndex for multi-level row and column labels with fromArrays, fromTuples, fromProduct, level access, and swapLevels.</p>
265265
<div class="status done">✅ Complete</div>
266266
</div>
267+
<div class="feature-card">
268+
<h3><a href="insert_pop.html" style="color: var(--accent); text-decoration: none;">📥 insertColumn / popColumn</a></h3>
269+
<p>Insert and remove DataFrame columns at precise positions. <code>insertColumn(df, loc, col, values)</code> inserts at integer position, <code>popColumn(df, col)</code> returns <code>{ series, df }</code>. Also includes <code>reorderColumns</code> and <code>moveColumn</code>. Mirrors <code>pandas.DataFrame.insert()</code> and <code>.pop()</code>.</p>
270+
<div class="status done">✅ Complete</div>
271+
</div>
267272
</div>
268273
</section>
269274
</main>

playground/insert_pop.html

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>tsb — insertColumn / popColumn</title>
7+
<style>
8+
body { font-family: system-ui, sans-serif; max-width: 860px; margin: 2rem auto; padding: 0 1rem; line-height: 1.6; color: #1a1a1a; }
9+
h1 { color: #0066cc; }
10+
h2 { color: #333; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
11+
pre { background: #f6f8fa; border-radius: 6px; padding: 1rem; overflow-x: auto; font-size: 0.9em; }
12+
code { font-family: 'Fira Code', 'Cascadia Code', monospace; }
13+
.note { background: #fffbea; border-left: 4px solid #f5a623; padding: 0.7rem 1rem; border-radius: 0 6px 6px 0; margin: 1rem 0; }
14+
table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
15+
th, td { border: 1px solid #ddd; padding: 0.5rem 0.75rem; text-align: left; }
16+
th { background: #f0f4f8; }
17+
a { color: #0066cc; }
18+
</style>
19+
</head>
20+
<body>
21+
<p><a href="index.html">← tsb playground</a></p>
22+
23+
<h1><code>insertColumn</code> / <code>popColumn</code></h1>
24+
<p>
25+
Column insertion and removal for DataFrames — mirrors
26+
<a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.insert.html" target="_blank">
27+
<code>pandas.DataFrame.insert()</code></a> and
28+
<a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.pop.html" target="_blank">
29+
<code>pandas.DataFrame.pop()</code></a>.
30+
</p>
31+
<p>
32+
Because tsb DataFrames are <strong>immutable</strong>, both functions return a new DataFrame
33+
rather than mutating the original. <code>popColumn</code> returns both the extracted
34+
<code>Series</code> and the resulting DataFrame.
35+
</p>
36+
37+
<h2>API summary</h2>
38+
<table>
39+
<thead><tr><th>Function</th><th>Pandas equivalent</th><th>Description</th></tr></thead>
40+
<tbody>
41+
<tr>
42+
<td><code>insertColumn(df, loc, col, values)</code></td>
43+
<td><code>df.insert(loc, col, value)</code></td>
44+
<td>Insert a new column at integer position <code>loc</code></td>
45+
</tr>
46+
<tr>
47+
<td><code>popColumn(df, col)</code></td>
48+
<td><code>df.pop(col)</code></td>
49+
<td>Remove a column; returns <code>{ series, df }</code></td>
50+
</tr>
51+
<tr>
52+
<td><code>reorderColumns(df, order)</code></td>
53+
<td><code>df[order]</code></td>
54+
<td>Reorder (and optionally subset) columns</td>
55+
</tr>
56+
<tr>
57+
<td><code>moveColumn(df, col, newLoc)</code></td>
58+
<td></td>
59+
<td>Move an existing column to a new integer position</td>
60+
</tr>
61+
</tbody>
62+
</table>
63+
64+
<h2>Example 1 — <code>insertColumn</code></h2>
65+
<pre><code>import { DataFrame, insertColumn } from "tsb";
66+
67+
const df = DataFrame.fromColumns({
68+
name: ["Alice", "Bob", "Carol"],
69+
age: [30, 25, 35],
70+
});
71+
// columns: ["name", "age"]
72+
73+
// Insert "city" between "name" and "age"
74+
const df2 = insertColumn(df, 1, "city", ["NY", "LA", "SF"]);
75+
// df2.columns.values → ["name", "city", "age"]
76+
// df2.col("city").values → ["NY", "LA", "SF"]
77+
78+
// Original is unchanged
79+
// df.columns.values → ["name", "age"]
80+
</code></pre>
81+
82+
<h2>Example 2 — Insert with a Series</h2>
83+
<pre><code>import { DataFrame, Series, insertColumn } from "tsb";
84+
85+
const df = DataFrame.fromColumns({ a: [1, 2, 3], b: [4, 5, 6] });
86+
const salary = new Series({ data: [100_000, 90_000, 120_000], name: "salary" });
87+
88+
const df2 = insertColumn(df, 0, "salary", salary);
89+
// df2.columns.values → ["salary", "a", "b"]
90+
</code></pre>
91+
92+
<h2>Example 3 — <code>popColumn</code></h2>
93+
<pre><code>import { DataFrame, popColumn } from "tsb";
94+
95+
const df = DataFrame.fromColumns({
96+
id: [1, 2, 3],
97+
name: ["Alice", "Bob", "Carol"],
98+
age: [30, 25, 35],
99+
});
100+
101+
// Remove "age" and keep the Series
102+
const { series: ageSeries, df: df2 } = popColumn(df, "age");
103+
// ageSeries.values → [30, 25, 35]
104+
// df2.columns.values → ["id", "name"]
105+
// df.columns.values → ["id", "name", "age"] ← original unchanged
106+
</code></pre>
107+
108+
<h2>Example 4 — <code>reorderColumns</code></h2>
109+
<pre><code>import { DataFrame, reorderColumns } from "tsb";
110+
111+
const df = DataFrame.fromColumns({ a: [1], b: [2], c: [3], d: [4] });
112+
113+
// Reverse the column order
114+
const df2 = reorderColumns(df, ["d", "c", "b", "a"]);
115+
// df2.columns.values → ["d", "c", "b", "a"]
116+
117+
// Select a subset (drops columns not listed)
118+
const df3 = reorderColumns(df, ["a", "c"]);
119+
// df3.columns.values → ["a", "c"] (b and d are dropped)
120+
</code></pre>
121+
122+
<h2>Example 5 — <code>moveColumn</code></h2>
123+
<pre><code>import { DataFrame, moveColumn } from "tsb";
124+
125+
const df = DataFrame.fromColumns({
126+
year: [2020, 2021, 2022],
127+
value: [10, 20, 30],
128+
label: ["a", "b", "c"],
129+
});
130+
// columns: ["year", "value", "label"]
131+
132+
// Move "label" to the front
133+
const df2 = moveColumn(df, "label", 0);
134+
// df2.columns.values → ["label", "year", "value"]
135+
</code></pre>
136+
137+
<h2>Error cases</h2>
138+
<pre><code>// Duplicate column name (default: not allowed)
139+
insertColumn(df, 1, "a", [1, 2, 3]);
140+
// → RangeError: Column "a" already exists. Use allowDuplicates=true to permit...
141+
142+
// Out-of-range loc
143+
insertColumn(df, 99, "x", [1, 2, 3]);
144+
// → RangeError: loc=99 is out of range [0, 2].
145+
146+
// Wrong number of values
147+
insertColumn(df, 0, "x", [1]); // df has 3 rows
148+
// → RangeError: values length 1 does not match DataFrame row count 3.
149+
150+
// Column not found
151+
popColumn(df, "missing");
152+
// → RangeError: Column "missing" not found in DataFrame.
153+
</code></pre>
154+
155+
<div class="note">
156+
<strong>Immutability:</strong> Like all tsb DataFrame operations, these functions never
157+
mutate the original DataFrame. Always assign the return value to a new variable.
158+
</div>
159+
160+
<h2>pandas equivalence table</h2>
161+
<table>
162+
<thead>
163+
<tr><th>pandas</th><th>tsb</th></tr>
164+
</thead>
165+
<tbody>
166+
<tr><td><code>df.insert(1, "x", [1,2,3])</code> *(mutates)*</td><td><code>insertColumn(df, 1, "x", [1,2,3])</code></td></tr>
167+
<tr><td><code>series = df.pop("col")</code> *(mutates)*</td><td><code>const { series, df: df2 } = popColumn(df, "col")</code></td></tr>
168+
<tr><td><code>df[["c","a","b"]]</code></td><td><code>reorderColumns(df, ["c","a","b"])</code></td></tr>
169+
</tbody>
170+
</table>
171+
</body>
172+
</html>

src/core/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export { CategoricalAccessor } from "./cat_accessor.ts";
1515
export type { CatSeriesLike } from "./cat_accessor.ts";
1616
export { MultiIndex } from "./multi_index.ts";
1717
export type { MultiIndexOptions } from "./multi_index.ts";
18+
export { insertColumn, popColumn, reorderColumns, moveColumn, dataFrameFromPairs } from "./insert_pop.ts";
19+
export type { PopResult } from "./insert_pop.ts";

0 commit comments

Comments
 (0)