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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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
+