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