Skip to content

Commit c10914c

Browse files
Iteration 72: Add value_counts — count unique values in Series and DataFrame
- `valueCounts(series, opts)`: count or normalize unique values, with sort/ascending/dropna - `dataFrameValueCounts(df, opts)`: count unique row combinations across all or subset of columns - 25 unit + property-based tests (fast-check); all pass - Interactive playground page: playground/value_counts.html - Wire up elem_ops exports (clip/seriesAbs/seriesRound) to stats barrel and main index Run: https://github.com/githubnext/tsessebe/actions/runs/24010521196 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 66ce337 commit c10914c

3 files changed

Lines changed: 661 additions & 0 deletions

File tree

playground/value_counts.html

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 — value_counts</title>
7+
<style>
8+
body {
9+
font-family: system-ui, sans-serif;
10+
max-width: 860px;
11+
margin: 0 auto;
12+
padding: 2rem;
13+
line-height: 1.6;
14+
color: #1a1a1a;
15+
}
16+
h1 { color: #2563eb; }
17+
h2 { color: #1e40af; margin-top: 2rem; }
18+
pre {
19+
background: #f1f5f9;
20+
border-left: 4px solid #2563eb;
21+
padding: 1rem;
22+
overflow-x: auto;
23+
border-radius: 4px;
24+
}
25+
code { font-family: "Fira Code", monospace; font-size: 0.9em; }
26+
.output {
27+
background: #f0fdf4;
28+
border-left: 4px solid #16a34a;
29+
padding: 1rem;
30+
margin-top: 0.5rem;
31+
border-radius: 4px;
32+
white-space: pre;
33+
font-family: monospace;
34+
}
35+
.note {
36+
background: #eff6ff;
37+
border-left: 4px solid #3b82f6;
38+
padding: 0.75rem 1rem;
39+
margin: 1rem 0;
40+
border-radius: 4px;
41+
}
42+
a { color: #2563eb; }
43+
</style>
44+
</head>
45+
<body>
46+
<h1>tsb — <code>value_counts</code></h1>
47+
<p>
48+
Count unique values in a <strong>Series</strong> or unique row combinations in a
49+
<strong>DataFrame</strong>. Mirrors
50+
<a href="https://pandas.pydata.org/docs/reference/api/pandas.Series.value_counts.html"
51+
><code>pandas.Series.value_counts()</code></a
52+
>
53+
and
54+
<a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.value_counts.html"
55+
><code>pandas.DataFrame.value_counts()</code></a
56+
>.
57+
</p>
58+
59+
<h2>1 · Basic usage (Series)</h2>
60+
<pre><code>import { Series } from "tsb";
61+
import { valueCounts } from "tsb";
62+
63+
const s = new Series({ data: ["apple", "banana", "apple", "cherry", "banana", "apple"] });
64+
const vc = valueCounts(s);
65+
// index → ["apple", "banana", "cherry"]
66+
// values → [3, 2, 1] (sorted descending by default)</code></pre>
67+
<div class="output">index → apple=3 banana=2 cherry=1</div>
68+
69+
<h2>2 · Normalize — return proportions</h2>
70+
<pre><code>const pct = valueCounts(s, { normalize: true });
71+
// values → [0.5, 0.333…, 0.167…] (must sum to 1)</code></pre>
72+
<div class="output">apple=0.50 banana=0.33 cherry=0.17</div>
73+
74+
<h2>3 · Sort order</h2>
75+
<pre><code>// Ascending order (least frequent first)
76+
const asc = valueCounts(s, { ascending: true });
77+
78+
// Preserve insertion order (no sort)
79+
const ins = valueCounts(s, { sort: false });</code></pre>
80+
81+
<h2>4 · Missing-value handling</h2>
82+
<pre><code>const sWithNull = new Series({ data: ["a", null, "b", null, "a"] });
83+
84+
// dropna=true (default) — exclude nulls
85+
const dropNull = valueCounts(sWithNull);
86+
// index → ["a", "b"], values → [2, 1]
87+
88+
// dropna=false — include nulls
89+
const keepNull = valueCounts(sWithNull, { dropna: false });
90+
// index → ["a", "b", null], values → [2, 1, 2]</code></pre>
91+
<div class="output">dropna=true : a=2 b=1
92+
dropna=false: a=2 b=1 null=2</div>
93+
94+
<h2>5 · DataFrame value_counts</h2>
95+
<pre><code>import { DataFrame } from "tsb";
96+
import { dataFrameValueCounts } from "tsb";
97+
98+
const df = DataFrame.fromRecords([
99+
{ city: "NYC", temp: "hot" },
100+
{ city: "NYC", temp: "cold" },
101+
{ city: "LA", temp: "hot" },
102+
{ city: "NYC", temp: "hot" },
103+
]);
104+
105+
const vc = dataFrameValueCounts(df);
106+
// Counts each unique (city, temp) combination
107+
// "NYC|hot" → 2
108+
// "NYC|cold" → 1
109+
// "LA|hot" → 1</code></pre>
110+
<div class="output">NYC|hot=2 NYC|cold=1 LA|hot=1</div>
111+
112+
<h2>6 · DataFrame subset</h2>
113+
<pre><code>// Restrict to a subset of columns
114+
const vc2 = dataFrameValueCounts(df, { subset: ["city"] });
115+
// Only counts by city: NYC=3, LA=1</code></pre>
116+
<div class="output">NYC=3 LA=1</div>
117+
118+
<h2>7 · API reference</h2>
119+
<pre><code>// Series
120+
valueCounts(series, {
121+
normalize?: boolean, // default false — return proportions instead of counts
122+
sort?: boolean, // default true — sort by frequency
123+
ascending?: boolean, // default false — highest count first when sort=true
124+
dropna?: boolean, // default true — exclude missing values
125+
}): Series&lt;number&gt;
126+
127+
// DataFrame
128+
dataFrameValueCounts(df, {
129+
subset?: readonly string[], // columns to use (default: all)
130+
normalize?: boolean,
131+
sort?: boolean,
132+
ascending?: boolean,
133+
dropna?: boolean,
134+
}): Series&lt;number&gt;</code></pre>
135+
136+
<div class="note">
137+
<strong>Note:</strong> The result Series is indexed by the unique values (or composite
138+
<code>"v1|v2|…"</code> strings for DataFrames). Use <code>.index.values</code> and
139+
<code>.values</code> to inspect the labels and counts respectively.
140+
</div>
141+
142+
<p><a href="index.html">Back to feature list</a></p>
143+
</body>
144+
</html>

0 commit comments

Comments
 (0)