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