Sajid030 commited on
Commit
f892d45
·
verified ·
1 Parent(s): 5663a2a

Update AutoVisualizer/processing.py

Browse files
Files changed (1) hide show
  1. AutoVisualizer/processing.py +255 -250
AutoVisualizer/processing.py CHANGED
@@ -1,250 +1,255 @@
1
- # Module that will handle processing tasks of the user dataset
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- # A quick cleanliness checker function
7
- def check_dataset_cleanliness(df):
8
- issues_found = False
9
-
10
- # 1. Columns with null values
11
- null_cols = df.columns[df.isnull().any()].tolist()
12
- if null_cols:
13
- issues_found = True
14
- st.warning(f"⚠️ These columns contain missing (NaN) values: {null_cols}")
15
-
16
- # 2. Object columns that appear to be numeric (but aren't due to dirty values)
17
- misclassified_numeric = []
18
- for col in df.select_dtypes(include="object").columns:
19
- non_null = df[col].dropna().astype(str)
20
- sample_size = min(100, len(non_null))
21
- if sample_size == 0:
22
- continue # skip if column has no non-null values
23
- sample = non_null.sample(sample_size, random_state=1)
24
- numeric_like_ratio = sample.str.replace(",", "").str.replace(".", "", regex=False).str.isdigit().mean()
25
- if numeric_like_ratio > 0.6:
26
- misclassified_numeric.append(col)
27
-
28
- if misclassified_numeric:
29
- issues_found = True
30
- st.warning(
31
- f"⚠️ These columns are stored as `object` but mostly contain numeric values.\n"
32
- f"This may be due to the presence of invalid or non-numeric entries in a few rows: {misclassified_numeric}"
33
- )
34
-
35
- # 3. Checking duplicate records
36
- num_duplicates = df.duplicated().sum()
37
- if num_duplicates > 0:
38
- issues_found = True
39
- st.warning(f"⚠️ Your dataset contains {num_duplicates} duplicated rows.")
40
-
41
- # 4. Checking Constant Columns (No Variation)
42
- constant_cols = [col for col in df.columns if df[col].nunique(dropna=False) <= 1]
43
- if constant_cols:
44
- issues_found = True
45
- st.warning(f"⚠️ These columns contain only a single unique value and may be useless for analysis: {constant_cols}")
46
-
47
- # 5. Suspiciously High Cardinality in Categorical Columns
48
- high_card_cols = [col for col in df.select_dtypes(include='object') if df[col].nunique() > 100]
49
- if high_card_cols:
50
- issues_found = True
51
- st.warning(f"⚠️ These object-type columns have unusually high unique values (possibly IDs or noisy data): {high_card_cols}")
52
-
53
-
54
- # Final message
55
- if not issues_found:
56
- st.success("✅ No major issues detected. Dataset looks clean!")
57
-
58
-
59
- # Function that will identify the task of the dataset
60
- def task_type(df: pd.DataFrame, target_col: str) -> str:
61
- """
62
- Determine the machine learning task type based on the target column.
63
-
64
- Args:
65
- df (pd.DataFrame): The input dataset.
66
- target_col (str or None): The target column name, or None for unsupervised tasks.
67
-
68
- Returns:
69
- str: One of the following task types:
70
- - "Classification" if the target is categorical.
71
- - "Clustering" if the target is not provided.
72
- - "Regression" if the target is numerical.
73
- - "Unknown" if the type cannot be recognized.
74
- """
75
- # Handle unsupervised case (no target)
76
- if target_col == "No Target":
77
- return "Clustering"
78
-
79
- target_series = df[target_col]
80
- dtype = target_series.dtype
81
- n_unique = target_series.nunique()
82
-
83
- # Check for classification
84
- if dtype == 'object' or dtype == 'bool' or (dtype == 'category'):
85
- return "Classification"
86
-
87
- # Check for binary/multi-class classification represented as integers or floats
88
- if dtype.kind in ['i', 'u', 'f']: # Integer types
89
- if n_unique <= 10: # Arbitrary threshold for classification
90
- return "Classification"
91
- else:
92
- return "Regression"
93
-
94
- # Numeric types default to regression
95
- if dtype.kind in ['i', 'u', 'f']:
96
- return "Regression"
97
-
98
- return "Unknown"
99
-
100
- # Function that will identify if an object feature is truly categorical or not
101
- def is_probably_categorical(series: pd.Series, threshold_unique: int = 50, threshold_ratio: float = 0.1) -> bool:
102
- """
103
- Determines whether a given pandas Series is likely to be a categorical feature.
104
-
105
- Args:
106
- series (pd.Series): The input data column to analyze.
107
- threshold_unique (int, optional (default=50)) : Maximum number of unique values for an object-type column to be considered categorical.
108
- threshold_ratio (float, optional (default=0.1)) : Maximum ratio of unique values to total entries for object-type column to be treated as categorical.
109
-
110
- Returns:
111
- bool: True if the series is likely categorical, False otherwise.
112
- """
113
-
114
- # Heuristic for object types (e.g., strings): avoid classifying high-cardinality fields as categorical
115
- if series.dtype == 'object':
116
- num_unique = series.nunique()
117
- unique_ratio = num_unique / len(series)
118
-
119
- if num_unique <= threshold_unique and unique_ratio <= threshold_ratio:
120
- return True # categorical
121
- else:
122
- return False # high-cardinality non-categorical (like names)
123
-
124
- # Explicit categorical or boolean data types are considered categorical
125
- elif pd.api.types.is_categorical_dtype(series):
126
- return True
127
- elif pd.api.types.is_bool_dtype(series):
128
- return True
129
-
130
- return False
131
-
132
- # Function that will identify if an numerical feature is discrete or not
133
- def is_discrete(series: pd.Series, max_unique: int = 20) -> bool:
134
- """
135
- Determine whether a numeric series should be considered discrete.
136
-
137
- Args:
138
- series (pd.Series): The input numeric data column to analyze.
139
- max_unique (int, optional): Maximum number of unique values allowed
140
- to treat a column as discrete. Default is 20.
141
-
142
- Returns:
143
- bool: True if the series is likely discrete, False otherwise.
144
- """
145
- # Check if the series is of integer type
146
- if pd.api.types.is_integer_dtype(series):
147
- return series.nunique() <= max_unique
148
-
149
- if pd.api.types.is_float_dtype(series):
150
- # If all values are whole numbers AND unique count is low → treat as discrete
151
- if series.dropna().apply(float.is_integer).all():
152
- return series.nunique() <= max_unique
153
-
154
- return False
155
-
156
- # Function that will identify if an numerical feature is continuous or not
157
- def is_continuous(series: pd.Series, max_unique: int = 20) -> bool:
158
- """
159
- Determine whether a numeric series is continuous.
160
-
161
- Args:
162
- series (pd.Series): The input numeric data column to analyze.
163
- max_unique (int, optional): Threshold for unique values. If a float-type column
164
- contains only whole numbers and has fewer than this count, it is not considered continuous.
165
- Default is 20.
166
-
167
- Returns:
168
- bool: True if the series is likely continuous, False otherwise.
169
- """
170
- # Only float types are considered potentially continuous
171
- if pd.api.types.is_float_dtype(series):
172
- # If it's float but looks like discrete, then not continuous
173
- all_whole_numbers = series.dropna().apply(float.is_integer).all()
174
- if all_whole_numbers and series.nunique() <= max_unique:
175
- return False
176
- return True
177
- return False
178
-
179
- # Function that will identify if an feature is date-time format and then extract the time-based components
180
- def parse_datetime_columns(df: pd.DataFrame) -> tuple[pd.DataFrame, list, list]:
181
- """
182
- Detects and parses datetime columns in a DataFrame, and extracts useful
183
- date and/or time components into new columns.
184
-
185
- Args:
186
- df (pd.DataFrame): Input dataset.
187
-
188
- Returns:
189
- tuple:
190
- - pd.DataFrame: Updated DataFrame with extracted datetime components.
191
- - list: List of original columns identified as datetime.
192
- - list: List of newly extracted datetime-related feature names.
193
- """
194
- datetime_cols = []
195
- extracted_datetime = []
196
- today = pd.Timestamp.today() # Just the date, no time
197
-
198
- for col in df.columns:
199
- if pd.api.types.is_datetime64_any_dtype(df[col]):
200
- datetime_cols.append(col)
201
- elif df[col].dtype == "object":
202
- try:
203
- converted = pd.to_datetime(df[col], errors="raise")
204
- df[col] = converted
205
- datetime_cols.append(col)
206
- except Exception:
207
- continue
208
-
209
- for col in datetime_cols:
210
- # Flags for what actually exists
211
- has_date = True
212
- has_time = True
213
-
214
- # Check if all dates are "today" → probably not originally present
215
- # if df[col].dt.normalize().nunique() == 1 and df[col].dt.normalize().iloc[0] == today:
216
- if (df[col].dt.year == today.year).all() or (df[col].dt.month == today.month).all() or (df[col].dt.day == today.day).all():
217
- has_date = False
218
-
219
- # Check if all times are 00:00:00 → probably not originally present
220
- if (df[col].dt.hour == 0).all() and (df[col].dt.minute == 0).all() and (df[col].dt.second == 0).all():
221
- has_time = False
222
-
223
- if has_date:
224
- df[f"{col}_year"] = df[col].dt.year
225
- df[f"{col}_month"] = df[col].dt.month
226
- df[f"{col}_day"] = df[col].dt.day
227
- df[f"{col}_weekday"] = df[col].dt.day_name()
228
- extracted_datetime.extend([
229
- f"{col}_year", f"{col}_month", f"{col}_day", f"{col}_weekday"
230
- ])
231
- else:
232
- df[f"{col}_year"] = np.nan
233
- df[f"{col}_month"] = np.nan
234
- df[f"{col}_day"] = np.nan
235
- df[f"{col}_weekday"] = np.nan
236
-
237
- if has_time:
238
- df[f"{col}_hour"] = df[col].dt.hour
239
- df[f"{col}_minute"] = df[col].dt.minute
240
- extracted_datetime.extend([
241
- f"{col}_hour", f"{col}_minute"
242
- ])
243
- else:
244
- df[f"{col}_hour"] = np.nan
245
- df[f"{col}_minute"] = np.nan
246
-
247
- # Remove any columns that are now entirely NaN
248
- df = df.dropna(axis=1, how='all')
249
-
250
- return df, datetime_cols, extracted_datetime
 
 
 
 
 
 
1
+ # Module that will handle processing tasks of the user dataset
2
+ import numpy as np
3
+ import pandas as pd
4
+ import streamlit as st
5
+
6
+ # A quick cleanliness checker function
7
+ @st.cache_data
8
+ def check_dataset_cleanliness(df):
9
+ issues_found = False
10
+
11
+ # 1. Columns with null values
12
+ null_cols = df.columns[df.isnull().any()].tolist()
13
+ if null_cols:
14
+ issues_found = True
15
+ st.warning(f"⚠️ These columns contain missing (NaN) values: {null_cols}")
16
+
17
+ # 2. Object columns that appear to be numeric (but aren't due to dirty values)
18
+ misclassified_numeric = []
19
+ for col in df.select_dtypes(include="object").columns:
20
+ non_null = df[col].dropna().astype(str)
21
+ sample_size = min(100, len(non_null))
22
+ if sample_size == 0:
23
+ continue # skip if column has no non-null values
24
+ sample = non_null.sample(sample_size, random_state=1)
25
+ numeric_like_ratio = sample.str.replace(",", "").str.replace(".", "", regex=False).str.isdigit().mean()
26
+ if numeric_like_ratio > 0.6:
27
+ misclassified_numeric.append(col)
28
+
29
+ if misclassified_numeric:
30
+ issues_found = True
31
+ st.warning(
32
+ f"⚠️ These columns are stored as `object` but mostly contain numeric values.\n"
33
+ f"This may be due to the presence of invalid or non-numeric entries in a few rows: {misclassified_numeric}"
34
+ )
35
+
36
+ # 3. Checking duplicate records
37
+ num_duplicates = df.duplicated().sum()
38
+ if num_duplicates > 0:
39
+ issues_found = True
40
+ st.warning(f"⚠️ Your dataset contains {num_duplicates} duplicated rows.")
41
+
42
+ # 4. Checking Constant Columns (No Variation)
43
+ constant_cols = [col for col in df.columns if df[col].nunique(dropna=False) <= 1]
44
+ if constant_cols:
45
+ issues_found = True
46
+ st.warning(f"⚠️ These columns contain only a single unique value and may be useless for analysis: {constant_cols}")
47
+
48
+ # 5. Suspiciously High Cardinality in Categorical Columns
49
+ high_card_cols = [col for col in df.select_dtypes(include='object') if df[col].nunique() > 100]
50
+ if high_card_cols:
51
+ issues_found = True
52
+ st.warning(f"⚠️ These object-type columns have unusually high unique values (possibly IDs or noisy data): {high_card_cols}")
53
+
54
+
55
+ # Final message
56
+ if not issues_found:
57
+ st.success("✅ No major issues detected. Dataset looks clean!")
58
+
59
+ @st.cache_data
60
+ # Function that will identify the task of the dataset
61
+ def task_type(df: pd.DataFrame, target_col: str) -> str:
62
+ """
63
+ Determine the machine learning task type based on the target column.
64
+
65
+ Args:
66
+ df (pd.DataFrame): The input dataset.
67
+ target_col (str or None): The target column name, or None for unsupervised tasks.
68
+
69
+ Returns:
70
+ str: One of the following task types:
71
+ - "Classification" if the target is categorical.
72
+ - "Clustering" if the target is not provided.
73
+ - "Regression" if the target is numerical.
74
+ - "Unknown" if the type cannot be recognized.
75
+ """
76
+ # Handle unsupervised case (no target)
77
+ if target_col == "No Target":
78
+ return "Clustering"
79
+
80
+ target_series = df[target_col]
81
+ dtype = target_series.dtype
82
+ n_unique = target_series.nunique()
83
+
84
+ # Check for classification
85
+ if dtype == 'object' or dtype == 'bool' or (dtype == 'category'):
86
+ return "Classification"
87
+
88
+ # Check for binary/multi-class classification represented as integers or floats
89
+ if dtype.kind in ['i', 'u', 'f']: # Integer types
90
+ if n_unique <= 10: # Arbitrary threshold for classification
91
+ return "Classification"
92
+ else:
93
+ return "Regression"
94
+
95
+ # Numeric types default to regression
96
+ if dtype.kind in ['i', 'u', 'f']:
97
+ return "Regression"
98
+
99
+ return "Unknown"
100
+
101
+ @st.cache_data
102
+ # Function that will identify if an object feature is truly categorical or not
103
+ def is_probably_categorical(series: pd.Series, threshold_unique: int = 50, threshold_ratio: float = 0.1) -> bool:
104
+ """
105
+ Determines whether a given pandas Series is likely to be a categorical feature.
106
+
107
+ Args:
108
+ series (pd.Series): The input data column to analyze.
109
+ threshold_unique (int, optional (default=50)) : Maximum number of unique values for an object-type column to be considered categorical.
110
+ threshold_ratio (float, optional (default=0.1)) : Maximum ratio of unique values to total entries for object-type column to be treated as categorical.
111
+
112
+ Returns:
113
+ bool: True if the series is likely categorical, False otherwise.
114
+ """
115
+
116
+ # Heuristic for object types (e.g., strings): avoid classifying high-cardinality fields as categorical
117
+ if series.dtype == 'object':
118
+ num_unique = series.nunique()
119
+ unique_ratio = num_unique / len(series)
120
+
121
+ if num_unique <= threshold_unique and unique_ratio <= threshold_ratio:
122
+ return True # categorical
123
+ else:
124
+ return False # high-cardinality non-categorical (like names)
125
+
126
+ # Explicit categorical or boolean data types are considered categorical
127
+ elif pd.api.types.is_categorical_dtype(series):
128
+ return True
129
+ elif pd.api.types.is_bool_dtype(series):
130
+ return True
131
+
132
+ return False
133
+
134
+ @st.cache_data
135
+ # Function that will identify if an numerical feature is discrete or not
136
+ def is_discrete(series: pd.Series, max_unique: int = 20) -> bool:
137
+ """
138
+ Determine whether a numeric series should be considered discrete.
139
+
140
+ Args:
141
+ series (pd.Series): The input numeric data column to analyze.
142
+ max_unique (int, optional): Maximum number of unique values allowed
143
+ to treat a column as discrete. Default is 20.
144
+
145
+ Returns:
146
+ bool: True if the series is likely discrete, False otherwise.
147
+ """
148
+ # Check if the series is of integer type
149
+ if pd.api.types.is_integer_dtype(series):
150
+ return series.nunique() <= max_unique
151
+
152
+ if pd.api.types.is_float_dtype(series):
153
+ # If all values are whole numbers AND unique count is low → treat as discrete
154
+ if series.dropna().apply(float.is_integer).all():
155
+ return series.nunique() <= max_unique
156
+
157
+ return False
158
+
159
+ @st.cache_data
160
+ # Function that will identify if an numerical feature is continuous or not
161
+ def is_continuous(series: pd.Series, max_unique: int = 20) -> bool:
162
+ """
163
+ Determine whether a numeric series is continuous.
164
+
165
+ Args:
166
+ series (pd.Series): The input numeric data column to analyze.
167
+ max_unique (int, optional): Threshold for unique values. If a float-type column
168
+ contains only whole numbers and has fewer than this count, it is not considered continuous.
169
+ Default is 20.
170
+
171
+ Returns:
172
+ bool: True if the series is likely continuous, False otherwise.
173
+ """
174
+ # Only float types are considered potentially continuous
175
+ if pd.api.types.is_float_dtype(series):
176
+ # If it's float but looks like discrete, then not continuous
177
+ all_whole_numbers = series.dropna().apply(float.is_integer).all()
178
+ if all_whole_numbers and series.nunique() <= max_unique:
179
+ return False
180
+ return True
181
+ return False
182
+
183
+ @st.cache_data
184
+ # Function that will identify if an feature is date-time format and then extract the time-based components
185
+ def parse_datetime_columns(df: pd.DataFrame) -> tuple[pd.DataFrame, list, list]:
186
+ """
187
+ Detects and parses datetime columns in a DataFrame, and extracts useful
188
+ date and/or time components into new columns.
189
+
190
+ Args:
191
+ df (pd.DataFrame): Input dataset.
192
+
193
+ Returns:
194
+ tuple:
195
+ - pd.DataFrame: Updated DataFrame with extracted datetime components.
196
+ - list: List of original columns identified as datetime.
197
+ - list: List of newly extracted datetime-related feature names.
198
+ """
199
+ datetime_cols = []
200
+ extracted_datetime = []
201
+ today = pd.Timestamp.today() # Just the date, no time
202
+
203
+ for col in df.columns:
204
+ if pd.api.types.is_datetime64_any_dtype(df[col]):
205
+ datetime_cols.append(col)
206
+ elif df[col].dtype == "object":
207
+ try:
208
+ converted = pd.to_datetime(df[col], errors="raise")
209
+ df[col] = converted
210
+ datetime_cols.append(col)
211
+ except Exception:
212
+ continue
213
+
214
+ for col in datetime_cols:
215
+ # Flags for what actually exists
216
+ has_date = True
217
+ has_time = True
218
+
219
+ # Check if all dates are "today" → probably not originally present
220
+ # if df[col].dt.normalize().nunique() == 1 and df[col].dt.normalize().iloc[0] == today:
221
+ if (df[col].dt.year == today.year).all() or (df[col].dt.month == today.month).all() or (df[col].dt.day == today.day).all():
222
+ has_date = False
223
+
224
+ # Check if all times are 00:00:00 → probably not originally present
225
+ if (df[col].dt.hour == 0).all() and (df[col].dt.minute == 0).all() and (df[col].dt.second == 0).all():
226
+ has_time = False
227
+
228
+ if has_date:
229
+ df[f"{col}_year"] = df[col].dt.year
230
+ df[f"{col}_month"] = df[col].dt.month
231
+ df[f"{col}_day"] = df[col].dt.day
232
+ df[f"{col}_weekday"] = df[col].dt.day_name()
233
+ extracted_datetime.extend([
234
+ f"{col}_year", f"{col}_month", f"{col}_day", f"{col}_weekday"
235
+ ])
236
+ else:
237
+ df[f"{col}_year"] = np.nan
238
+ df[f"{col}_month"] = np.nan
239
+ df[f"{col}_day"] = np.nan
240
+ df[f"{col}_weekday"] = np.nan
241
+
242
+ if has_time:
243
+ df[f"{col}_hour"] = df[col].dt.hour
244
+ df[f"{col}_minute"] = df[col].dt.minute
245
+ extracted_datetime.extend([
246
+ f"{col}_hour", f"{col}_minute"
247
+ ])
248
+ else:
249
+ df[f"{col}_hour"] = np.nan
250
+ df[f"{col}_minute"] = np.nan
251
+
252
+ # Remove any columns that are now entirely NaN
253
+ df = df.dropna(axis=1, how='all')
254
+
255
+ return df, datetime_cols, extracted_datetime