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.
- dummy_preprocessor-0.1.0/PKG-INFO +25 -0
- dummy_preprocessor-0.1.0/README.md +9 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor/__init__.py +4 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor/preprocessor.py +1119 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor.egg-info/PKG-INFO +25 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor.egg-info/SOURCES.txt +9 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor.egg-info/dependency_links.txt +1 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor.egg-info/requires.txt +3 -0
- dummy_preprocessor-0.1.0/dummy_preprocessor.egg-info/top_level.txt +1 -0
- dummy_preprocessor-0.1.0/pyproject.toml +32 -0
- dummy_preprocessor-0.1.0/setup.cfg +4 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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*"]
|