gitlabds 2.1.5__tar.gz → 2.1.7__tar.gz
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.
- {gitlabds-2.1.5 → gitlabds-2.1.7}/PKG-INFO +1 -1
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/dummy.py +97 -20
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/model_evaluator.py +65 -23
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds.egg-info/PKG-INFO +1 -1
- {gitlabds-2.1.5 → gitlabds-2.1.7}/LICENSE +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/README.md +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/__init__.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/baselines.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/config_generator.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/feature_reduction.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/insights.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/memory_optimization.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/missing.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/missing_check.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/missing_fill.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/monitoring_metrics.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/outliers.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/split_data.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds/trends.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds.egg-info/SOURCES.txt +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds.egg-info/dependency_links.txt +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds.egg-info/requires.txt +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/gitlabds.egg-info/top_level.txt +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/setup.cfg +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/setup.py +0 -0
- {gitlabds-2.1.5 → gitlabds-2.1.7}/tests/__init__.py +0 -0
|
@@ -50,6 +50,10 @@ def dummy_code(
|
|
|
50
50
|
- The transformed DataFrame with dummy-coded columns
|
|
51
51
|
- Dictionary of dummy coding information that can be used with apply_dummy()
|
|
52
52
|
"""
|
|
53
|
+
import pandas as pd
|
|
54
|
+
import warnings
|
|
55
|
+
from typing import Dict, List, Any, Union, Optional, Tuple
|
|
56
|
+
|
|
53
57
|
# Parameter validation
|
|
54
58
|
if not isinstance(df, pd.DataFrame):
|
|
55
59
|
raise TypeError("df must be a pandas DataFrame")
|
|
@@ -103,7 +107,6 @@ def dummy_code(
|
|
|
103
107
|
# Determine number of levels for each field
|
|
104
108
|
cat_levels = df[var_list].select_dtypes(include="object").nunique(dropna=True, axis=0)
|
|
105
109
|
|
|
106
|
-
|
|
107
110
|
# Filter columns by level count criteria
|
|
108
111
|
eligible_cat_cols = cat_levels[(cat_levels <= categorical_max_levels) & (cat_levels > 1)]
|
|
109
112
|
|
|
@@ -156,7 +159,17 @@ def dummy_code(
|
|
|
156
159
|
for col in num_columns:
|
|
157
160
|
# Store unique values for later use
|
|
158
161
|
values = sorted(df[col].dropna().unique().tolist())
|
|
159
|
-
|
|
162
|
+
|
|
163
|
+
# create column names with float format for numeric data
|
|
164
|
+
formatted_values = []
|
|
165
|
+
for v in values:
|
|
166
|
+
if isinstance(v, (int, float)):
|
|
167
|
+
# Store as float to match get_dummies column naming
|
|
168
|
+
formatted_values.append(float(v))
|
|
169
|
+
else:
|
|
170
|
+
formatted_values.append(v)
|
|
171
|
+
|
|
172
|
+
dummy_info[col] = formatted_values
|
|
160
173
|
|
|
161
174
|
# Perform dummy coding if we have columns to encode
|
|
162
175
|
if num_columns:
|
|
@@ -222,6 +235,10 @@ def dummy_top(
|
|
|
222
235
|
- The transformed DataFrame with dummy-coded columns
|
|
223
236
|
- Dictionary of dummy coding information that can be used with apply_dummy()
|
|
224
237
|
"""
|
|
238
|
+
import pandas as pd
|
|
239
|
+
import warnings
|
|
240
|
+
from typing import Dict, List, Any, Union, Optional, Tuple
|
|
241
|
+
|
|
225
242
|
# Parameter validation
|
|
226
243
|
if not isinstance(df, pd.DataFrame):
|
|
227
244
|
raise TypeError("df must be a pandas DataFrame")
|
|
@@ -279,6 +296,10 @@ def dummy_top(
|
|
|
279
296
|
else:
|
|
280
297
|
print("No categorical columns found")
|
|
281
298
|
|
|
299
|
+
# Collect all dummy columns to create at once
|
|
300
|
+
all_dummy_cols = {}
|
|
301
|
+
columns_to_drop = []
|
|
302
|
+
|
|
282
303
|
# Create dummy codes for categorical fields that exceed min_threshold
|
|
283
304
|
for col in cat_columns:
|
|
284
305
|
# Calculate value frequencies
|
|
@@ -300,18 +321,32 @@ def dummy_top(
|
|
|
300
321
|
print(f"No levels exceed threshold for column '{col}'")
|
|
301
322
|
continue
|
|
302
323
|
|
|
303
|
-
#
|
|
304
|
-
|
|
324
|
+
# For numeric columns, ensure float format to match what get_dummies creates
|
|
325
|
+
if pd.api.types.is_numeric_dtype(df[col]):
|
|
326
|
+
formatted_values = [float(v) if isinstance(v, (int, float)) else v for v in high_freq_list]
|
|
327
|
+
else:
|
|
328
|
+
formatted_values = high_freq_list
|
|
329
|
+
|
|
330
|
+
dummy_info[col] = formatted_values
|
|
305
331
|
|
|
306
332
|
# Create dummies for values that exceed threshold
|
|
307
333
|
for value in high_freq_list:
|
|
308
334
|
dummy_name = f"{col}{prefix_sep}{value}"
|
|
309
335
|
# Create dummy column (1 if equal to value, 0 otherwise)
|
|
310
|
-
|
|
336
|
+
all_dummy_cols[dummy_name] = (work_df[col] == value).astype(int)
|
|
311
337
|
|
|
312
|
-
#
|
|
338
|
+
# Mark for dropping if requested
|
|
313
339
|
if drop_categorical:
|
|
314
|
-
|
|
340
|
+
columns_to_drop.append(col)
|
|
341
|
+
|
|
342
|
+
# Create all dummy columns at once using concat (prevents fragmentation)
|
|
343
|
+
if all_dummy_cols:
|
|
344
|
+
dummy_df = pd.DataFrame(all_dummy_cols, index=work_df.index)
|
|
345
|
+
work_df = pd.concat([work_df, dummy_df], axis=1)
|
|
346
|
+
|
|
347
|
+
# Drop categorical fields if requested
|
|
348
|
+
if columns_to_drop:
|
|
349
|
+
work_df = work_df.drop(columns=columns_to_drop)
|
|
315
350
|
|
|
316
351
|
# Store the prefix_sep in the dummy_info to use with apply_dummy
|
|
317
352
|
dummy_info["_config"] = {"prefix_sep": prefix_sep}
|
|
@@ -319,11 +354,12 @@ def dummy_top(
|
|
|
319
354
|
return work_df, dummy_info
|
|
320
355
|
|
|
321
356
|
|
|
357
|
+
|
|
322
358
|
def apply_dummy(
|
|
323
359
|
df: pd.DataFrame,
|
|
324
360
|
dummy_info: Dict[str, List[Any]],
|
|
325
361
|
drop_original: bool = False,
|
|
326
|
-
|
|
362
|
+
dummy_na: bool = False,
|
|
327
363
|
) -> pd.DataFrame:
|
|
328
364
|
"""
|
|
329
365
|
Apply dummy coding to a new dataframe using the information from a previous
|
|
@@ -338,12 +374,18 @@ def apply_dummy(
|
|
|
338
374
|
dummy_code() or dummy_top().
|
|
339
375
|
drop_original : bool, default=False
|
|
340
376
|
Whether to drop the original columns after dummy coding.
|
|
377
|
+
dummy_na : bool, default=False
|
|
378
|
+
Whether to create dummy variables for NA values.
|
|
341
379
|
|
|
342
380
|
Returns
|
|
343
381
|
-------
|
|
344
382
|
pd.DataFrame
|
|
345
383
|
Transformed dataframe with dummy-coded columns.
|
|
346
384
|
"""
|
|
385
|
+
import pandas as pd
|
|
386
|
+
import warnings
|
|
387
|
+
from typing import Dict, List, Any
|
|
388
|
+
|
|
347
389
|
# Validate input
|
|
348
390
|
if not isinstance(df, pd.DataFrame):
|
|
349
391
|
raise TypeError("df must be a pandas DataFrame")
|
|
@@ -353,43 +395,78 @@ def apply_dummy(
|
|
|
353
395
|
|
|
354
396
|
if df.empty:
|
|
355
397
|
raise ValueError("DataFrame is empty")
|
|
356
|
-
|
|
398
|
+
|
|
357
399
|
# Get the prefix_sep from dummy_info or use default
|
|
358
400
|
prefix_sep = "_dummy_"
|
|
401
|
+
processing_dummy_info = dummy_info.copy()
|
|
402
|
+
|
|
359
403
|
if "_config" in dummy_info and "prefix_sep" in dummy_info["_config"]:
|
|
360
404
|
prefix_sep = dummy_info["_config"]["prefix_sep"]
|
|
361
405
|
# Remove _config from processing
|
|
362
|
-
|
|
406
|
+
processing_dummy_info = {k: v for k, v in dummy_info.items() if k != "_config"}
|
|
363
407
|
|
|
364
408
|
# Create a copy to avoid modifying the original
|
|
365
409
|
work_df = df.copy(deep=True)
|
|
366
410
|
|
|
411
|
+
# Collect all new dummy columns in a dictionary
|
|
412
|
+
new_dummy_cols = {}
|
|
413
|
+
columns_to_drop = []
|
|
414
|
+
|
|
367
415
|
# Process each column in the dummy_info dictionary
|
|
368
|
-
for col, values in
|
|
416
|
+
for col, values in processing_dummy_info.items():
|
|
369
417
|
if col not in work_df.columns:
|
|
370
418
|
warnings.warn(f"Column '{col}' not found in dataframe and will be skipped")
|
|
371
419
|
continue
|
|
372
|
-
|
|
420
|
+
|
|
373
421
|
# Create dummies for each value
|
|
374
422
|
for value in values:
|
|
375
423
|
dummy_name = f"{col}{prefix_sep}{value}"
|
|
376
424
|
|
|
377
|
-
# Handle
|
|
378
|
-
|
|
379
|
-
|
|
425
|
+
# Handle type mismatches between config and actual data
|
|
426
|
+
# Try both the original value and converted versions
|
|
427
|
+
if pd.api.types.is_numeric_dtype(work_df[col]):
|
|
428
|
+
# For numeric columns, try both int and float versions
|
|
429
|
+
try:
|
|
430
|
+
# First try exact match
|
|
431
|
+
mask = (work_df[col] == value)
|
|
432
|
+
|
|
433
|
+
# If no matches and value types differ, try conversion
|
|
434
|
+
if not mask.any():
|
|
435
|
+
if isinstance(value, float) and work_df[col].dtype in ['int64', 'int32', 'int8']:
|
|
436
|
+
# Config expects float (1.0) but data is int (1)
|
|
437
|
+
mask = (work_df[col] == int(value))
|
|
438
|
+
elif isinstance(value, int) and work_df[col].dtype in ['float64', 'float32']:
|
|
439
|
+
# Config expects int (1) but data is float (1.0)
|
|
440
|
+
mask = (work_df[col] == float(value))
|
|
441
|
+
|
|
442
|
+
new_dummy_cols[dummy_name] = mask.astype(int)
|
|
443
|
+
|
|
444
|
+
except (ValueError, TypeError):
|
|
445
|
+
# Fallback to string comparison if numeric conversion fails
|
|
446
|
+
new_dummy_cols[dummy_name] = (work_df[col].astype(str) == str(value)).astype(int)
|
|
380
447
|
else:
|
|
381
|
-
|
|
448
|
+
# For non-numeric columns, use direct comparison
|
|
449
|
+
new_dummy_cols[dummy_name] = (work_df[col] == value).astype(int)
|
|
382
450
|
|
|
383
451
|
# Create dummy for NA values if requested
|
|
384
452
|
if dummy_na:
|
|
385
|
-
dummy_name = f"{col}
|
|
386
|
-
|
|
453
|
+
dummy_name = f"{col}{prefix_sep}nan"
|
|
454
|
+
new_dummy_cols[dummy_name] = work_df[col].isna().astype(int)
|
|
387
455
|
|
|
388
|
-
#
|
|
456
|
+
# Mark column for dropping if requested
|
|
389
457
|
if drop_original:
|
|
390
458
|
# For categorical columns (from dummy_code) or any column (from dummy_top)
|
|
391
459
|
# if its object type or explicitly requested
|
|
392
460
|
if pd.api.types.is_object_dtype(work_df[col]) or drop_original:
|
|
393
|
-
|
|
461
|
+
columns_to_drop.append(col)
|
|
462
|
+
|
|
463
|
+
# Create all dummy columns at once using concat
|
|
464
|
+
if new_dummy_cols:
|
|
465
|
+
dummy_df = pd.DataFrame(new_dummy_cols, index=work_df.index)
|
|
466
|
+
work_df = pd.concat([work_df, dummy_df], axis=1)
|
|
467
|
+
|
|
468
|
+
# Drop original columns if requested
|
|
469
|
+
if columns_to_drop:
|
|
470
|
+
work_df = work_df.drop(columns=columns_to_drop)
|
|
394
471
|
|
|
395
472
|
return work_df
|
|
@@ -965,7 +965,7 @@ class ModelEvaluator:
|
|
|
965
965
|
# Calculate metrics efficiently
|
|
966
966
|
class_lift[f"class_{class_label}_total"] = grouped.size()
|
|
967
967
|
class_lift[f"class_{class_label}_count"] = grouped.apply(
|
|
968
|
-
lambda x: sum(x["actual"] == class_label)
|
|
968
|
+
lambda x: sum(x["actual"] == class_label), include_groups=False
|
|
969
969
|
)
|
|
970
970
|
class_lift[f"class_{class_label}_accuracy"] = (
|
|
971
971
|
class_lift[f"class_{class_label}_count"] /
|
|
@@ -1938,7 +1938,6 @@ class ModelEvaluator:
|
|
|
1938
1938
|
ax.set_yticks([])
|
|
1939
1939
|
ax.set_title(f"Class {class_idx}")
|
|
1940
1940
|
|
|
1941
|
-
|
|
1942
1941
|
def plot_shap_beeswarm(
|
|
1943
1942
|
self,
|
|
1944
1943
|
n_features: int = 20,
|
|
@@ -1954,6 +1953,13 @@ class ModelEvaluator:
|
|
|
1954
1953
|
) -> None:
|
|
1955
1954
|
"""
|
|
1956
1955
|
Create a SHAP beeswarm plot for feature importance.
|
|
1956
|
+
|
|
1957
|
+
Parameters
|
|
1958
|
+
----------
|
|
1959
|
+
class_index : int, optional
|
|
1960
|
+
For multi-class classification, specify which class to plot.
|
|
1961
|
+
If None, plots global importance (averaged across classes).
|
|
1962
|
+
Only applicable for multi-class models.
|
|
1957
1963
|
"""
|
|
1958
1964
|
# Set defaults if not provided
|
|
1959
1965
|
save_plot = self.save_plots if save_plot is None else save_plot
|
|
@@ -1986,33 +1992,62 @@ class ModelEvaluator:
|
|
|
1986
1992
|
# Get SHAP values
|
|
1987
1993
|
shap_values = explainer(X)
|
|
1988
1994
|
|
|
1989
|
-
# For multi-class classification
|
|
1995
|
+
# For multi-class classification
|
|
1990
1996
|
if self.classification and self.n_classes > 2:
|
|
1991
|
-
# For multi-class, we need to create a 2D representation for beeswarm
|
|
1992
1997
|
if hasattr(shap_values, 'values') and len(shap_values.values.shape) == 3:
|
|
1993
|
-
#
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
1998
|
+
# Validate class_index if provided
|
|
1999
|
+
if class_index is not None:
|
|
2000
|
+
if not isinstance(class_index, int) or class_index < 0 or class_index >= self.n_classes:
|
|
2001
|
+
logger.warning(f"Invalid class_index {class_index}. Must be integer between 0 and {self.n_classes-1}. Using global view.")
|
|
2002
|
+
class_index = None
|
|
2003
|
+
|
|
2004
|
+
if class_index is not None:
|
|
2005
|
+
# Plot for specific class
|
|
2006
|
+
class_values = shap_values.values[:, :, class_index]
|
|
2007
|
+
class_exp = shap.Explanation(
|
|
2008
|
+
values=class_values,
|
|
2009
|
+
data=X.values,
|
|
2010
|
+
feature_names=X.columns.tolist()
|
|
2011
|
+
)
|
|
2012
|
+
|
|
2013
|
+
# Create a figure first
|
|
2014
|
+
plt.figure(figsize=(12, 8))
|
|
2015
|
+
|
|
2016
|
+
# Use the class-specific explanation for plotting
|
|
2017
|
+
if plot_type == "beeswarm":
|
|
2018
|
+
shap.plots.beeswarm(class_exp, max_display=n_features, show=False)
|
|
2019
|
+
else:
|
|
2020
|
+
shap.plots.bar(class_exp, max_display=n_features, show=False)
|
|
2021
|
+
|
|
2022
|
+
plt.title(f"SHAP Values for Class {class_index} ({dataset.title()} Data)")
|
|
2023
|
+
|
|
2007
2024
|
else:
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2025
|
+
# Create global explanation by taking the mean across the class dimension
|
|
2026
|
+
global_values = shap_values.values.mean(axis=2)
|
|
2027
|
+
global_exp = shap.Explanation(
|
|
2028
|
+
values=global_values,
|
|
2029
|
+
data=X.values,
|
|
2030
|
+
feature_names=X.columns.tolist()
|
|
2031
|
+
)
|
|
2032
|
+
|
|
2033
|
+
# Create a figure first
|
|
2034
|
+
plt.figure(figsize=(12, 8))
|
|
2035
|
+
|
|
2036
|
+
# Use the global explanation for plotting
|
|
2037
|
+
if plot_type == "beeswarm":
|
|
2038
|
+
shap.plots.beeswarm(global_exp, max_display=n_features, show=False)
|
|
2039
|
+
else:
|
|
2040
|
+
shap.plots.bar(global_exp, max_display=n_features, show=False)
|
|
2041
|
+
|
|
2042
|
+
plt.title(f"SHAP Values - Global View ({dataset.title()} Data)")
|
|
2011
2043
|
|
|
2012
|
-
# Handle saving
|
|
2044
|
+
# Handle saving and showing
|
|
2013
2045
|
if save_plot:
|
|
2014
2046
|
os.makedirs(plot_dir, exist_ok=True)
|
|
2015
|
-
|
|
2047
|
+
if class_index is not None:
|
|
2048
|
+
save_path = os.path.join(plot_dir, f"{plot_name}_class_{class_index}.{plot_save_format}")
|
|
2049
|
+
else:
|
|
2050
|
+
save_path = os.path.join(plot_dir, f"{plot_name}.{plot_save_format}")
|
|
2016
2051
|
plt.savefig(save_path, format=plot_save_format, dpi=plot_save_dpi, bbox_inches="tight")
|
|
2017
2052
|
|
|
2018
2053
|
# Show or close
|
|
@@ -2024,10 +2059,15 @@ class ModelEvaluator:
|
|
|
2024
2059
|
return
|
|
2025
2060
|
else:
|
|
2026
2061
|
# If the SHAP values don't have the expected format
|
|
2062
|
+
if class_index is not None:
|
|
2063
|
+
logger.warning("Multi-class SHAP values don't have expected format for class-specific plotting. Using global view.")
|
|
2027
2064
|
logger.warning("Multi-class SHAP values don't have expected format. Unable to create beeswarm plot.")
|
|
2028
2065
|
return
|
|
2029
2066
|
|
|
2030
2067
|
# Binary classification or regression - use the original behavior
|
|
2068
|
+
if class_index is not None:
|
|
2069
|
+
logger.warning(f"class_index parameter ignored for {('binary classification' if self.classification else 'regression')} model.")
|
|
2070
|
+
|
|
2031
2071
|
# Create a figure first
|
|
2032
2072
|
plt.figure(figsize=(12, 8))
|
|
2033
2073
|
|
|
@@ -2056,6 +2096,8 @@ class ModelEvaluator:
|
|
|
2056
2096
|
print(f"Error: {str(e)}")
|
|
2057
2097
|
|
|
2058
2098
|
|
|
2099
|
+
|
|
2100
|
+
|
|
2059
2101
|
def plot_score_distribution(
|
|
2060
2102
|
self,
|
|
2061
2103
|
bins=None,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|