rage-tools 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.
- rage_tools/__init__.py +75 -0
- rage_tools/data_cleaning.py +78 -0
- rage_tools/eda.py +95 -0
- rage_tools/evaluation.py +75 -0
- rage_tools/preprocessing.py +161 -0
- rage_tools/utils.py +52 -0
- rage_tools-0.1.0.dist-info/METADATA +16 -0
- rage_tools-0.1.0.dist-info/RECORD +10 -0
- rage_tools-0.1.0.dist-info/WHEEL +5 -0
- rage_tools-0.1.0.dist-info/top_level.txt +1 -0
rage_tools/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from .data_cleaning import (
|
|
2
|
+
clean_column_names,
|
|
3
|
+
remove_duplicates,
|
|
4
|
+
missing_value_summary,
|
|
5
|
+
handle_missing_values,
|
|
6
|
+
)
|
|
7
|
+
from .eda import (
|
|
8
|
+
dataset_overview,
|
|
9
|
+
numeric_summary,
|
|
10
|
+
categorical_summary,
|
|
11
|
+
detect_outliers_iqr,
|
|
12
|
+
)
|
|
13
|
+
from .preprocessing import (
|
|
14
|
+
cap_outliers_iqr,
|
|
15
|
+
scale_numeric_features,
|
|
16
|
+
encode_categorical,
|
|
17
|
+
split_features_target,
|
|
18
|
+
auto_preprocess, # <-- Added here!
|
|
19
|
+
)
|
|
20
|
+
from .evaluation import (
|
|
21
|
+
evaluate_regression,
|
|
22
|
+
evaluate_classification,
|
|
23
|
+
)
|
|
24
|
+
from .utils import (
|
|
25
|
+
timer,
|
|
26
|
+
reduce_memory_usage,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"clean_column_names",
|
|
31
|
+
"remove_duplicates",
|
|
32
|
+
"missing_value_summary",
|
|
33
|
+
"handle_missing_values",
|
|
34
|
+
"dataset_overview",
|
|
35
|
+
"numeric_summary",
|
|
36
|
+
"categorical_summary",
|
|
37
|
+
"detect_outliers_iqr",
|
|
38
|
+
"cap_outliers_iqr",
|
|
39
|
+
"scale_numeric_features",
|
|
40
|
+
"encode_categorical",
|
|
41
|
+
"split_features_target",
|
|
42
|
+
"auto_preprocess", # <-- And added here!
|
|
43
|
+
"evaluate_regression",
|
|
44
|
+
"evaluate_classification",
|
|
45
|
+
"timer",
|
|
46
|
+
"reduce_memory_usage",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
from .data_cleaning import (
|
|
50
|
+
clean_column_names,
|
|
51
|
+
remove_duplicates,
|
|
52
|
+
missing_value_summary,
|
|
53
|
+
handle_missing_values,
|
|
54
|
+
)
|
|
55
|
+
from .eda import (
|
|
56
|
+
dataset_overview,
|
|
57
|
+
numeric_summary,
|
|
58
|
+
categorical_summary,
|
|
59
|
+
detect_outliers_iqr,
|
|
60
|
+
)
|
|
61
|
+
from .preprocessing import (
|
|
62
|
+
cap_outliers_iqr,
|
|
63
|
+
scale_numeric_features,
|
|
64
|
+
encode_categorical,
|
|
65
|
+
split_features_target,
|
|
66
|
+
auto_preprocess,
|
|
67
|
+
)
|
|
68
|
+
from .evaluation import (
|
|
69
|
+
evaluate_regression,
|
|
70
|
+
evaluate_classification,
|
|
71
|
+
)
|
|
72
|
+
from .utils import (
|
|
73
|
+
timer,
|
|
74
|
+
reduce_memory_usage,
|
|
75
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Missing values, duplicates, column names
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from typing import Optional, List, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def clean_column_names(df: pd.DataFrame) -> pd.DataFrame:
|
|
7
|
+
"""Standardize DataFrame column names to snake_case."""
|
|
8
|
+
df = df.copy()
|
|
9
|
+
df.columns = (
|
|
10
|
+
df.columns
|
|
11
|
+
.str.strip()
|
|
12
|
+
.str.lower()
|
|
13
|
+
.str.replace(r"\s+", "_", regex=True)
|
|
14
|
+
.str.replace(r"[^a-z0-9_]", "", regex=True)
|
|
15
|
+
)
|
|
16
|
+
return df
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def remove_duplicates(
|
|
20
|
+
df: pd.DataFrame,
|
|
21
|
+
subset: Optional[List[str]] = None,
|
|
22
|
+
keep: str = "first"
|
|
23
|
+
) -> pd.DataFrame:
|
|
24
|
+
"""Drop duplicate rows and reset the index."""
|
|
25
|
+
df = df.copy()
|
|
26
|
+
df = df.drop_duplicates(subset=subset, keep=keep).reset_index(drop=True)
|
|
27
|
+
return df
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def missing_value_summary(df: pd.DataFrame) -> pd.DataFrame:
|
|
31
|
+
"""Return a summary table of columns containing missing values."""
|
|
32
|
+
missing_count = df.isna().sum()
|
|
33
|
+
missing_pct = (missing_count / len(df)) * 100
|
|
34
|
+
|
|
35
|
+
summary = pd.DataFrame({
|
|
36
|
+
"missing_count": missing_count,
|
|
37
|
+
"missing_pct": missing_pct.round(2),
|
|
38
|
+
"dtype": df.dtypes
|
|
39
|
+
})
|
|
40
|
+
return summary[summary["missing_count"] > 0].sort_values(
|
|
41
|
+
by="missing_pct", ascending=False
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_missing_values(
|
|
46
|
+
df: pd.DataFrame,
|
|
47
|
+
numeric_strategy: str = "median",
|
|
48
|
+
categorical_strategy: str = "mode",
|
|
49
|
+
fill_value: Optional[Union[str, int, float]] = None
|
|
50
|
+
) -> pd.DataFrame:
|
|
51
|
+
"""
|
|
52
|
+
Fill missing values automatically across numeric and categorical columns.
|
|
53
|
+
numeric_strategy: 'mean', 'median', or 'zero'
|
|
54
|
+
categorical_strategy: 'mode' or 'constant'
|
|
55
|
+
"""
|
|
56
|
+
df = df.copy()
|
|
57
|
+
|
|
58
|
+
# 1. Handle Numeric Columns
|
|
59
|
+
num_cols = df.select_dtypes(include=["number"]).columns
|
|
60
|
+
for col in num_cols:
|
|
61
|
+
if df[col].isna().sum() > 0:
|
|
62
|
+
if numeric_strategy == "median":
|
|
63
|
+
df[col] = df[col].fillna(df[col].median())
|
|
64
|
+
elif numeric_strategy == "mean":
|
|
65
|
+
df[col] = df[col].fillna(df[col].mean())
|
|
66
|
+
elif numeric_strategy == "zero":
|
|
67
|
+
df[col] = df[col].fillna(0)
|
|
68
|
+
|
|
69
|
+
# 2. Handle Categorical / Object Columns
|
|
70
|
+
cat_cols = df.select_dtypes(include=["object", "category"]).columns
|
|
71
|
+
for col in cat_cols:
|
|
72
|
+
if df[col].isna().sum() > 0:
|
|
73
|
+
if categorical_strategy == "mode" and not df[col].mode().empty:
|
|
74
|
+
df[col] = df[col].fillna(df[col].mode()[0])
|
|
75
|
+
elif categorical_strategy == "constant":
|
|
76
|
+
df[col] = df[col].fillna(fill_value if fill_value is not None else "Unknown")
|
|
77
|
+
|
|
78
|
+
return df
|
rage_tools/eda.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Statistics, correlations, outliers
|
|
2
|
+
|
|
3
|
+
# Dataset overview, numeric/categorical summaries, and outlier detection
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def dataset_overview(df: pd.DataFrame) -> pd.DataFrame:
|
|
10
|
+
"""
|
|
11
|
+
Generate a comprehensive column-by-column health report of the DataFrame
|
|
12
|
+
(dtype, missing count/%, unique values, and sample value).
|
|
13
|
+
"""
|
|
14
|
+
overview = pd.DataFrame({
|
|
15
|
+
"dtype": df.dtypes,
|
|
16
|
+
"non_null_count": df.notna().sum(),
|
|
17
|
+
"missing_count": df.isna().sum(),
|
|
18
|
+
"missing_pct": ((df.isna().sum() / len(df)) * 100).round(2),
|
|
19
|
+
"unique_values": df.nunique(),
|
|
20
|
+
"sample_value": [df[col].dropna().iloc[0] if not df[col].dropna().empty else None for col in df.columns]
|
|
21
|
+
})
|
|
22
|
+
return overview
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def numeric_summary(df: pd.DataFrame) -> pd.DataFrame:
|
|
26
|
+
"""
|
|
27
|
+
Extended descriptive statistics for numeric columns, including
|
|
28
|
+
skewness, kurtosis, and Interquartile Range (IQR).
|
|
29
|
+
"""
|
|
30
|
+
num_df = df.select_dtypes(include=["number"])
|
|
31
|
+
if num_df.empty:
|
|
32
|
+
return pd.DataFrame()
|
|
33
|
+
|
|
34
|
+
stats = num_df.describe().T
|
|
35
|
+
stats["skewness"] = num_df.skew().round(3)
|
|
36
|
+
stats["kurtosis"] = num_df.kurt().round(3)
|
|
37
|
+
stats["iqr"] = (stats["75%"] - stats["25%"]).round(3)
|
|
38
|
+
return stats.round(3)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def categorical_summary(df: pd.DataFrame) -> pd.DataFrame:
|
|
42
|
+
"""
|
|
43
|
+
Summary of categorical/object columns showing unique classes,
|
|
44
|
+
most frequent value (top), and its percentage share.
|
|
45
|
+
"""
|
|
46
|
+
cat_df = df.select_dtypes(include=["object", "category"])
|
|
47
|
+
if cat_df.empty:
|
|
48
|
+
return pd.DataFrame()
|
|
49
|
+
|
|
50
|
+
records = []
|
|
51
|
+
for col in cat_df.columns:
|
|
52
|
+
non_null = cat_df[col].dropna()
|
|
53
|
+
top_val = non_null.mode().iloc[0] if not non_null.empty else None
|
|
54
|
+
top_freq = (non_null == top_val).sum() if top_val is not None else 0
|
|
55
|
+
|
|
56
|
+
records.append({
|
|
57
|
+
"column": col,
|
|
58
|
+
"unique_classes": cat_df[col].nunique(),
|
|
59
|
+
"top_value": top_val,
|
|
60
|
+
"top_freq": top_freq,
|
|
61
|
+
"top_pct": round((top_freq / len(df)) * 100, 2) if len(df) > 0 else 0.0
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return pd.DataFrame(records).set_index("column")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def detect_outliers_iqr(df: pd.DataFrame, factor: float = 1.5) -> pd.DataFrame:
|
|
68
|
+
"""
|
|
69
|
+
Detect outliers in numeric columns using the 1.5 * IQR rule.
|
|
70
|
+
Returns a summary table of outlier counts, percentages, and bounds.
|
|
71
|
+
"""
|
|
72
|
+
num_df = df.select_dtypes(include=["number"])
|
|
73
|
+
records = []
|
|
74
|
+
|
|
75
|
+
for col in num_df.columns:
|
|
76
|
+
series = num_df[col].dropna()
|
|
77
|
+
if series.empty:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
q1 = series.quantile(0.25)
|
|
81
|
+
q3 = series.quantile(0.75)
|
|
82
|
+
iqr = q3 - q1
|
|
83
|
+
lower_bound = q1 - (factor * iqr)
|
|
84
|
+
upper_bound = q3 + (factor * iqr)
|
|
85
|
+
|
|
86
|
+
outliers = series[(series < lower_bound) | (series > upper_bound)]
|
|
87
|
+
records.append({
|
|
88
|
+
"column": col,
|
|
89
|
+
"outlier_count": len(outliers),
|
|
90
|
+
"outlier_pct": round((len(outliers) / len(df)) * 100, 2),
|
|
91
|
+
"lower_bound": round(lower_bound, 2),
|
|
92
|
+
"upper_bound": round(upper_bound, 2)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return pd.DataFrame(records).set_index("column")
|
rage_tools/evaluation.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Model metrics and evaluation helpers
|
|
2
|
+
|
|
3
|
+
# Regression and Classification evaluation metrics
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Union, Optional
|
|
7
|
+
|
|
8
|
+
ArrayLike = Union[pd.Series, np.ndarray, list]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def evaluate_regression(
|
|
12
|
+
y_true: ArrayLike,
|
|
13
|
+
y_pred: ArrayLike,
|
|
14
|
+
n_features: Optional[int] = None
|
|
15
|
+
) -> pd.DataFrame:
|
|
16
|
+
"""
|
|
17
|
+
Calculate MAE, MSE, RMSE, R2, and (optionally) Adjusted R2 for regression models.
|
|
18
|
+
"""
|
|
19
|
+
y_t = np.asarray(y_true, dtype=float)
|
|
20
|
+
y_p = np.asarray(y_pred, dtype=float)
|
|
21
|
+
|
|
22
|
+
mae = np.mean(np.abs(y_t - y_p))
|
|
23
|
+
mse = np.mean((y_t - y_p) ** 2)
|
|
24
|
+
rmse = np.sqrt(mse)
|
|
25
|
+
|
|
26
|
+
ss_res = np.sum((y_t - y_p) ** 2)
|
|
27
|
+
ss_tot = np.sum((y_t - np.mean(y_t)) ** 2)
|
|
28
|
+
r2 = 1 - (ss_res / ss_tot) if ss_tot != 0 else 0.0
|
|
29
|
+
|
|
30
|
+
metrics = {
|
|
31
|
+
"MAE": round(mae, 4),
|
|
32
|
+
"MSE": round(mse, 4),
|
|
33
|
+
"RMSE": round(rmse, 4),
|
|
34
|
+
"R2": round(r2, 4),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if n_features is not None and len(y_t) > n_features + 1:
|
|
38
|
+
n = len(y_t)
|
|
39
|
+
adj_r2 = 1 - ((1 - r2) * (n - 1) / (n - n_features - 1))
|
|
40
|
+
metrics["Adjusted_R2"] = round(adj_r2, 4)
|
|
41
|
+
|
|
42
|
+
return pd.DataFrame([metrics], index=["Regression Metrics"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def evaluate_classification(y_true: ArrayLike, y_pred: ArrayLike) -> pd.DataFrame:
|
|
46
|
+
"""
|
|
47
|
+
Calculate Accuracy, Precision, Recall, F1-Score, and Confusion Matrix counts
|
|
48
|
+
for binary classification (0 and 1).
|
|
49
|
+
"""
|
|
50
|
+
y_t = np.asarray(y_true, dtype=int)
|
|
51
|
+
y_p = np.asarray(y_pred, dtype=int)
|
|
52
|
+
|
|
53
|
+
tp = int(np.sum((y_t == 1) & (y_p == 1)))
|
|
54
|
+
tn = int(np.sum((y_t == 0) & (y_p == 0)))
|
|
55
|
+
fp = int(np.sum((y_t == 0) & (y_p == 1)))
|
|
56
|
+
fn = int(np.sum((y_t == 1) & (y_p == 0)))
|
|
57
|
+
|
|
58
|
+
total = len(y_t)
|
|
59
|
+
accuracy = (tp + tn) / total if total > 0 else 0.0
|
|
60
|
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
|
|
61
|
+
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
|
|
62
|
+
f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
|
|
63
|
+
|
|
64
|
+
metrics = {
|
|
65
|
+
"Accuracy": round(accuracy, 4),
|
|
66
|
+
"Precision": round(precision, 4),
|
|
67
|
+
"Recall": round(recall, 4),
|
|
68
|
+
"F1_Score": round(f1, 4),
|
|
69
|
+
"TP": tp,
|
|
70
|
+
"TN": tn,
|
|
71
|
+
"FP": fp,
|
|
72
|
+
"FN": fn,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return pd.DataFrame([metrics], index=["Classification Metrics"])
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Outlier capping, feature scaling, categorical encoding, train-test split, and auto-pipeline
|
|
2
|
+
import re
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from rage_tools.data_cleaning import (
|
|
8
|
+
clean_column_names,
|
|
9
|
+
remove_duplicates,
|
|
10
|
+
handle_missing_values,
|
|
11
|
+
)
|
|
12
|
+
from rage_tools.utils import timer, reduce_memory_usage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def cap_outliers_iqr(
|
|
16
|
+
df: pd.DataFrame,
|
|
17
|
+
columns: Optional[List[str]] = None,
|
|
18
|
+
factor: float = 1.5
|
|
19
|
+
) -> pd.DataFrame:
|
|
20
|
+
"""
|
|
21
|
+
Cap (clip) extreme outliers in numeric columns to the lower and upper IQR bounds
|
|
22
|
+
instead of dropping rows.
|
|
23
|
+
"""
|
|
24
|
+
df = df.copy()
|
|
25
|
+
if columns is None:
|
|
26
|
+
columns = df.select_dtypes(include=["number"]).columns.tolist()
|
|
27
|
+
|
|
28
|
+
for col in columns:
|
|
29
|
+
q1 = df[col].quantile(0.25)
|
|
30
|
+
q3 = df[col].quantile(0.75)
|
|
31
|
+
iqr = q3 - q1
|
|
32
|
+
lower_bound = q1 - (factor * iqr)
|
|
33
|
+
upper_bound = q3 + (factor * iqr)
|
|
34
|
+
df[col] = df[col].clip(lower=lower_bound, upper=upper_bound)
|
|
35
|
+
|
|
36
|
+
return df
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def scale_numeric_features(
|
|
40
|
+
df: pd.DataFrame,
|
|
41
|
+
columns: Optional[List[str]] = None,
|
|
42
|
+
method: str = "standard"
|
|
43
|
+
) -> pd.DataFrame:
|
|
44
|
+
"""
|
|
45
|
+
Scale numeric features using 'standard' (Z-score) or 'minmax' (0 to 1) scaling.
|
|
46
|
+
"""
|
|
47
|
+
df = df.copy()
|
|
48
|
+
if columns is None:
|
|
49
|
+
columns = df.select_dtypes(include=["number"]).columns.tolist()
|
|
50
|
+
|
|
51
|
+
for col in columns:
|
|
52
|
+
if method == "standard":
|
|
53
|
+
std = df[col].std()
|
|
54
|
+
if std != 0 and not np.isnan(std):
|
|
55
|
+
df[col] = (df[col] - df[col].mean()) / std
|
|
56
|
+
elif method == "minmax":
|
|
57
|
+
min_val = df[col].min()
|
|
58
|
+
max_val = df[col].max()
|
|
59
|
+
if max_val != min_val:
|
|
60
|
+
df[col] = (df[col] - min_val) / (max_val - min_val)
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError("method must be 'standard' or 'minmax'")
|
|
63
|
+
|
|
64
|
+
return df
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def encode_categorical(
|
|
68
|
+
df: pd.DataFrame,
|
|
69
|
+
columns: Optional[List[str]] = None,
|
|
70
|
+
drop_first: bool = True
|
|
71
|
+
) -> pd.DataFrame:
|
|
72
|
+
"""
|
|
73
|
+
One-hot encode categorical columns and return numeric (0/1) columns.
|
|
74
|
+
"""
|
|
75
|
+
df = df.copy()
|
|
76
|
+
if columns is None:
|
|
77
|
+
columns = df.select_dtypes(include=["object", "category"]).columns.tolist()
|
|
78
|
+
|
|
79
|
+
if not columns:
|
|
80
|
+
return df
|
|
81
|
+
|
|
82
|
+
return pd.get_dummies(df, columns=columns, drop_first=drop_first, dtype=int)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def split_features_target(
|
|
86
|
+
df: pd.DataFrame,
|
|
87
|
+
target_col: str,
|
|
88
|
+
test_size: float = 0.2,
|
|
89
|
+
random_state: int = 42
|
|
90
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series, pd.Series]:
|
|
91
|
+
"""
|
|
92
|
+
Shuffle and split DataFrame into X_train, X_test, y_train, y_test.
|
|
93
|
+
"""
|
|
94
|
+
if target_col not in df.columns:
|
|
95
|
+
raise ValueError(f"Target column '{target_col}' not found in DataFrame.")
|
|
96
|
+
|
|
97
|
+
df_shuffled = df.sample(frac=1.0, random_state=random_state).reset_index(drop=True)
|
|
98
|
+
split_idx = int(len(df_shuffled) * (1 - test_size))
|
|
99
|
+
|
|
100
|
+
train_df = df_shuffled.iloc[:split_idx]
|
|
101
|
+
test_df = df_shuffled.iloc[split_idx:]
|
|
102
|
+
|
|
103
|
+
X_train = train_df.drop(columns=[target_col])
|
|
104
|
+
y_train = train_df[target_col]
|
|
105
|
+
X_test = test_df.drop(columns=[target_col])
|
|
106
|
+
y_test = test_df[target_col]
|
|
107
|
+
|
|
108
|
+
return X_train, X_test, y_train, y_test
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@timer
|
|
112
|
+
def auto_preprocess(
|
|
113
|
+
df: pd.DataFrame,
|
|
114
|
+
target_col: Optional[str] = None,
|
|
115
|
+
cap_outliers: bool = True,
|
|
116
|
+
scale_method: Optional[str] = "standard",
|
|
117
|
+
encode_cats: bool = True,
|
|
118
|
+
optimize_memory: bool = True,
|
|
119
|
+
) -> pd.DataFrame:
|
|
120
|
+
"""
|
|
121
|
+
Master 1-click pipeline:
|
|
122
|
+
1. Cleans column names
|
|
123
|
+
2. Removes duplicates
|
|
124
|
+
3. Imputes missing values
|
|
125
|
+
4. Caps IQR outliers (excluding target_col)
|
|
126
|
+
5. Scales numeric features (excluding target_col)
|
|
127
|
+
6. One-hot encodes categorical features (excluding target_col)
|
|
128
|
+
7. Optimizes memory usage
|
|
129
|
+
"""
|
|
130
|
+
df = clean_column_names(df)
|
|
131
|
+
df = remove_duplicates(df)
|
|
132
|
+
df = handle_missing_values(df)
|
|
133
|
+
|
|
134
|
+
# Clean target_col name the exact same way clean_column_names does
|
|
135
|
+
target_clean = None
|
|
136
|
+
if target_col:
|
|
137
|
+
target_clean = re.sub(r"\s+", "_", target_col.strip().lower())
|
|
138
|
+
target_clean = re.sub(r"[^a-z0-9_]", "", target_clean)
|
|
139
|
+
|
|
140
|
+
num_cols = [
|
|
141
|
+
c for c in df.select_dtypes(include=["number"]).columns
|
|
142
|
+
if c != target_clean
|
|
143
|
+
]
|
|
144
|
+
cat_cols = [
|
|
145
|
+
c for c in df.select_dtypes(include=["object", "category"]).columns
|
|
146
|
+
if c != target_clean
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
if cap_outliers and num_cols:
|
|
150
|
+
df = cap_outliers_iqr(df, columns=num_cols)
|
|
151
|
+
|
|
152
|
+
if scale_method and num_cols:
|
|
153
|
+
df = scale_numeric_features(df, columns=num_cols, method=scale_method)
|
|
154
|
+
|
|
155
|
+
if encode_cats and cat_cols:
|
|
156
|
+
df = encode_categorical(df, columns=cat_cols, drop_first=True)
|
|
157
|
+
|
|
158
|
+
if optimize_memory:
|
|
159
|
+
df = reduce_memory_usage(df, verbose=True)
|
|
160
|
+
|
|
161
|
+
return df
|
rage_tools/utils.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# General reusable functions
|
|
2
|
+
|
|
3
|
+
# Memory optimization, execution timer decorator, and reproducibility helpers
|
|
4
|
+
import time
|
|
5
|
+
import functools
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from typing import Callable, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def timer(func: Callable) -> Callable:
|
|
12
|
+
"""Decorator to measure and print the execution time of any function."""
|
|
13
|
+
@functools.wraps(func)
|
|
14
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
15
|
+
start_time = time.perf_counter()
|
|
16
|
+
result = func(*args, **kwargs)
|
|
17
|
+
elapsed = time.perf_counter() - start_time
|
|
18
|
+
print(f"[TIMER] '{func.__name__}' executed in {elapsed:.4f}s")
|
|
19
|
+
return result
|
|
20
|
+
return wrapper
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def reduce_memory_usage(df: pd.DataFrame, verbose: bool = True) -> pd.DataFrame:
|
|
24
|
+
"""
|
|
25
|
+
Downcast numeric columns (int64/float64) to the smallest safe subtype
|
|
26
|
+
to save RAM on large datasets.
|
|
27
|
+
"""
|
|
28
|
+
df = df.copy()
|
|
29
|
+
start_mem = df.memory_usage().sum() / (1024 ** 2)
|
|
30
|
+
|
|
31
|
+
for col in df.select_dtypes(include=["int", "float"]).columns:
|
|
32
|
+
col_type = df[col].dtype
|
|
33
|
+
c_min = df[col].min()
|
|
34
|
+
c_max = df[col].max()
|
|
35
|
+
|
|
36
|
+
if str(col_type).startswith("int"):
|
|
37
|
+
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
|
|
38
|
+
df[col] = df[col].astype(np.int8)
|
|
39
|
+
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
|
|
40
|
+
df[col] = df[col].astype(np.int16)
|
|
41
|
+
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
|
|
42
|
+
df[col] = df[col].astype(np.int32)
|
|
43
|
+
else:
|
|
44
|
+
if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
|
|
45
|
+
df[col] = df[col].astype(np.float32)
|
|
46
|
+
|
|
47
|
+
end_mem = df.memory_usage().sum() / (1024 ** 2)
|
|
48
|
+
if verbose:
|
|
49
|
+
reduction = 100 * (start_mem - end_mem) / start_mem if start_mem > 0 else 0
|
|
50
|
+
print(f"Memory reduced from {start_mem:.4f} MB to {end_mem:.4f} MB ({reduction:.1f}% saved)")
|
|
51
|
+
|
|
52
|
+
return df
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rage-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RAGE Data Science & ML Toolkit for fast cleaning, EDA, preprocessing, and evaluation.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pandas>=2.0.0
|
|
8
|
+
Requires-Dist: numpy>=1.24.0
|
|
9
|
+
|
|
10
|
+
# 🛠️ ds_toolkit
|
|
11
|
+
|
|
12
|
+
A fast, modular Python library for automating daily Data Science & Machine Learning workflows.
|
|
13
|
+
|
|
14
|
+
## 🚀 Installation (Editable Mode)
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e .
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
rage_tools/__init__.py,sha256=InEHcLusWCNUQs6hl569ppWimRSLopf0EHe9MVWlL8Q,1651
|
|
2
|
+
rage_tools/data_cleaning.py,sha256=MV-PVXOuVToTsyeeCds6FduCsXTyx_udJKocreYJxeU,2635
|
|
3
|
+
rage_tools/eda.py,sha256=Tf0QUAVuXNl4MOwu_mAKYV4ow6-wwUb6TpFM5GfeQ6A,3283
|
|
4
|
+
rage_tools/evaluation.py,sha256=FbxLDRHprVyS-dRmagMKQjlPPrpUxR1PHg3ZyqNGWM4,2336
|
|
5
|
+
rage_tools/preprocessing.py,sha256=BO_n95HrmjE4jcLlYdAoc8P082aQDDacagzELS7IlY0,4927
|
|
6
|
+
rage_tools/utils.py,sha256=Bu0PcMi9FBLk1mGYXDkaBjwUeXhFXbNqhsTTDlGU0kg,1998
|
|
7
|
+
rage_tools-0.1.0.dist-info/METADATA,sha256=F8FTqPC_dmzibwCw0cNaXWYI8PJLejF2sv6ihVQEDq8,467
|
|
8
|
+
rage_tools-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
rage_tools-0.1.0.dist-info/top_level.txt,sha256=b3xFrOfZkbaIf2S247-KuNfQlxPcY-KCqlE0Phjr_wY,11
|
|
10
|
+
rage_tools-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rage_tools
|