1- <!doctype html>
1+ <!DOCTYPE html>
22< 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 — Grouper</ title >
7- < style >
8- body { font-family : system-ui, sans-serif; max-width : 800px ; margin : 2rem auto; padding : 0 1rem ; }
9- h1 { color : # 1a1a2e ; }
10- h2 { color : # 16213e ; border-bottom : 2px solid # e0e0e0 ; padding-bottom : 0.3rem ; }
11- pre { background : # f5f5f5 ; padding : 1rem ; border-radius : 6px ; overflow-x : auto; }
12- code { background : # f0f0f0 ; padding : 0.1em 0.3em ; border-radius : 3px ; }
13- .note { background : # fff3cd ; border-left : 4px solid # ffc107 ; padding : 0.75rem 1rem ; margin : 1rem 0 ; }
14- a { color : # 0366d6 ; }
15- </ style >
16- </ head >
17- < body >
18- < p > < a href ="index.html "> ← tsb playground</ a > </ p >
19- < h1 > pd.Grouper</ h1 >
3+ < head >
4+ < meta charset ="UTF-8 " />
5+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
6+ < title > tsb — Grouper</ title >
7+ < style >
8+ : root {
9+ --bg : # 0d1117 ;
10+ --surface : # 161b22 ;
11+ --border : # 30363d ;
12+ --text : # e6edf3 ;
13+ --accent : # 58a6ff ;
14+ --green : # 3fb950 ;
15+ --orange : # d29922 ;
16+ --red : # f85149 ;
17+ --font-mono : "Cascadia Code" , "Fira Code" , "JetBrains Mono" , monospace;
18+ }
19+ * { box-sizing : border-box; margin : 0 ; padding : 0 ; }
20+ body {
21+ background : var (--bg );
22+ color : var (--text );
23+ font-family : system-ui, -apple-system, sans-serif;
24+ line-height : 1.6 ;
25+ padding : 2rem ;
26+ max-width : 900px ;
27+ margin : 0 auto;
28+ }
29+ a { color : var (--accent ); }
30+ h1 { color : var (--accent ); margin-bottom : 0.5rem ; }
31+ h2 { margin-top : 0 ; margin-bottom : 0.5rem ; font-size : 1.25rem ; }
32+ p { color : # 8b949e ; margin-bottom : 1rem ; }
33+ code {
34+ font-family : var (--font-mono );
35+ font-size : 0.875em ;
36+ background : var (--surface );
37+ border : 1px solid var (--border );
38+ border-radius : 0.3rem ;
39+ padding : 0.1rem 0.4rem ;
40+ }
41+ .back { margin-bottom : 2rem ; display : inline-block; }
42+ .subtitle { margin-bottom : 1.5rem ; }
2043
21- < p >
22- < code > Grouper</ code > is a specification object that encapsulates groupby parameters so they
23- can be passed as a reusable spec to < code > groupby()</ code > . It mirrors
24- < a href ="https://pandas.pydata.org/docs/reference/api/pandas.Grouper.html "> < code > pandas.Grouper</ code > </ a > .
25- </ p >
44+ # playground-loading {
45+ position : fixed; inset : 0 ;
46+ background : rgba (13 , 17 , 23 , 0.92 );
47+ display : flex; flex-direction : column;
48+ align-items : center; justify-content : center;
49+ z-index : 1000 ; gap : 1rem ;
50+ }
51+ .spinner {
52+ width : 40px ; height : 40px ;
53+ border : 3px solid var (--border );
54+ border-top-color : var (--accent );
55+ border-radius : 50% ;
56+ animation : spin 0.8s linear infinite;
57+ }
58+ @keyframes spin { to { transform : rotate (360deg ); } }
59+ # playground-status { color : # 8b949e ; font-size : 0.95rem ; }
60+
61+ .section {
62+ background : var (--surface );
63+ border : 1px solid var (--border );
64+ border-radius : 0.75rem ;
65+ padding : 1.5rem ;
66+ margin-bottom : 1.5rem ;
67+ }
68+ .section p { margin-bottom : 0.75rem ; }
69+
70+ .playground-block { margin-top : 0.75rem ; }
71+ .playground-header {
72+ display : flex; align-items : center; justify-content : space-between;
73+ background : # 1c2128 ;
74+ border : 1px solid var (--border );
75+ border-bottom : none;
76+ border-radius : 0.5rem 0.5rem 0 0 ;
77+ padding : 0.4rem 0.75rem ;
78+ }
79+ .playground-label {
80+ font-size : 0.75rem ; color : # 8b949e ;
81+ text-transform : uppercase; letter-spacing : 0.05em ;
82+ }
83+ .playground-actions { display : flex; gap : 0.5rem ; }
84+ .playground-actions button {
85+ background : transparent; color : var (--accent );
86+ border : 1px solid var (--border );
87+ border-radius : 0.35rem ;
88+ padding : 0.25rem 0.7rem ;
89+ font-size : 0.8rem ; cursor : pointer;
90+ font-family : system-ui, sans-serif;
91+ transition : background 0.15s , border-color 0.15s ;
92+ }
93+ .playground-actions button : hover : not (: disabled ) {
94+ background : rgba (88 , 166 , 255 , 0.1 );
95+ border-color : var (--accent );
96+ }
97+ .playground-actions button : disabled { opacity : 0.4 ; cursor : not-allowed; }
98+ .playground-run { font-weight : 600 ; }
99+
100+ .playground-editor {
101+ display : block; width : 100% ; min-height : 80px ;
102+ background : # 0d1117 ; color : var (--text );
103+ border : 1px solid var (--border );
104+ border-top : none; border-bottom : none;
105+ padding : 1rem ;
106+ font-family : var (--font-mono );
107+ font-size : 0.875rem ; line-height : 1.55 ;
108+ resize : vertical; outline : none;
109+ tab-size : 2 ; white-space : pre; overflow-x : auto;
110+ }
111+ .playground-editor : focus {
112+ border-color : var (--accent );
113+ box-shadow : inset 0 0 0 1px var (--accent );
114+ }
26115
27- < h2 > Key vs Level grouping</ h2 >
28- < pre > < code > import { Grouper, isGrouper, DataFrame } from "tsb";
116+ .playground-output {
117+ background : # 1c2333 ;
118+ border : 1px solid var (--border );
119+ border-radius : 0 0 0.5rem 0.5rem ;
120+ padding : 0.75rem 1rem ;
121+ font-family : var (--font-mono );
122+ font-size : 0.85rem ; color : # 8b949e ;
123+ white-space : pre-wrap; min-height : 2rem ;
124+ word-break : break-word;
125+ }
126+ .playground-output .active { color : var (--green ); border-color : var (--green ); }
127+ .playground-output .error { color : var (--red ); border-color : var (--red ); }
128+ .playground-hint {
129+ font-size : 0.75rem ; color : # 484f58 ;
130+ margin-top : 0.35rem ; text-align : right;
131+ }
132+
133+ footer {
134+ text-align : center;
135+ padding : 2rem 0 ;
136+ color : # 8b949e ;
137+ font-size : 0.85rem ;
138+ border-top : 1px solid var (--border );
139+ margin-top : 2rem ;
140+ }
141+ </ style >
142+ </ head >
143+ < body >
144+
145+ < div id ="playground-loading ">
146+ < div class ="spinner "> </ div >
147+ < div id ="playground-status "> Initializing playground…</ div >
148+ </ div >
149+
150+ < a class ="back " href ="index.html "> ← Back to roadmap</ a >
151+ < h1 > pd.Grouper</ h1 >
152+ < p class ="subtitle "> < code > Grouper</ code > is a specification object that encapsulates groupby parameters — mirrors < a href ="https://pandas.pydata.org/docs/reference/api/pandas.Grouper.html "> < code > pandas.Grouper</ code > </ a > .</ p >
153+
154+ < div class ="section ">
155+ < h2 > 1 — Key vs Level grouping</ h2 >
156+ < p > Create a < code > Grouper</ code > for column-key grouping or index-level grouping.</ p >
157+ < div class ="playground-block ">
158+ < div class ="playground-header ">
159+ < span class ="playground-label "> TypeScript</ span >
160+ < div class ="playground-actions ">
161+ < button class ="playground-run " disabled > ▶ Run</ button >
162+ < button class ="playground-reset "> ↺ Reset</ button >
163+ </ div >
164+ </ div >
165+ < textarea class ="playground-editor " spellcheck ="false "> import { Grouper, isGrouper } from "tsb";
29166
30167// Group by a column key
31168const byDept = new Grouper({ key: "dept" });
32- console.log(byDept.isKeyGrouper()); // true
169+ console.log(byDept.isKeyGrouper()); // true
170+ console.log(byDept.isLevelGrouper()); // false
33171
34172// Group by an index level
35173const byLevel = new Grouper({ level: 0 });
@@ -38,61 +176,79 @@ <h2>Key vs Level grouping</h2>
38176// Frequency-based (time) grouping
39177const byMonth = new Grouper({ key: "date", freq: "ME" });
40178console.log(byMonth.isFreqGrouper()); // true
41- </ code > </ pre >
42179
43- < h2 > Options</ h2 >
44- < pre > < code > const g = new Grouper({
180+ // isGrouper type-guard
181+ console.log(isGrouper(byDept)); // true
182+ console.log(isGrouper("dept")); // false</ textarea >
183+ < div class ="playground-output "> Click ▶ Run to execute</ div >
184+ < div class ="playground-hint "> Ctrl+Enter to run · Tab to indent</ div >
185+ </ div >
186+ </ div >
187+
188+ < div class ="section ">
189+ < h2 > 2 — Options & toString</ h2 >
190+ < p > Full set of Grouper options: < code > freq</ code > , < code > sort</ code > , < code > dropna</ code > , < code > closed</ code > , < code > label</ code > .</ p >
191+ < div class ="playground-block ">
192+ < div class ="playground-header ">
193+ < span class ="playground-label "> TypeScript</ span >
194+ < div class ="playground-actions ">
195+ < button class ="playground-run " disabled > ▶ Run</ button >
196+ < button class ="playground-reset "> ↺ Reset</ button >
197+ </ div >
198+ </ div >
199+ < textarea class ="playground-editor " spellcheck ="false "> import { Grouper } from "tsb";
200+
201+ const g = new Grouper({
45202 key: "date",
46- freq: "QS", // quarter-start frequency
47- sort: true, // sort group keys
48- dropna: false, // keep NaN group keys
49- closed: "left", // left-closed bins
50- label: "left", // label bins with left edge
203+ freq: "QS", // quarter-start frequency
204+ sort: true, // sort group keys
205+ dropna: false, // keep NaN group keys
206+ closed: "left", // left-closed bins
207+ label: "left", // label bins with left edge
51208});
52209
53210console.log(g.toString());
54- // Grouper(key="date", freq="QS", sort=true, dropna=false)
55- </ code > </ pre >
56-
57- < h2 > isGrouper helper</ h2 >
58- < pre > < code > isGrouper(new Grouper({ key: "dept" })); // true
59- isGrouper("dept"); // false
60- isGrouper({ key: "dept" }); // false
61- </ code > </ pre >
62-
63- < h2 > Usage with groupby</ h2 >
64- < div class ="note ">
65- < strong > Note:</ strong > Full integration of < code > Grouper</ code > with < code > DataFrame.groupby()</ code >
66- for freq-based and level-based grouping is a future iteration. Currently < code > Grouper</ code >
67- serves as the spec object — you can use < code > groupby(g.key!)</ code > with a key-based grouper.
211+ // Grouper(key="date", freq="QS", sort=true, dropna=false)</ textarea >
212+ < div class ="playground-output "> Click ▶ Run to execute</ div >
213+ < div class ="playground-hint "> Ctrl+Enter to run · Tab to indent</ div >
68214 </ div >
69- < pre > < code > const df = DataFrame.fromColumns({
70- dept: ["Engineering", "Engineering", "Marketing"],
71- salary: [100_000, 120_000, 80_000],
215+ </ div >
216+
217+ < div class ="section ">
218+ < h2 > 3 — Usage with groupby</ h2 >
219+ < p > Use < code > g.key!</ code > to pass the key directly to < code > groupby()</ code > . Full Grouper integration (freq/level) is a future iteration.</ p >
220+ < div class ="playground-block ">
221+ < div class ="playground-header ">
222+ < span class ="playground-label "> TypeScript</ span >
223+ < div class ="playground-actions ">
224+ < button class ="playground-run " disabled > ▶ Run</ button >
225+ < button class ="playground-reset "> ↺ Reset</ button >
226+ </ div >
227+ </ div >
228+ < textarea class ="playground-editor " spellcheck ="false "> import { Grouper, DataFrame } from "tsb";
229+
230+ const df = DataFrame.fromColumns({
231+ dept: ["Engineering", "Engineering", "Marketing"],
232+ salary: [100_000, 120_000, 80_000],
72233});
73234
74235const g = new Grouper({ key: "dept", sort: true });
75- // When groupby supports Grouper natively:
76- // df.groupby(g).mean()
77- //
78- // For now, use the key directly:
79- df.groupby(g.key!).mean();
80- // DataFrame { dept: ["Engineering","Marketing"], salary: [110000, 80000] }
81- </ code > </ pre >
82-
83- < h2 > Python pandas equivalent</ h2 >
84- < pre > < code > import pandas as pd
85-
86- df = pd.DataFrame({
87- "dept": ["Engineering", "Engineering", "Marketing"],
88- "salary": [100_000, 120_000, 80_000],
89- })
90-
91- g = pd.Grouper(key="dept", sort=True)
92- df.groupby(g)["salary"].mean()
93- # dept
94- # Engineering 110000.0
95- # Marketing 80000.0
96- </ code > </ pre >
97- </ body >
236+
237+ // Use the key directly until full Grouper integration lands:
238+ const result = df.groupby(g.key!).mean();
239+ console.log([...result.col("salary").values]); // [110000, 80000]</ textarea >
240+ < div class ="playground-output "> Click ▶ Run to execute</ div >
241+ < div class ="playground-hint "> Ctrl+Enter to run · Tab to indent</ div >
242+ </ div >
243+ </ div >
244+
245+ < footer >
246+ < p >
247+ < a href ="index.html "> tsb playground</ a > ·
248+ Built by < a href ="https://github.com/githubnext/autoloop "> Autoloop</ a >
249+ </ p >
250+ </ footer >
251+
252+ < script type ="module " src ="playground-runtime.js "> </ script >
253+ </ body >
98254</ html >
0 commit comments