pyspan 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyspan-0.1.0.dist-info/LICENSE +21 -0
- pyspan-0.1.0.dist-info/METADATA +201 -0
- pyspan-0.1.0.dist-info/RECORD +6 -0
- pyspan-0.1.0.dist-info/WHEEL +5 -0
- pyspan-0.1.0.dist-info/top_level.txt +1 -0
- pyspan.py +840 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 pyspan24
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyspan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for efficient data cleaning and preprocessing with Pandas.
|
|
5
|
+
Home-page: https://github.com/yourusername/my_simple_lib
|
|
6
|
+
Author: Noor Surani, Amynah Reimoo
|
|
7
|
+
Author-email: nsurani@hotmail.com, amynahreimoo@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy ==2.0.1
|
|
15
|
+
Requires-Dist: pandas ==2.2.2
|
|
16
|
+
Requires-Dist: pyspellchecker ==0.8.1
|
|
17
|
+
Requires-Dist: python-dateutil ==2.9.0.post0
|
|
18
|
+
Requires-Dist: pytz ==2024.1
|
|
19
|
+
Requires-Dist: setuptools ==72.2.0
|
|
20
|
+
Requires-Dist: six ==1.16.0
|
|
21
|
+
Requires-Dist: tzdata ==2024.1
|
|
22
|
+
|
|
23
|
+
# pyspan
|
|
24
|
+
|
|
25
|
+
## 'pyspan' is a Python package designed to facilitate data cleaning and preprocessing using Pandas. It provides various functions to handle missing values, detect outliers, spell check data, and more. Additionally, it includes a logging utility to keep track of function calls and their parameters.
|
|
26
|
+
|
|
27
|
+
Installation
|
|
28
|
+
|
|
29
|
+
To use pyspan, simply install the package using pip:
|
|
30
|
+
|
|
31
|
+
``bash
|
|
32
|
+
pip install pyspan
|
|
33
|
+
|
|
34
|
+
# Functions
|
|
35
|
+
|
|
36
|
+
1. handle_nulls(data: pd.DataFrame, columns: List[str], method: str, value: Optional[Union[str, float]] = None) -> pd.DataFrame
|
|
37
|
+
Handles missing values in the specified columns of a DataFrame.
|
|
38
|
+
Parameters:
|
|
39
|
+
data: DataFrame with missing values.
|
|
40
|
+
columns: List of column names to apply the fill operation.
|
|
41
|
+
method: Strategy to use for imputing missing values ('mean', 'median', 'mode', 'interpolate', 'forward_fill', 'backward_fill').
|
|
42
|
+
value: Custom value to fill NaNs with (optional).
|
|
43
|
+
|
|
44
|
+
2. remove_duplicates(df: pd.DataFrame) -> pd.DataFrame
|
|
45
|
+
Removes duplicate rows from a DataFrame.
|
|
46
|
+
Parameters:
|
|
47
|
+
df: DataFrame to remove duplicates from.
|
|
48
|
+
|
|
49
|
+
3. remove_columns(df: pd.DataFrame, columns: List[str]) -> pd.DataFrame
|
|
50
|
+
Removes specified columns from a DataFrame.
|
|
51
|
+
Parameters:
|
|
52
|
+
df: DataFrame to remove columns from.
|
|
53
|
+
columns: List of column names to remove.
|
|
54
|
+
|
|
55
|
+
4. auto_rename_columns(df: pd.DataFrame) -> pd.DataFrame
|
|
56
|
+
Automatically renames columns to remove spaces and special characters.
|
|
57
|
+
Parameters:
|
|
58
|
+
df: DataFrame to rename columns in.
|
|
59
|
+
|
|
60
|
+
5. rename_dataframe_columns(df: pd.DataFrame, rename_dict: dict) -> pd.DataFrame
|
|
61
|
+
Renames columns in a DataFrame using a provided dictionary mapping.
|
|
62
|
+
Parameters:
|
|
63
|
+
df: DataFrame to rename columns in.
|
|
64
|
+
rename_dict: Dictionary mapping current column names to new column names.
|
|
65
|
+
|
|
66
|
+
6. change_dt(df: pd.DataFrame, columns: Union[str, List[str]], date_format: str = "%Y-%m-%d", time_format: str = "%H:%M:%S", from_timezone: Optional[str] = None, to_timezone: Optional[str] = None) -> pd.DataFrame
|
|
67
|
+
Changes the format of date and time columns and handles timezone conversion.
|
|
68
|
+
Parameters:
|
|
69
|
+
df: DataFrame containing date/time columns to reformat.
|
|
70
|
+
columns: Name(s) of the column(s) to be reformatted.
|
|
71
|
+
date_format: Desired date format.
|
|
72
|
+
time_format: Desired time format.
|
|
73
|
+
from_timezone: Original timezone of the datetime column(s).
|
|
74
|
+
to_timezone: Desired timezone for the datetime column(s).
|
|
75
|
+
|
|
76
|
+
7. detect_delimiter(series: pd.Series) -> str
|
|
77
|
+
Detects the most common delimiter in a Series of strings.
|
|
78
|
+
Parameters:
|
|
79
|
+
series: Series containing strings to analyze.
|
|
80
|
+
|
|
81
|
+
8. split_column(df: pd.DataFrame, column_name: str, delimiter: str = None) -> pd.DataFrame
|
|
82
|
+
Splits a single column into multiple columns based on a delimiter.
|
|
83
|
+
Parameters:
|
|
84
|
+
df: DataFrame containing the column to split.
|
|
85
|
+
column_name: Name of the column to be split.
|
|
86
|
+
delimiter: Delimiter to use for splitting (optional).
|
|
87
|
+
|
|
88
|
+
9. impute(data, by=None, value=None, columns=None) -> pd.DataFrame
|
|
89
|
+
Handles missing values using specified strategy or custom value.
|
|
90
|
+
Parameters:
|
|
91
|
+
data: DataFrame or Series with missing values.
|
|
92
|
+
by: Strategy for imputing missing values ('mean', 'median', 'mode', 'interpolate', 'forward_fill', 'backward_fill').
|
|
93
|
+
value: Custom value to fill NaNs with.
|
|
94
|
+
columns: List of column names to apply the fill operation.
|
|
95
|
+
|
|
96
|
+
10. spell_check_dataframe(data: pd.DataFrame, dictionary='en_US', columns=None) -> dict
|
|
97
|
+
Performs spell check on specified columns of a DataFrame.
|
|
98
|
+
Parameters:
|
|
99
|
+
data: DataFrame containing columns to spell check.
|
|
100
|
+
dictionary: Dictionary to use for spell checking ('en_US', 'en_GB', 'en_AU', 'en_IE').
|
|
101
|
+
columns: List of column names to perform spell check on.
|
|
102
|
+
|
|
103
|
+
11. detect_invalid_dates(series: pd.Series) -> pd.Series
|
|
104
|
+
Detects invalid date values in a Series.
|
|
105
|
+
Parameters:
|
|
106
|
+
series: Series to check for invalid dates.
|
|
107
|
+
|
|
108
|
+
12. detect_data_entry_errors(data, spellcheck_dict='en_US', date_columns=None, numeric_columns=None, text_columns=None) -> pd.DataFrame
|
|
109
|
+
Detects and flags data entry errors including invalid dates and misspelled words.
|
|
110
|
+
Parameters:
|
|
111
|
+
data: DataFrame to analyze.
|
|
112
|
+
spellcheck_dict: Dictionary to use for spell checking.
|
|
113
|
+
date_columns: List of columns to check for invalid dates.
|
|
114
|
+
numeric_columns: List of columns to check for numeric format errors.
|
|
115
|
+
text_columns: List of text columns to perform spell checking on.
|
|
116
|
+
|
|
117
|
+
13. data_type_conversions(data, column=None) -> pd.DataFrame or pd.Series
|
|
118
|
+
Recommends and applies data type conversions based on the analysis of each column's data.
|
|
119
|
+
Parameters:
|
|
120
|
+
data: DataFrame or Series to analyze.
|
|
121
|
+
column: Specific column to analyze (optional).
|
|
122
|
+
|
|
123
|
+
14. detect_outliers(data, method='iqr', threshold=1.5, columns=None, handle_missing=True) -> pd.DataFrame
|
|
124
|
+
Detects outliers in a dataset using specified method and threshold.
|
|
125
|
+
Parameters:
|
|
126
|
+
data: DataFrame or Series to analyze.
|
|
127
|
+
method: Outlier detection method ('z-score', 'iqr').
|
|
128
|
+
threshold: Threshold for outlier detection.
|
|
129
|
+
columns: List of columns to apply the outlier detection on (optional).
|
|
130
|
+
handle_missing: Whether to handle missing values by dropping them or not.
|
|
131
|
+
|
|
132
|
+
Logging Functions
|
|
133
|
+
1. display_logs()
|
|
134
|
+
Prints stored log entries.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
## Example Usage
|
|
138
|
+
Here are some examples to illustrate the usage of the functions provided in pyspan:
|
|
139
|
+
|
|
140
|
+
import pandas as pd
|
|
141
|
+
from pyspan import handle_nulls, remove_duplicates, remove_columns, auto_rename_columns, rename_dataframe_columns
|
|
142
|
+
from pyspan import change_dt, detect_delimiter, split_column, impute, spell_check_dataframe
|
|
143
|
+
from pyspan import detect_invalid_dates, detect_data_entry_errors, data_type_conversions, detect_outliers
|
|
144
|
+
from pyspan import log_function_call, display_logs
|
|
145
|
+
|
|
146
|
+
# Load a dataset
|
|
147
|
+
df = pd.read_csv('/content/GlobalSharkAttacks.csv')
|
|
148
|
+
|
|
149
|
+
# Example usage of handle_nulls
|
|
150
|
+
df_filled = handle_nulls(df, columns=['Column1', 'Column2'], method='mean')
|
|
151
|
+
|
|
152
|
+
# Example usage of remove_duplicates
|
|
153
|
+
df_unique = remove_duplicates(df)
|
|
154
|
+
|
|
155
|
+
# Example usage of remove_columns
|
|
156
|
+
df_reduced = remove_columns(df, columns=['ColumnToRemove'])
|
|
157
|
+
|
|
158
|
+
# Example usage of auto_rename_columns
|
|
159
|
+
auto_rename_columns(df)
|
|
160
|
+
|
|
161
|
+
# Example usage of rename_dataframe_columns
|
|
162
|
+
rename_dict = {'OldName': 'NewName'}
|
|
163
|
+
df_renamed_dict = rename_dataframe_columns(df, rename_dict)
|
|
164
|
+
|
|
165
|
+
# Example usage of change_dt
|
|
166
|
+
df_formatted = change_dt(df, columns=['Date'], date_format='%d-%m-%Y', time_format='%I:%M %p')
|
|
167
|
+
|
|
168
|
+
# Example usage of detect_delimiter
|
|
169
|
+
delimiter = detect_delimiter(df['ColumnWithDelimiters'])
|
|
170
|
+
|
|
171
|
+
# Example usage of split_column
|
|
172
|
+
df_split = split_column(df, column_name='ColumnWithDelimiters')
|
|
173
|
+
|
|
174
|
+
# Example usage of impute
|
|
175
|
+
df_imputed = impute(df, by='mean', columns=['Column1'])
|
|
176
|
+
|
|
177
|
+
# Example usage of spell_check_dataframe
|
|
178
|
+
misspelled = spell_check_dataframe(df, dictionary='en_US', columns=['TextColumn'])
|
|
179
|
+
|
|
180
|
+
# Example usage of detect_invalid_dates
|
|
181
|
+
invalid_dates = detect_invalid_dates(df['DateColumn'])
|
|
182
|
+
|
|
183
|
+
# Example usage of detect_data_entry_errors
|
|
184
|
+
errors = detect_data_entry_errors(df, spellcheck_dict='en_US', date_columns=['DateColumn'], numeric_columns=['NumericColumn'])
|
|
185
|
+
|
|
186
|
+
# Example usage of data_type_conversions
|
|
187
|
+
df_converted = data_type_conversions(df)
|
|
188
|
+
|
|
189
|
+
# Example usage of detect_outliers
|
|
190
|
+
outliers = detect_outliers(df, method='iqr', threshold=1.5)
|
|
191
|
+
|
|
192
|
+
# Example usage of display_logs
|
|
193
|
+
display_logs()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# License
|
|
197
|
+
This package is licensed under the MIT License. See the LICENSE file for more details.
|
|
198
|
+
|
|
199
|
+
# Contact
|
|
200
|
+
For issues or questions, please contact [amynahreimoo@gmail.com].
|
|
201
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
pyspan.py,sha256=l5kwhw9kDUfV5DLtOihCa27hx-gRHqQjMVLcBU7PJsI,30598
|
|
2
|
+
pyspan-0.1.0.dist-info/LICENSE,sha256=qmzMFXi0LfLXJ55V26ORye9KAohzQYLIYlEka6VU35E,1085
|
|
3
|
+
pyspan-0.1.0.dist-info/METADATA,sha256=WM5KCzWJLztArCQh3eMw3EicNMU_sGn3FfvpsriHQ7g,8215
|
|
4
|
+
pyspan-0.1.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
|
5
|
+
pyspan-0.1.0.dist-info/top_level.txt,sha256=bIR06TOL19Id7q05FDSRnwn1NhH0eWSUj-BzzdaO1BI,7
|
|
6
|
+
pyspan-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyspan
|
pyspan.py
ADDED
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
# Store logs in a list for retrieval
|
|
5
|
+
log_entries = []
|
|
6
|
+
|
|
7
|
+
def log_function_call(func):
|
|
8
|
+
def wrapper(*args, **kwargs):
|
|
9
|
+
# Get the name of the calling function
|
|
10
|
+
caller = inspect.stack()[1]
|
|
11
|
+
caller_function = caller.function
|
|
12
|
+
caller_file = caller.filename
|
|
13
|
+
|
|
14
|
+
# Create a log entry
|
|
15
|
+
log_entry = f'Function "{func.__name__}" was called from "{caller_function}" in file "{caller_file}".'
|
|
16
|
+
log_entries.append(log_entry)
|
|
17
|
+
|
|
18
|
+
# Optionally also log to a file
|
|
19
|
+
logging.info(log_entry)
|
|
20
|
+
|
|
21
|
+
return func(*args, **kwargs)
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
def display_logs():
|
|
25
|
+
if not log_entries:
|
|
26
|
+
print("No logs available.")
|
|
27
|
+
else:
|
|
28
|
+
for entry in log_entries:
|
|
29
|
+
print(entry)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
import pandas as pd
|
|
34
|
+
from typing import Optional, Union, List
|
|
35
|
+
@log_function_call
|
|
36
|
+
|
|
37
|
+
def handle_nulls(
|
|
38
|
+
df: pd.DataFrame,
|
|
39
|
+
columns: Optional[Union[str, List[str]]] = None,
|
|
40
|
+
action: str = 'remove',
|
|
41
|
+
with_val: Optional[Union[int, float, str]] = None,
|
|
42
|
+
inplace: bool = False
|
|
43
|
+
) -> Optional[pd.DataFrame]:
|
|
44
|
+
"""
|
|
45
|
+
Handle rows with null values in a DataFrame by removing or replacing them.
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
- df (pd.DataFrame): The input DataFrame.
|
|
49
|
+
- columns (Optional[Union[str, List[str]]]): Column(s) to check for null values.
|
|
50
|
+
If None, apply to the entire DataFrame. Default is None.
|
|
51
|
+
- action (str): Action to perform on rows with null values.
|
|
52
|
+
Options are 'remove' to drop rows or 'replace' to fill nulls with a custom value. Default is 'remove'.
|
|
53
|
+
- with_val (Optional[Union[int, float, str]]): Custom value to replace nulls with, applicable if action is 'replace'.
|
|
54
|
+
- inplace (bool): If True, modify the DataFrame in place. If False, return a new DataFrame. Default is False.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
- Optional[pd.DataFrame]: DataFrame with null values handled according to specified action, or None if inplace=True.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
if not isinstance(df, pd.DataFrame):
|
|
61
|
+
raise TypeError("The 'df' parameter must be a pandas DataFrame.")
|
|
62
|
+
|
|
63
|
+
# Validate the action parameter
|
|
64
|
+
if action not in ['remove', 'replace']:
|
|
65
|
+
raise ValueError("Action must be either 'remove' or 'replace'.")
|
|
66
|
+
|
|
67
|
+
# Determine columns to process
|
|
68
|
+
if columns is not None:
|
|
69
|
+
if isinstance(columns, str):
|
|
70
|
+
columns = [columns] # Convert a single column name to a list
|
|
71
|
+
subset_df = df[columns]
|
|
72
|
+
else:
|
|
73
|
+
subset_df = df
|
|
74
|
+
|
|
75
|
+
# Remove or replace rows with nulls
|
|
76
|
+
if action == 'remove':
|
|
77
|
+
df_cleaned = df.dropna(subset=subset_df.columns)
|
|
78
|
+
elif action == 'replace':
|
|
79
|
+
if with_val is None:
|
|
80
|
+
raise ValueError("with value must be provided when action is 'replace'.")
|
|
81
|
+
df_cleaned = df.fillna({col: with_val for col in subset_df.columns})
|
|
82
|
+
|
|
83
|
+
if inplace:
|
|
84
|
+
df.update(df_cleaned)
|
|
85
|
+
return None
|
|
86
|
+
else:
|
|
87
|
+
return df_cleaned
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
import pandas as pd
|
|
93
|
+
from typing import Optional, Union, List
|
|
94
|
+
@log_function_call
|
|
95
|
+
|
|
96
|
+
def remove_duplicates(
|
|
97
|
+
df: pd.DataFrame,
|
|
98
|
+
columns: Optional[Union[str, List[str]]] = None,
|
|
99
|
+
keep: Optional[str] = 'first',
|
|
100
|
+
consider_all: bool = True,
|
|
101
|
+
inplace: bool = False
|
|
102
|
+
) -> Optional[pd.DataFrame]:
|
|
103
|
+
"""
|
|
104
|
+
Remove duplicate rows from a DataFrame based on specified columns or the entire row.
|
|
105
|
+
|
|
106
|
+
Parameters:
|
|
107
|
+
- df (pd.DataFrame): The input DataFrame from which duplicates are to be removed.
|
|
108
|
+
- columns (Optional[Union[str, List[str]]]): Specific column(s) to check for duplicates.
|
|
109
|
+
If None, checks for duplicates in the entire DataFrame. Default is None.
|
|
110
|
+
- keep (Optional[str]): Determines which duplicates to keep. Options are:
|
|
111
|
+
'first': Keep the first occurrence of each duplicate.
|
|
112
|
+
'last': Keep the last occurrence of each duplicate.
|
|
113
|
+
'none': Remove all duplicates.
|
|
114
|
+
Default is None, which keeps the first occurrence.
|
|
115
|
+
- consider_all (bool): Whether to consider all columns in the DataFrame if duplicates are found in the specified columns.
|
|
116
|
+
True means removing the entire row if any duplicates are found in the specified columns. Default is True.
|
|
117
|
+
- inplace (bool): If True, modify the DataFrame in place. If False, return a new DataFrame. Default is False.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
- Optional[pd.DataFrame]: DataFrame with duplicates removed according to specified criteria, or None if inplace=True.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
if not isinstance(df, pd.DataFrame):
|
|
124
|
+
raise TypeError("The 'df' parameter must be a pandas DataFrame.")
|
|
125
|
+
|
|
126
|
+
# Validate the keep parameter
|
|
127
|
+
if keep not in ['first', 'last', 'none', None]:
|
|
128
|
+
raise ValueError("keep must be one of ['first', 'last', 'none'] or None.")
|
|
129
|
+
|
|
130
|
+
# Validate columns input
|
|
131
|
+
if columns is not None:
|
|
132
|
+
if isinstance(columns, str):
|
|
133
|
+
columns = [columns] # Convert single column name to a list
|
|
134
|
+
|
|
135
|
+
# Check if all columns in columns exist in the DataFrame
|
|
136
|
+
missing_columns = [col for col in columns if col not in df.columns]
|
|
137
|
+
if missing_columns:
|
|
138
|
+
raise ValueError(f"Columns {missing_columns} not found in DataFrame.")
|
|
139
|
+
|
|
140
|
+
# Remove duplicates
|
|
141
|
+
if keep == 'none':
|
|
142
|
+
df_cleaned = df.drop_duplicates(subset=columns, keep=False)
|
|
143
|
+
else:
|
|
144
|
+
if consider_all:
|
|
145
|
+
df_cleaned = df.drop_duplicates(subset=columns, keep=keep or 'first')
|
|
146
|
+
else:
|
|
147
|
+
df_cleaned = df.drop_duplicates(keep=keep or 'first')
|
|
148
|
+
|
|
149
|
+
if inplace:
|
|
150
|
+
df.update(df_cleaned)
|
|
151
|
+
return None
|
|
152
|
+
else:
|
|
153
|
+
return df_cleaned
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
import pandas as pd
|
|
161
|
+
from typing import Union, List
|
|
162
|
+
@log_function_call
|
|
163
|
+
|
|
164
|
+
def remove_columns(df: pd.DataFrame, columns: Union[str, List[str]]) -> pd.DataFrame:
|
|
165
|
+
"""
|
|
166
|
+
Remove specified columns from a DataFrame.
|
|
167
|
+
|
|
168
|
+
Parameters:
|
|
169
|
+
- df (pd.DataFrame): The input DataFrame from which columns are to be removed.
|
|
170
|
+
- columns (Union[str, List[str]]): A column name or a list of column names to be removed.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
- pd.DataFrame: A DataFrame with the specified columns removed.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
- ValueError: If any of the specified columns do not exist in the DataFrame.
|
|
177
|
+
- TypeError: If columns is not a string or a list of strings.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
# Validate input
|
|
181
|
+
if isinstance(columns, str):
|
|
182
|
+
columns = [columns] # Convert a single column name to a list
|
|
183
|
+
elif not isinstance(columns, list) or not all(isinstance(col, str) for col in columns):
|
|
184
|
+
raise TypeError("columns must be a string or a list of column names.")
|
|
185
|
+
|
|
186
|
+
# Check if columns exist in the DataFrame
|
|
187
|
+
missing_columns = [col for col in columns if col not in df.columns]
|
|
188
|
+
if missing_columns:
|
|
189
|
+
raise ValueError(f"Columns {missing_columns} not found in DataFrame.")
|
|
190
|
+
|
|
191
|
+
# Remove specified columns
|
|
192
|
+
df_reduced = df.drop(columns=columns)
|
|
193
|
+
|
|
194
|
+
return df_reduced
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
import pandas as pd
|
|
204
|
+
import re
|
|
205
|
+
from spellchecker import SpellChecker
|
|
206
|
+
@log_function_call
|
|
207
|
+
|
|
208
|
+
def clean_column_name(column_name: str) -> str:
|
|
209
|
+
"""
|
|
210
|
+
Cleans and recommends a new column name by correcting spelling and removing special characters.
|
|
211
|
+
|
|
212
|
+
Parameters:
|
|
213
|
+
- column_name (str): The original column name.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
- str: The recommended clean column name.
|
|
217
|
+
"""
|
|
218
|
+
# Initialize spell checker
|
|
219
|
+
spell = SpellChecker()
|
|
220
|
+
|
|
221
|
+
# Remove special characters and replace them with spaces
|
|
222
|
+
clean_name = re.sub(r'[^a-zA-Z0-9 ]', ' ', column_name)
|
|
223
|
+
|
|
224
|
+
# Correct spelling of words
|
|
225
|
+
words = clean_name.split()
|
|
226
|
+
corrected_words = [spell.correction(word) for word in words]
|
|
227
|
+
|
|
228
|
+
# Join words with spaces and convert to title case
|
|
229
|
+
recommended_name = ' '.join(corrected_words).title()
|
|
230
|
+
return recommended_name
|
|
231
|
+
|
|
232
|
+
def rename_columns(df: pd.DataFrame) -> dict:
|
|
233
|
+
"""
|
|
234
|
+
Automatically recommend readable column names for a DataFrame.
|
|
235
|
+
|
|
236
|
+
Parameters:
|
|
237
|
+
- df (pd.DataFrame): The DataFrame with columns to be analyzed.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
- dict: A dictionary where keys are current column names and values are recommended new names.
|
|
241
|
+
"""
|
|
242
|
+
recommendations = {}
|
|
243
|
+
|
|
244
|
+
for column in df.columns:
|
|
245
|
+
recommended_name = clean_column_name(column)
|
|
246
|
+
if recommended_name != column:
|
|
247
|
+
recommendations[column] = recommended_name
|
|
248
|
+
|
|
249
|
+
return recommendations
|
|
250
|
+
|
|
251
|
+
def apply_column_renames(df: pd.DataFrame, rename_map: dict) -> None:
|
|
252
|
+
"""
|
|
253
|
+
Apply the recommended column name changes to the DataFrame.
|
|
254
|
+
|
|
255
|
+
Parameters:
|
|
256
|
+
- df (pd.DataFrame): The DataFrame to rename columns in.
|
|
257
|
+
- rename_map (dict): The dictionary of column renaming recommendations.
|
|
258
|
+
"""
|
|
259
|
+
if rename_map:
|
|
260
|
+
df.rename(columns=rename_map, inplace=True)
|
|
261
|
+
|
|
262
|
+
def auto_rename_columns(df: pd.DataFrame) -> None:
|
|
263
|
+
"""
|
|
264
|
+
Provide an interactive prompt for users to apply recommended column renaming.
|
|
265
|
+
|
|
266
|
+
Parameters:
|
|
267
|
+
- df (pd.DataFrame): The DataFrame whose columns will be analyzed and potentially renamed.
|
|
268
|
+
"""
|
|
269
|
+
# Get column rename recommendations
|
|
270
|
+
rename_recommendations = rename_columns(df)
|
|
271
|
+
|
|
272
|
+
if not rename_recommendations:
|
|
273
|
+
print("All column names are already clean and readable!")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Print recommendations
|
|
277
|
+
print("\nRecommended Column Renames:")
|
|
278
|
+
for original, recommended in rename_recommendations.items():
|
|
279
|
+
print(f"Original: {original}, Recommended: {recommended}")
|
|
280
|
+
|
|
281
|
+
# Ask the user if they want to apply the changes
|
|
282
|
+
apply_changes = input("\nDo you want to apply these changes? (yes/no): ").strip().lower()
|
|
283
|
+
if apply_changes == 'yes':
|
|
284
|
+
apply_column_renames(df, rename_recommendations)
|
|
285
|
+
print("\nRenamed DataFrame Column Names:")
|
|
286
|
+
print(df.columns)
|
|
287
|
+
else:
|
|
288
|
+
print("\nNo changes were made.")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
import pandas as pd
|
|
296
|
+
@log_function_call
|
|
297
|
+
|
|
298
|
+
def rename_dataframe_columns(df: pd.DataFrame, rename_dict: dict) -> pd.DataFrame:
|
|
299
|
+
"""
|
|
300
|
+
Rename columns in a DataFrame using a provided dictionary mapping.
|
|
301
|
+
|
|
302
|
+
Parameters:
|
|
303
|
+
- df (pd.DataFrame): The DataFrame whose columns are to be renamed.
|
|
304
|
+
- rename_dict (dict): A dictionary mapping current column names (keys) to new column names (values).
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
- pd.DataFrame: A new DataFrame with columns renamed according to the dictionary.
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
- KeyError: If any of the specified columns in the dictionary keys do not exist in the DataFrame.
|
|
311
|
+
- ValueError: If the rename_dict is empty.
|
|
312
|
+
"""
|
|
313
|
+
if not isinstance(rename_dict, dict) or not rename_dict:
|
|
314
|
+
raise ValueError("rename_dict must be a non-empty dictionary.")
|
|
315
|
+
|
|
316
|
+
# Check for columns that are not in the DataFrame
|
|
317
|
+
missing_columns = [col for col in rename_dict if col not in df.columns]
|
|
318
|
+
if missing_columns:
|
|
319
|
+
raise KeyError(f"These columns do not exist in the DataFrame: {missing_columns}")
|
|
320
|
+
|
|
321
|
+
# Rename columns using the provided dictionary
|
|
322
|
+
renamed_df = df.rename(columns=rename_dict)
|
|
323
|
+
|
|
324
|
+
return renamed_df
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
import pandas as pd
|
|
332
|
+
import pytz
|
|
333
|
+
from typing import Union, List, Optional
|
|
334
|
+
@log_function_call
|
|
335
|
+
|
|
336
|
+
def change_dt(
|
|
337
|
+
df: pd.DataFrame,
|
|
338
|
+
columns: Union[str, List[str]],
|
|
339
|
+
date_format: str = "%Y-%m-%d",
|
|
340
|
+
time_format: str = "%H:%M:%S",
|
|
341
|
+
from_timezone: Optional[str] = None,
|
|
342
|
+
to_timezone: Optional[str] = None
|
|
343
|
+
) -> pd.DataFrame:
|
|
344
|
+
"""
|
|
345
|
+
Change the format of date and time columns in a DataFrame and handle timezone conversion.
|
|
346
|
+
|
|
347
|
+
Parameters:
|
|
348
|
+
- df (pd.DataFrame): The DataFrame containing date/time columns to reformat.
|
|
349
|
+
- columns (Union[str, List[str]]): The name(s) of the column(s) to be reformatted.
|
|
350
|
+
- date_format (str): The desired date format (default: "%Y-%m-%d").
|
|
351
|
+
- time_format (str): The desired time format (default: "%H:%M:%S").
|
|
352
|
+
- from_timezone (Optional[str]): The original timezone of the datetime column(s).
|
|
353
|
+
If None, no timezone conversion will be applied (default: None).
|
|
354
|
+
- to_timezone (Optional[str]): The desired timezone for the datetime column(s).
|
|
355
|
+
If None, no timezone conversion will be applied (default: None).
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
- pd.DataFrame: A new DataFrame with the date/time columns reformatted.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
# Ensure columns is a list
|
|
362
|
+
if isinstance(columns, str):
|
|
363
|
+
columns = [columns]
|
|
364
|
+
|
|
365
|
+
# Check if the provided columns exist in the DataFrame
|
|
366
|
+
missing_columns = [col for col in columns if col not in df.columns]
|
|
367
|
+
if missing_columns:
|
|
368
|
+
raise KeyError(f"These columns do not exist in the DataFrame: {missing_columns}")
|
|
369
|
+
|
|
370
|
+
# Clone the DataFrame to avoid modifying the original one
|
|
371
|
+
formatted_df = df.copy()
|
|
372
|
+
|
|
373
|
+
# Change the date and time format and timezone for each specified column
|
|
374
|
+
for col in columns:
|
|
375
|
+
# Convert to datetime if not already in datetime format
|
|
376
|
+
formatted_df[col] = pd.to_datetime(formatted_df[col], errors='coerce')
|
|
377
|
+
|
|
378
|
+
if from_timezone and to_timezone:
|
|
379
|
+
# Convert timezone if both from and to timezones are specified
|
|
380
|
+
formatted_df[col] = (
|
|
381
|
+
formatted_df[col]
|
|
382
|
+
.dt.tz_localize(from_timezone, ambiguous='NaT', nonexistent='NaT')
|
|
383
|
+
.dt.tz_convert(to_timezone)
|
|
384
|
+
)
|
|
385
|
+
elif from_timezone or to_timezone:
|
|
386
|
+
raise ValueError("Both from_timezone and to_timezone must be specified for timezone conversion.")
|
|
387
|
+
|
|
388
|
+
# Apply date and time format
|
|
389
|
+
formatted_df[col] = formatted_df[col].dt.strftime(f"{date_format} {time_format}")
|
|
390
|
+
|
|
391
|
+
return formatted_df
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
import pandas as pd
|
|
399
|
+
from collections import Counter
|
|
400
|
+
@log_function_call
|
|
401
|
+
|
|
402
|
+
def detect_delimiter(series: pd.Series) -> str:
|
|
403
|
+
"""
|
|
404
|
+
Detect the most common delimiter in a Series of strings.
|
|
405
|
+
|
|
406
|
+
Parameters:
|
|
407
|
+
- series (pd.Series): A Series containing strings to analyze.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
- str: The most common delimiter found.
|
|
411
|
+
"""
|
|
412
|
+
# Define a list of potential delimiters
|
|
413
|
+
potential_delimiters = [',', ';', '|', '\t', ':']
|
|
414
|
+
|
|
415
|
+
# Collect all delimiters used in the series
|
|
416
|
+
delimiters = [delimiter for text in series.dropna() for delimiter in potential_delimiters if delimiter in text]
|
|
417
|
+
|
|
418
|
+
# If no delimiters are found, return None
|
|
419
|
+
if not delimiters:
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
# Return the most common delimiter
|
|
423
|
+
most_common_delimiter, _ = Counter(delimiters).most_common(1)[0]
|
|
424
|
+
return most_common_delimiter
|
|
425
|
+
|
|
426
|
+
def split_column(df: pd.DataFrame, column_name: str, delimiter: str = None) -> pd.DataFrame:
|
|
427
|
+
"""
|
|
428
|
+
Split a single column into multiple columns based on a delimiter.
|
|
429
|
+
|
|
430
|
+
Parameters:
|
|
431
|
+
- df (pd.DataFrame): The DataFrame containing the column to split.
|
|
432
|
+
- column_name (str): The name of the column to be split.
|
|
433
|
+
- delimiter (str): The delimiter to use for splitting. If None, detect the most common delimiter.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
- pd.DataFrame: A new DataFrame with the specified column split into multiple columns.
|
|
437
|
+
"""
|
|
438
|
+
# Validate input
|
|
439
|
+
if column_name not in df.columns:
|
|
440
|
+
raise ValueError(f"Column '{column_name}' does not exist in the DataFrame.")
|
|
441
|
+
|
|
442
|
+
if delimiter is None:
|
|
443
|
+
# Detect the delimiter if not provided
|
|
444
|
+
delimiter = detect_delimiter(df[column_name])
|
|
445
|
+
if delimiter is None:
|
|
446
|
+
raise ValueError("No delimiter detected and none provided.")
|
|
447
|
+
|
|
448
|
+
# Split the column based on the delimiter
|
|
449
|
+
expanded_columns = df[column_name].str.split(delimiter, expand=True)
|
|
450
|
+
|
|
451
|
+
# Drop the original column and concatenate the new columns
|
|
452
|
+
df_expanded = df.drop(columns=[column_name]).join(expanded_columns)
|
|
453
|
+
|
|
454
|
+
# Rename new columns with a suffix to identify them
|
|
455
|
+
df_expanded.columns = [f"{column_name}_{i+1}" if i < len(expanded_columns.columns) else col
|
|
456
|
+
for i, col in enumerate(df_expanded.columns)]
|
|
457
|
+
|
|
458
|
+
return df_expanded
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
import pandas as pd
|
|
465
|
+
import numpy as np
|
|
466
|
+
@log_function_call
|
|
467
|
+
|
|
468
|
+
def impute(data, by=None, value=None, columns=None):
|
|
469
|
+
"""
|
|
470
|
+
Handle missing values in a DataFrame or Series using the specified strategy, custom value, and columns.
|
|
471
|
+
|
|
472
|
+
Parameters:
|
|
473
|
+
data (pd.DataFrame or pd.Series): The DataFrame or Series with missing values (NaN represents missing values).
|
|
474
|
+
by (str or None): The strategy to use for imputing missing values.
|
|
475
|
+
Options: 'mean', 'median', 'mode', 'interpolate', 'forward_fill', 'backward_fill'.
|
|
476
|
+
If None, a value must be provided.
|
|
477
|
+
value: A custom value to fill NaNs with. Supports various data types.
|
|
478
|
+
columns (list of str or None): The list of column names to apply the fill operation.
|
|
479
|
+
If None, applies to all columns.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
pd.DataFrame or pd.Series: A new DataFrame or Series with missing values handled.
|
|
483
|
+
"""
|
|
484
|
+
# Validate input
|
|
485
|
+
if not isinstance(data, (pd.DataFrame, pd.Series)):
|
|
486
|
+
raise TypeError("Data must be a pandas DataFrame or Series.")
|
|
487
|
+
|
|
488
|
+
if isinstance(data, pd.Series):
|
|
489
|
+
data = data.to_frame()
|
|
490
|
+
|
|
491
|
+
if columns is None:
|
|
492
|
+
# Apply to all columns if none specified
|
|
493
|
+
columns = data.columns
|
|
494
|
+
|
|
495
|
+
# Dictionary to map strategy to pandas methods
|
|
496
|
+
strategies = {
|
|
497
|
+
'mean': lambda col: col.fillna(col.mean()),
|
|
498
|
+
'median': lambda col: col.fillna(col.median()),
|
|
499
|
+
'mode': lambda col: col.fillna(col.mode().iloc[0]),
|
|
500
|
+
'interpolate': lambda col: col.interpolate(),
|
|
501
|
+
'forward_fill': lambda col: col.ffill(),
|
|
502
|
+
'backward_fill': lambda col: col.bfill()
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
# Handle missing values
|
|
506
|
+
if by is not None and by not in strategies:
|
|
507
|
+
raise ValueError("Invalid strategy. Use one of ['mean', 'median', 'mode', 'interpolate', 'forward_fill', 'backward_fill'].")
|
|
508
|
+
|
|
509
|
+
result = data.copy()
|
|
510
|
+
|
|
511
|
+
for col in columns:
|
|
512
|
+
if col not in data.columns:
|
|
513
|
+
raise ValueError(f"Column {col} is not in the DataFrame.")
|
|
514
|
+
|
|
515
|
+
if by:
|
|
516
|
+
# Apply the selected strategy
|
|
517
|
+
result[col] = strategies[by](result[col])
|
|
518
|
+
elif value is not None:
|
|
519
|
+
# Use the custom value for filling
|
|
520
|
+
result[col].fillna(value, inplace=True)
|
|
521
|
+
else:
|
|
522
|
+
raise ValueError("Either a strategy ('by') or a value must be provided.")
|
|
523
|
+
|
|
524
|
+
return result
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
import pandas as pd
|
|
531
|
+
from spellchecker import SpellChecker
|
|
532
|
+
|
|
533
|
+
# Initialize SpellCheckers for different dictionaries
|
|
534
|
+
spell_checker_dict = {
|
|
535
|
+
'en_US': SpellChecker(language='en'),
|
|
536
|
+
# 'en_GB': SpellChecker(language='en_GB'),
|
|
537
|
+
# 'en_AU': SpellChecker(language='en_AU'),
|
|
538
|
+
# 'en_IE': SpellChecker(language='en_IE')
|
|
539
|
+
}
|
|
540
|
+
@log_function_call
|
|
541
|
+
|
|
542
|
+
def spell_check_dataframe(data, dictionary='en_US', columns=None):
|
|
543
|
+
"""
|
|
544
|
+
Perform spell check on the specified columns of a DataFrame using the specified dictionary.
|
|
545
|
+
|
|
546
|
+
Parameters:
|
|
547
|
+
data (pd.DataFrame): The DataFrame containing columns to spell check.
|
|
548
|
+
dictionary (str): The dictionary to use for spell checking.
|
|
549
|
+
Options: 'en_US' (American English, default), 'en_GB' (British English),
|
|
550
|
+
'en_AU' (Australian English), 'en_IE' (Irish English).
|
|
551
|
+
columns (list of str or None): The list of column names to perform spell check on.
|
|
552
|
+
If None, performs spell check on all string columns.
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
dict: A dictionary where keys are column names and values are lists of misspelled words.
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
# Validate input
|
|
559
|
+
if not isinstance(data, pd.DataFrame):
|
|
560
|
+
raise TypeError("Data must be a pandas DataFrame.")
|
|
561
|
+
|
|
562
|
+
if columns is None:
|
|
563
|
+
# Select all object (string) columns if no specific columns are provided
|
|
564
|
+
columns = data.select_dtypes(include=['object']).columns
|
|
565
|
+
|
|
566
|
+
if dictionary not in spell_checker_dict:
|
|
567
|
+
raise ValueError("Dictionary must be one of ['en_US', 'en_GB', 'en_AU', 'en_IE'].")
|
|
568
|
+
|
|
569
|
+
# Initialize the spell checker with the specified language
|
|
570
|
+
spell_checker = spell_checker_dict[dictionary]
|
|
571
|
+
|
|
572
|
+
# Dictionary to store misspelled words per column
|
|
573
|
+
misspelled_words = {}
|
|
574
|
+
|
|
575
|
+
# Check spelling for specified columns
|
|
576
|
+
for column in columns:
|
|
577
|
+
if column not in data.columns:
|
|
578
|
+
raise ValueError(f"Column {column} is not in the DataFrame.")
|
|
579
|
+
|
|
580
|
+
# Extract text from the column
|
|
581
|
+
text_data = data[column].dropna()
|
|
582
|
+
|
|
583
|
+
# Find misspelled words
|
|
584
|
+
misspelled = []
|
|
585
|
+
for text in text_data:
|
|
586
|
+
words = text.split() # Split text into words
|
|
587
|
+
misspelled.extend([word for word in words if word.lower() not in spell_checker])
|
|
588
|
+
|
|
589
|
+
# Store misspelled words in the dictionary
|
|
590
|
+
misspelled_words[column] = list(set(misspelled)) # Remove duplicates
|
|
591
|
+
|
|
592
|
+
return misspelled_words
|
|
593
|
+
|
|
594
|
+
def detect_invalid_dates(series):
|
|
595
|
+
"""
|
|
596
|
+
Detect invalid date values in a pandas Series.
|
|
597
|
+
|
|
598
|
+
Parameters:
|
|
599
|
+
series (pd.Series): The Series to check for invalid dates.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
pd.Series: A boolean Series where True indicates invalid date values.
|
|
603
|
+
"""
|
|
604
|
+
try:
|
|
605
|
+
pd.to_datetime(series, errors='raise')
|
|
606
|
+
return pd.Series([False] * len(series), index=series.index)
|
|
607
|
+
except (ValueError, TypeError):
|
|
608
|
+
return pd.to_datetime(series, errors='coerce').isna()
|
|
609
|
+
|
|
610
|
+
def detect_data_entry_errors(data, spellcheck_dict='en_US', date_columns=None, numeric_columns=None, text_columns=None):
|
|
611
|
+
"""
|
|
612
|
+
Detect and flag data entry errors in a DataFrame, including invalid dates and misspelled words.
|
|
613
|
+
|
|
614
|
+
Parameters:
|
|
615
|
+
data (pd.DataFrame): The DataFrame to analyze.
|
|
616
|
+
spellcheck_dict (str): The dictionary to use for spell checking.
|
|
617
|
+
Options: 'en_US' (American English, default), 'en_GB' (British English),
|
|
618
|
+
'en_AU' (Australian English), 'en_IE' (Irish English).
|
|
619
|
+
date_columns (list of str or None): The list of columns to check for invalid dates.
|
|
620
|
+
numeric_columns (list of str or None): The list of columns to check for numeric format errors.
|
|
621
|
+
text_columns (list of str or None): The list of text columns to perform spell checking on.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
pd.DataFrame: A DataFrame indicating detected errors with columns: 'Column', 'Error Type', 'Value'.
|
|
625
|
+
"""
|
|
626
|
+
errors = []
|
|
627
|
+
|
|
628
|
+
# Validate input
|
|
629
|
+
if not isinstance(data, pd.DataFrame):
|
|
630
|
+
raise TypeError("Data must be a pandas DataFrame.")
|
|
631
|
+
|
|
632
|
+
# Check for invalid dates
|
|
633
|
+
if date_columns is None:
|
|
634
|
+
date_columns = data.select_dtypes(include=['object']).columns # Default to all object columns
|
|
635
|
+
|
|
636
|
+
for col in date_columns:
|
|
637
|
+
if col in data.columns:
|
|
638
|
+
invalid_dates = detect_invalid_dates(data[col])
|
|
639
|
+
for idx in invalid_dates[invalid_dates].index:
|
|
640
|
+
errors.append({'Column': col, 'Error Type': 'Invalid Date', 'Value': data.at[idx, col]})
|
|
641
|
+
|
|
642
|
+
# Check for invalid numeric formats
|
|
643
|
+
if numeric_columns is None:
|
|
644
|
+
numeric_columns = data.select_dtypes(include=['object']).columns # Default to all object columns
|
|
645
|
+
|
|
646
|
+
for col in numeric_columns:
|
|
647
|
+
if col in data.columns:
|
|
648
|
+
non_numeric = pd.to_numeric(data[col], errors='coerce').isna() & data[col].notna()
|
|
649
|
+
for idx in non_numeric[non_numeric].index:
|
|
650
|
+
errors.append({'Column': col, 'Error Type': 'Invalid Numeric', 'Value': data.at[idx, col]})
|
|
651
|
+
|
|
652
|
+
# Spell check on text columns
|
|
653
|
+
misspelled_words = spell_check_dataframe(data, dictionary=spellcheck_dict, columns=text_columns)
|
|
654
|
+
|
|
655
|
+
for col, words in misspelled_words.items():
|
|
656
|
+
for word in words:
|
|
657
|
+
indices = data[col].apply(lambda x: word in x if isinstance(x, str) else False)
|
|
658
|
+
for idx in indices[indices].index:
|
|
659
|
+
errors.append({'Column': col, 'Error Type': 'Misspelled Word', 'Value': word})
|
|
660
|
+
|
|
661
|
+
return pd.DataFrame(errors)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
import pandas as pd
|
|
668
|
+
@log_function_call
|
|
669
|
+
|
|
670
|
+
def data_type_conversions(data, column=None):
|
|
671
|
+
"""
|
|
672
|
+
Recommend and apply data type conversions for a given DataFrame or Series based on the analysis of each column's data.
|
|
673
|
+
|
|
674
|
+
Parameters:
|
|
675
|
+
data (pd.DataFrame or pd.Series): The input data to analyze.
|
|
676
|
+
column (str or None): The specific column to analyze. If None, the function analyzes all columns.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
pd.DataFrame or pd.Series: The data with applied type conversions.
|
|
680
|
+
"""
|
|
681
|
+
if not isinstance(data, (pd.DataFrame, pd.Series)):
|
|
682
|
+
raise TypeError("Data must be a pandas DataFrame or Series.")
|
|
683
|
+
|
|
684
|
+
if column:
|
|
685
|
+
if column not in data.columns:
|
|
686
|
+
raise ValueError(f"Column {column} does not exist in the DataFrame.")
|
|
687
|
+
data = data[[column]]
|
|
688
|
+
|
|
689
|
+
recommendations = {}
|
|
690
|
+
|
|
691
|
+
# Helper function to suggest data type conversions
|
|
692
|
+
def suggest_conversion(col):
|
|
693
|
+
suggestions = []
|
|
694
|
+
|
|
695
|
+
# Check if the column can be converted to numeric
|
|
696
|
+
if pd.api.types.is_object_dtype(col):
|
|
697
|
+
try:
|
|
698
|
+
pd.to_numeric(col, errors='raise')
|
|
699
|
+
if pd.api.types.is_float_dtype(col):
|
|
700
|
+
# Check if there are any decimal places
|
|
701
|
+
if col.dropna().apply(lambda x: x % 1 != 0).sum() == 0:
|
|
702
|
+
suggestions.append('Convert to integer')
|
|
703
|
+
else:
|
|
704
|
+
suggestions.append('Convert to numeric')
|
|
705
|
+
except ValueError:
|
|
706
|
+
pass
|
|
707
|
+
|
|
708
|
+
# Check if the column can be converted to category
|
|
709
|
+
if pd.api.types.is_object_dtype(col) and col.nunique() < 10:
|
|
710
|
+
suggestions.append('Convert to category')
|
|
711
|
+
|
|
712
|
+
# Check if the column can be converted to datetime
|
|
713
|
+
if pd.api.types.is_object_dtype(col):
|
|
714
|
+
try:
|
|
715
|
+
pd.to_datetime(col, errors='raise')
|
|
716
|
+
suggestions.append('Convert to datetime')
|
|
717
|
+
except ValueError:
|
|
718
|
+
pass
|
|
719
|
+
|
|
720
|
+
# Check if the column is already a boolean type
|
|
721
|
+
if pd.api.types.is_bool_dtype(col):
|
|
722
|
+
suggestions.append('Column is already boolean')
|
|
723
|
+
|
|
724
|
+
return suggestions
|
|
725
|
+
|
|
726
|
+
# Analyze each column
|
|
727
|
+
for col_name, col_data in data.items():
|
|
728
|
+
current_dtype = col_data.dtype
|
|
729
|
+
suggestions = suggest_conversion(col_data)
|
|
730
|
+
|
|
731
|
+
if suggestions:
|
|
732
|
+
recommendations[col_name] = {
|
|
733
|
+
'current_dtype': current_dtype,
|
|
734
|
+
'suggestions': suggestions
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
# Display recommendations and get user confirmation
|
|
738
|
+
for col_name, rec in recommendations.items():
|
|
739
|
+
print(f"\nColumn: {col_name}")
|
|
740
|
+
print(f"Current Data Type: {rec['current_dtype']}")
|
|
741
|
+
print(f"Recommended Conversions: {', '.join(rec['suggestions'])}")
|
|
742
|
+
|
|
743
|
+
# Ask user to confirm each conversion
|
|
744
|
+
for suggestion in rec['suggestions']:
|
|
745
|
+
user_input = input(f"Do you want to {suggestion.lower()} for column '{col_name}'? (yes/no): ")
|
|
746
|
+
if user_input.lower() == 'yes':
|
|
747
|
+
if suggestion == 'Convert to integer':
|
|
748
|
+
data[col_name] = pd.to_numeric(data[col_name], errors='coerce').fillna(0).astype(int)
|
|
749
|
+
elif suggestion == 'Convert to numeric':
|
|
750
|
+
data[col_name] = pd.to_numeric(data[col_name], errors='coerce')
|
|
751
|
+
elif suggestion == 'Convert to category':
|
|
752
|
+
data[col_name] = data[col_name].astype('category')
|
|
753
|
+
elif suggestion == 'Convert to datetime':
|
|
754
|
+
data[col_name] = pd.to_datetime(data[col_name], errors='coerce')
|
|
755
|
+
print(f"Column '{col_name}' converted to {data[col_name].dtype}.")
|
|
756
|
+
else:
|
|
757
|
+
print(f"Column '{col_name}' not converted.")
|
|
758
|
+
|
|
759
|
+
return data
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
import pandas as pd
|
|
766
|
+
import numpy as np
|
|
767
|
+
@log_function_call
|
|
768
|
+
|
|
769
|
+
def detect_outliers(data, method='iqr', threshold=1.5, columns=None, handle_missing=True):
|
|
770
|
+
"""
|
|
771
|
+
Detect outliers in a dataset using specified method and threshold.
|
|
772
|
+
|
|
773
|
+
Parameters:
|
|
774
|
+
data (Union[pd.Series, pd.DataFrame]): The input data to analyze.
|
|
775
|
+
method (str, default='iqr'): The outlier detection method to use. Options include 'z-score' and 'iqr'.
|
|
776
|
+
threshold (float, default=1.5): The threshold for outlier detection.
|
|
777
|
+
columns (List[str], optional): List of columns to apply the outlier detection on. If None, applies to all numerical columns.
|
|
778
|
+
handle_missing (bool, default=True): Whether to handle missing values by dropping them or not.
|
|
779
|
+
|
|
780
|
+
Returns:
|
|
781
|
+
pd.DataFrame: A DataFrame where outliers are marked as True and non-outliers are marked as False.
|
|
782
|
+
"""
|
|
783
|
+
|
|
784
|
+
# Validate input
|
|
785
|
+
if not isinstance(data, (pd.Series, pd.DataFrame)):
|
|
786
|
+
raise TypeError("Data must be a pandas DataFrame or Series.")
|
|
787
|
+
|
|
788
|
+
if not isinstance(threshold, (int, float)):
|
|
789
|
+
raise TypeError("Threshold must be a numeric value.")
|
|
790
|
+
|
|
791
|
+
if method not in ['z-score', 'iqr']:
|
|
792
|
+
raise ValueError("Method must be one of ['z-score', 'iqr'].")
|
|
793
|
+
|
|
794
|
+
if isinstance(data, pd.Series):
|
|
795
|
+
data = data.to_frame()
|
|
796
|
+
|
|
797
|
+
# Handle missing values
|
|
798
|
+
if handle_missing:
|
|
799
|
+
data = data.dropna()
|
|
800
|
+
|
|
801
|
+
# Select numerical columns if columns is None
|
|
802
|
+
if columns is None:
|
|
803
|
+
columns = data.select_dtypes(include=[np.number]).columns
|
|
804
|
+
|
|
805
|
+
# Initialize outliers as False for all values
|
|
806
|
+
outliers = pd.DataFrame(index=data.index, columns=columns, data=False)
|
|
807
|
+
|
|
808
|
+
if method == 'z-score':
|
|
809
|
+
for col in columns:
|
|
810
|
+
if col not in data.columns:
|
|
811
|
+
raise ValueError(f"Column {col} is not in the DataFrame.")
|
|
812
|
+
|
|
813
|
+
mean = data[col].mean()
|
|
814
|
+
std = data[col].std()
|
|
815
|
+
|
|
816
|
+
# Calculate Z-score
|
|
817
|
+
z_scores = (data[col] - mean) / std
|
|
818
|
+
# Mark outliers
|
|
819
|
+
outliers[col] = np.abs(z_scores) > threshold
|
|
820
|
+
|
|
821
|
+
elif method == 'iqr':
|
|
822
|
+
for col in columns:
|
|
823
|
+
if col not in data.columns:
|
|
824
|
+
raise ValueError(f"Column {col} is not in the DataFrame.")
|
|
825
|
+
|
|
826
|
+
Q1 = data[col].quantile(0.25)
|
|
827
|
+
Q3 = data[col].quantile(0.75)
|
|
828
|
+
IQR = Q3 - Q1
|
|
829
|
+
|
|
830
|
+
# Define outlier bounds
|
|
831
|
+
lower_bound = Q1 - threshold * IQR
|
|
832
|
+
upper_bound = Q3 + threshold * IQR
|
|
833
|
+
|
|
834
|
+
# Mark outliers
|
|
835
|
+
outliers[col] = (data[col] < lower_bound) | (data[col] > upper_bound)
|
|
836
|
+
|
|
837
|
+
return outliers if handle_missing else data.isna() | outliers
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
|