juxscript 1.1.240 → 1.1.244

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/index.js +7 -137
  2. package/lib/components/dataframe.ts +0 -569
  3. package/lib/components/include.ts +229 -229
  4. package/lib/components/tag.ts +68 -0
  5. package/lib/styles/shadcn.css +17 -5
  6. package/lib/utils/idgen.ts +6 -0
  7. package/package.json +5 -6
  8. package/index.d.ts +0 -239
  9. package/index.d.ts.map +0 -1
  10. package/lib/components/alert.d.ts +0 -36
  11. package/lib/components/alert.d.ts.map +0 -1
  12. package/lib/components/alert.js +0 -172
  13. package/lib/components/alert.ts +0 -219
  14. package/lib/components/app.d.ts +0 -89
  15. package/lib/components/app.d.ts.map +0 -1
  16. package/lib/components/app.js +0 -175
  17. package/lib/components/app.ts +0 -247
  18. package/lib/components/badge.d.ts +0 -26
  19. package/lib/components/badge.d.ts.map +0 -1
  20. package/lib/components/badge.js +0 -91
  21. package/lib/components/badge.ts +0 -118
  22. package/lib/components/base/Animations.d.ts +0 -36
  23. package/lib/components/base/Animations.d.ts.map +0 -1
  24. package/lib/components/base/Animations.js +0 -70
  25. package/lib/components/base/Animations.ts +0 -112
  26. package/lib/components/base/BaseComponent.d.ts +0 -294
  27. package/lib/components/base/BaseComponent.d.ts.map +0 -1
  28. package/lib/components/base/BaseComponent.js +0 -735
  29. package/lib/components/base/BaseComponent.ts +0 -884
  30. package/lib/components/base/FormInput.d.ts +0 -77
  31. package/lib/components/base/FormInput.d.ts.map +0 -1
  32. package/lib/components/base/FormInput.js +0 -171
  33. package/lib/components/base/FormInput.ts +0 -237
  34. package/lib/components/blueprint.d.ts +0 -40
  35. package/lib/components/blueprint.d.ts.map +0 -1
  36. package/lib/components/blueprint.js +0 -327
  37. package/lib/components/button.d.ts +0 -70
  38. package/lib/components/button.d.ts.map +0 -1
  39. package/lib/components/button.js +0 -177
  40. package/lib/components/button.ts +0 -237
  41. package/lib/components/card.d.ts +0 -35
  42. package/lib/components/card.d.ts.map +0 -1
  43. package/lib/components/card.js +0 -130
  44. package/lib/components/card.ts +0 -177
  45. package/lib/components/chart.d.ts +0 -49
  46. package/lib/components/chart.d.ts.map +0 -1
  47. package/lib/components/chart.js +0 -205
  48. package/lib/components/chart.ts +0 -254
  49. package/lib/components/checkbox.d.ts +0 -33
  50. package/lib/components/checkbox.d.ts.map +0 -1
  51. package/lib/components/checkbox.js +0 -202
  52. package/lib/components/checkbox.ts +0 -260
  53. package/lib/components/code.d.ts +0 -52
  54. package/lib/components/code.d.ts.map +0 -1
  55. package/lib/components/code.js +0 -201
  56. package/lib/components/code.ts +0 -260
  57. package/lib/components/container.d.ts +0 -60
  58. package/lib/components/container.d.ts.map +0 -1
  59. package/lib/components/container.js +0 -195
  60. package/lib/components/container.ts +0 -259
  61. package/lib/components/data.d.ts +0 -36
  62. package/lib/components/data.d.ts.map +0 -1
  63. package/lib/components/data.js +0 -110
  64. package/lib/components/data.ts +0 -135
  65. package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
  66. package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
  67. package/lib/components/dataframe/DataFrameSource.js +0 -421
  68. package/lib/components/dataframe/DataFrameSource.ts +0 -532
  69. package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
  70. package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
  71. package/lib/components/dataframe/ImportSettingsModal.js +0 -442
  72. package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
  73. package/lib/components/dataframe.d.ts +0 -110
  74. package/lib/components/dataframe.d.ts.map +0 -1
  75. package/lib/components/dataframe.js +0 -470
  76. package/lib/components/datepicker.d.ts +0 -40
  77. package/lib/components/datepicker.d.ts.map +0 -1
  78. package/lib/components/datepicker.js +0 -193
  79. package/lib/components/datepicker.ts +0 -251
  80. package/lib/components/dialog.d.ts +0 -39
  81. package/lib/components/dialog.d.ts.map +0 -1
  82. package/lib/components/dialog.js +0 -131
  83. package/lib/components/dialog.ts +0 -178
  84. package/lib/components/divider.d.ts +0 -31
  85. package/lib/components/divider.d.ts.map +0 -1
  86. package/lib/components/divider.js +0 -72
  87. package/lib/components/divider.ts +0 -104
  88. package/lib/components/dropdown-menu.d.ts +0 -42
  89. package/lib/components/dropdown-menu.d.ts.map +0 -1
  90. package/lib/components/dropdown-menu.js +0 -177
  91. package/lib/components/dropdown-menu.ts +0 -214
  92. package/lib/components/dropdown.d.ts +0 -41
  93. package/lib/components/dropdown.d.ts.map +0 -1
  94. package/lib/components/dropdown.js +0 -136
  95. package/lib/components/dropdown.ts +0 -188
  96. package/lib/components/element.d.ts +0 -51
  97. package/lib/components/element.d.ts.map +0 -1
  98. package/lib/components/element.js +0 -209
  99. package/lib/components/element.ts +0 -271
  100. package/lib/components/event-chain.d.ts +0 -9
  101. package/lib/components/event-chain.d.ts.map +0 -1
  102. package/lib/components/event-chain.js +0 -33
  103. package/lib/components/fileupload.d.ts +0 -98
  104. package/lib/components/fileupload.d.ts.map +0 -1
  105. package/lib/components/fileupload.js +0 -351
  106. package/lib/components/fileupload.ts +0 -449
  107. package/lib/components/grid.d.ts +0 -88
  108. package/lib/components/grid.d.ts.map +0 -1
  109. package/lib/components/grid.js +0 -208
  110. package/lib/components/grid.ts +0 -295
  111. package/lib/components/heading.d.ts +0 -25
  112. package/lib/components/heading.d.ts.map +0 -1
  113. package/lib/components/heading.js +0 -83
  114. package/lib/components/heading.ts +0 -113
  115. package/lib/components/helpers.d.ts +0 -9
  116. package/lib/components/helpers.d.ts.map +0 -1
  117. package/lib/components/helpers.js +0 -30
  118. package/lib/components/helpers.ts +0 -41
  119. package/lib/components/hero.d.ts +0 -60
  120. package/lib/components/hero.d.ts.map +0 -1
  121. package/lib/components/hero.js +0 -239
  122. package/lib/components/hero.ts +0 -302
  123. package/lib/components/history/StateHistory.d.ts +0 -91
  124. package/lib/components/history/StateHistory.d.ts.map +0 -1
  125. package/lib/components/history/StateHistory.js +0 -154
  126. package/lib/components/history/StateHistory.ts +0 -200
  127. package/lib/components/icon.d.ts +0 -36
  128. package/lib/components/icon.d.ts.map +0 -1
  129. package/lib/components/icon.js +0 -135
  130. package/lib/components/icon.ts +0 -182
  131. package/lib/components/icons.d.ts +0 -25
  132. package/lib/components/icons.d.ts.map +0 -1
  133. package/lib/components/icons.js +0 -440
  134. package/lib/components/icons.ts +0 -464
  135. package/lib/components/image.d.ts +0 -42
  136. package/lib/components/image.d.ts.map +0 -1
  137. package/lib/components/image.js +0 -204
  138. package/lib/components/image.ts +0 -260
  139. package/lib/components/include.d.ts +0 -86
  140. package/lib/components/include.d.ts.map +0 -1
  141. package/lib/components/include.js +0 -238
  142. package/lib/components/input.d.ts +0 -85
  143. package/lib/components/input.d.ts.map +0 -1
  144. package/lib/components/input.js +0 -362
  145. package/lib/components/input.ts +0 -473
  146. package/lib/components/layer.d.ts +0 -72
  147. package/lib/components/layer.d.ts.map +0 -1
  148. package/lib/components/layer.js +0 -219
  149. package/lib/components/layer.ts +0 -304
  150. package/lib/components/link.d.ts +0 -41
  151. package/lib/components/link.d.ts.map +0 -1
  152. package/lib/components/link.js +0 -216
  153. package/lib/components/link.ts +0 -268
  154. package/lib/components/list.d.ts +0 -83
  155. package/lib/components/list.d.ts.map +0 -1
  156. package/lib/components/list.js +0 -314
  157. package/lib/components/list.ts +0 -423
  158. package/lib/components/loading.d.ts +0 -25
  159. package/lib/components/loading.d.ts.map +0 -1
  160. package/lib/components/loading.js +0 -76
  161. package/lib/components/loading.ts +0 -104
  162. package/lib/components/menu.d.ts +0 -38
  163. package/lib/components/menu.d.ts.map +0 -1
  164. package/lib/components/menu.js +0 -205
  165. package/lib/components/menu.ts +0 -279
  166. package/lib/components/modal.d.ts +0 -97
  167. package/lib/components/modal.d.ts.map +0 -1
  168. package/lib/components/modal.js +0 -463
  169. package/lib/components/modal.ts +0 -576
  170. package/lib/components/nav.d.ts +0 -46
  171. package/lib/components/nav.d.ts.map +0 -1
  172. package/lib/components/nav.js +0 -193
  173. package/lib/components/nav.ts +0 -261
  174. package/lib/components/paragraph.d.ts +0 -30
  175. package/lib/components/paragraph.d.ts.map +0 -1
  176. package/lib/components/paragraph.js +0 -93
  177. package/lib/components/paragraph.ts +0 -123
  178. package/lib/components/pen.d.ts +0 -125
  179. package/lib/components/pen.d.ts.map +0 -1
  180. package/lib/components/pen.js +0 -443
  181. package/lib/components/pen.ts +0 -567
  182. package/lib/components/progress.d.ts +0 -40
  183. package/lib/components/progress.d.ts.map +0 -1
  184. package/lib/components/progress.js +0 -116
  185. package/lib/components/progress.ts +0 -163
  186. package/lib/components/radio.d.ts +0 -43
  187. package/lib/components/radio.d.ts.map +0 -1
  188. package/lib/components/radio.js +0 -226
  189. package/lib/components/radio.ts +0 -303
  190. package/lib/components/registry.d.ts +0 -34
  191. package/lib/components/registry.d.ts.map +0 -1
  192. package/lib/components/registry.js +0 -163
  193. package/lib/components/registry.ts +0 -193
  194. package/lib/components/req.d.ts +0 -155
  195. package/lib/components/req.d.ts.map +0 -1
  196. package/lib/components/req.js +0 -253
  197. package/lib/components/req.ts +0 -303
  198. package/lib/components/script.d.ts +0 -14
  199. package/lib/components/script.d.ts.map +0 -1
  200. package/lib/components/script.js +0 -33
  201. package/lib/components/script.ts +0 -41
  202. package/lib/components/select.d.ts +0 -42
  203. package/lib/components/select.d.ts.map +0 -1
  204. package/lib/components/select.js +0 -209
  205. package/lib/components/select.ts +0 -281
  206. package/lib/components/sidebar.d.ts +0 -59
  207. package/lib/components/sidebar.d.ts.map +0 -1
  208. package/lib/components/sidebar.js +0 -298
  209. package/lib/components/sidebar.ts +0 -395
  210. package/lib/components/stack/BaseStack.d.ts +0 -65
  211. package/lib/components/stack/BaseStack.d.ts.map +0 -1
  212. package/lib/components/stack/BaseStack.js +0 -274
  213. package/lib/components/stack/BaseStack.ts +0 -328
  214. package/lib/components/stack/HStack.d.ts +0 -18
  215. package/lib/components/stack/HStack.d.ts.map +0 -1
  216. package/lib/components/stack/HStack.js +0 -22
  217. package/lib/components/stack/HStack.ts +0 -25
  218. package/lib/components/stack/VStack.d.ts +0 -19
  219. package/lib/components/stack/VStack.d.ts.map +0 -1
  220. package/lib/components/stack/VStack.js +0 -23
  221. package/lib/components/stack/VStack.ts +0 -26
  222. package/lib/components/stack/ZStack.d.ts +0 -18
  223. package/lib/components/stack/ZStack.d.ts.map +0 -1
  224. package/lib/components/stack/ZStack.js +0 -22
  225. package/lib/components/stack/ZStack.ts +0 -25
  226. package/lib/components/style.d.ts +0 -14
  227. package/lib/components/style.d.ts.map +0 -1
  228. package/lib/components/style.js +0 -33
  229. package/lib/components/style.ts +0 -41
  230. package/lib/components/switch.d.ts +0 -34
  231. package/lib/components/switch.d.ts.map +0 -1
  232. package/lib/components/switch.js +0 -209
  233. package/lib/components/switch.ts +0 -272
  234. package/lib/components/table.d.ts +0 -137
  235. package/lib/components/table.d.ts.map +0 -1
  236. package/lib/components/table.js +0 -1019
  237. package/lib/components/table.ts +0 -1225
  238. package/lib/components/tabs.d.ts +0 -53
  239. package/lib/components/tabs.d.ts.map +0 -1
  240. package/lib/components/tabs.js +0 -275
  241. package/lib/components/tabs.ts +0 -349
  242. package/lib/components/theme-toggle.d.ts +0 -45
  243. package/lib/components/theme-toggle.d.ts.map +0 -1
  244. package/lib/components/theme-toggle.js +0 -218
  245. package/lib/components/theme-toggle.ts +0 -297
  246. package/lib/components/tooltip.d.ts +0 -31
  247. package/lib/components/tooltip.d.ts.map +0 -1
  248. package/lib/components/tooltip.js +0 -112
  249. package/lib/components/tooltip.ts +0 -148
  250. package/lib/components/watcher.d.ts +0 -195
  251. package/lib/components/watcher.d.ts.map +0 -1
  252. package/lib/components/watcher.js +0 -241
  253. package/lib/components/watcher.ts +0 -261
  254. package/lib/components/write.d.ts +0 -107
  255. package/lib/components/write.d.ts.map +0 -1
  256. package/lib/components/write.js +0 -222
  257. package/lib/components/write.ts +0 -272
  258. package/lib/data/DataPipeline.d.ts +0 -113
  259. package/lib/data/DataPipeline.d.ts.map +0 -1
  260. package/lib/data/DataPipeline.js +0 -359
  261. package/lib/data/DataPipeline.ts +0 -452
  262. package/lib/facades/dataframe.jux +0 -0
  263. package/lib/globals.d.ts +0 -21
  264. package/lib/reactivity/state.d.ts +0 -36
  265. package/lib/reactivity/state.d.ts.map +0 -1
  266. package/lib/reactivity/state.js +0 -67
  267. package/lib/reactivity/state.ts +0 -78
  268. package/lib/storage/DataFrame.d.ts +0 -284
  269. package/lib/storage/DataFrame.d.ts.map +0 -1
  270. package/lib/storage/DataFrame.js +0 -1022
  271. package/lib/storage/DataFrame.ts +0 -1195
  272. package/lib/storage/DataFrameSource.d.ts +0 -158
  273. package/lib/storage/DataFrameSource.d.ts.map +0 -1
  274. package/lib/storage/DataFrameSource.js +0 -409
  275. package/lib/storage/DataFrameSource.ts +0 -556
  276. package/lib/storage/FileStorage.d.ts +0 -53
  277. package/lib/storage/FileStorage.d.ts.map +0 -1
  278. package/lib/storage/FileStorage.js +0 -80
  279. package/lib/storage/FileStorage.ts +0 -95
  280. package/lib/storage/IndexedDBDriver.d.ts +0 -75
  281. package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
  282. package/lib/storage/IndexedDBDriver.js +0 -177
  283. package/lib/storage/IndexedDBDriver.ts +0 -226
  284. package/lib/storage/TabularDriver.d.ts +0 -118
  285. package/lib/storage/TabularDriver.d.ts.map +0 -1
  286. package/lib/storage/TabularDriver.js +0 -731
  287. package/lib/storage/TabularDriver.ts +0 -874
  288. package/lib/utils/codeparser.d.ts +0 -29
  289. package/lib/utils/codeparser.d.ts.map +0 -1
  290. package/lib/utils/codeparser.js +0 -409
  291. package/lib/utils/fetch.d.ts +0 -176
  292. package/lib/utils/fetch.d.ts.map +0 -1
  293. package/lib/utils/fetch.js +0 -427
  294. package/lib/utils/formatId.d.ts +0 -16
  295. package/lib/utils/formatId.d.ts.map +0 -1
  296. package/lib/utils/formatId.js +0 -27
  297. package/lib/utils/path-resolver.js +0 -23
@@ -1,1195 +0,0 @@
1
- /**
2
- * DataFrame - A pandas-like data manipulation class for JavaScript
3
- *
4
- * Core capabilities:
5
- * - Column operations (select, drop, rename, reorder)
6
- * - Row operations (filter, head, tail, sample, slice)
7
- * - Data transformation (withColumn, apply, map)
8
- * - Aggregation (groupBy, agg, pivot)
9
- * - Joins (merge, concat, union)
10
- * - Type inference and conversion
11
- * - Sorting and indexing
12
- * - Statistics and describe
13
- * - Missing data handling
14
- */
15
-
16
- export type DataType = 'string' | 'number' | 'boolean' | 'date' | 'null' | 'mixed';
17
-
18
- export interface ColumnSchema {
19
- name: string;
20
- dtype: DataType;
21
- nullable: boolean;
22
- unique?: number;
23
- min?: any;
24
- max?: any;
25
- }
26
-
27
- export interface DataFrameOptions {
28
- inferTypes?: boolean;
29
- columns?: string[];
30
- }
31
-
32
- export class DataFrame {
33
- private _columns!: string[];
34
- private _data!: Map<string, any[]>;
35
- private _height!: number;
36
- private _schema: Map<string, ColumnSchema>;
37
- private _inferTypes: boolean;
38
-
39
- constructor(
40
- data: Record<string, any>[] | Record<string, any[]> | DataFrame,
41
- options: DataFrameOptions = {}
42
- ) {
43
- this._inferTypes = options.inferTypes ?? true;
44
- this._schema = new Map();
45
-
46
- if (data instanceof DataFrame) {
47
- // Clone from another DataFrame
48
- this._columns = [...data._columns];
49
- this._data = new Map();
50
- data._columns.forEach(col => {
51
- this._data.set(col, [...data._data.get(col)!]);
52
- });
53
- this._height = data._height;
54
- this._schema = new Map(data._schema);
55
- } else if (Array.isArray(data)) {
56
- // Row-oriented: [{col1: val1, col2: val2}, ...]
57
- this._initFromRows(data, options.columns);
58
- } else {
59
- // Column-oriented: {col1: [val1, val2], col2: [val1, val2]}
60
- this._initFromColumns(data, options.columns);
61
- }
62
-
63
- if (this._inferTypes) {
64
- this._inferSchema();
65
- }
66
- }
67
-
68
- private _initFromRows(rows: Record<string, any>[], columnOrder?: string[]): void {
69
- if (rows.length === 0) {
70
- this._columns = columnOrder || [];
71
- this._data = new Map();
72
- this._height = 0;
73
- return;
74
- }
75
-
76
- // Get all unique columns across all rows
77
- const colSet = new Set<string>();
78
- rows.forEach(row => Object.keys(row).forEach(k => colSet.add(k)));
79
-
80
- this._columns = columnOrder || Array.from(colSet);
81
- this._data = new Map();
82
- this._height = rows.length;
83
-
84
- this._columns.forEach(col => {
85
- this._data.set(col, rows.map(row => row[col] ?? null));
86
- });
87
- }
88
-
89
- private _initFromColumns(cols: Record<string, any[]>, columnOrder?: string[]): void {
90
- this._columns = columnOrder || Object.keys(cols);
91
- this._data = new Map();
92
-
93
- const lengths = this._columns.map(c => cols[c]?.length ?? 0);
94
- this._height = Math.max(0, ...lengths);
95
-
96
- this._columns.forEach(col => {
97
- const values = cols[col] || [];
98
- // Pad shorter columns with null
99
- const padded = [...values];
100
- while (padded.length < this._height) {
101
- padded.push(null);
102
- }
103
- this._data.set(col, padded);
104
- });
105
- }
106
-
107
- private _inferSchema(): void {
108
- this._columns.forEach(col => {
109
- const values = this._data.get(col)!;
110
- const schema = this._inferColumnSchema(col, values);
111
- this._schema.set(col, schema);
112
- });
113
- }
114
-
115
- private _inferColumnSchema(name: string, values: any[]): ColumnSchema {
116
- let hasNull = false;
117
- let hasString = false;
118
- let hasNumber = false;
119
- let hasBoolean = false;
120
- let hasDate = false;
121
- let min: any = undefined;
122
- let max: any = undefined;
123
- const uniqueValues = new Set<any>();
124
-
125
- values.forEach(v => {
126
- if (v === null || v === undefined || v === '') {
127
- hasNull = true;
128
- return;
129
- }
130
-
131
- uniqueValues.add(v);
132
-
133
- if (typeof v === 'boolean') {
134
- hasBoolean = true;
135
- } else if (typeof v === 'number' && !isNaN(v)) {
136
- hasNumber = true;
137
- if (min === undefined || v < min) min = v;
138
- if (max === undefined || v > max) max = v;
139
- } else if (v instanceof Date) {
140
- hasDate = true;
141
- } else if (typeof v === 'string') {
142
- // Try to parse as number
143
- const num = Number(v);
144
- if (!isNaN(num) && v.trim() !== '') {
145
- hasNumber = true;
146
- if (min === undefined || num < min) min = num;
147
- if (max === undefined || num > max) max = num;
148
- } else if (v === 'true' || v === 'false') {
149
- hasBoolean = true;
150
- } else {
151
- hasString = true;
152
- }
153
- }
154
- });
155
-
156
- let dtype: DataType = 'null';
157
- const typeCount = [hasString, hasNumber, hasBoolean, hasDate].filter(Boolean).length;
158
-
159
- if (typeCount === 0) {
160
- dtype = 'null';
161
- } else if (typeCount > 1) {
162
- dtype = 'mixed';
163
- } else if (hasNumber) {
164
- dtype = 'number';
165
- } else if (hasBoolean) {
166
- dtype = 'boolean';
167
- } else if (hasDate) {
168
- dtype = 'date';
169
- } else {
170
- dtype = 'string';
171
- }
172
-
173
- return {
174
- name,
175
- dtype,
176
- nullable: hasNull,
177
- unique: uniqueValues.size,
178
- min,
179
- max
180
- };
181
- }
182
-
183
- /* ═══════════════════════════════════════════════════
184
- * ACCESSORS
185
- * ═══════════════════════════════════════════════════ */
186
-
187
- get columns(): string[] {
188
- return [...this._columns];
189
- }
190
-
191
- get height(): number {
192
- return this._height;
193
- }
194
-
195
- get width(): number {
196
- return this._columns.length;
197
- }
198
-
199
- get shape(): [number, number] {
200
- return [this._height, this._columns.length];
201
- }
202
-
203
- get dtypes(): Record<string, DataType> {
204
- const result: Record<string, DataType> = {};
205
- this._schema.forEach((schema, col) => {
206
- result[col] = schema.dtype;
207
- });
208
- return result;
209
- }
210
-
211
- get schema(): ColumnSchema[] {
212
- return this._columns.map(col => this._schema.get(col)!);
213
- }
214
-
215
- get isEmpty(): boolean {
216
- return this._height === 0;
217
- }
218
-
219
- /* ═══════════════════════════════════════════════════
220
- * DATA ACCESS
221
- * ═══════════════════════════════════════════════════ */
222
-
223
- /**
224
- * Get a column as an array
225
- */
226
- col(name: string): any[] {
227
- const data = this._data.get(name);
228
- if (!data) throw new Error(`Column '${name}' not found`);
229
- return [...data];
230
- }
231
-
232
- /**
233
- * Get column (alias for col)
234
- */
235
- getColumn(name: string): any[] {
236
- return this.col(name);
237
- }
238
-
239
- /**
240
- * Get a single row by index
241
- */
242
- row(index: number): Record<string, any> {
243
- if (index < 0 || index >= this._height) {
244
- throw new Error(`Row index ${index} out of bounds`);
245
- }
246
- const result: Record<string, any> = {};
247
- this._columns.forEach(col => {
248
- result[col] = this._data.get(col)![index];
249
- });
250
- return result;
251
- }
252
-
253
- /**
254
- * Get value at specific row and column
255
- */
256
- at(row: number, col: string): any {
257
- const data = this._data.get(col);
258
- if (!data) throw new Error(`Column '${col}' not found`);
259
- if (row < 0 || row >= this._height) throw new Error(`Row index ${row} out of bounds`);
260
- return data[row];
261
- }
262
-
263
- /**
264
- * Set value at specific row and column (mutates!)
265
- */
266
- setAt(row: number, col: string, value: any): this {
267
- const data = this._data.get(col);
268
- if (!data) throw new Error(`Column '${col}' not found`);
269
- if (row < 0 || row >= this._height) throw new Error(`Row index ${row} out of bounds`);
270
- data[row] = value;
271
- return this;
272
- }
273
-
274
- /**
275
- * Convert to array of row objects
276
- */
277
- toRows(): Record<string, any>[] {
278
- const rows: Record<string, any>[] = [];
279
- for (let i = 0; i < this._height; i++) {
280
- rows.push(this.row(i));
281
- }
282
- return rows;
283
- }
284
-
285
- /**
286
- * Convert to column-oriented object
287
- */
288
- toColumns(): Record<string, any[]> {
289
- const result: Record<string, any[]> = {};
290
- this._columns.forEach(col => {
291
- result[col] = [...this._data.get(col)!];
292
- });
293
- return result;
294
- }
295
-
296
- /**
297
- * Get distinct/unique values in a column
298
- */
299
- distinct(column: string): any[] {
300
- const data = this._data.get(column);
301
- if (!data) throw new Error(`Column '${column}' not found`);
302
- return [...new Set(data)];
303
- }
304
-
305
- /**
306
- * Alias for distinct
307
- */
308
- unique(column: string): any[] {
309
- return this.distinct(column);
310
- }
311
-
312
- /**
313
- * Count occurrences of each value in a column
314
- */
315
- valueCounts(column: string): Record<any, number> {
316
- const data = this._data.get(column);
317
- if (!data) throw new Error(`Column '${column}' not found`);
318
-
319
- const counts: Record<any, number> = {};
320
- data.forEach(v => {
321
- const key = v === null ? '__null__' : String(v);
322
- counts[key] = (counts[key] || 0) + 1;
323
- });
324
- return counts;
325
- }
326
-
327
- /* ═══════════════════════════════════════════════════
328
- * COLUMN OPERATIONS
329
- * ═══════════════════════════════════════════════════ */
330
-
331
- /**
332
- * Select specific columns
333
- */
334
- select(...cols: string[]): DataFrame {
335
- const missing = cols.filter(c => !this._data.has(c));
336
- if (missing.length > 0) {
337
- throw new Error(`Columns not found: ${missing.join(', ')}`);
338
- }
339
-
340
- const newData: Record<string, any[]> = {};
341
- cols.forEach(col => {
342
- newData[col] = [...this._data.get(col)!];
343
- });
344
-
345
- return new DataFrame(newData, { columns: cols, inferTypes: this._inferTypes });
346
- }
347
-
348
- /**
349
- * Drop columns
350
- */
351
- drop(...cols: string[]): DataFrame {
352
- const keepCols = this._columns.filter(c => !cols.includes(c));
353
- return this.select(...keepCols);
354
- }
355
-
356
- /**
357
- * Rename columns
358
- */
359
- rename(mapping: Record<string, string>): DataFrame {
360
- const newData: Record<string, any[]> = {};
361
- const newCols: string[] = [];
362
-
363
- this._columns.forEach(col => {
364
- const newName = mapping[col] || col;
365
- newCols.push(newName);
366
- newData[newName] = [...this._data.get(col)!];
367
- });
368
-
369
- return new DataFrame(newData, { columns: newCols, inferTypes: this._inferTypes });
370
- }
371
-
372
- /**
373
- * Reorder columns
374
- */
375
- reorder(...cols: string[]): DataFrame {
376
- // Include any columns not specified at the end
377
- const remaining = this._columns.filter(c => !cols.includes(c));
378
- return this.select(...cols, ...remaining);
379
- }
380
-
381
- /**
382
- * Add or replace a column with computed values
383
- */
384
- withColumn(name: string, fn: (row: Record<string, any>, index: number) => any): DataFrame {
385
- const newValues = this.toRows().map((row, i) => fn(row, i));
386
-
387
- const newData: Record<string, any[]> = {};
388
- this._columns.forEach(col => {
389
- newData[col] = [...this._data.get(col)!];
390
- });
391
- newData[name] = newValues;
392
-
393
- const newCols = this._columns.includes(name)
394
- ? this._columns
395
- : [...this._columns, name];
396
-
397
- return new DataFrame(newData, { columns: newCols, inferTypes: this._inferTypes });
398
- }
399
-
400
- /**
401
- * Add multiple computed columns at once
402
- */
403
- withColumns(columns: Record<string, (row: Record<string, any>, index: number) => any>): DataFrame {
404
- let result: DataFrame = this;
405
- Object.entries(columns).forEach(([name, fn]) => {
406
- result = result.withColumn(name, fn);
407
- });
408
- return result;
409
- }
410
-
411
- /**
412
- * Cast column to a specific type
413
- */
414
- asType(column: string, dtype: 'string' | 'number' | 'boolean' | 'date'): DataFrame {
415
- return this.withColumn(column, (row) => {
416
- const val = row[column];
417
- if (val === null || val === undefined) return null;
418
-
419
- switch (dtype) {
420
- case 'string':
421
- return String(val);
422
- case 'number':
423
- const num = Number(val);
424
- return isNaN(num) ? null : num;
425
- case 'boolean':
426
- if (typeof val === 'boolean') return val;
427
- if (val === 'true' || val === '1' || val === 1) return true;
428
- if (val === 'false' || val === '0' || val === 0) return false;
429
- return null;
430
- case 'date':
431
- const date = new Date(val);
432
- return isNaN(date.getTime()) ? null : date;
433
- default:
434
- return val;
435
- }
436
- });
437
- }
438
-
439
- /* ═══════════════════════════════════════════════════
440
- * ROW OPERATIONS
441
- * ═══════════════════════════════════════════════════ */
442
-
443
- /**
444
- * Filter rows by predicate
445
- */
446
- filter(predicate: (row: Record<string, any>, index: number) => boolean): DataFrame {
447
- const rows = this.toRows();
448
- const filtered = rows.filter((row, i) => predicate(row, i));
449
- return new DataFrame(filtered, { columns: this._columns, inferTypes: this._inferTypes });
450
- }
451
-
452
- /**
453
- * Filter with SQL-like where clause
454
- */
455
- where(
456
- col: string,
457
- op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'not in' | 'contains' | 'startsWith' | 'endsWith' | 'isNull' | 'notNull',
458
- value?: any
459
- ): DataFrame {
460
- return this.filter(row => {
461
- const v = row[col];
462
-
463
- switch (op) {
464
- case '==': return v === value;
465
- case '!=': return v !== value;
466
- case '>': return v > value;
467
- case '<': return v < value;
468
- case '>=': return v >= value;
469
- case '<=': return v <= value;
470
- case 'in': return Array.isArray(value) && value.includes(v);
471
- case 'not in': return Array.isArray(value) && !value.includes(v);
472
- case 'contains': return String(v).includes(String(value));
473
- case 'startsWith': return String(v).startsWith(String(value));
474
- case 'endsWith': return String(v).endsWith(String(value));
475
- case 'isNull': return v === null || v === undefined || v === '';
476
- case 'notNull': return v !== null && v !== undefined && v !== '';
477
- default: return true;
478
- }
479
- });
480
- }
481
-
482
- /**
483
- * Get first N rows
484
- */
485
- head(n: number = 5): DataFrame {
486
- return this.slice(0, n);
487
- }
488
-
489
- /**
490
- * Get last N rows
491
- */
492
- tail(n: number = 5): DataFrame {
493
- return this.slice(Math.max(0, this._height - n));
494
- }
495
-
496
- /**
497
- * Slice rows by index range
498
- */
499
- slice(start: number, end?: number): DataFrame {
500
- const rows = this.toRows().slice(start, end);
501
- return new DataFrame(rows, { columns: this._columns, inferTypes: this._inferTypes });
502
- }
503
-
504
- /**
505
- * Get random sample of rows
506
- */
507
- sample(n: number, seed?: number): DataFrame {
508
- const rows = this.toRows();
509
- const shuffled = [...rows];
510
-
511
- // Simple shuffle (use seed if provided for reproducibility)
512
- for (let i = shuffled.length - 1; i > 0; i--) {
513
- const j = Math.floor(Math.random() * (i + 1));
514
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
515
- }
516
-
517
- return new DataFrame(shuffled.slice(0, n), { columns: this._columns, inferTypes: this._inferTypes });
518
- }
519
-
520
- /**
521
- * Drop duplicate rows
522
- */
523
- dropDuplicates(columns?: string[]): DataFrame {
524
- const cols = columns || this._columns;
525
- const seen = new Set<string>();
526
-
527
- const rows = this.toRows().filter(row => {
528
- const key = cols.map(c => JSON.stringify(row[c])).join('|');
529
- if (seen.has(key)) return false;
530
- seen.add(key);
531
- return true;
532
- });
533
-
534
- return new DataFrame(rows, { columns: this._columns, inferTypes: this._inferTypes });
535
- }
536
-
537
- /**
538
- * Drop rows with null values
539
- */
540
- dropna(columns?: string[]): DataFrame {
541
- const cols = columns || this._columns;
542
- return this.filter(row => {
543
- return cols.every(c => row[c] !== null && row[c] !== undefined && row[c] !== '');
544
- });
545
- }
546
-
547
- /**
548
- * Fill null values
549
- */
550
- fillna(value: any, columns?: string[]): DataFrame {
551
- const cols = columns || this._columns;
552
- let result: DataFrame = this;
553
-
554
- cols.forEach(col => {
555
- result = result.withColumn(col, row => {
556
- const v = row[col];
557
- return (v === null || v === undefined || v === '') ? value : v;
558
- });
559
- });
560
-
561
- return result;
562
- }
563
-
564
- /* ═══════════════════════════════════════════════════
565
- * SORTING
566
- * ═══════════════════════════════════════════════════ */
567
-
568
- /**
569
- * Sort by column(s)
570
- */
571
- sort(column: string | string[], descending: boolean | boolean[] = false): DataFrame {
572
- const cols = Array.isArray(column) ? column : [column];
573
- const descs = Array.isArray(descending) ? descending : cols.map(() => descending);
574
-
575
- const rows = this.toRows();
576
- rows.sort((a, b) => {
577
- for (let i = 0; i < cols.length; i++) {
578
- const col = cols[i];
579
- const desc = descs[i] || false;
580
- const aVal = a[col];
581
- const bVal = b[col];
582
-
583
- if (aVal === bVal) continue;
584
- if (aVal === null || aVal === undefined) return 1;
585
- if (bVal === null || bVal === undefined) return -1;
586
-
587
- const cmp = aVal < bVal ? -1 : 1;
588
- return desc ? -cmp : cmp;
589
- }
590
- return 0;
591
- });
592
-
593
- return new DataFrame(rows, { columns: this._columns, inferTypes: this._inferTypes });
594
- }
595
-
596
- /**
597
- * Sort descending (shorthand)
598
- */
599
- sortDesc(column: string): DataFrame {
600
- return this.sort(column, true);
601
- }
602
-
603
- /* ═══════════════════════════════════════════════════
604
- * AGGREGATION
605
- * ═══════════════════════════════════════════════════ */
606
-
607
- /**
608
- * Group by column(s) and aggregate
609
- */
610
- groupBy(columns: string | string[]): GroupedDataFrame {
611
- return new GroupedDataFrame(this, Array.isArray(columns) ? columns : [columns]);
612
- }
613
-
614
- /**
615
- * Aggregate entire DataFrame
616
- */
617
- agg(aggregations: Record<string, 'sum' | 'mean' | 'min' | 'max' | 'count' | 'first' | 'last' | ((values: any[]) => any)>): DataFrame {
618
- const result: Record<string, any> = {};
619
-
620
- Object.entries(aggregations).forEach(([col, aggFn]) => {
621
- const values = this._data.get(col);
622
- if (!values) throw new Error(`Column '${col}' not found`);
623
-
624
- const nonNull = values.filter(v => v !== null && v !== undefined);
625
-
626
- if (typeof aggFn === 'function') {
627
- result[col] = aggFn(nonNull);
628
- } else {
629
- switch (aggFn) {
630
- case 'sum':
631
- result[col] = nonNull.reduce((a, b) => a + Number(b), 0);
632
- break;
633
- case 'mean':
634
- result[col] = nonNull.length > 0
635
- ? nonNull.reduce((a, b) => a + Number(b), 0) / nonNull.length
636
- : null;
637
- break;
638
- case 'min':
639
- result[col] = nonNull.length > 0 ? Math.min(...nonNull.map(Number)) : null;
640
- break;
641
- case 'max':
642
- result[col] = nonNull.length > 0 ? Math.max(...nonNull.map(Number)) : null;
643
- break;
644
- case 'count':
645
- result[col] = nonNull.length;
646
- break;
647
- case 'first':
648
- result[col] = nonNull[0] ?? null;
649
- break;
650
- case 'last':
651
- result[col] = nonNull[nonNull.length - 1] ?? null;
652
- break;
653
- }
654
- }
655
- });
656
-
657
- return new DataFrame([result], { inferTypes: this._inferTypes });
658
- }
659
-
660
- /**
661
- * Count rows
662
- */
663
- count(): number {
664
- return this._height;
665
- }
666
-
667
- /**
668
- * Sum of numeric column
669
- */
670
- sum(column: string): number {
671
- const values = this._data.get(column);
672
- if (!values) throw new Error(`Column '${column}' not found`);
673
- return values.reduce((a, b) => a + (Number(b) || 0), 0);
674
- }
675
-
676
- /**
677
- * Mean of numeric column
678
- */
679
- mean(column: string): number {
680
- const values = this._data.get(column);
681
- if (!values) throw new Error(`Column '${column}' not found`);
682
- const nonNull = values.filter(v => v !== null && v !== undefined);
683
- if (nonNull.length === 0) return 0;
684
- return nonNull.reduce((a, b) => a + Number(b), 0) / nonNull.length;
685
- }
686
-
687
- /**
688
- * Min of column
689
- */
690
- min(column: string): any {
691
- const values = this._data.get(column);
692
- if (!values) throw new Error(`Column '${column}' not found`);
693
- const nonNull = values.filter(v => v !== null && v !== undefined);
694
- if (nonNull.length === 0) return null;
695
- return Math.min(...nonNull.map(Number));
696
- }
697
-
698
- /**
699
- * Max of column
700
- */
701
- max(column: string): any {
702
- const values = this._data.get(column);
703
- if (!values) throw new Error(`Column '${column}' not found`);
704
- const nonNull = values.filter(v => v !== null && v !== undefined);
705
- if (nonNull.length === 0) return null;
706
- return Math.max(...nonNull.map(Number));
707
- }
708
-
709
- /* ═══════════════════════════════════════════════════
710
- * JOINS & MERGING
711
- * ═══════════════════════════════════════════════════ */
712
-
713
- /**
714
- * Merge with another DataFrame (SQL-like join)
715
- */
716
- merge(
717
- other: DataFrame,
718
- options: {
719
- on?: string | string[];
720
- leftOn?: string | string[];
721
- rightOn?: string | string[];
722
- how?: 'inner' | 'left' | 'right' | 'outer';
723
- suffixes?: [string, string];
724
- } = {}
725
- ): DataFrame {
726
- const {
727
- on,
728
- leftOn = on,
729
- rightOn = on,
730
- how = 'inner',
731
- suffixes = ['_x', '_y']
732
- } = options;
733
-
734
- if (!leftOn || !rightOn) {
735
- throw new Error('Must specify join columns with "on" or "leftOn"/"rightOn"');
736
- }
737
-
738
- const leftKeys = Array.isArray(leftOn) ? leftOn : [leftOn];
739
- const rightKeys = Array.isArray(rightOn) ? rightOn : [rightOn];
740
-
741
- // Build index on right DataFrame
742
- const rightIndex = new Map<string, number[]>();
743
- other.toRows().forEach((row, i) => {
744
- const key = rightKeys.map(k => JSON.stringify(row[k])).join('|');
745
- if (!rightIndex.has(key)) rightIndex.set(key, []);
746
- rightIndex.get(key)!.push(i);
747
- });
748
-
749
- const leftRows = this.toRows();
750
- const rightRows = other.toRows();
751
- const result: Record<string, any>[] = [];
752
-
753
- // Determine output columns
754
- const leftCols = this._columns;
755
- const rightCols = other._columns.filter(c => !rightKeys.includes(c));
756
-
757
- // Handle column name conflicts
758
- const colMapping: Record<string, string> = {};
759
- rightCols.forEach(c => {
760
- if (leftCols.includes(c)) {
761
- colMapping[c] = c + suffixes[1];
762
- } else {
763
- colMapping[c] = c;
764
- }
765
- });
766
-
767
- const matchedRight = new Set<number>();
768
-
769
- // Process left rows
770
- leftRows.forEach(leftRow => {
771
- const key = leftKeys.map(k => JSON.stringify(leftRow[k])).join('|');
772
- const matches = rightIndex.get(key) || [];
773
-
774
- if (matches.length > 0) {
775
- matches.forEach(rightIdx => {
776
- matchedRight.add(rightIdx);
777
- const rightRow = rightRows[rightIdx];
778
- const merged: Record<string, any> = { ...leftRow };
779
- rightCols.forEach(c => {
780
- merged[colMapping[c]] = rightRow[c];
781
- });
782
- result.push(merged);
783
- });
784
- } else if (how === 'left' || how === 'outer') {
785
- const merged: Record<string, any> = { ...leftRow };
786
- rightCols.forEach(c => {
787
- merged[colMapping[c]] = null;
788
- });
789
- result.push(merged);
790
- }
791
- });
792
-
793
- // For right/outer joins, add unmatched right rows
794
- if (how === 'right' || how === 'outer') {
795
- rightRows.forEach((rightRow, i) => {
796
- if (!matchedRight.has(i)) {
797
- const merged: Record<string, any> = {};
798
- leftCols.forEach(c => {
799
- merged[c] = null;
800
- });
801
- // Copy join keys from right
802
- rightKeys.forEach((rk, idx) => {
803
- merged[leftKeys[idx]] = rightRow[rk];
804
- });
805
- rightCols.forEach(c => {
806
- merged[colMapping[c]] = rightRow[c];
807
- });
808
- result.push(merged);
809
- }
810
- });
811
- }
812
-
813
- const outputCols = [
814
- ...leftCols,
815
- ...rightCols.map(c => colMapping[c])
816
- ];
817
-
818
- return new DataFrame(result, { columns: outputCols, inferTypes: this._inferTypes });
819
- }
820
-
821
- /**
822
- * Concatenate DataFrames vertically (union)
823
- */
824
- concat(other: DataFrame | DataFrame[], ignoreIndex: boolean = true): DataFrame {
825
- const others = Array.isArray(other) ? other : [other];
826
- const allDfs = [this, ...others];
827
-
828
- // Collect all unique columns
829
- const allCols = new Set<string>();
830
- allDfs.forEach(df => df._columns.forEach(c => allCols.add(c)));
831
- const columns = Array.from(allCols);
832
-
833
- // Combine all rows
834
- const rows: Record<string, any>[] = [];
835
- allDfs.forEach(df => {
836
- df.toRows().forEach(row => {
837
- const newRow: Record<string, any> = {};
838
- columns.forEach(c => {
839
- newRow[c] = row[c] ?? null;
840
- });
841
- rows.push(newRow);
842
- });
843
- });
844
-
845
- return new DataFrame(rows, { columns, inferTypes: this._inferTypes });
846
- }
847
-
848
- /**
849
- * Union (alias for concat)
850
- */
851
- union(other: DataFrame): DataFrame {
852
- return this.concat(other);
853
- }
854
-
855
- /* ═══════════════════════════════════════════════════
856
- * TRANSFORMATION
857
- * ═══════════════════════════════════════════════════ */
858
-
859
- /**
860
- * Apply function to each row
861
- */
862
- apply(fn: (row: Record<string, any>, index: number) => Record<string, any>): DataFrame {
863
- const rows = this.toRows().map((row, i) => fn(row, i));
864
- return new DataFrame(rows, { inferTypes: this._inferTypes });
865
- }
866
-
867
- /**
868
- * Map function over a column
869
- */
870
- map(column: string, fn: (value: any, index: number) => any): DataFrame {
871
- return this.withColumn(column, (row, i) => fn(row[column], i));
872
- }
873
-
874
- /**
875
- * Pivot table
876
- */
877
- pivot(
878
- index: string,
879
- columns: string,
880
- values: string,
881
- aggFunc: 'sum' | 'mean' | 'count' | 'first' = 'first'
882
- ): DataFrame {
883
- const pivotValues = this.distinct(columns);
884
- const indexValues = this.distinct(index);
885
-
886
- const result: Record<string, any>[] = [];
887
-
888
- indexValues.forEach(idxVal => {
889
- const row: Record<string, any> = { [index]: idxVal };
890
-
891
- pivotValues.forEach(pivotVal => {
892
- const filtered = this
893
- .where(index, '==', idxVal)
894
- .where(columns, '==', pivotVal);
895
-
896
- const vals = filtered.col(values).filter(v => v !== null && v !== undefined);
897
-
898
- let aggValue: any = null;
899
- if (vals.length > 0) {
900
- switch (aggFunc) {
901
- case 'sum':
902
- aggValue = vals.reduce((a, b) => a + Number(b), 0);
903
- break;
904
- case 'mean':
905
- aggValue = vals.reduce((a, b) => a + Number(b), 0) / vals.length;
906
- break;
907
- case 'count':
908
- aggValue = vals.length;
909
- break;
910
- case 'first':
911
- aggValue = vals[0];
912
- break;
913
- }
914
- }
915
-
916
- row[String(pivotVal)] = aggValue;
917
- });
918
-
919
- result.push(row);
920
- });
921
-
922
- return new DataFrame(result, { inferTypes: this._inferTypes });
923
- }
924
-
925
- /**
926
- * Melt (unpivot) wide format to long format
927
- */
928
- melt(
929
- idVars: string[],
930
- valueVars?: string[],
931
- varName: string = 'variable',
932
- valueName: string = 'value'
933
- ): DataFrame {
934
- const valueCols = valueVars || this._columns.filter(c => !idVars.includes(c));
935
- const result: Record<string, any>[] = [];
936
-
937
- this.toRows().forEach(row => {
938
- valueCols.forEach(col => {
939
- const newRow: Record<string, any> = {};
940
- idVars.forEach(id => {
941
- newRow[id] = row[id];
942
- });
943
- newRow[varName] = col;
944
- newRow[valueName] = row[col];
945
- result.push(newRow);
946
- });
947
- });
948
-
949
- return new DataFrame(result, { inferTypes: this._inferTypes });
950
- }
951
-
952
- /* ═══════════════════════════════════════════════════
953
- * STATISTICS
954
- * ═══════════════════════════════════════════════════ */
955
-
956
- /**
957
- * Describe numeric columns
958
- */
959
- describe(): Record<string, any> {
960
- const stats: Record<string, any> = {};
961
-
962
- this._columns.forEach(col => {
963
- const values = this._data.get(col)!;
964
- const numeric = values.filter(v => typeof v === 'number' && !isNaN(v));
965
-
966
- if (numeric.length === 0) {
967
- stats[col] = {
968
- count: values.filter(v => v !== null && v !== undefined).length,
969
- unique: new Set(values).size,
970
- dtype: this._schema.get(col)?.dtype || 'unknown'
971
- };
972
- } else {
973
- const sorted = [...numeric].sort((a, b) => a - b);
974
- const sum = numeric.reduce((a, b) => a + b, 0);
975
- const mean = sum / numeric.length;
976
- const variance = numeric.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / numeric.length;
977
-
978
- stats[col] = {
979
- count: numeric.length,
980
- mean: mean,
981
- std: Math.sqrt(variance),
982
- min: sorted[0],
983
- '25%': sorted[Math.floor(sorted.length * 0.25)],
984
- '50%': sorted[Math.floor(sorted.length * 0.5)],
985
- '75%': sorted[Math.floor(sorted.length * 0.75)],
986
- max: sorted[sorted.length - 1]
987
- };
988
- }
989
- });
990
-
991
- return stats;
992
- }
993
-
994
- /**
995
- * Get info about DataFrame
996
- */
997
- info(): {
998
- rows: number;
999
- columns: number;
1000
- dtypes: Record<string, DataType>;
1001
- nullCounts: Record<string, number>;
1002
- memoryEstimate: number;
1003
- } {
1004
- const nullCounts: Record<string, number> = {};
1005
- let memoryEstimate = 0;
1006
-
1007
- this._columns.forEach(col => {
1008
- const values = this._data.get(col)!;
1009
- nullCounts[col] = values.filter(v => v === null || v === undefined || v === '').length;
1010
- memoryEstimate += values.reduce((acc, v) => acc + (typeof v === 'string' ? v.length * 2 : 8), 0);
1011
- });
1012
-
1013
- return {
1014
- rows: this._height,
1015
- columns: this._columns.length,
1016
- dtypes: this.dtypes,
1017
- nullCounts,
1018
- memoryEstimate
1019
- };
1020
- }
1021
-
1022
- /* ═══════════════════════════════════════════════════
1023
- * EXPORT
1024
- * ═══════════════════════════════════════════════════ */
1025
-
1026
- /**
1027
- * Convert to CSV string
1028
- */
1029
- toCSV(delimiter: string = ','): string {
1030
- const escape = (val: any): string => {
1031
- if (val === null || val === undefined) return '';
1032
- const str = String(val);
1033
- if (str.includes(delimiter) || str.includes('"') || str.includes('\n')) {
1034
- return `"${str.replace(/"/g, '""')}"`;
1035
- }
1036
- return str;
1037
- };
1038
-
1039
- const header = this._columns.map(escape).join(delimiter);
1040
- const rows = this.toRows().map(row =>
1041
- this._columns.map(col => escape(row[col])).join(delimiter)
1042
- );
1043
-
1044
- return [header, ...rows].join('\n');
1045
- }
1046
-
1047
- /**
1048
- * Convert to JSON string
1049
- */
1050
- toJSON(orient: 'records' | 'columns' | 'split' = 'records'): string {
1051
- switch (orient) {
1052
- case 'records':
1053
- return JSON.stringify(this.toRows());
1054
- case 'columns':
1055
- return JSON.stringify(this.toColumns());
1056
- case 'split':
1057
- return JSON.stringify({
1058
- columns: this._columns,
1059
- data: this.toRows().map(row => this._columns.map(c => row[c]))
1060
- });
1061
- default:
1062
- return JSON.stringify(this.toRows());
1063
- }
1064
- }
1065
-
1066
- /**
1067
- * Clone the DataFrame
1068
- */
1069
- clone(): DataFrame {
1070
- return new DataFrame(this);
1071
- }
1072
-
1073
- /**
1074
- * Print preview (for debugging)
1075
- */
1076
- print(n: number = 10): void {
1077
- console.log(`DataFrame: ${this._height} rows × ${this._columns.length} columns`);
1078
- console.log('Columns:', this._columns.join(', '));
1079
- console.table(this.head(n).toRows());
1080
- }
1081
- }
1082
-
1083
- /**
1084
- * GroupedDataFrame for group-by operations
1085
- */
1086
- export class GroupedDataFrame {
1087
- private _df: DataFrame;
1088
- private _groupCols: string[];
1089
- private _groups: Map<string, number[]>;
1090
-
1091
- constructor(df: DataFrame, groupCols: string[]) {
1092
- this._df = df;
1093
- this._groupCols = groupCols;
1094
- this._groups = new Map();
1095
-
1096
- // Build groups
1097
- df.toRows().forEach((row, i) => {
1098
- const key = groupCols.map(c => JSON.stringify(row[c])).join('|');
1099
- if (!this._groups.has(key)) this._groups.set(key, []);
1100
- this._groups.get(key)!.push(i);
1101
- });
1102
- }
1103
-
1104
- /**
1105
- * Aggregate groups
1106
- */
1107
- agg(aggregations: Record<string, 'sum' | 'mean' | 'min' | 'max' | 'count' | 'first' | 'last' | ((values: any[]) => any)>): DataFrame {
1108
- const result: Record<string, any>[] = [];
1109
- const allRows = this._df.toRows();
1110
-
1111
- this._groups.forEach((indices, key) => {
1112
- const groupRows = indices.map(i => allRows[i]);
1113
- const row: Record<string, any> = {};
1114
-
1115
- // Add group columns
1116
- this._groupCols.forEach(c => {
1117
- row[c] = groupRows[0][c];
1118
- });
1119
-
1120
- // Add aggregated values
1121
- Object.entries(aggregations).forEach(([col, aggFn]) => {
1122
- const values = groupRows.map(r => r[col]).filter(v => v !== null && v !== undefined);
1123
-
1124
- if (typeof aggFn === 'function') {
1125
- row[col] = aggFn(values);
1126
- } else {
1127
- switch (aggFn) {
1128
- case 'sum':
1129
- row[col] = values.reduce((a, b) => a + Number(b), 0);
1130
- break;
1131
- case 'mean':
1132
- row[col] = values.length > 0
1133
- ? values.reduce((a, b) => a + Number(b), 0) / values.length
1134
- : null;
1135
- break;
1136
- case 'min':
1137
- row[col] = values.length > 0 ? Math.min(...values.map(Number)) : null;
1138
- break;
1139
- case 'max':
1140
- row[col] = values.length > 0 ? Math.max(...values.map(Number)) : null;
1141
- break;
1142
- case 'count':
1143
- row[col] = values.length;
1144
- break;
1145
- case 'first':
1146
- row[col] = values[0] ?? null;
1147
- break;
1148
- case 'last':
1149
- row[col] = values[values.length - 1] ?? null;
1150
- break;
1151
- }
1152
- }
1153
- });
1154
-
1155
- result.push(row);
1156
- });
1157
-
1158
- return new DataFrame(result);
1159
- }
1160
-
1161
- /**
1162
- * Count per group
1163
- */
1164
- count(): DataFrame {
1165
- return this.agg({ _count: 'count' }).rename({ _count: 'count' });
1166
- }
1167
-
1168
- /**
1169
- * Sum per group
1170
- */
1171
- sum(column: string): DataFrame {
1172
- return this.agg({ [column]: 'sum' });
1173
- }
1174
-
1175
- /**
1176
- * Mean per group
1177
- */
1178
- mean(column: string): DataFrame {
1179
- return this.agg({ [column]: 'mean' });
1180
- }
1181
-
1182
- /**
1183
- * Get number of groups
1184
- */
1185
- get ngroups(): number {
1186
- return this._groups.size;
1187
- }
1188
-
1189
- /**
1190
- * Get group keys
1191
- */
1192
- get groups(): string[] {
1193
- return Array.from(this._groups.keys());
1194
- }
1195
- }