dera 1.0.0__py3-none-any.whl
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.
- backend/dataset/polars_transforms.py +878 -0
- backend/dataset/precompute.py +354 -0
- backend/dist/assets/index-Cia0M6bV.css +1 -0
- backend/dist/assets/index-pMVPPjFG.js +169 -0
- backend/dist/favicon.png +0 -0
- backend/dist/icons.svg +24 -0
- backend/dist/index.html +21 -0
- backend/graphs/chart_generator.py +390 -0
- backend/graphs/chart_styles.py +14 -0
- backend/graphs/main.py +215 -0
- backend/graphs/validators.py +51 -0
- backend/main.py +2970 -0
- dera-1.0.0.dist-info/METADATA +33 -0
- dera-1.0.0.dist-info/RECORD +18 -0
- dera-1.0.0.dist-info/WHEEL +5 -0
- dera-1.0.0.dist-info/entry_points.txt +2 -0
- dera-1.0.0.dist-info/licenses/LICENSE +21 -0
- dera-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
def handle_drop_columns(lf, params):
|
|
7
|
+
cols = params.get('columns', [])
|
|
8
|
+
if not cols and params.get('column'):
|
|
9
|
+
cols = [params.get('column')]
|
|
10
|
+
cols = [c for c in cols if c in lf.columns]
|
|
11
|
+
if cols:
|
|
12
|
+
lf = lf.drop(cols)
|
|
13
|
+
return lf
|
|
14
|
+
|
|
15
|
+
def handle_fill_null(lf, params):
|
|
16
|
+
cols = params.get('columns', [])
|
|
17
|
+
if not cols and params.get('column'):
|
|
18
|
+
cols = [params.get('column')]
|
|
19
|
+
strategy = params.get('strategy', 'mean')
|
|
20
|
+
val = params.get('value')
|
|
21
|
+
|
|
22
|
+
for col in cols:
|
|
23
|
+
if col not in lf.columns:
|
|
24
|
+
continue
|
|
25
|
+
if strategy == 'mean':
|
|
26
|
+
lf = lf.with_columns(pl.col(col).fill_null(pl.col(col).mean()))
|
|
27
|
+
elif strategy == 'median':
|
|
28
|
+
lf = lf.with_columns(pl.col(col).fill_null(pl.col(col).median()))
|
|
29
|
+
elif strategy == 'mode':
|
|
30
|
+
lf = lf.with_columns(pl.col(col).fill_null(pl.col(col).mode().first()))
|
|
31
|
+
elif strategy == 'constant':
|
|
32
|
+
if val is not None:
|
|
33
|
+
# Get the schema type of the column to cast the constant value correctly
|
|
34
|
+
schema = lf.schema
|
|
35
|
+
col_type = schema.get(col)
|
|
36
|
+
if col_type in (pl.Int64, pl.Int32, pl.Int16, pl.Int8):
|
|
37
|
+
try:
|
|
38
|
+
c_val = int(float(val))
|
|
39
|
+
except ValueError:
|
|
40
|
+
c_val = val
|
|
41
|
+
elif col_type in (pl.Float64, pl.Float32):
|
|
42
|
+
try:
|
|
43
|
+
c_val = float(val)
|
|
44
|
+
except ValueError:
|
|
45
|
+
c_val = val
|
|
46
|
+
elif col_type == pl.Boolean:
|
|
47
|
+
c_val = str(val).lower() in ('true', '1', 'yes', 'y', 't')
|
|
48
|
+
else:
|
|
49
|
+
c_val = str(val)
|
|
50
|
+
lf = lf.with_columns(pl.col(col).fill_null(pl.lit(c_val)))
|
|
51
|
+
return lf
|
|
52
|
+
|
|
53
|
+
def handle_remove_duplicates(lf, params):
|
|
54
|
+
return lf.unique()
|
|
55
|
+
|
|
56
|
+
def handle_rename_column(lf, params):
|
|
57
|
+
mapping = params.get('columns')
|
|
58
|
+
if not mapping and params.get('oldName') and params.get('newName'):
|
|
59
|
+
mapping = {params.get('oldName'): params.get('newName')}
|
|
60
|
+
if mapping:
|
|
61
|
+
mapping = {k: v for k, v in mapping.items() if k in lf.columns}
|
|
62
|
+
if mapping:
|
|
63
|
+
lf = lf.rename(mapping)
|
|
64
|
+
return lf
|
|
65
|
+
|
|
66
|
+
def handle_min_max_scale(lf, params):
|
|
67
|
+
cols = params.get('columns', [])
|
|
68
|
+
if not cols and params.get('column'):
|
|
69
|
+
cols = [params.get('column')]
|
|
70
|
+
for col in cols:
|
|
71
|
+
if col in lf.columns:
|
|
72
|
+
col_min = pl.col(col).min()
|
|
73
|
+
col_max = pl.col(col).max()
|
|
74
|
+
lf = lf.with_columns(
|
|
75
|
+
pl.when(col_max == col_min)
|
|
76
|
+
.then(pl.lit(0.0))
|
|
77
|
+
.otherwise((pl.col(col) - col_min) / (col_max - col_min))
|
|
78
|
+
.alias(col)
|
|
79
|
+
)
|
|
80
|
+
return lf
|
|
81
|
+
|
|
82
|
+
def handle_standardize(lf, params):
|
|
83
|
+
cols = params.get('columns', [])
|
|
84
|
+
if not cols and params.get('column'):
|
|
85
|
+
cols = [params.get('column')]
|
|
86
|
+
for col in cols:
|
|
87
|
+
if col in lf.columns:
|
|
88
|
+
col_mean = pl.col(col).mean()
|
|
89
|
+
col_std = pl.col(col).std()
|
|
90
|
+
lf = lf.with_columns(
|
|
91
|
+
pl.when(col_std == 0)
|
|
92
|
+
.then(pl.lit(0.0))
|
|
93
|
+
.otherwise((pl.col(col) - col_mean) / col_std)
|
|
94
|
+
.alias(col)
|
|
95
|
+
)
|
|
96
|
+
return lf
|
|
97
|
+
|
|
98
|
+
def handle_lowercase(lf, params):
|
|
99
|
+
cols = params.get('columns', [])
|
|
100
|
+
if not cols and params.get('column'):
|
|
101
|
+
cols = [params.get('column')]
|
|
102
|
+
for col in cols:
|
|
103
|
+
if col in lf.columns:
|
|
104
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.to_lowercase().alias(col))
|
|
105
|
+
return lf
|
|
106
|
+
|
|
107
|
+
def handle_uppercase(lf, params):
|
|
108
|
+
cols = params.get('columns', [])
|
|
109
|
+
if not cols and params.get('column'):
|
|
110
|
+
cols = [params.get('column')]
|
|
111
|
+
for col in cols:
|
|
112
|
+
if col in lf.columns:
|
|
113
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.to_uppercase().alias(col))
|
|
114
|
+
return lf
|
|
115
|
+
|
|
116
|
+
def handle_trim_spaces(lf, params):
|
|
117
|
+
cols = params.get('columns', [])
|
|
118
|
+
if not cols and params.get('column'):
|
|
119
|
+
cols = [params.get('column')]
|
|
120
|
+
for col in cols:
|
|
121
|
+
if col in lf.columns:
|
|
122
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.strip_chars().alias(col))
|
|
123
|
+
return lf
|
|
124
|
+
|
|
125
|
+
def handle_toggle_bool(lf, params):
|
|
126
|
+
cols = params.get('columns', [])
|
|
127
|
+
if not cols and params.get('column'):
|
|
128
|
+
cols = [params.get('column')]
|
|
129
|
+
for col in cols:
|
|
130
|
+
if col in lf.columns:
|
|
131
|
+
lf = lf.with_columns(pl.col(col).not_().alias(col))
|
|
132
|
+
return lf
|
|
133
|
+
|
|
134
|
+
def handle_one_hot_encode(lf, params):
|
|
135
|
+
cols = params.get('columns', [])
|
|
136
|
+
if not cols and params.get('column'):
|
|
137
|
+
cols = [params.get('column')]
|
|
138
|
+
cols = [c for c in cols if c in lf.columns]
|
|
139
|
+
for col in cols:
|
|
140
|
+
unique_vals = lf.select(col).unique().collect().get_column(col).to_list()
|
|
141
|
+
if len(unique_vals) > 0:
|
|
142
|
+
unique_vals = [v for v in unique_vals if v is not None]
|
|
143
|
+
unique_vals.sort()
|
|
144
|
+
# drop_first=True
|
|
145
|
+
for val in unique_vals[1:]:
|
|
146
|
+
col_name = f"{col}_{val}"
|
|
147
|
+
lf = lf.with_columns((pl.col(col) == val).cast(pl.Int32).alias(col_name))
|
|
148
|
+
lf = lf.drop(col)
|
|
149
|
+
return lf
|
|
150
|
+
|
|
151
|
+
def handle_change_datatype(lf, params):
|
|
152
|
+
cols = params.get('columns', [])
|
|
153
|
+
if not cols and params.get('column'):
|
|
154
|
+
cols = [params.get('column')]
|
|
155
|
+
new_type = params.get('dtype')
|
|
156
|
+
if new_type:
|
|
157
|
+
type_map = {
|
|
158
|
+
'int64': pl.Int64,
|
|
159
|
+
'float64': pl.Float64,
|
|
160
|
+
'object': pl.String,
|
|
161
|
+
'bool': pl.Boolean,
|
|
162
|
+
'str': pl.String,
|
|
163
|
+
'string': pl.String,
|
|
164
|
+
'datetime': pl.Datetime
|
|
165
|
+
}
|
|
166
|
+
pl_type = type_map.get(new_type)
|
|
167
|
+
if pl_type:
|
|
168
|
+
for col in cols:
|
|
169
|
+
if col in lf.columns:
|
|
170
|
+
if pl_type == pl.Datetime:
|
|
171
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.to_datetime(strict=False).alias(col))
|
|
172
|
+
else:
|
|
173
|
+
lf = lf.with_columns(pl.col(col).cast(pl_type).alias(col))
|
|
174
|
+
return lf
|
|
175
|
+
|
|
176
|
+
def handle_filter_rows(lf, params):
|
|
177
|
+
col = params.get('column')
|
|
178
|
+
op = params.get('operator')
|
|
179
|
+
val = params.get('value')
|
|
180
|
+
if col in lf.columns and op and val is not None:
|
|
181
|
+
if op == '==':
|
|
182
|
+
lf = lf.filter(pl.col(col) == val)
|
|
183
|
+
elif op == '!=':
|
|
184
|
+
lf = lf.filter(pl.col(col) != val)
|
|
185
|
+
elif op == '>':
|
|
186
|
+
try:
|
|
187
|
+
lf = lf.filter(pl.col(col) > float(val))
|
|
188
|
+
except:
|
|
189
|
+
lf = lf.filter(pl.col(col) > val)
|
|
190
|
+
elif op == '<':
|
|
191
|
+
try:
|
|
192
|
+
lf = lf.filter(pl.col(col) < float(val))
|
|
193
|
+
except:
|
|
194
|
+
lf = lf.filter(pl.col(col) < val)
|
|
195
|
+
elif op == 'contains':
|
|
196
|
+
lf = lf.filter(pl.col(col).cast(pl.String).str.contains(str(val)))
|
|
197
|
+
return lf
|
|
198
|
+
|
|
199
|
+
def handle_sort_column(lf, params):
|
|
200
|
+
col = params.get('column')
|
|
201
|
+
ascending = params.get('ascending', True)
|
|
202
|
+
if col in lf.columns:
|
|
203
|
+
lf = lf.sort(col, descending=not ascending)
|
|
204
|
+
return lf
|
|
205
|
+
|
|
206
|
+
def handle_reorder_column(lf, params):
|
|
207
|
+
col = params.get('column')
|
|
208
|
+
direction = params.get('direction')
|
|
209
|
+
if col not in lf.columns:
|
|
210
|
+
return lf
|
|
211
|
+
cols = list(lf.columns)
|
|
212
|
+
idx = cols.index(col)
|
|
213
|
+
if direction == 'start':
|
|
214
|
+
cols.remove(col)
|
|
215
|
+
cols.insert(0, col)
|
|
216
|
+
elif direction == 'end':
|
|
217
|
+
cols.remove(col)
|
|
218
|
+
cols.append(col)
|
|
219
|
+
elif direction == 'left' and idx > 0:
|
|
220
|
+
cols[idx], cols[idx-1] = cols[idx-1], cols[idx]
|
|
221
|
+
elif direction == 'right' and idx < len(cols) - 1:
|
|
222
|
+
cols[idx], cols[idx+1] = cols[idx+1], cols[idx]
|
|
223
|
+
return lf.select(cols)
|
|
224
|
+
|
|
225
|
+
def handle_duplicate_column(lf, params):
|
|
226
|
+
col = params.get('column')
|
|
227
|
+
new_name = params.get('new_name')
|
|
228
|
+
if col in lf.columns and new_name:
|
|
229
|
+
lf = lf.with_columns(pl.col(col).alias(new_name))
|
|
230
|
+
return lf
|
|
231
|
+
|
|
232
|
+
def handle_split_column(lf, params):
|
|
233
|
+
col = params.get('column')
|
|
234
|
+
delimiter = params.get('delimiter', ',')
|
|
235
|
+
if col not in lf.columns:
|
|
236
|
+
return lf
|
|
237
|
+
split_lf = lf.select(pl.col(col).cast(pl.String).str.split(delimiter).alias("_splits"))
|
|
238
|
+
max_splits = split_lf.select(pl.col("_splits").list.len().max()).collect().item()
|
|
239
|
+
if max_splits and max_splits > 0:
|
|
240
|
+
exprs = []
|
|
241
|
+
for i in range(max_splits):
|
|
242
|
+
exprs.append(pl.col(col).cast(pl.String).str.split(delimiter).list.get(i, null_on_oob=True).alias(f"{col}_split_{i+1}"))
|
|
243
|
+
lf = lf.with_columns(exprs)
|
|
244
|
+
return lf
|
|
245
|
+
|
|
246
|
+
def handle_merge_columns(lf, params):
|
|
247
|
+
col1 = params.get('column')
|
|
248
|
+
col2 = params.get('column2')
|
|
249
|
+
separator = params.get('separator', ' ')
|
|
250
|
+
new_name = params.get('new_name')
|
|
251
|
+
if col1 in lf.columns and col2 in lf.columns and new_name:
|
|
252
|
+
lf = lf.with_columns(
|
|
253
|
+
(pl.col(col1).cast(pl.String) + pl.lit(separator) + pl.col(col2).cast(pl.String)).alias(new_name)
|
|
254
|
+
)
|
|
255
|
+
return lf
|
|
256
|
+
|
|
257
|
+
def handle_ffill(lf, params):
|
|
258
|
+
col = params.get('column')
|
|
259
|
+
if col in lf.columns:
|
|
260
|
+
lf = lf.with_columns(pl.col(col).forward_fill().alias(col))
|
|
261
|
+
return lf
|
|
262
|
+
|
|
263
|
+
def handle_bfill(lf, params):
|
|
264
|
+
col = params.get('column')
|
|
265
|
+
if col in lf.columns:
|
|
266
|
+
lf = lf.with_columns(pl.col(col).backward_fill().alias(col))
|
|
267
|
+
return lf
|
|
268
|
+
|
|
269
|
+
def handle_interpolate(lf, params):
|
|
270
|
+
col = params.get('column')
|
|
271
|
+
if col in lf.columns:
|
|
272
|
+
lf = lf.with_columns(pl.col(col).interpolate().alias(col))
|
|
273
|
+
return lf
|
|
274
|
+
|
|
275
|
+
def handle_flag_null(lf, params):
|
|
276
|
+
col = params.get('column')
|
|
277
|
+
if col in lf.columns:
|
|
278
|
+
lf = lf.with_columns(pl.col(col).is_null().cast(pl.Int32).alias(f"{col}_isnull"))
|
|
279
|
+
return lf
|
|
280
|
+
|
|
281
|
+
def handle_drop_null_rows(lf, params):
|
|
282
|
+
scope = params.get('scope', 'column')
|
|
283
|
+
col = params.get('column')
|
|
284
|
+
if scope == 'column' and col in lf.columns:
|
|
285
|
+
lf = lf.filter(pl.col(col).is_not_null())
|
|
286
|
+
elif scope == 'any':
|
|
287
|
+
lf = lf.drop_nulls()
|
|
288
|
+
elif scope == 'all':
|
|
289
|
+
lf = lf.filter(~pl.all_horizontal(pl.all().is_null()))
|
|
290
|
+
return lf
|
|
291
|
+
|
|
292
|
+
def handle_drop_cols_null_threshold(lf, params):
|
|
293
|
+
threshold = float(params.get('threshold', 50)) / 100.0
|
|
294
|
+
null_counts = lf.null_count().collect()
|
|
295
|
+
total_rows = lf.select(pl.len()).collect().item()
|
|
296
|
+
if total_rows > 0:
|
|
297
|
+
cols_to_keep = []
|
|
298
|
+
for col in lf.columns:
|
|
299
|
+
null_pct = null_counts.get_column(col)[0] / total_rows
|
|
300
|
+
if null_pct <= threshold:
|
|
301
|
+
cols_to_keep.append(col)
|
|
302
|
+
lf = lf.select(cols_to_keep)
|
|
303
|
+
return lf
|
|
304
|
+
|
|
305
|
+
def handle_deduplicate_subset(lf, params):
|
|
306
|
+
cols = params.get('columns', [])
|
|
307
|
+
cols = [c for c in cols if c in lf.columns]
|
|
308
|
+
if cols:
|
|
309
|
+
lf = lf.unique(subset=cols)
|
|
310
|
+
return lf
|
|
311
|
+
|
|
312
|
+
def handle_sample_rows(lf, params):
|
|
313
|
+
method = params.get('method', 'count')
|
|
314
|
+
val = float(params.get('value', 100))
|
|
315
|
+
random_state = int(params.get('random_state', 42))
|
|
316
|
+
|
|
317
|
+
df = lf.collect()
|
|
318
|
+
if method == 'count':
|
|
319
|
+
n = min(int(val), len(df))
|
|
320
|
+
df = df.sample(n=n, seed=random_state)
|
|
321
|
+
else:
|
|
322
|
+
frac = min(val, 1.0)
|
|
323
|
+
df = df.sample(fraction=frac, seed=random_state)
|
|
324
|
+
return df.lazy()
|
|
325
|
+
|
|
326
|
+
def handle_drop_rows_index(lf, params):
|
|
327
|
+
start = params.get('start')
|
|
328
|
+
end = params.get('end')
|
|
329
|
+
if start is not None and end is not None:
|
|
330
|
+
lf = lf.with_row_index("_idx")
|
|
331
|
+
lf = lf.filter((pl.col("_idx") < int(start)) | (pl.col("_idx") > int(end)))
|
|
332
|
+
lf = lf.drop("_idx")
|
|
333
|
+
return lf
|
|
334
|
+
|
|
335
|
+
def handle_label_encode(lf, params):
|
|
336
|
+
col = params.get('column')
|
|
337
|
+
if col in lf.columns:
|
|
338
|
+
lf = lf.with_columns(pl.col(col).cast(pl.Categorical).to_physical().alias(col))
|
|
339
|
+
return lf
|
|
340
|
+
|
|
341
|
+
def handle_ordinal_encode(lf, params):
|
|
342
|
+
col = params.get('column')
|
|
343
|
+
order = params.get('order', '')
|
|
344
|
+
if col in lf.columns and order:
|
|
345
|
+
categories = [cat.strip() for cat in order.split(',') if cat.strip()]
|
|
346
|
+
if categories:
|
|
347
|
+
expr = pl.when(pl.col(col) == categories[0]).then(0)
|
|
348
|
+
for idx, cat in enumerate(categories[1:]):
|
|
349
|
+
expr = expr.when(pl.col(col) == cat).then(idx + 1)
|
|
350
|
+
expr = expr.otherwise(-1).alias(col)
|
|
351
|
+
lf = lf.with_columns(expr)
|
|
352
|
+
return lf
|
|
353
|
+
|
|
354
|
+
def handle_binary_encode(lf, params):
|
|
355
|
+
col = params.get('column')
|
|
356
|
+
if col in lf.columns:
|
|
357
|
+
phys_lf = lf.with_columns(pl.col(col).cast(pl.Categorical).to_physical().alias("_code"))
|
|
358
|
+
max_code = phys_lf.select(pl.col("_code").max()).collect().item()
|
|
359
|
+
if max_code is None or max_code <= 0:
|
|
360
|
+
lf = lf.with_columns(pl.lit(0).alias(f"{col}_bin_0"))
|
|
361
|
+
return lf
|
|
362
|
+
num_bits = int(math.ceil(math.log2(max_code + 1)))
|
|
363
|
+
for i in range(num_bits):
|
|
364
|
+
expr = ((pl.col("_code") / (2**i)).floor().cast(pl.Int64) % 2).alias(f"{col}_bin_{i}")
|
|
365
|
+
phys_lf = phys_lf.with_columns(expr)
|
|
366
|
+
lf = phys_lf.drop(["_code", col])
|
|
367
|
+
return lf
|
|
368
|
+
|
|
369
|
+
def handle_robust_scale(lf, params):
|
|
370
|
+
col = params.get('column')
|
|
371
|
+
if col in lf.columns:
|
|
372
|
+
stats = lf.select([
|
|
373
|
+
pl.col(col).quantile(0.25).alias("q25"),
|
|
374
|
+
pl.col(col).median().alias("med"),
|
|
375
|
+
pl.col(col).quantile(0.75).alias("q75")
|
|
376
|
+
]).collect()
|
|
377
|
+
q25 = stats.get_column("q25")[0]
|
|
378
|
+
med = stats.get_column("med")[0]
|
|
379
|
+
q75 = stats.get_column("q75")[0]
|
|
380
|
+
iqr = q75 - q25
|
|
381
|
+
if iqr == 0:
|
|
382
|
+
lf = lf.with_columns(pl.lit(0.0).alias(col))
|
|
383
|
+
else:
|
|
384
|
+
lf = lf.with_columns(((pl.col(col) - med) / iqr).alias(col))
|
|
385
|
+
return lf
|
|
386
|
+
|
|
387
|
+
def handle_log_transform(lf, params):
|
|
388
|
+
col = params.get('column')
|
|
389
|
+
shift = float(params.get('shift', 1))
|
|
390
|
+
if col in lf.columns:
|
|
391
|
+
lf = lf.with_columns((pl.col(col) + shift).log().alias(col))
|
|
392
|
+
return lf
|
|
393
|
+
|
|
394
|
+
def handle_sqrt_transform(lf, params):
|
|
395
|
+
col = params.get('column')
|
|
396
|
+
if col in lf.columns:
|
|
397
|
+
lf = lf.with_columns(pl.col(col).sqrt().alias(col))
|
|
398
|
+
return lf
|
|
399
|
+
|
|
400
|
+
def handle_power_transform(lf, params):
|
|
401
|
+
col = params.get('column')
|
|
402
|
+
exponent = float(params.get('exponent', 2))
|
|
403
|
+
if col in lf.columns:
|
|
404
|
+
lf = lf.with_columns((pl.col(col) ** exponent).alias(col))
|
|
405
|
+
return lf
|
|
406
|
+
|
|
407
|
+
def handle_custom_formula(lf, params):
|
|
408
|
+
formula = params.get('formula')
|
|
409
|
+
new_name = params.get('new_name')
|
|
410
|
+
if formula and new_name:
|
|
411
|
+
df = lf.collect().to_pandas()
|
|
412
|
+
try:
|
|
413
|
+
df[new_name] = df.eval(formula)
|
|
414
|
+
return pl.from_pandas(df).lazy()
|
|
415
|
+
except:
|
|
416
|
+
pass
|
|
417
|
+
return lf
|
|
418
|
+
|
|
419
|
+
def handle_bin_bucket(lf, params):
|
|
420
|
+
col = params.get('column')
|
|
421
|
+
num_bins = int(params.get('bins', 5))
|
|
422
|
+
new_name = params.get('new_name') or f"{col}_binned"
|
|
423
|
+
if col in lf.columns:
|
|
424
|
+
stats = lf.select([pl.col(col).min().alias("min_v"), pl.col(col).max().alias("max_v")]).collect()
|
|
425
|
+
min_v = stats.get_column("min_v")[0]
|
|
426
|
+
max_v = stats.get_column("max_v")[0]
|
|
427
|
+
if min_v is not None and max_v is not None and max_v > min_v:
|
|
428
|
+
bin_width = (max_v - min_v) / num_bins
|
|
429
|
+
breaks = [min_v + i * bin_width for i in range(1, num_bins)]
|
|
430
|
+
lf = lf.with_columns(pl.col(col).cut(breaks).cast(pl.String).alias(new_name))
|
|
431
|
+
return lf
|
|
432
|
+
|
|
433
|
+
def handle_date_parts(lf, params):
|
|
434
|
+
col = params.get('column')
|
|
435
|
+
parts = params.get('parts', ['year', 'month', 'day'])
|
|
436
|
+
if not col or col not in lf.columns:
|
|
437
|
+
raise ValueError(f"Column '{col}' not found in dataset.")
|
|
438
|
+
exprs = []
|
|
439
|
+
for part in parts:
|
|
440
|
+
if part == 'year':
|
|
441
|
+
exprs.append(pl.col(col).dt.year().alias(f"{col}_year"))
|
|
442
|
+
elif part == 'month':
|
|
443
|
+
exprs.append(pl.col(col).dt.month().alias(f"{col}_month"))
|
|
444
|
+
elif part == 'day':
|
|
445
|
+
exprs.append(pl.col(col).dt.day().alias(f"{col}_day"))
|
|
446
|
+
elif part == 'dayofweek':
|
|
447
|
+
exprs.append((pl.col(col).dt.weekday() - 1).alias(f"{col}_dayofweek"))
|
|
448
|
+
elif part == 'hour':
|
|
449
|
+
exprs.append(pl.col(col).dt.hour().alias(f"{col}_hour"))
|
|
450
|
+
elif part == 'quarter':
|
|
451
|
+
exprs.append(pl.col(col).dt.quarter().alias(f"{col}_quarter"))
|
|
452
|
+
elif part == 'minute':
|
|
453
|
+
exprs.append(pl.col(col).dt.minute().alias(f"{col}_minute"))
|
|
454
|
+
elif part == 'second':
|
|
455
|
+
exprs.append(pl.col(col).dt.second().alias(f"{col}_second"))
|
|
456
|
+
if exprs:
|
|
457
|
+
lf = lf.with_columns(exprs)
|
|
458
|
+
return lf
|
|
459
|
+
|
|
460
|
+
def handle_regex_extraction(lf, params):
|
|
461
|
+
col = params.get('column')
|
|
462
|
+
pattern = params.get('pattern')
|
|
463
|
+
new_name = params.get('new_name')
|
|
464
|
+
if col in lf.columns and pattern and new_name:
|
|
465
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.extract(pattern).alias(new_name))
|
|
466
|
+
return lf
|
|
467
|
+
|
|
468
|
+
def handle_rolling_window(lf, params):
|
|
469
|
+
col = params.get('column')
|
|
470
|
+
window = int(params.get('window', 3))
|
|
471
|
+
op = params.get('operation', 'mean')
|
|
472
|
+
new_name = params.get('new_name') or f"{col}_rolling_{op}_{window}"
|
|
473
|
+
if col in lf.columns:
|
|
474
|
+
if op == 'mean':
|
|
475
|
+
lf = lf.with_columns(pl.col(col).rolling_mean(window_size=window, min_periods=1).alias(new_name))
|
|
476
|
+
elif op == 'std':
|
|
477
|
+
lf = lf.with_columns(pl.col(col).rolling_std(window_size=window, min_periods=1).alias(new_name))
|
|
478
|
+
elif op == 'sum':
|
|
479
|
+
lf = lf.with_columns(pl.col(col).rolling_sum(window_size=window, min_periods=1).alias(new_name))
|
|
480
|
+
return lf
|
|
481
|
+
|
|
482
|
+
def handle_interaction_terms(lf, params):
|
|
483
|
+
col1 = params.get('column')
|
|
484
|
+
col2 = params.get('column2')
|
|
485
|
+
new_name = params.get('new_name') or f"{col1}_x_{col2}"
|
|
486
|
+
if col1 in lf.columns and col2 in lf.columns:
|
|
487
|
+
lf = lf.with_columns((pl.col(col1) * pl.col(col2)).alias(new_name))
|
|
488
|
+
return lf
|
|
489
|
+
|
|
490
|
+
def handle_correlation_filter(lf, params):
|
|
491
|
+
df = lf.collect()
|
|
492
|
+
target = params.get('target')
|
|
493
|
+
threshold = float(params.get('threshold', 0.1))
|
|
494
|
+
if target in df.columns:
|
|
495
|
+
numeric_cols = [c for c in df.columns if df[c].dtype.is_numeric()]
|
|
496
|
+
if target in numeric_cols:
|
|
497
|
+
corrs = {col: np.corrcoef(df[col].to_numpy(), df[target].to_numpy())[0, 1] for col in numeric_cols if col != target}
|
|
498
|
+
cols_to_drop = [col for col, corr in corrs.items() if not np.isnan(corr) and abs(corr) < threshold]
|
|
499
|
+
if cols_to_drop:
|
|
500
|
+
df = df.drop(cols_to_drop)
|
|
501
|
+
return df.lazy()
|
|
502
|
+
|
|
503
|
+
def handle_variance_threshold(lf, params):
|
|
504
|
+
df = lf.collect()
|
|
505
|
+
threshold = float(params.get('threshold', 0.0))
|
|
506
|
+
numeric_cols = [c for c in df.columns if df[c].dtype.is_numeric()]
|
|
507
|
+
cols_to_drop = [col for col in numeric_cols if df[col].var() <= threshold]
|
|
508
|
+
if cols_to_drop:
|
|
509
|
+
df = df.drop(cols_to_drop)
|
|
510
|
+
return df.lazy()
|
|
511
|
+
|
|
512
|
+
def handle_select_k_best(lf, params):
|
|
513
|
+
df = lf.collect()
|
|
514
|
+
target = params.get('target')
|
|
515
|
+
k = int(params.get('k', 5))
|
|
516
|
+
if target in df.columns:
|
|
517
|
+
numeric_cols = [c for c in df.columns if df[c].dtype.is_numeric() and c != target]
|
|
518
|
+
corrs = {}
|
|
519
|
+
for col in numeric_cols:
|
|
520
|
+
c_val = np.corrcoef(df[col].to_numpy(), df[target].to_numpy())[0, 1]
|
|
521
|
+
corrs[col] = abs(c_val) if not np.isnan(c_val) else 0.0
|
|
522
|
+
top_k_cols = sorted(corrs, key=corrs.get, reverse=True)[:k]
|
|
523
|
+
non_numeric = [c for c in df.columns if not df[c].dtype.is_numeric()]
|
|
524
|
+
df = df.select(top_k_cols + [target] + [c for c in non_numeric if c != target])
|
|
525
|
+
return df.lazy()
|
|
526
|
+
|
|
527
|
+
def handle_remove_constant_cols(lf, params):
|
|
528
|
+
df = lf.collect()
|
|
529
|
+
cols_to_keep = [col for col in df.columns if df[col].n_unique() > 1]
|
|
530
|
+
df = df.select(cols_to_keep)
|
|
531
|
+
return df.lazy()
|
|
532
|
+
|
|
533
|
+
def handle_remove_highly_correlated(lf, params):
|
|
534
|
+
df = lf.collect()
|
|
535
|
+
threshold = float(params.get('threshold', 0.9))
|
|
536
|
+
numeric_cols = [c for c in df.columns if df[c].dtype.is_numeric()]
|
|
537
|
+
if len(numeric_cols) > 1:
|
|
538
|
+
corr_matrix = np.corrcoef([df[c].to_numpy() for c in numeric_cols])
|
|
539
|
+
to_drop = []
|
|
540
|
+
for i in range(len(numeric_cols)):
|
|
541
|
+
for j in range(i+1, len(numeric_cols)):
|
|
542
|
+
if abs(corr_matrix[i, j]) > threshold:
|
|
543
|
+
to_drop.append(numeric_cols[j])
|
|
544
|
+
if to_drop:
|
|
545
|
+
df = df.drop(list(set(to_drop)))
|
|
546
|
+
return df.lazy()
|
|
547
|
+
|
|
548
|
+
def handle_detect_iqr(lf, params):
|
|
549
|
+
col = params.get('column')
|
|
550
|
+
if col in lf.columns:
|
|
551
|
+
stats = lf.select([
|
|
552
|
+
pl.col(col).quantile(0.25).alias("q25"),
|
|
553
|
+
pl.col(col).quantile(0.75).alias("q75")
|
|
554
|
+
]).collect()
|
|
555
|
+
q25 = stats.get_column("q25")[0]
|
|
556
|
+
q75 = stats.get_column("q75")[0]
|
|
557
|
+
iqr = q75 - q25
|
|
558
|
+
lower = q25 - 1.5 * iqr
|
|
559
|
+
upper = q75 + 1.5 * iqr
|
|
560
|
+
lf = lf.with_columns(
|
|
561
|
+
((pl.col(col) < lower) | (pl.col(col) > upper)).cast(pl.Int32).alias(f"{col}_outlier_iqr")
|
|
562
|
+
)
|
|
563
|
+
return lf
|
|
564
|
+
|
|
565
|
+
def handle_detect_zscore(lf, params):
|
|
566
|
+
col = params.get('column')
|
|
567
|
+
threshold = float(params.get('threshold', 3.0))
|
|
568
|
+
if col in lf.columns:
|
|
569
|
+
stats = lf.select([
|
|
570
|
+
pl.col(col).mean().alias("mean"),
|
|
571
|
+
pl.col(col).std().alias("std")
|
|
572
|
+
]).collect()
|
|
573
|
+
mean = stats.get_column("mean")[0]
|
|
574
|
+
std = stats.get_column("std")[0]
|
|
575
|
+
if std and std > 0:
|
|
576
|
+
lf = lf.with_columns(
|
|
577
|
+
(((pl.col(col) - mean) / std).abs() > threshold).cast(pl.Int32).alias(f"{col}_outlier_z")
|
|
578
|
+
)
|
|
579
|
+
return lf
|
|
580
|
+
|
|
581
|
+
def handle_cap_clip(lf, params):
|
|
582
|
+
col = params.get('column')
|
|
583
|
+
lower_q = float(params.get('lower_q', 0.01))
|
|
584
|
+
upper_q = float(params.get('upper_q', 0.99))
|
|
585
|
+
if col in lf.columns:
|
|
586
|
+
stats = lf.select([
|
|
587
|
+
pl.col(col).quantile(lower_q).alias("lower"),
|
|
588
|
+
pl.col(col).quantile(upper_q).alias("upper")
|
|
589
|
+
]).collect()
|
|
590
|
+
lower = stats.get_column("lower")[0]
|
|
591
|
+
upper = stats.get_column("upper")[0]
|
|
592
|
+
lf = lf.with_columns(
|
|
593
|
+
pl.col(col).clip(lower_bound=lower, upper_bound=upper).alias(col)
|
|
594
|
+
)
|
|
595
|
+
return lf
|
|
596
|
+
|
|
597
|
+
def handle_remove_outliers(lf, params):
|
|
598
|
+
col = params.get('column')
|
|
599
|
+
method = params.get('method', 'iqr')
|
|
600
|
+
if col in lf.columns:
|
|
601
|
+
if method == 'iqr':
|
|
602
|
+
stats = lf.select([
|
|
603
|
+
pl.col(col).quantile(0.25).alias("q25"),
|
|
604
|
+
pl.col(col).quantile(0.75).alias("q75")
|
|
605
|
+
]).collect()
|
|
606
|
+
q25 = stats.get_column("q25")[0]
|
|
607
|
+
q75 = stats.get_column("q75")[0]
|
|
608
|
+
iqr = q75 - q25
|
|
609
|
+
lower = q25 - 1.5 * iqr
|
|
610
|
+
upper = q75 + 1.5 * iqr
|
|
611
|
+
lf = lf.filter((pl.col(col) >= lower) & (pl.col(col) <= upper))
|
|
612
|
+
else:
|
|
613
|
+
threshold = float(params.get('threshold', 3.0))
|
|
614
|
+
stats = lf.select([
|
|
615
|
+
pl.col(col).mean().alias("mean"),
|
|
616
|
+
pl.col(col).std().alias("std")
|
|
617
|
+
]).collect()
|
|
618
|
+
mean = stats.get_column("mean")[0]
|
|
619
|
+
std = stats.get_column("std")[0]
|
|
620
|
+
if std and std > 0:
|
|
621
|
+
lf = lf.filter((((pl.col(col) - mean) / std).abs() <= threshold))
|
|
622
|
+
return lf
|
|
623
|
+
|
|
624
|
+
def handle_replace_substring(lf, params):
|
|
625
|
+
col = params.get('column')
|
|
626
|
+
old_val = params.get('old_val', '')
|
|
627
|
+
new_val = params.get('new_val', '')
|
|
628
|
+
if col in lf.columns:
|
|
629
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.replace(old_val, new_val, literal=True).alias(col))
|
|
630
|
+
return lf
|
|
631
|
+
|
|
632
|
+
def handle_regex_replace(lf, params):
|
|
633
|
+
col = params.get('column')
|
|
634
|
+
pattern = params.get('pattern', '')
|
|
635
|
+
replacement = params.get('replacement', '')
|
|
636
|
+
if col in lf.columns:
|
|
637
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.replace(pattern, replacement, literal=False).alias(col))
|
|
638
|
+
return lf
|
|
639
|
+
|
|
640
|
+
def handle_remove_special_chars(lf, params):
|
|
641
|
+
col = params.get('column')
|
|
642
|
+
if col in lf.columns:
|
|
643
|
+
lf = lf.with_columns(pl.col(col).cast(pl.String).str.replace_all(r'[^a-zA-Z0-9\s]', '').alias(col))
|
|
644
|
+
return lf
|
|
645
|
+
|
|
646
|
+
def handle_extract_domain(lf, params):
|
|
647
|
+
col = params.get('column')
|
|
648
|
+
if col in lf.columns:
|
|
649
|
+
emails = pl.col(col).cast(pl.String).str.extract(r'@([^\s]+)')
|
|
650
|
+
urls = pl.col(col).cast(pl.String).str.extract(r'https?://(?:www\.)?([^/\s]+)')
|
|
651
|
+
lf = lf.with_columns(
|
|
652
|
+
pl.coalesce([emails, urls, pl.lit('')]).alias(f"{col}_domain")
|
|
653
|
+
)
|
|
654
|
+
return lf
|
|
655
|
+
|
|
656
|
+
def handle_groupby_aggregate(lf, params):
|
|
657
|
+
group_cols = params.get('group_cols', [])
|
|
658
|
+
agg_col = params.get('agg_col')
|
|
659
|
+
agg_type = params.get('agg_type', 'mean')
|
|
660
|
+
if group_cols and agg_col in lf.columns:
|
|
661
|
+
if agg_type == 'mean':
|
|
662
|
+
lf = lf.group_by(group_cols).agg(pl.col(agg_col).mean().alias(agg_col))
|
|
663
|
+
elif agg_type == 'sum':
|
|
664
|
+
lf = lf.group_by(group_cols).agg(pl.col(agg_col).sum().alias(agg_col))
|
|
665
|
+
elif agg_type == 'count':
|
|
666
|
+
lf = lf.group_by(group_cols).agg(pl.col(agg_col).count().alias(agg_col))
|
|
667
|
+
elif agg_type == 'min':
|
|
668
|
+
lf = lf.group_by(group_cols).agg(pl.col(agg_col).min().alias(agg_col))
|
|
669
|
+
elif agg_type == 'max':
|
|
670
|
+
lf = lf.group_by(group_cols).agg(pl.col(agg_col).max().alias(agg_col))
|
|
671
|
+
return lf
|
|
672
|
+
|
|
673
|
+
def handle_pivot_table(lf, params):
|
|
674
|
+
df = lf.collect().to_pandas()
|
|
675
|
+
index = params.get('index')
|
|
676
|
+
columns = params.get('columns_col')
|
|
677
|
+
values = params.get('values')
|
|
678
|
+
aggfunc = params.get('aggfunc', 'mean')
|
|
679
|
+
if index in df.columns and columns in df.columns and values in df.columns:
|
|
680
|
+
df = df.pivot_table(index=index, columns=columns, values=values, aggfunc=aggfunc).reset_index()
|
|
681
|
+
return pl.from_pandas(df).lazy()
|
|
682
|
+
return lf
|
|
683
|
+
|
|
684
|
+
def handle_melt(lf, params):
|
|
685
|
+
df = lf.collect().to_pandas()
|
|
686
|
+
id_vars = params.get('id_vars', [])
|
|
687
|
+
value_vars = params.get('value_vars', [])
|
|
688
|
+
id_vars = [c for c in id_vars if c in df.columns]
|
|
689
|
+
value_vars = [c for c in value_vars if c in df.columns]
|
|
690
|
+
if id_vars or value_vars:
|
|
691
|
+
df = pd.melt(df, id_vars=id_vars, value_vars=value_vars)
|
|
692
|
+
return pl.from_pandas(df).lazy()
|
|
693
|
+
return lf
|
|
694
|
+
|
|
695
|
+
def handle_transpose(lf, params):
|
|
696
|
+
df = lf.collect().transpose(include_header=True, header_name="index")
|
|
697
|
+
return df.lazy()
|
|
698
|
+
|
|
699
|
+
def _handle_custom_binning(lf, params):
|
|
700
|
+
col = params.get('column')
|
|
701
|
+
out_col = params.get('outputColumn')
|
|
702
|
+
bins = params.get('bins', [])
|
|
703
|
+
default_label = params.get('defaultLabel')
|
|
704
|
+
|
|
705
|
+
if not out_col:
|
|
706
|
+
out_col = f"{col}_Bin"
|
|
707
|
+
|
|
708
|
+
if col not in lf.columns:
|
|
709
|
+
raise ValueError(f"Column '{col}' not found in dataset.")
|
|
710
|
+
|
|
711
|
+
parsed_bins = []
|
|
712
|
+
for idx, b in enumerate(bins):
|
|
713
|
+
b_from_val = b.get('from')
|
|
714
|
+
b_to_val = b.get('to')
|
|
715
|
+
b_label = b.get('label')
|
|
716
|
+
|
|
717
|
+
if b_from_val is None or b_to_val is None:
|
|
718
|
+
raise ValueError(f"Bin {idx+1} is missing 'from' or 'to' values.")
|
|
719
|
+
|
|
720
|
+
if b_label is None or str(b_label).strip() == '':
|
|
721
|
+
b_label = f"{b_from_val}–{b_to_val}"
|
|
722
|
+
else:
|
|
723
|
+
b_label = str(b_label).strip()
|
|
724
|
+
|
|
725
|
+
try:
|
|
726
|
+
b_from = float(b_from_val)
|
|
727
|
+
b_to = float(b_to_val)
|
|
728
|
+
except ValueError:
|
|
729
|
+
raise ValueError(f"Bin {idx+1} has non-numeric range values: '{b_from_val}' to '{b_to_val}'.")
|
|
730
|
+
|
|
731
|
+
if b_from > b_to:
|
|
732
|
+
raise ValueError(f"Invalid range in Bin {idx+1} ({b_label}): 'from' ({b_from}) cannot be greater than 'to' ({b_to}).")
|
|
733
|
+
|
|
734
|
+
from_inc = b.get('from_inclusive', True)
|
|
735
|
+
to_inc = b.get('to_inclusive', True)
|
|
736
|
+
|
|
737
|
+
parsed_bins.append((b_from, b_to, b_label, from_inc, to_inc))
|
|
738
|
+
|
|
739
|
+
sorted_parsed = sorted(parsed_bins, key=lambda x: x[0])
|
|
740
|
+
|
|
741
|
+
for i in range(1, len(sorted_parsed)):
|
|
742
|
+
prev_to = sorted_parsed[i-1][1]
|
|
743
|
+
curr_from = sorted_parsed[i][0]
|
|
744
|
+
if curr_from <= prev_to:
|
|
745
|
+
raise ValueError(
|
|
746
|
+
f"Overlapping ranges detected: Bin '{sorted_parsed[i-1][2]}' ({sorted_parsed[i-1][0]} to {sorted_parsed[i-1][1]}) "
|
|
747
|
+
f"overlaps with Bin '{sorted_parsed[i][2]}' ({sorted_parsed[i][0]} to {sorted_parsed[i][1]})."
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
if not sorted_parsed:
|
|
751
|
+
if default_label is not None:
|
|
752
|
+
expr = pl.lit(default_label)
|
|
753
|
+
else:
|
|
754
|
+
expr = pl.lit(None).cast(pl.String)
|
|
755
|
+
return lf.with_columns(expr.alias(out_col))
|
|
756
|
+
|
|
757
|
+
main_expr = None
|
|
758
|
+
for b_from, b_to, b_label, from_inc, to_inc in sorted_parsed:
|
|
759
|
+
cond_from = pl.col(col) >= b_from if from_inc else pl.col(col) > b_from
|
|
760
|
+
cond_to = pl.col(col) <= b_to if to_inc else pl.col(col) < b_to
|
|
761
|
+
cond = cond_from & cond_to
|
|
762
|
+
|
|
763
|
+
if main_expr is None:
|
|
764
|
+
main_expr = pl.when(cond).then(pl.lit(b_label))
|
|
765
|
+
else:
|
|
766
|
+
main_expr = main_expr.when(cond).then(pl.lit(b_label))
|
|
767
|
+
|
|
768
|
+
if main_expr is not None:
|
|
769
|
+
if default_label is not None:
|
|
770
|
+
main_expr = main_expr.otherwise(pl.lit(default_label))
|
|
771
|
+
else:
|
|
772
|
+
main_expr = main_expr.otherwise(pl.lit(None).cast(pl.String))
|
|
773
|
+
lf = lf.with_columns(main_expr.alias(out_col))
|
|
774
|
+
|
|
775
|
+
return lf
|
|
776
|
+
|
|
777
|
+
def _handle_equal_width_binning(lf, params):
|
|
778
|
+
raise NotImplementedError("Equal Width Binning mode not yet implemented.")
|
|
779
|
+
|
|
780
|
+
def _handle_equal_frequency_binning(lf, params):
|
|
781
|
+
raise NotImplementedError("Equal Frequency Binning mode not yet implemented.")
|
|
782
|
+
|
|
783
|
+
def _handle_quantile_binning(lf, params):
|
|
784
|
+
raise NotImplementedError("Quantile Binning mode not yet implemented.")
|
|
785
|
+
|
|
786
|
+
def handle_binning(lf, params):
|
|
787
|
+
mode = params.get('mode', 'custom')
|
|
788
|
+
if mode == 'custom':
|
|
789
|
+
return _handle_custom_binning(lf, params)
|
|
790
|
+
elif mode == 'equal_width':
|
|
791
|
+
return _handle_equal_width_binning(lf, params)
|
|
792
|
+
elif mode == 'equal_frequency':
|
|
793
|
+
return _handle_equal_frequency_binning(lf, params)
|
|
794
|
+
elif mode == 'quantile':
|
|
795
|
+
return _handle_quantile_binning(lf, params)
|
|
796
|
+
else:
|
|
797
|
+
raise ValueError(f"Unsupported binning mode: '{mode}'")
|
|
798
|
+
|
|
799
|
+
TRANSFORMS = {
|
|
800
|
+
'drop_columns': handle_drop_columns,
|
|
801
|
+
'fill_null': handle_fill_null,
|
|
802
|
+
'remove_duplicates': handle_remove_duplicates,
|
|
803
|
+
'rename_column': handle_rename_column,
|
|
804
|
+
'min_max_scale': handle_min_max_scale,
|
|
805
|
+
'standardize': handle_standardize,
|
|
806
|
+
'lowercase': handle_lowercase,
|
|
807
|
+
'uppercase': handle_uppercase,
|
|
808
|
+
'trim_spaces': handle_trim_spaces,
|
|
809
|
+
'toggle_bool': handle_toggle_bool,
|
|
810
|
+
'one_hot_encode': handle_one_hot_encode,
|
|
811
|
+
'change_datatype': handle_change_datatype,
|
|
812
|
+
'filter_rows': handle_filter_rows,
|
|
813
|
+
'sort_column': handle_sort_column,
|
|
814
|
+
'reorder_column': handle_reorder_column,
|
|
815
|
+
'duplicate_column': handle_duplicate_column,
|
|
816
|
+
'split_column': handle_split_column,
|
|
817
|
+
'merge_columns': handle_merge_columns,
|
|
818
|
+
'ffill': handle_ffill,
|
|
819
|
+
'bfill': handle_bfill,
|
|
820
|
+
'interpolate': handle_interpolate,
|
|
821
|
+
'flag_null': handle_flag_null,
|
|
822
|
+
'drop_null_rows': handle_drop_null_rows,
|
|
823
|
+
'drop_cols_null_threshold': handle_drop_cols_null_threshold,
|
|
824
|
+
'deduplicate_subset': handle_deduplicate_subset,
|
|
825
|
+
'sample_rows': handle_sample_rows,
|
|
826
|
+
'drop_rows_index': handle_drop_rows_index,
|
|
827
|
+
'label_encode': handle_label_encode,
|
|
828
|
+
'ordinal_encode': handle_ordinal_encode,
|
|
829
|
+
'binary_encode': handle_binary_encode,
|
|
830
|
+
'robust_scale': handle_robust_scale,
|
|
831
|
+
'log_transform': handle_log_transform,
|
|
832
|
+
'sqrt_transform': handle_sqrt_transform,
|
|
833
|
+
'power_transform': handle_power_transform,
|
|
834
|
+
'custom_formula': handle_custom_formula,
|
|
835
|
+
'bin_bucket': handle_bin_bucket,
|
|
836
|
+
'date_parts': handle_date_parts,
|
|
837
|
+
'regex_extraction': handle_regex_extraction,
|
|
838
|
+
'rolling_window': handle_rolling_window,
|
|
839
|
+
'interaction_terms': handle_interaction_terms,
|
|
840
|
+
'correlation_filter': handle_correlation_filter,
|
|
841
|
+
'variance_threshold': handle_variance_threshold,
|
|
842
|
+
'select_k_best': handle_select_k_best,
|
|
843
|
+
'remove_constant_cols': handle_remove_constant_cols,
|
|
844
|
+
'remove_highly_correlated': handle_remove_highly_correlated,
|
|
845
|
+
'detect_iqr': handle_detect_iqr,
|
|
846
|
+
'detect_zscore': handle_detect_zscore,
|
|
847
|
+
'cap_clip': handle_cap_clip,
|
|
848
|
+
'remove_outliers': handle_remove_outliers,
|
|
849
|
+
'replace_substring': handle_replace_substring,
|
|
850
|
+
'regex_replace': handle_regex_replace,
|
|
851
|
+
'remove_special_chars': handle_remove_special_chars,
|
|
852
|
+
'extract_domain': handle_extract_domain,
|
|
853
|
+
'groupby_aggregate': handle_groupby_aggregate,
|
|
854
|
+
'pivot_table': handle_pivot_table,
|
|
855
|
+
'melt': handle_melt,
|
|
856
|
+
'transpose': handle_transpose,
|
|
857
|
+
'binning': handle_binning,
|
|
858
|
+
'custom_binning': handle_binning
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
def load_dataset(path):
|
|
862
|
+
path_lower = path.lower()
|
|
863
|
+
if path_lower.endswith(('.xlsx', '.xls')):
|
|
864
|
+
return pl.read_excel(path).lazy()
|
|
865
|
+
elif path_lower.endswith('.parquet'):
|
|
866
|
+
return pl.scan_parquet(path)
|
|
867
|
+
else:
|
|
868
|
+
return pl.scan_csv(path)
|
|
869
|
+
|
|
870
|
+
def apply_pipeline(lf, steps):
|
|
871
|
+
for step in steps:
|
|
872
|
+
step_type = step.get('type')
|
|
873
|
+
step_params = step.get('params', {})
|
|
874
|
+
if step_type in TRANSFORMS:
|
|
875
|
+
lf = TRANSFORMS[step_type](lf, step_params)
|
|
876
|
+
else:
|
|
877
|
+
raise ValueError(f"Unsupported transformation action: '{step_type}'")
|
|
878
|
+
return lf
|