dummy-preprocessor 0.1.0__tar.gz

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.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: dummy-preprocessor
3
+ Version: 0.1.0
4
+ Summary: An automated data preprocessing and EDA pipeline with AI server integration.
5
+ Author-email: Akira <sardor77nematov@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/akirahatsu/DUMMY-PREPROCESSOR
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: pandas>=1.3.0
14
+ Requires-Dist: numpy>=1.20.0
15
+ Requires-Dist: requests>=2.0.0
16
+
17
+
18
+ # DUMMY PREPROCESSOR
19
+
20
+ An automated data preprocessing and EDA pipeline with AI server integration.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install dummy-preprocessor
@@ -0,0 +1,9 @@
1
+
2
+ # DUMMY PREPROCESSOR
3
+
4
+ An automated data preprocessing and EDA pipeline with AI server integration.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install dummy-preprocessor
@@ -0,0 +1,4 @@
1
+
2
+ from .preprocessor import DUMMY_PI
3
+
4
+ __all__ = ["DUMMY_PI"]
@@ -0,0 +1,1119 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import time
4
+ import warnings
5
+ import os
6
+ import requests
7
+
8
+ class DUMMY_PI:
9
+
10
+
11
+
12
+ #DATA REVEIW AI CHECK
13
+ #INIT PART
14
+
15
+
16
+
17
+
18
+ def __init__(self , input = None):
19
+
20
+ if input is None:
21
+ input = pd.DataFrame()
22
+ if isinstance(input, (str, os.PathLike)):
23
+ self.input = pd.read_csv(input)
24
+
25
+ elif isinstance(input, pd.DataFrame):
26
+ self.input = input.copy()
27
+
28
+ else:
29
+ raise TypeError("Input must be a file path or pandas DataFrame.")
30
+
31
+ # Initialize prep_data here so it is always available
32
+ self.prep_data = self.input.copy()
33
+
34
+ # Ai checking Part
35
+
36
+ # AI checking Part
37
+ def ai_check(self):
38
+
39
+ url = "https://dummy-preprocessor.onrender.com/ai-check"
40
+
41
+ try:
42
+ statistics = self.input.describe().to_string()
43
+
44
+ response = requests.post(
45
+ url,
46
+ json={"statistics": statistics},
47
+ timeout=120
48
+ )
49
+ if response.status_code == 200:
50
+ result = response.json().get("result")
51
+ if result:
52
+ return result
53
+ print("AI CHECK: Server returned an empty response.")
54
+ return None
55
+
56
+
57
+ print(f"AI CHECK: Server returned status {response.status_code}.")
58
+ return None
59
+
60
+ except requests.exceptions.Timeout:
61
+ print("AI CHECK: Request timed out. Continuing without AI.")
62
+ return None
63
+
64
+ except requests.exceptions.ConnectionError:
65
+ print("AI CHECK: No internet connection or server unavailable. Continuing without AI.")
66
+ return None
67
+
68
+ except requests.exceptions.RequestException as e:
69
+ print(f"AI CHECK: Network problem: {e}. Continuing without AI.")
70
+ return None
71
+
72
+ except (ValueError, KeyError) as e:
73
+ print(f"AI CHECK: Invalid server response: {e}. Continuing without AI.")
74
+ return None
75
+
76
+
77
+
78
+ def get_instruction(self ):
79
+
80
+ text_me = """ \033[92m╔══════════════════════════════════════════════════════════════════════╗
81
+ ║ ║
82
+ ║ ║
83
+ ║ ██████╗ ██╗ ██╗███╗ ███╗███╗ ███╗██╗ ██╗ ║
84
+ ║ ██╔══██╗██║ ██║████╗ ████║████╗ ████║╚██╗ ██╔╝ ║
85
+ ║ ██║ ██║██║ ██║██╔████╔██║██╔████╔██║ ╚████╔╝ ║
86
+ ║ ██║ ██║██║ ██║██║╚██╔╝██║██║╚██╔╝██║ ╚██╔╝ ║
87
+ ║ ██████╔╝╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║ ██║ ║
88
+ ║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ║
89
+ ║ ║
90
+ ║ D U M M Y P R E P R O C E S S O R ║
91
+ ║ ║
92
+ ║ ║
93
+ ╚══════════════════════════════════════════════════════════════════════╝\033[0m"""
94
+
95
+ for el in text_me:
96
+ print(el , end="")
97
+ time.sleep(0.001)
98
+
99
+ print()
100
+ instruction = '''
101
+ INSTRUCTION !!!
102
+
103
+
104
+
105
+ ------ A U T O F I X ____ Preprocessing Data ------
106
+
107
+
108
+
109
+ -- x = DUMMY_PI(input = "YOUR DATA PATH OR pd.DataFrame")
110
+ \033[92mInit process\033[0m
111
+
112
+ ````Method name: \033[91mx.get_auto_fix()\033[0m
113
+
114
+ Short info : get_auto_fix()
115
+ ||| Automatically handles preprocessing tasks:
116
+ <Review Data> <Missing Data Handling> <Time Column Handling>
117
+ <Normalisation> <Encoding (when enabled)> |||
118
+
119
+ Param Def
120
+ \033[93mDefault\033[0m ___ \033[92minteractive=False\033[0m ___ if True --> (you get manual access to choose mean or median values, or stop the process)
121
+
122
+ \033[93mDefault\033[0m ___ \033[92mnormalisation=False\033[0m ___ if True --> (numerical values will be normalised)
123
+
124
+ \033[93mDefault\033[0m ___ \033[92mtrain=True\033[0m ___ (if train=True, we save min and max values from training data. If train=False, we reuse the saved min and max values from training data)
125
+
126
+ \033[93mDefault\033[0m ___ \033[92mencoding=False\033[0m ___ if True --> (categorical values will be one-hot encoded)
127
+
128
+
129
+
130
+ ------ M A N U A L F I X ____ Preprocessing Data ------
131
+
132
+
133
+
134
+ -- x = DUMMY_PI(input = "YOUR DATA PATH OR pd.DataFrame")
135
+ \033[92mInit process\033[0m
136
+
137
+ ````Method name : \033[91mx.get_view()\033[0m:
138
+
139
+ Short info : get_view()
140
+ ||| AI Review (\033[91mInternet connection required\033[0m)
141
+ <Detecting Missing Data> <Detecting Time Columns> <Data Overview> |||
142
+
143
+ Param Def
144
+ \033[93mDefault\033[0m ___ \033[92mai_review=True\033[0m ___ if False --> (ai_check() method will not be activated)
145
+
146
+ \033[93mDefault\033[0m ___ \033[92mdescribe=True\033[0m ___ if False --> (pd.describe() will not be printed)
147
+
148
+
149
+
150
+ ````Method name : \033[91mx.get_data_extract()\033[0m
151
+
152
+ Short info : get_data_extract()
153
+ ||| <Unix Timestamp Detection --> Datetime>
154
+ <Converting Datetime into Trainable Features> |||
155
+
156
+ Param Def
157
+ \033[93mDefault\033[0m ___ #\033[92mdate_extract = Dictionary --> {All Params:None}\033[0m
158
+
159
+ date_extract = {"year":None , "month":None, "day_of_week":None,
160
+ "day_of_month":None, "week_of_year":None,
161
+ "day_of_year":None, "hour_of_week":None,
162
+ "second":None, "minute":None,
163
+ "millisecond":None, "hour":None}
164
+
165
+
166
+
167
+ ````Method name : \033[91mx.get_fill_missing()\033[0m
168
+
169
+ Short info : get_fill_missing()
170
+ ||| <Filling Missing Numerical and Non-Numerical Data>
171
+ <Automatically Choosing Mean or Median Based on Practical Use>
172
+ <Interquartile Range Calculation to Help Decide Between Mean and Median> |||
173
+
174
+ \033[93mDefault\033[0m ___ #\033[92moperations="auto"\033[0m
175
+ if operations="custom" --> (you can fix specific columns by providing columns=[])
176
+
177
+ \033[93mDefault\033[0m ___ #\033[92mcolumns=None\033[0m
178
+ if None --> (the method will process all detected missing-value columns)
179
+ \033[91mcolumns = column names should be provided as a list\033[0m
180
+
181
+ \033[93mDefault\033[0m ___ #\033[92minter=True\033[0m
182
+ inter means INTERACTION. It activates the response() method,
183
+ allowing users to manually choose mean or median values
184
+ when filling missing data, or stop the process.
185
+
186
+ \033[93mDefault\033[0m ___ #\033[92mnorma=False\033[0m
187
+ norma means NORMALISATION. If True, numerical columns will
188
+ be normalised after missing-value handling.
189
+
190
+
191
+
192
+ ````Method name : \033[91mx.normalize()\033[0m
193
+
194
+ Short info : normalize()
195
+ ||| <Normalisation> <Saving Min and Max Values> |||
196
+
197
+ \033[93mDefault\033[0m ___ #\033[92mtrain=True\033[0m
198
+ --> (if train=True, save min and max values from training data.
199
+ If train=False, reuse the saved min and max values from training data)
200
+
201
+
202
+
203
+ ````Method name : \033[91mx.one_hot_encode()\033[0m
204
+
205
+ \033[93mDefault\033[0m ___ #\033[92mexclude=None\033[0m
206
+ --> (you can exclude specific columns)
207
+
208
+ \033[93mDefault\033[0m ___ #\033[92mmax_categories=100\033[0m
209
+ --> (specify the maximum number of unique categories per column
210
+ to prevent unnecessary feature expansion)
211
+
212
+
213
+
214
+ \033[92mWARNING !\033[0m
215
+ High-cardinality columns may be skipped.
216
+ Skipped columns will remain in their original format.
217
+
218
+
219
+
220
+ ````Method name : \033[91mx.normalize_y()\033[0m
221
+
222
+ \033[93mParameter\033[0m ___ #\033[92my_true\033[0m
223
+ --> (target values will be normalised separately using their own min and max values)
224
+
225
+
226
+
227
+ ````Method name : \033[91mx.back_norm()\033[0m
228
+
229
+ \033[93mParameter\033[0m ___ #\033[92mprediction\033[0m
230
+ --> (converts normalised predictions back to their original scale
231
+ using the saved min and max values from training target data)
232
+
233
+
234
+
235
+ '''
236
+ print("\n\n",instruction)
237
+ def dtype_check(self):
238
+ df = self.input.copy()
239
+ self.check_list = {}
240
+ for col in df.select_dtypes(include=["object", "string" ,"float"]).columns:
241
+
242
+ numeric = pd.to_numeric(df[col], errors="coerce")
243
+
244
+ valid = df[col].notna()
245
+
246
+ if valid.sum() == 0:
247
+ continue
248
+
249
+ numeric_rate = numeric.notna().sum() / valid.sum()
250
+
251
+ self.check_list[col] = numeric_rate * 100
252
+
253
+ # Veiw Data
254
+ def get_veiw(self ,ai_review = True ,describe = True):
255
+
256
+ self.dtype_check()
257
+
258
+ if hasattr(self , "prep_data"):
259
+ self.input = self.prep_data
260
+
261
+
262
+ self.raw_columns = {el[0] : el[1] for el in zip(self.input.columns , self.input.count())}
263
+ median_vol = np.median(list(self.raw_columns.values()))
264
+
265
+ print("\n")
266
+ print(f"\033[92mDataset inspection\n\nRows : {median_vol}\nColunms : {len(self.raw_columns)}\033[0m\n\n")
267
+
268
+ # We will save missing value coulmn names in self.missing value
269
+
270
+ self.missing_value = []
271
+ total_rows = len(self.input)
272
+ obj = []
273
+
274
+ for key, val in self.raw_columns.items():
275
+ if self.raw_columns[key] < total_rows:
276
+ #Missing value view
277
+ self.missing_value.append(key)
278
+ self.missing_precent = (self.input[key].isna().sum() / len(self.input) * 100)
279
+
280
+ print(f"\033[92mMissing value : {key} \033[0m",("_"*(25-len(key))) ,f" \033[91m{round(self.missing_precent ,2)}%\033[0m")
281
+
282
+ if self.input[key].dtype == "object":
283
+ #Categorical coulmn view
284
+ obj.append(self.input[key].dtype == "object")
285
+ elif self.input[key].dtype != "object":
286
+ obj.append(self.input[key].dtype == "object")
287
+
288
+ for key ,val in self.check_list.items():
289
+ if 0 < val < 100:
290
+ print(f"WARNING : {key} DATA CONTAIN MIXED TYPE VALUES __ {val}")
291
+
292
+ if any(obj):
293
+ print("\033[92mCategorical Column detected \033[0m" ,"_"*13 ," \033[91mD e t e c t e d\033[0m")
294
+
295
+ else:
296
+ print("\033[92mCategorical Column detected \033[0m" ,"_"*13 ," \033[92mN O T D E T E C T E D\033[0m")
297
+
298
+ #Date coulmns view
299
+ if self.date_check() or self.detect_unix_timestamp():
300
+ print("\033[92mDate Column \033[0m","_"*30," \033[91mD e t e c t e d\033[0m")
301
+ if np.mean(self.input.count()) == median_vol:
302
+ print("\033[92mMissing value : \033[0m",("_"*(33-len(key))) ," \033[92mN O T D E T E C T E D\033[0m")
303
+ if ai_review:
304
+ try:
305
+ result = self.ai_check()
306
+
307
+ if result:
308
+ print(
309
+ f"\n\n\033[93mA I R E V I E W\033[0m : "
310
+ f"\033[96m{result}\033[0m\n\n"
311
+ )
312
+ else:
313
+ print("\nAI REVIEW: Unavailable. Continuing preprocessing.\n")
314
+
315
+ except Exception as e:
316
+ print(f"\nAI REVIEW ERROR: {e}. Continuing preprocessing.\n")
317
+ if describe:
318
+ print(self.input.describe())
319
+
320
+
321
+
322
+
323
+
324
+
325
+ # TIME RELATED DATA
326
+
327
+
328
+
329
+
330
+
331
+ # Date chcking is it contain date or not date format -- datetime
332
+
333
+ def date_check(self):
334
+
335
+ # if hasattr(self , "get_fill_missing"):
336
+ # self.input = self.prep_data
337
+
338
+ date_columns = []
339
+
340
+ for col in self.input.columns:
341
+
342
+ if not (
343
+ pd.api.types.is_object_dtype(self.input[col])
344
+ or pd.api.types.is_datetime64_any_dtype(self.input[col])
345
+ ):
346
+ continue
347
+
348
+ with warnings.catch_warnings():
349
+ warnings.simplefilter("ignore", UserWarning)
350
+
351
+ converted = pd.to_datetime(
352
+ self.input[col],
353
+ errors="coerce",
354
+ format="mixed"
355
+ )
356
+
357
+ non_missing = self.input[col].notna().sum()
358
+
359
+ if non_missing == 0:
360
+ continue
361
+
362
+ success_rate = converted.notna().sum() / non_missing
363
+
364
+ if success_rate >= 0.8:
365
+ date_columns.append(col)
366
+ # print(f"Date column detected: {col}")
367
+
368
+ return date_columns
369
+
370
+
371
+ # unix date format checking
372
+
373
+ def detect_unix_timestamp(self):
374
+
375
+ unix_columns = {}
376
+
377
+ for col in self.input.columns:
378
+
379
+ if not pd.api.types.is_numeric_dtype(self.input[col]):
380
+ continue
381
+
382
+ sample = self.input[col].dropna()
383
+
384
+ if len(sample) < 3:
385
+ continue
386
+
387
+ sample = sample.sample(n=3, random_state=42)
388
+
389
+ for unit in ["s", "ms"]:
390
+
391
+ converted = pd.to_datetime(
392
+ sample,
393
+ unit=unit,
394
+ errors="coerce",
395
+ utc=True
396
+ )
397
+
398
+ # Check whether all 3 values produce plausible dates
399
+ if converted.notna().all():
400
+
401
+ years_valid = converted.dt.year.between(
402
+ 2000, 2100
403
+ ).all()
404
+
405
+ if years_valid:
406
+ unix_columns[col] = unit
407
+ break
408
+
409
+ return unix_columns
410
+
411
+
412
+
413
+ # copnverting unix_timestamp to date time :
414
+
415
+ def unix_timestamp_converter(self, key):
416
+ unix_cols = self.detect_unix_timestamp()
417
+ if key in unix_cols:
418
+ unit = unix_cols[key]
419
+ self.time_col = pd.to_datetime(self.input[key], unit=unit, errors="coerce")
420
+ return self.time_col
421
+ return None
422
+
423
+
424
+ # Date components
425
+
426
+ def date_components(self , series):
427
+
428
+ values = series.dropna().astype(str).head(100)
429
+
430
+ result = {
431
+ "has_year": False,
432
+ "has_month": False,
433
+ "has_day": False,
434
+ "has_hour": False,
435
+ "has_minute": False,
436
+ "has_second": False,
437
+ "has_millisecond" : False
438
+ }
439
+
440
+ for value in values:
441
+
442
+ try:
443
+ parsed = pd.to_datetime(value, format="mixed")
444
+
445
+ if parsed.year:
446
+ result["has_year"] = True
447
+
448
+ if parsed.month:
449
+ result["has_month"] = True
450
+
451
+ if parsed.day:
452
+ result["has_day"] = True
453
+ if ":" in value:
454
+
455
+ result["has_hour"] = True
456
+
457
+ time_part = value.split()[-1]
458
+ time_parts = time_part.split(":")
459
+
460
+ if len(time_parts) >= 2:
461
+ result["has_minute"] = True
462
+
463
+ if len(time_parts) >= 3:
464
+ result["has_second"] = True
465
+
466
+ if len(time_parts) >= 3 and "." in time_parts[2]:
467
+ result["has_millisecond"] = True
468
+
469
+ except (ValueError, TypeError):
470
+ continue
471
+
472
+ return result
473
+
474
+ # makes dictionary from both date types
475
+
476
+ def date_columns_dict(self):
477
+
478
+ date_columns = {}
479
+
480
+ text_dates = self.date_check()
481
+ unix_dates = self.detect_unix_timestamp()
482
+
483
+ self._parsed_date_columns = {}
484
+
485
+ # Regular date columns
486
+ for col in text_dates:
487
+
488
+ parsed = pd.to_datetime(
489
+ self.input[col],
490
+ errors="coerce",
491
+ format="mixed"
492
+ )
493
+
494
+ self._parsed_date_columns[col] = parsed
495
+ date_columns[col] = self.date_components(parsed)
496
+
497
+ # Unix timestamp columns
498
+ for col, unit in unix_dates.items():
499
+
500
+ parsed = pd.to_datetime(
501
+ self.input[col],
502
+ unit=unit,
503
+ errors="coerce",
504
+ utc=True
505
+ )
506
+
507
+ self._parsed_date_columns[col] = parsed
508
+ date_columns[col] = self.date_components(parsed)
509
+
510
+ return date_columns
511
+
512
+
513
+
514
+ # Get date
515
+
516
+ def get_data_extract(self , date_extract = {"year":None , "month":None,
517
+ "day_of_week":None,"day_of_month":None,"week_of_year":None,
518
+ "day_of_year":None,"hour_of_week":None,
519
+ "second":None ,"minute":None,"millisecond":None, "hour":None}) :
520
+ date_info = self.date_columns_dict()
521
+ self.added_date_value = []
522
+ self.input_x = self.input.copy()
523
+
524
+ # date_extract = {key :True for key ,val in date_extract.items()}
525
+
526
+ for col_n , val in date_info.items():
527
+ # for chr_feature , bool_val in val.items():
528
+
529
+ self.input_x[col_n] = self._parsed_date_columns[col_n]
530
+
531
+ if date_info[col_n]["has_year"] and date_info[col_n]["has_month"] and date_info[col_n]["has_day"]:
532
+
533
+ if "month" in date_extract.keys() and date_extract.get("month"): # keys( checks deos this feature exist ) , get() checks true or false
534
+ self.input_x["month"] = self.input_x[col_n].dt.month
535
+ self.added_date_value.append("month")
536
+ if "year" in date_extract.keys() and date_extract.get("year"):
537
+ self.input_x["year"] = self.input_x[col_n].dt.year
538
+ self.added_date_value.append("year")
539
+ if "day_of_week" in date_extract.keys() and date_extract.get("day_of_week"):
540
+ self.input_x["day_of_week"] = self.input_x[col_n].dt.dayofweek
541
+ self.added_date_value.append("day_of_week")
542
+ if "day_of_month" in date_extract.keys() and date_extract.get("day_of_month"):
543
+ self.input_x["day_of_month"] = self.input_x[col_n].dt.day
544
+ self.added_date_value.append("day_of_month")
545
+ if "week_of_year" in date_extract.keys() and date_extract.get("week_of_year"):
546
+ self.input_x["week_of_year"] = self.input_x[col_n].dt.isocalendar().week.astype(int)
547
+ self.added_date_value.append("week_of_year")
548
+ if "day_of_year" in date_extract.keys() and date_extract.get("day_of_year"):
549
+ self.input_x["day_of_year"] = self.input_x[col_n].dt.dayofyear
550
+ self.added_date_value.append("day_of_year")
551
+
552
+ if date_info[col_n]["has_hour"] and date_info[col_n]["has_minute"]:
553
+ if "hour_of_week" in date_extract.keys() and date_extract.get("hour_of_week"):
554
+ self.input_x["hour_of_week"] = (
555
+ self.input_x[col_n].dt.dayofweek * 24 + self.input_x[col_n].dt.hour)
556
+ self.added_date_value.append("hour_of_week")
557
+ if "minute" in date_extract.keys() and date_extract.get("minute"):
558
+ self.input_x["minute"] = self.input_x[col_n].dt.minute
559
+ self.added_date_value.append("minute")
560
+
561
+
562
+
563
+ if date_info[col_n]["has_hour"]:
564
+ self.input_x["hour"] = self.input_x[col_n].dt.hour
565
+ self.added_date_value.append("hour")
566
+ if date_info[col_n]["has_minute"]:
567
+ if "minute" in date_extract.keys() and date_extract.get("minute"):
568
+ self.input_x["minute"] = self.input_x[col_n].dt.minute
569
+ self.added_date_value.append("minute")
570
+ if date_info[col_n]["has_second"]:
571
+ if "second" in date_extract.keys() and date_extract.get("second"):
572
+ self.input_x["second"] = self.input_x[col_n].dt.second
573
+ self.added_date_value.append("has_second")
574
+
575
+ if date_info[col_n]["has_millisecond"]:
576
+ if "millisecond" in date_extract.keys() and date_extract.get("millisecond"):
577
+ self.input_x["millisecond"] = self.input_x[col_n].dt.microsecond // 1000
578
+ self.added_date_value.append("millisecond")
579
+
580
+
581
+
582
+ if any(date_extract.values()):
583
+ self.input_x = self.input_x.drop(columns=[col_n])
584
+
585
+ self.prep_data = self.input_x
586
+ print(f"\033[92mget_data_extract mathod added\033[0m ___ {self.added_date_value}\n\n")
587
+ return self.input_x
588
+
589
+
590
+
591
+
592
+
593
+ # MISSING VALUES HANDLING
594
+
595
+
596
+
597
+
598
+
599
+ # Interquartile range claculation
600
+
601
+ def get_iqr(self, column_name):
602
+
603
+ values = self.input[column_name].dropna()
604
+
605
+ if values.empty:
606
+ return 0
607
+
608
+ q1 = values.quantile(0.25)
609
+ q3 = values.quantile(0.75)
610
+
611
+ iqr = q3 - q1
612
+
613
+ low = q1 - (1.5 * iqr)
614
+ high = q3 + (1.5 * iqr)
615
+
616
+ outliers = ((values < low) | (values > high)).sum()
617
+
618
+ outlier_percent = (outliers / len(values)) * 100
619
+
620
+ return outlier_percent
621
+
622
+
623
+ # Missing value response checking
624
+
625
+ def response(self, val , interactive = True):
626
+ self.method = "mean"
627
+ response_user = None
628
+ self.contin = True
629
+ self.missing_precent = (
630
+ self.input[val].isna().mean() * 100)
631
+
632
+
633
+ if hasattr(self, "missing_precent"):
634
+
635
+ if self.missing_precent < 5:
636
+ self.method = "mean"
637
+ return # Safely exit early since no user prompt is needed
638
+
639
+ if interactive :
640
+ if 5 < self.missing_precent < 30:
641
+ response_user = input(
642
+ f"Missing values {val}__{round((self.missing_precent), 2)}% ______"
643
+ " Do you agree use Median value ? Y/N/STOP ___ "
644
+ )
645
+
646
+ elif self.missing_precent >= 30:
647
+ response_user2 = input(
648
+ f"\033[93mWarning !\033[0m \nMissing values"
649
+ f" {int(self.missing_precent)}%\nAdvice - checking Data ... \nDo you"
650
+ " agree use Median value ? Y/N/STOP___ "
651
+ )
652
+ if response_user2.upper() == "Y":
653
+ self.method = "median"
654
+ return
655
+ elif response_user2.upper() in ["STOP", "N"]:
656
+ self.contin = False
657
+ return
658
+ else:
659
+ self.contin = False
660
+ return
661
+
662
+ # Safe check: only evaluate if user input was captured
663
+ if response_user and response_user.upper() == "Y":
664
+ self.method = "median"
665
+ elif response_user and response_user.upper() == "N":
666
+ if self.get_iqr(column_name=val) > 1:
667
+ response_user_iqr = input(
668
+ "Interquartile range > 1 --> Effects to mean value ____ Do you"
669
+ " want to continue ? __ Y/N/STOP___ "
670
+ )
671
+ if response_user_iqr.upper() == "Y":
672
+ self.method = "mean"
673
+ elif response_user_iqr.upper() == "N":
674
+ self.method = "median"
675
+ elif response_user_iqr.upper() == "STOP":
676
+ print("Operation cancelled.")
677
+ self.contin = False
678
+ return self.input
679
+ else:
680
+ print(f"Given response __ {response_user_iqr} is not valid Enter ")
681
+ self.contin = False
682
+ else:
683
+ print("Missing value filling mode set to MEAN (not median)")
684
+ self.method = "mean"
685
+ elif response_user and response_user.upper() == "STOP":
686
+ print("Operation cancelled.")
687
+ self.contin = False
688
+ return self.input
689
+ elif response_user:
690
+ print(f"Given response __ {response_user} is not valid Enter ")
691
+ self.contin = False
692
+
693
+ # interactive False
694
+ else :
695
+ if 5 < self.missing_precent < 30:
696
+ self.method = "median"
697
+ elif self.missing_precent >= 30:
698
+ print("Missing data more than 30% ")
699
+ self.contin = False
700
+
701
+ #numeric mising values checking
702
+
703
+ def numeric_miss_val(self , input_c):
704
+
705
+ if self.input[input_c].notna().sum() == 0:
706
+ print(f"{input_c}: No valid values available for filling.")
707
+ return
708
+
709
+ if self.method :
710
+
711
+ iqr_txt = ""
712
+
713
+ if self.get_iqr(column_name = input_c) > 1 and self.method == "median" :
714
+ mean_calc = self.input[input_c].median()
715
+ self.method_txt = "M E D I A N V A L U E S"
716
+ self.input[input_c] = self.input[input_c].fillna( #filling with median volue if numeric
717
+ mean_calc)
718
+ iqr_txt = f"{input_c.upper()} _________ Interquartile range ((x < low) | (x > high)) outlier_percent = {round(self.get_iqr(column_name = input_c) ,2)} %\n"
719
+
720
+ else :
721
+ if self.method == "mean":
722
+ mean_calc = self.input[input_c].mean()
723
+ self.method_txt = "M E A N V A L U E S"
724
+ self.input[input_c] = self.input[input_c].fillna( #filling with mean volue if numeric
725
+ mean_calc)
726
+ else: # if data contain more than 5% missing data median will be used
727
+ mean_calc = self.input[input_c].median()
728
+ self.method_txt = "M E D I A N V A L U E S"
729
+ self.input[input_c] = self.input[input_c].fillna( #filling with median volue if numeric
730
+ mean_calc)
731
+ print(f"\n\nMissing value : {input_c} ",("_"*(25-len(input_c))) , f"\033[92mF I L L E D W I T H {self.method_txt}\033[0m\n{iqr_txt}")
732
+
733
+
734
+
735
+ #non numeric missing val checking
736
+
737
+ def non_numeric_mis_val(self, input_c, norm=False):
738
+
739
+ date_columns = set(self.date_check())
740
+ unix_columns = set(self.detect_unix_timestamp())
741
+
742
+ all_date_columns = date_columns | unix_columns
743
+
744
+ if input_c in all_date_columns:
745
+ print(f"{input_c} is a date column. Skipping automatic filling.")
746
+ return
747
+
748
+ modes = self.input[input_c].mode()
749
+
750
+ if not modes.empty:
751
+ fill_value = modes.iloc[0]
752
+
753
+ self.input[input_c] = self.input[input_c].fillna(fill_value)
754
+
755
+ print(f"Missing value : {input_c} ",
756
+ "_" * (25 - len(input_c)),
757
+ "\033[92mF I L L E D W I T H M O S T F R E Q U E N T V A L U E\033[0m")
758
+
759
+ if norm:
760
+ self.input[input_c] = self.input[input_c].astype("category").cat.codes
761
+
762
+ else:
763
+ print(f"No valid mode found for {input_c}. Skipping.")
764
+
765
+
766
+
767
+
768
+ # Main missing value
769
+
770
+
771
+
772
+ def get_fill_missing(self, operations="auto", exclude_columns = None,columns=None, inter=True, norma=False):
773
+
774
+ self.exclude_columns(columns=exclude_columns)
775
+ self.norma = norma
776
+
777
+ self.get_veiw()
778
+ # No missing values
779
+ if not self.missing_value:
780
+
781
+ print("\n\n")
782
+ print(
783
+ "MISSING VALUE : _________________________",
784
+ "\033[92mN O T D E T E C T E D\033[0m"
785
+ )
786
+ print("\n\n")
787
+
788
+ self.prep_data = self.input.copy()
789
+ print(self.prep_data.describe())
790
+ return self.prep_data
791
+
792
+ # Select columns
793
+ if operations == "auto":
794
+
795
+ list_mis_val = self.missing_value.copy()
796
+
797
+ elif operations == "custom":
798
+
799
+ if columns:
800
+ if all(x in self.input.columns for x in columns):
801
+ list_mis_val = columns.copy()
802
+ else:
803
+ print("INPUT IS NOT VALID. PLEASE CHECK columns.")
804
+ return
805
+
806
+ else:
807
+ print("PLEASE ENTER columns.")
808
+ return
809
+
810
+ else:
811
+ print("INPUT FOR operations IS NOT VALID!")
812
+ return
813
+
814
+ # Detect date columns once
815
+ date_columns = set(self.date_check())
816
+ unix_columns = set(self.detect_unix_timestamp())
817
+
818
+ all_date_columns = date_columns | unix_columns
819
+
820
+ self.prep_data = self.input.copy()
821
+
822
+ processed_columns = []
823
+
824
+ for c in list_mis_val:
825
+
826
+ # Skip date columns
827
+ if c in all_date_columns:
828
+ print(f"{c}: Date column detected. Skipping automatic filling.")
829
+ continue
830
+
831
+ # Skip columns that have no missing values
832
+ if not self.input[c].isna().any():
833
+ processed_columns.append(c)
834
+ continue
835
+
836
+ # Ask user / select filling method
837
+ self.response(c, interactive=inter)
838
+
839
+ if not self.contin:
840
+ break
841
+
842
+ # Numeric or non-numeric filling
843
+ if pd.api.types.is_numeric_dtype(self.input[c]):
844
+
845
+ self.numeric_miss_val(input_c=c)
846
+
847
+ else:
848
+
849
+ self.non_numeric_mis_val(
850
+ input_c=c,
851
+ norm=norma
852
+ )
853
+
854
+ # Update prepared data
855
+ self.prep_data[c] = self.input[c]
856
+
857
+ # Track successfully processed columns
858
+ if not self.input[c].isna().any():
859
+ processed_columns.append(c)
860
+
861
+ # Preserve columns that still need processing
862
+ self.missing_value = [
863
+ c for c in self.missing_value
864
+ if c not in processed_columns
865
+ ]
866
+
867
+ self.restore_columns()
868
+ return self.prep_data
869
+
870
+
871
+ #NORMALISATION PART
872
+
873
+
874
+ def normalize(self, train=True ,manual = True):
875
+
876
+ if manual:
877
+ self.norma = True
878
+ if not hasattr(self, "prep_data") or not self.norma:
879
+ return
880
+
881
+ numeric_cols = self.prep_data.select_dtypes(
882
+ include="number"
883
+ ).columns
884
+
885
+ if train:
886
+
887
+ self.norm_params = {}
888
+
889
+ for col in numeric_cols:
890
+ self.norm_params[col] = {
891
+ "min": self.prep_data[col].min(),
892
+ "max": self.prep_data[col].max()
893
+ }
894
+
895
+ elif not self.norm_params:
896
+ raise ValueError(
897
+ "No training normalization parameters found. "
898
+ "Fit using training data first."
899
+ )
900
+
901
+ for col in numeric_cols:
902
+
903
+ if col not in self.norm_params:
904
+ raise ValueError(f"Missing normalization parameters for {col}")
905
+
906
+ min_val = self.norm_params[col]["min"]
907
+ max_val = self.norm_params[col]["max"]
908
+
909
+ if max_val != min_val:
910
+ self.prep_data[col] = (
911
+ self.prep_data[col] - min_val
912
+ ) / (max_val - min_val)
913
+
914
+ else:
915
+ self.prep_data[col] = 0.0
916
+
917
+ return self.prep_data
918
+
919
+ # Y_TRUE NORMOLISATION
920
+
921
+ def normalize_y(self, y_true):
922
+
923
+ self.y_min = np.min(y_true)
924
+ self.y_max = np.max(y_true)
925
+
926
+ if self.y_max == self.y_min:
927
+ raise ValueError("Target values have zero range.")
928
+
929
+ return (y_true - self.y_min) / (self.y_max - self.y_min)
930
+
931
+ def back_norm(self, prediction):
932
+
933
+ if not hasattr(self, "y_min"):
934
+ raise ValueError("Normalize y_true before using back_norm().")
935
+
936
+ return prediction * (self.y_max - self.y_min) + self.y_min
937
+
938
+
939
+
940
+
941
+
942
+ # One hot encoding part
943
+
944
+
945
+ def one_hot_encode(self, train=True, exclude=None, max_categories=100):
946
+
947
+ if not hasattr(self, "prep_data"):
948
+ raise ValueError("Run get_fill_missing() before one_hot_encode().")
949
+
950
+ exclude = set(exclude or [])
951
+
952
+ # Find date columns so we don't encode them
953
+ date_columns = set(self.date_check())
954
+ unix_columns = set(self.detect_unix_timestamp())
955
+
956
+ skip_columns = date_columns | unix_columns | exclude
957
+
958
+ # Select categorical columns
959
+ if train:
960
+
961
+ categorical_cols = [
962
+ col for col in self.prep_data.columns
963
+ if (
964
+ pd.api.types.is_object_dtype(self.prep_data[col])
965
+ or pd.api.types.is_string_dtype(self.prep_data[col])
966
+ or isinstance(self.prep_data[col].dtype, pd.CategoricalDtype)
967
+ )
968
+ and col not in skip_columns
969
+ ]
970
+
971
+ self.onehot_categories = {}
972
+ self.onehot_encoded_columns = {}
973
+
974
+ for col in categorical_cols:
975
+
976
+ categories = list(pd.unique(self.prep_data[col].dropna()))
977
+
978
+ # Avoid creating thousands of features
979
+ if len(categories) > max_categories:
980
+ print(
981
+ f"{col}: {len(categories)} unique values. "
982
+ "Skipped due to high cardinality."
983
+ )
984
+ continue
985
+
986
+ if not categories:
987
+ print(f"{col}: No valid categories. Skipping.")
988
+ continue
989
+
990
+ self.onehot_categories[col] = categories
991
+
992
+ encoded = pd.get_dummies(
993
+ pd.Categorical(
994
+ self.prep_data[col],
995
+ categories=categories
996
+ ),
997
+ prefix=col,
998
+ dtype="int8"
999
+ )
1000
+
1001
+ self.onehot_encoded_columns[col] = encoded.columns.tolist()
1002
+
1003
+ self.prep_data = pd.concat(
1004
+ [self.prep_data.drop(columns=[col]), encoded],
1005
+ axis=1
1006
+ )
1007
+
1008
+ else:
1009
+
1010
+ if not hasattr(self, "onehot_categories"):
1011
+ raise ValueError(
1012
+ "No training categories found. Run one_hot_encode(train=True) first."
1013
+ )
1014
+
1015
+ for col, categories in self.onehot_categories.items():
1016
+
1017
+ if col not in self.prep_data.columns:
1018
+ raise ValueError(
1019
+ f"Column '{col}' is missing from validation data."
1020
+ )
1021
+
1022
+ encoded = pd.get_dummies(
1023
+ pd.Categorical(
1024
+ self.prep_data[col],
1025
+ categories=categories
1026
+ ),
1027
+ prefix=col,
1028
+ dtype="int8"
1029
+ )
1030
+
1031
+ # Guarantee the same columns as training
1032
+ encoded = encoded.reindex(
1033
+ columns=self.onehot_encoded_columns[col],
1034
+ fill_value=0
1035
+ )
1036
+
1037
+ self.prep_data = pd.concat(
1038
+ [self.prep_data.drop(columns=[col]), encoded],
1039
+ axis=1
1040
+ )
1041
+
1042
+ print("ONE-HOT ENCODING COMPLETED")
1043
+ return self.prep_data
1044
+ #
1045
+ def exclude_columns(self, columns=None):
1046
+
1047
+ if not columns:
1048
+ self.except_data = pd.DataFrame(index=self.prep_data.index)
1049
+ return
1050
+
1051
+ if not all(col in self.prep_data.columns for col in columns):
1052
+ raise ValueError("Some columns do not exist in the dataset.")
1053
+
1054
+ self.except_data = self.prep_data[columns].copy()
1055
+
1056
+ self.prep_data = self.prep_data.drop(columns=columns)
1057
+
1058
+
1059
+ def restore_columns(self):
1060
+
1061
+ if hasattr(self, "except_data") and not self.except_data.empty:
1062
+ self.prep_data = pd.concat(
1063
+ [self.prep_data, self.except_data.loc[self.prep_data.index]],
1064
+ axis=1
1065
+ )
1066
+
1067
+ return self.prep_data
1068
+
1069
+ #GET AUTO FIX
1070
+
1071
+
1072
+
1073
+ def get_auto_fix(
1074
+ self,
1075
+ interactive=False,
1076
+ exclude_columns=None,
1077
+ normalisation=False,
1078
+ train=True,
1079
+ encoding=False,
1080
+ max_categories=100
1081
+ ):
1082
+ if not hasattr(self , "get_veiw"):
1083
+ self.get_veiw(describe=False)
1084
+
1085
+ self.exclude_columns(columns=exclude_columns)
1086
+
1087
+ self.get_fill_missing(
1088
+ operations="auto",
1089
+ inter=interactive,
1090
+ norma=normalisation
1091
+ )
1092
+
1093
+ date_extract_val = {
1094
+ "year": True,
1095
+ "month": True,
1096
+ "day_of_week": True,
1097
+ "day_of_month": True,
1098
+ "week_of_year": True,
1099
+ "day_of_year": True,
1100
+ "hour_of_week": True,
1101
+ "second": True,
1102
+ "minute": True,
1103
+ "millisecond": True,
1104
+ "hour": True
1105
+ }
1106
+
1107
+ self.get_data_extract(date_extract=date_extract_val)
1108
+
1109
+ self.normalize(train=train , manual = normalisation)
1110
+
1111
+ if encoding:
1112
+ self.one_hot_encode(
1113
+ train=train,
1114
+ max_categories=max_categories
1115
+ )
1116
+
1117
+ self.restore_columns()
1118
+
1119
+ return self.prep_data
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: dummy-preprocessor
3
+ Version: 0.1.0
4
+ Summary: An automated data preprocessing and EDA pipeline with AI server integration.
5
+ Author-email: Akira <sardor77nematov@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/akirahatsu/DUMMY-PREPROCESSOR
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: pandas>=1.3.0
14
+ Requires-Dist: numpy>=1.20.0
15
+ Requires-Dist: requests>=2.0.0
16
+
17
+
18
+ # DUMMY PREPROCESSOR
19
+
20
+ An automated data preprocessing and EDA pipeline with AI server integration.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install dummy-preprocessor
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ dummy_preprocessor/__init__.py
4
+ dummy_preprocessor/preprocessor.py
5
+ dummy_preprocessor.egg-info/PKG-INFO
6
+ dummy_preprocessor.egg-info/SOURCES.txt
7
+ dummy_preprocessor.egg-info/dependency_links.txt
8
+ dummy_preprocessor.egg-info/requires.txt
9
+ dummy_preprocessor.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ pandas>=1.3.0
2
+ numpy>=1.20.0
3
+ requests>=2.0.0
@@ -0,0 +1 @@
1
+ dummy_preprocessor
@@ -0,0 +1,32 @@
1
+
2
+ [build-system]
3
+ requires = ["setuptools>=61.0.0", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "dummy-preprocessor"
8
+ version = "0.1.0"
9
+ description = "An automated data preprocessing and EDA pipeline with AI server integration."
10
+ readme = "README.md"
11
+ authors = [
12
+ { name = "Akira", email = "sardor77nematov@gmail.com" }
13
+ ]
14
+ license = { text = "MIT" }
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ requires-python = ">=3.9"
21
+ dependencies = [
22
+ "pandas>=1.3.0",
23
+ "numpy>=1.20.0",
24
+ "requests>=2.0.0"
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/akirahatsu/DUMMY-PREPROCESSOR"
29
+ [tool.setuptools.packages.find]
30
+ where = ["."]
31
+ include = ["dummy_preprocessor*"]
32
+ exclude = ["sample_data*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+