Skip to content

Commit 66ce337

Browse files
Iteration 71: Add element-wise ops — clip(), seriesAbs(), seriesRound() for Series and DataFrame
Run: https://github.com/githubnext/tsessebe/actions/runs/24010099827 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d235034 commit 66ce337

3 files changed

Lines changed: 711 additions & 0 deletions

File tree

playground/elem_ops.html

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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 — element-wise operations</title>
7+
<script src="playground-runtime.js"></script>
8+
<style>
9+
body { font-family: system-ui, sans-serif; max-width: 860px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
10+
h1 { font-size: 2rem; margin-bottom: .25rem; }
11+
.subtitle { color: #555; margin-bottom: 2rem; }
12+
section { margin-bottom: 2.5rem; }
13+
h2 { font-size: 1.25rem; border-bottom: 2px solid #e0e0e0; padding-bottom: .35rem; margin-bottom: 1rem; }
14+
pre { background: #f5f5f5; border-radius: 6px; padding: 1rem; overflow-x: auto; font-size: .875rem; }
15+
code { font-family: "JetBrains Mono", "Fira Code", monospace; }
16+
.output { background: #eaf7ea; border-left: 4px solid #4caf50; padding: .75rem 1rem; border-radius: 0 6px 6px 0; margin-top: .5rem; font-size: .875rem; white-space: pre; font-family: monospace; }
17+
.note { background: #fff8e1; border-left: 4px solid #ffc107; padding: .6rem 1rem; border-radius: 0 6px 6px 0; font-size: .9rem; margin: .75rem 0; }
18+
nav { margin-bottom: 1.5rem; font-size: .9rem; color: #555; }
19+
nav a { color: #0366d6; text-decoration: none; }
20+
nav a:hover { text-decoration: underline; }
21+
</style>
22+
</head>
23+
<body>
24+
<nav><a href="index.html">← tsb playground</a></nav>
25+
<h1>element-wise operations</h1>
26+
<p class="subtitle">
27+
Scalar transforms applied independently to each element —
28+
mirrors <code>pandas.Series.clip()</code>, <code>.abs()</code>, and <code>.round()</code>.
29+
</p>
30+
31+
<section>
32+
<h2>1 — clip: bound values to a range</h2>
33+
<p>
34+
<code>clip(series, { lower, upper })</code> replaces any value below <code>lower</code>
35+
with <code>lower</code>, and any value above <code>upper</code> with <code>upper</code>.
36+
Pass only one bound to clip from one side only.
37+
Mirrors <code>pandas.Series.clip()</code>.
38+
</p>
39+
<pre><code id="ex1-code">import { Series, clip } from "tsb";
40+
41+
const s = new Series({ data: [-3, 0, 1, 5, 10] });
42+
const clipped = clip(s, { lower: 0, upper: 6 });
43+
console.log([...clipped.values]); // [0, 0, 1, 5, 6]
44+
45+
// One-sided: only lower bound
46+
const pos = clip(s, { lower: 0 });
47+
console.log([...pos.values]); // [0, 0, 1, 5, 10]
48+
</code></pre>
49+
<div class="output" id="ex1-output">Loading…</div>
50+
</section>
51+
52+
<section>
53+
<h2>2 — clip on a DataFrame</h2>
54+
<p>
55+
<code>dataFrameClip(df, { lower, upper })</code> applies the same clipping to every
56+
numeric column. Mirrors <code>pandas.DataFrame.clip()</code>.
57+
</p>
58+
<pre><code id="ex2-code">import { DataFrame, dataFrameClip } from "tsb";
59+
60+
const df = DataFrame.fromColumns({
61+
temperature: [-10, 5, 20, 45],
62+
humidity: [ 0, 30, 80, 110],
63+
});
64+
const safe = dataFrameClip(df, { lower: 0, upper: 100 });
65+
console.log([...safe.col("temperature").values]); // [0, 5, 20, 45]
66+
console.log([...safe.col("humidity").values]); // [0, 30, 80, 100]
67+
</code></pre>
68+
<div class="output" id="ex2-output">Loading…</div>
69+
</section>
70+
71+
<section>
72+
<h2>3 — seriesAbs: absolute value</h2>
73+
<p>
74+
<code>seriesAbs(series)</code> returns a new Series where every element is replaced
75+
by its absolute value. Mirrors <code>pandas.Series.abs()</code>.
76+
</p>
77+
<pre><code id="ex3-code">import { Series, seriesAbs } from "tsb";
78+
79+
const returns = new Series({ data: [-0.05, 0.12, -0.08, 0.03] });
80+
const magnitude = seriesAbs(returns);
81+
console.log([...magnitude.values]); // [0.05, 0.12, 0.08, 0.03]
82+
</code></pre>
83+
<div class="output" id="ex3-output">Loading…</div>
84+
</section>
85+
86+
<section>
87+
<h2>4 — dataFrameAbs: absolute values for all columns</h2>
88+
<p>
89+
<code>dataFrameAbs(df)</code> applies <code>abs()</code> column-by-column.
90+
Mirrors <code>pandas.DataFrame.abs()</code>.
91+
</p>
92+
<pre><code id="ex4-code">import { DataFrame, dataFrameAbs } from "tsb";
93+
94+
const df = DataFrame.fromColumns({
95+
x: [-1, 2, -3],
96+
y: [ 4, -5, 6],
97+
});
98+
const abs = dataFrameAbs(df);
99+
console.log([...abs.col("x").values]); // [1, 2, 3]
100+
console.log([...abs.col("y").values]); // [4, 5, 6]
101+
</code></pre>
102+
<div class="output" id="ex4-output">Loading…</div>
103+
</section>
104+
105+
<section>
106+
<h2>5 — seriesRound: round to N decimal places</h2>
107+
<p>
108+
<code>seriesRound(series, { decimals })</code> rounds each value to the given number
109+
of decimal places (default 0). Negative <code>decimals</code> rounds to the left of
110+
the decimal point (e.g. <code>-1</code> rounds to the nearest 10).
111+
Mirrors <code>pandas.Series.round()</code>.
112+
</p>
113+
<pre><code id="ex5-code">import { Series, seriesRound } from "tsb";
114+
115+
const prices = new Series({ data: [1.2349, 9.9999, 3.14159] });
116+
const r2 = seriesRound(prices, { decimals: 2 });
117+
console.log([...r2.values]); // [1.23, 10.0, 3.14]
118+
119+
// Round to nearest 10
120+
const big = new Series({ data: [14, 25, 36] });
121+
console.log([...seriesRound(big, { decimals: -1 }).values]); // [10, 30, 40]
122+
</code></pre>
123+
<div class="output" id="ex5-output">Loading…</div>
124+
</section>
125+
126+
<section>
127+
<h2>6 — dataFrameRound: round all columns</h2>
128+
<p>
129+
<code>dataFrameRound(df, { decimals })</code> rounds every numeric column of a DataFrame.
130+
Mirrors <code>pandas.DataFrame.round()</code>.
131+
</p>
132+
<pre><code id="ex6-code">import { DataFrame, dataFrameRound } from "tsb";
133+
134+
const df = DataFrame.fromColumns({
135+
lat: [51.5074, 48.8566, 40.7128],
136+
lon: [-0.1278, 2.3522, -74.006],
137+
});
138+
const r2 = dataFrameRound(df, { decimals: 2 });
139+
console.log([...r2.col("lat").values]); // [51.51, 48.86, 40.71]
140+
console.log([...r2.col("lon").values]); // [-0.13, 2.35, -74.01]
141+
</code></pre>
142+
<div class="output" id="ex6-output">Loading…</div>
143+
</section>
144+
145+
<section>
146+
<h2>7 — missing values pass through</h2>
147+
<p>
148+
All three operations propagate <code>null</code> and <code>NaN</code> unchanged —
149+
consistent with pandas' behaviour.
150+
</p>
151+
<pre><code id="ex7-code">import { Series, clip, seriesAbs, seriesRound } from "tsb";
152+
153+
const s = new Series({ data: [null, -5, NaN, 7] });
154+
155+
console.log([...clip(s, { lower: 0, upper: 5 }).values]);
156+
// [null, 0, NaN, 5]
157+
158+
console.log([...seriesAbs(s).values]);
159+
// [null, 5, NaN, 7]
160+
161+
console.log([...seriesRound(s, { decimals: 0 }).values]);
162+
// [null, -5, NaN, 7]
163+
</code></pre>
164+
<div class="output" id="ex7-output">Loading…</div>
165+
</section>
166+
167+
<script>
168+
const examples = [
169+
{ id: "ex1", imports: ["Series", "clip"] },
170+
{ id: "ex2", imports: ["DataFrame", "dataFrameClip"] },
171+
{ id: "ex3", imports: ["Series", "seriesAbs"] },
172+
{ id: "ex4", imports: ["DataFrame", "dataFrameAbs"] },
173+
{ id: "ex5", imports: ["Series", "seriesRound"] },
174+
{ id: "ex6", imports: ["DataFrame", "dataFrameRound"] },
175+
{ id: "ex7", imports: ["Series", "clip", "seriesAbs", "seriesRound"] },
176+
];
177+
for (const ex of examples) {
178+
runExample(ex.id + "-code", ex.id + "-output", ex.imports);
179+
}
180+
</script>
181+
</body>
182+
</html>

0 commit comments

Comments
 (0)