one-stop-analytics 1.13.11__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.
Files changed (38) hide show
  1. one_stop_analytics/__init__.py +49 -0
  2. one_stop_analytics/advanced_ml.py +139 -0
  3. one_stop_analytics/advanced_statistics.py +114 -0
  4. one_stop_analytics/analytics.py +564 -0
  5. one_stop_analytics/batch.py +183 -0
  6. one_stop_analytics/business_analytics.py +94 -0
  7. one_stop_analytics/cache.py +262 -0
  8. one_stop_analytics/cleaning.py +223 -0
  9. one_stop_analytics/cli.py +218 -0
  10. one_stop_analytics/connectors.py +66 -0
  11. one_stop_analytics/credit_note_reconciliation.py +139 -0
  12. one_stop_analytics/eda.py +554 -0
  13. one_stop_analytics/excel.py +113 -0
  14. one_stop_analytics/exceptions.py +12 -0
  15. one_stop_analytics/io.py +341 -0
  16. one_stop_analytics/logging_utils.py +21 -0
  17. one_stop_analytics/metrics.py +86 -0
  18. one_stop_analytics/ml.py +192 -0
  19. one_stop_analytics/platform.py +401 -0
  20. one_stop_analytics/profiling.py +46 -0
  21. one_stop_analytics/py.typed +0 -0
  22. one_stop_analytics/quality_audit.py +82 -0
  23. one_stop_analytics/report.py +1070 -0
  24. one_stop_analytics/retail_accounting.py +32 -0
  25. one_stop_analytics/statistics.py +49 -0
  26. one_stop_analytics/streaming_audit.py +243 -0
  27. one_stop_analytics/time_series.py +236 -0
  28. one_stop_analytics/transform.py +41 -0
  29. one_stop_analytics/validation.py +147 -0
  30. one_stop_analytics/visualization.py +157 -0
  31. one_stop_analytics/workspace.py +413 -0
  32. one_stop_analytics-1.13.11.dist-info/METADATA +500 -0
  33. one_stop_analytics-1.13.11.dist-info/RECORD +38 -0
  34. one_stop_analytics-1.13.11.dist-info/WHEEL +5 -0
  35. one_stop_analytics-1.13.11.dist-info/entry_points.txt +2 -0
  36. one_stop_analytics-1.13.11.dist-info/licenses/COPYRIGHT +7 -0
  37. one_stop_analytics-1.13.11.dist-info/licenses/LICENSE +661 -0
  38. one_stop_analytics-1.13.11.dist-info/top_level.txt +1 -0
@@ -0,0 +1,49 @@
1
+ """One Stop Analytics - lazy, cross-platform data analytics toolkit."""
2
+
3
+ __version__ = "1.13.11"
4
+ __all__ = ["OSAError", "ConfigurationError", "ResourceLimitError", "BatchError","Analytics", "DataCleaner", "Metrics", "Profiler", "EDAEngine", "batch_analyze", "clear_cache", "Transformer", "time_series", "forecast", "auto_ml", "bootstrap_ci", "rfm", "abc_analysis", "cohort", "retention", "funnel", "clv", "pareto", "Workspace", "open_workspace", "CanonicalReport", "LargeDataEngine", "DatabaseConnector", "build_canonical_report", "export_report_bundle", "export_excel_report", "create_interactive_dashboard", "reconcile_credit_note_lines"]
5
+
6
+ _LAZY = {
7
+ "OSAError": (".exceptions", "OSAError"),
8
+ "ConfigurationError": (".exceptions", "ConfigurationError"),
9
+ "ResourceLimitError": (".exceptions", "ResourceLimitError"),
10
+ "BatchError": (".exceptions", "BatchError"),
11
+ "Analytics": (".analytics", "Analytics"),
12
+ "Workspace": (".workspace", "Workspace"),
13
+ "open_workspace": (".workspace", "open_workspace"),
14
+ "CanonicalReport": (".platform", "CanonicalReport"),
15
+ "LargeDataEngine": (".platform", "LargeDataEngine"),
16
+ "DatabaseConnector": (".platform", "DatabaseConnector"),
17
+ "build_canonical_report": (".platform", "build_canonical_report"),
18
+ "export_report_bundle": (".platform", "export_report_bundle"),
19
+ "export_excel_report": (".platform", "export_excel_report"),
20
+ "create_interactive_dashboard": (".platform", "create_interactive_dashboard"),
21
+ "reconcile_credit_note_lines": (".credit_note_reconciliation", "reconcile_credit_note_lines"),
22
+ "DataCleaner": (".cleaning", "DataCleaner"),
23
+ "Metrics": (".metrics", "Metrics"),
24
+ "Profiler": (".profiling", "Profiler"),
25
+ "EDAEngine": (".eda", "EDAEngine"),
26
+ "batch_analyze": (".batch", "batch_analyze"),
27
+ "clear_cache": (".cache", "clear_cache"),
28
+ "Transformer": (".transform", "Transformer"),
29
+ "time_series": (".time_series", "analyze_timeseries"),
30
+ "forecast": (".time_series", "analyze_timeseries"),
31
+ "auto_ml": (".advanced_ml", "auto_ml"),
32
+ "bootstrap_ci": (".advanced_statistics", "bootstrap_ci"),
33
+ "rfm": (".business_analytics", "rfm"),
34
+ "abc_analysis": (".business_analytics", "abc_analysis"),
35
+ "cohort": (".business_analytics", "cohort"),
36
+ "retention": (".business_analytics", "retention"),
37
+ "funnel": (".business_analytics", "funnel"),
38
+ "clv": (".business_analytics", "clv"),
39
+ "pareto": (".business_analytics", "pareto"),
40
+ }
41
+
42
+ def __getattr__(name):
43
+ if name in _LAZY:
44
+ module_name, attr_name = _LAZY[name]
45
+ from importlib import import_module
46
+ value = getattr(import_module(module_name, __name__), attr_name)
47
+ globals()[name] = value
48
+ return value
49
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,139 @@
1
+
2
+ """Advanced ML and lightweight AutoML without heavyweight external ML frameworks."""
3
+ from __future__ import annotations
4
+
5
+ def _clean_xy(X, y, max_rows=30000, random_state=42):
6
+ import pandas as pd
7
+ from .ml import _normalize_frame
8
+ X = _normalize_frame(X).reset_index(drop=True)
9
+ y = pd.Series(y).reset_index(drop=True)
10
+ if len(X) != len(y):
11
+ raise ValueError(f"X and y must have the same number of rows (got {len(X)} and {len(y)}).")
12
+ # Remove unlabeled rows before CV or fitting; pd.NA cannot be a target.
13
+ valid = y.notna()
14
+ X, y = X.loc[valid].reset_index(drop=True), y.loc[valid].reset_index(drop=True)
15
+ if pd.api.types.is_numeric_dtype(y.dtype):
16
+ y = y.astype("int64") if pd.api.types.is_integer_dtype(y.dtype) else y.astype("float64")
17
+ else:
18
+ y = y.astype(object)
19
+ # Make datetime predictors ML-safe while retaining temporal signal.
20
+ for c in list(X.columns):
21
+ if pd.api.types.is_datetime64_any_dtype(X[c]):
22
+ dt = pd.to_datetime(X[c], errors="coerce")
23
+ X[c] = (dt.astype("int64") / 86400000000000.0).replace({-9.223372036854776e+18: float("nan")})
24
+ valid = y.notna()
25
+ X, y = X.loc[valid].reset_index(drop=True), y.loc[valid].reset_index(drop=True)
26
+ if max_rows and len(X) > max_rows:
27
+ from sklearn.model_selection import train_test_split
28
+ try:
29
+ X, _, y, _ = train_test_split(X, y, train_size=max_rows, random_state=random_state,
30
+ stratify=y if y.nunique() <= 50 else None)
31
+ except Exception:
32
+ idx = X.sample(n=max_rows, random_state=random_state).index
33
+ X, y = X.loc[idx], y.loc[idx]
34
+ X, y = X.reset_index(drop=True), y.reset_index(drop=True)
35
+ return X, y
36
+
37
+ def _prep(X, scale=False, max_categories=50):
38
+ import numpy as np
39
+ from sklearn.compose import ColumnTransformer
40
+ from sklearn.impute import SimpleImputer
41
+ from sklearn.pipeline import Pipeline
42
+ from sklearn.preprocessing import OneHotEncoder, StandardScaler
43
+ num = X.select_dtypes(include=np.number).columns.tolist()
44
+ cat = X.select_dtypes(exclude=np.number).columns.tolist()
45
+ ns = [("imputer", SimpleImputer(strategy="median"))]
46
+ if scale: ns.append(("scaler", StandardScaler()))
47
+ cs = [("imputer", SimpleImputer(strategy="most_frequent")),
48
+ ("onehot", OneHotEncoder(handle_unknown="ignore", min_frequency=2,
49
+ max_categories=max_categories))]
50
+ return ColumnTransformer([("num", Pipeline(ns), num), ("cat", Pipeline(cs), cat)],
51
+ remainder="drop")
52
+
53
+ def _metrics(task, y, pred, proba=None):
54
+ from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
55
+ from sklearn.metrics import roc_auc_score, mean_absolute_error, mean_squared_error, r2_score
56
+ if task == "classification":
57
+ m = {"accuracy": float(accuracy_score(y,pred)),
58
+ "precision": float(precision_score(y,pred,average="weighted",zero_division=0)),
59
+ "recall": float(recall_score(y,pred,average="weighted",zero_division=0)),
60
+ "f1": float(f1_score(y,pred,average="weighted",zero_division=0))}
61
+ if proba is not None and len(getattr(proba, "shape", ())) == 2 and proba.shape[1] == 2:
62
+ try: m["roc_auc"] = float(roc_auc_score(y, proba[:,1]))
63
+ except (ValueError, TypeError, IndexError):
64
+ m["roc_auc"] = None
65
+ return m
66
+ return {"r2": float(r2_score(y,pred)),
67
+ "rmse": float(mean_squared_error(y,pred)**0.5),
68
+ "mae": float(mean_absolute_error(y,pred))}
69
+
70
+ def auto_ml(X, y, task="auto", cv=3, max_rows=30000, random_state=42,
71
+ max_categories=50, scoring=None, include_svm=True, n_jobs=1):
72
+ """Small, robust AutoML model comparison with cross-validation."""
73
+ import numpy as np
74
+ from .ml import _normalize_frame
75
+ from sklearn.pipeline import Pipeline
76
+ from sklearn.preprocessing import FunctionTransformer
77
+ from sklearn.model_selection import cross_val_score
78
+ from sklearn.linear_model import LogisticRegression, Ridge
79
+ from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier
80
+ from sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor, GradientBoostingRegressor
81
+ from sklearn.svm import SVC, SVR
82
+
83
+ X, y = _clean_xy(X,y,max_rows,random_state)
84
+ if task == "auto":
85
+ task = "classification" if y.dtype == "object" or y.nunique() <= 10 else "regression"
86
+ if task not in {"classification","regression"}:
87
+ raise ValueError("task must be auto, classification, or regression")
88
+ if task == "classification":
89
+ models = {
90
+ "logistic": LogisticRegression(max_iter=1200),
91
+ "random_forest": RandomForestClassifier(n_estimators=120,max_depth=18,n_jobs=1,random_state=random_state),
92
+ "extra_trees": ExtraTreesClassifier(n_estimators=120,max_depth=18,n_jobs=1,random_state=random_state),
93
+ "gradient_boosting": GradientBoostingClassifier(n_estimators=120,max_depth=3,random_state=random_state),
94
+ }
95
+ if include_svm: models["svm"] = SVC(probability=True, random_state=random_state)
96
+ default_scoring = scoring or "f1_weighted"
97
+ else:
98
+ models = {
99
+ "ridge": Ridge(),
100
+ "random_forest": RandomForestRegressor(n_estimators=120,max_depth=18,n_jobs=1,random_state=random_state),
101
+ "extra_trees": ExtraTreesRegressor(n_estimators=120,max_depth=18,n_jobs=1,random_state=random_state),
102
+ "gradient_boosting": GradientBoostingRegressor(n_estimators=120,max_depth=3,random_state=random_state),
103
+ }
104
+ if include_svm: models["svm"] = SVR()
105
+ default_scoring = scoring or "r2"
106
+
107
+ results=[]
108
+ best_name=None; best_score=-float("inf"); best_pipe=None
109
+ for name, model in models.items():
110
+ pipe=Pipeline([("normalize", FunctionTransformer(_normalize_frame, feature_names_out="one-to-one")),
111
+ ("preprocess",_prep(X,scale=name in {"logistic","ridge","svm"},max_categories=max_categories)),
112
+ ("model",model)])
113
+ try:
114
+ scores=cross_val_score(pipe,X,y,cv=cv,scoring=default_scoring,n_jobs=n_jobs)
115
+ score=float(np.mean(scores))
116
+ results.append({"model":name,"cv_score":score,"cv_std":float(np.std(scores)),"scoring":default_scoring})
117
+ if score > best_score:
118
+ best_score, best_name, best_pipe = score, name, pipe
119
+ except Exception as e:
120
+ results.append({"model":name,"cv_score":None,"error":str(e),"scoring":default_scoring})
121
+ if best_pipe is None:
122
+ raise RuntimeError("AutoML could not fit any candidate model.")
123
+ best_pipe.fit(X,y)
124
+ return {"task":task,"scoring":default_scoring,"leaderboard":results,
125
+ "best_model":best_name,"best_score":float(best_score),"model":best_pipe}
126
+
127
+ def gradient_boosting(X,y,task="auto",max_rows=30000,random_state=42,**kwargs):
128
+ """Gradient boosting wrapper using histogram-based sklearn estimators."""
129
+ from .ml import _normalize_frame
130
+ from sklearn.ensemble import HistGradientBoostingClassifier, HistGradientBoostingRegressor
131
+ from sklearn.pipeline import Pipeline
132
+ from sklearn.preprocessing import FunctionTransformer
133
+ X, y = _clean_xy(X,y,max_rows,random_state)
134
+ if task=="auto": task="classification" if y.dtype=="object" or y.nunique()<=10 else "regression"
135
+ model = HistGradientBoostingClassifier(random_state=random_state, **kwargs) if task=="classification" else HistGradientBoostingRegressor(random_state=random_state, **kwargs)
136
+ pipe=Pipeline([("normalize", FunctionTransformer(_normalize_frame, feature_names_out="one-to-one")),
137
+ ("preprocess",_prep(X,max_categories=50)),("model",model)])
138
+ pipe.fit(X,y)
139
+ return pipe
@@ -0,0 +1,114 @@
1
+
2
+ """Advanced statistical analysis helpers."""
3
+ from __future__ import annotations
4
+
5
+ def _arr(x):
6
+ import numpy as np
7
+ a = np.asarray(x, dtype=float)
8
+ return a[np.isfinite(a)]
9
+
10
+ def effect_size(a, b, kind="cohens_d"):
11
+ import numpy as np
12
+ x, y = _arr(a), _arr(b)
13
+ if len(x) < 2 or len(y) < 2:
14
+ return float("nan")
15
+ if kind.lower() in {"cohens_d", "d"}:
16
+ nx, ny = len(x), len(y)
17
+ pooled = np.sqrt(((nx-1)*np.var(x, ddof=1)+(ny-1)*np.var(y, ddof=1))/(nx+ny-2))
18
+ return float((np.mean(x)-np.mean(y))/pooled) if pooled else float("nan")
19
+ if kind.lower() in {"cliffs_delta", "cliff"}:
20
+ return float(_cliffs_delta(x, y))
21
+ raise ValueError("kind must be cohens_d or cliffs_delta")
22
+
23
+ def _cliffs_delta(x, y):
24
+ import numpy as np
25
+ # Efficient rank-based calculation
26
+ z = np.concatenate([x, y])
27
+ # rankdata already performs the required sort and tie handling.
28
+ from scipy.stats import rankdata
29
+ ranks = rankdata(z, method="average")
30
+ rx = ranks[:len(x)].sum()
31
+ ry = ranks[len(x):].sum()
32
+ u_x = rx - len(x)*(len(x)+1)/2
33
+ return (2*u_x)/(len(x)*len(y))-1
34
+
35
+ def mann_whitney(a, b):
36
+ from scipy import stats
37
+ return stats.mannwhitneyu(_arr(a), _arr(b), alternative="two-sided")
38
+
39
+ def wilcoxon(a, b):
40
+ from scipy import stats
41
+ return stats.wilcoxon(_arr(a), _arr(b))
42
+
43
+ def kruskal(*groups):
44
+ from scipy import stats
45
+ return stats.kruskal(*[_arr(g) for g in groups])
46
+
47
+ def spearman(x, y):
48
+ from scipy import stats
49
+ return stats.spearmanr(_arr(x), _arr(y), nan_policy="omit")
50
+
51
+ def kendall(x, y):
52
+ from scipy import stats
53
+ return stats.kendalltau(_arr(x), _arr(y), nan_policy="omit")
54
+
55
+ def bootstrap_ci(x, statistic="mean", confidence=.95, n_resamples=2000, random_state=42):
56
+ import numpy as np
57
+ from scipy.stats import bootstrap
58
+ a = _arr(x)
59
+ if len(a) < 2:
60
+ return (float("nan"), float("nan"))
61
+ if statistic == "mean":
62
+ fn = lambda z, axis: np.mean(z, axis=axis)
63
+ elif statistic == "median":
64
+ fn = lambda z, axis: np.median(z, axis=axis)
65
+ else:
66
+ raise ValueError("statistic must be mean or median")
67
+ rng = np.random.default_rng(random_state)
68
+ res = bootstrap((a,), fn, confidence_level=confidence, n_resamples=n_resamples,
69
+ random_state=rng, method="percentile")
70
+ return float(res.confidence_interval.low), float(res.confidence_interval.high)
71
+
72
+ def regression_diagnostics(X, y):
73
+ """OLS coefficients, R², adjusted R², VIF and residual diagnostics."""
74
+ import numpy as np, pandas as pd
75
+ import statsmodels.api as sm
76
+ from statsmodels.stats.outliers_influence import variance_inflation_factor
77
+ Xn = X.select_dtypes(include=np.number).copy()
78
+ y = pd.Series(y).astype(float)
79
+ mask = y.notna()
80
+ Xn = Xn.loc[mask].replace([np.inf,-np.inf], np.nan)
81
+ data = pd.concat([Xn, y.loc[mask].rename("__y")], axis=1).dropna()
82
+ if data.empty or data.shape[1] < 2:
83
+ raise ValueError("Need numeric predictors and a numeric target.")
84
+ Xd = sm.add_constant(data.drop(columns="__y"), has_constant="add")
85
+ model = sm.OLS(data["__y"], Xd).fit()
86
+ vif = {}
87
+ for i, c in enumerate(Xd.columns):
88
+ if c == "const": continue
89
+ try: vif[c] = float(variance_inflation_factor(Xd.values, i))
90
+ except Exception: vif[c] = float("nan")
91
+ return {
92
+ "model": model,
93
+ "coefficients": model.params,
94
+ "pvalues": model.pvalues,
95
+ "r2": float(model.rsquared),
96
+ "adjusted_r2": float(model.rsquared_adj),
97
+ "aic": float(model.aic),
98
+ "bic": float(model.bic),
99
+ "vif": pd.Series(vif),
100
+ "residual_mean": float(model.resid.mean()),
101
+ "residual_std": float(model.resid.std(ddof=1)),
102
+ }
103
+
104
+ def anova_effects(groups):
105
+ """One-way ANOVA plus eta-squared effect size."""
106
+ import numpy as np
107
+ from scipy import stats
108
+ clean = [_arr(g) for g in groups]
109
+ f, p = stats.f_oneway(*clean)
110
+ grand = np.mean(np.concatenate(clean))
111
+ ss_between = sum(len(g)*(np.mean(g)-grand)**2 for g in clean)
112
+ ss_total = sum(((g-grand)**2).sum() for g in clean)
113
+ eta2 = ss_between/ss_total if ss_total else float("nan")
114
+ return {"f": float(f), "p": float(p), "eta_squared": float(eta2)}