yaeda 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.
yaeda/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ """tabulareda - Automated Tabular EDA & Golden Feature Engineering Toolkit."""
2
+
3
+ from .analyze import TabularEDA
4
+ from .clustering import ClusterReport, TabularClusterAnalyzer
5
+ from .correlation import CorrelationReport, FeatureTargetAnalyzer
6
+ from .extract import FeatureProfile, TableProfile, TabularDataProfiler
7
+ from .feature_importance import FeatureImportanceAnalyzer, FeatureImportanceReport
8
+ from .interaction import FeatureInteractionAnalyzer, InteractionReport
9
+ from .model_diagnostics import ModelDiagnosticsAnalyzer, ModelDiagnosticsReport
10
+
11
+ __all__ = [
12
+ "ClusterReport",
13
+ "CorrelationReport",
14
+ "FeatureImportanceAnalyzer",
15
+ "FeatureImportanceReport",
16
+ "FeatureInteractionAnalyzer",
17
+ "FeatureProfile",
18
+ "FeatureTargetAnalyzer",
19
+ "InteractionReport",
20
+ "ModelDiagnosticsAnalyzer",
21
+ "ModelDiagnosticsReport",
22
+ "TableProfile",
23
+ "TabularClusterAnalyzer",
24
+ "TabularDataProfiler",
25
+ "TabularEDA",
26
+ ]
27
+
28
+ __version__ = "0.1.0"
yaeda/analyze.py ADDED
@@ -0,0 +1,354 @@
1
+ from typing import Literal, Any
2
+ from pathlib import Path
3
+
4
+ import pandas as pd
5
+
6
+ # import weasyprint
7
+
8
+ from .extract import TabularDataProfiler, TableProfile
9
+ from .correlation import FeatureTargetAnalyzer, CorrelationReport
10
+ from .feature_importance import FeatureImportanceAnalyzer, FeatureImportanceReport
11
+ from .interaction import FeatureInteractionAnalyzer, InteractionReport
12
+
13
+ from .clustering import TabularClustersCall, ClusterReport
14
+ from .model_diagnostics import (
15
+ ModelDiagnosticsCall,
16
+ ModelDiagnosticsReport,
17
+ ModelDiagnosticsInputs,
18
+ )
19
+
20
+ from .charts import EDAChartGenerator
21
+ from .exports.html import EDAHTMLDashboardBuilder
22
+ from .exports.markdown import EDAMarkdownReportBuilder
23
+
24
+ # from .exports.pdf import EDAPDFReportBuilder
25
+ from .exports.export import StructuredDataExporter
26
+
27
+
28
+ class TabularEDA:
29
+ def __init__(
30
+ self,
31
+ df: pd.DataFrame,
32
+ target: str,
33
+ features: list[str] | None = None,
34
+ *,
35
+ diagnostics: dict[str, Any] | None = None,
36
+ target_type: Literal["classification", "regression"] | None = None,
37
+ n_clusters: list[int] | None = None,
38
+ n_frequent: int = 3,
39
+ n_extremes: int = 3,
40
+ outlier_irq_factor: float = 1.5,
41
+ collinear_threshold: float = 0.80,
42
+ test_size: float = 0.25,
43
+ shap_sample_limit: int = 500,
44
+ seed: int = 42,
45
+ ):
46
+ self._df = df
47
+ self._target = target
48
+ self._features = features
49
+
50
+ self._diagnostics = (
51
+ [ModelDiagnosticsInputs(**input) for input in diagnostics]
52
+ if diagnostics is not None
53
+ else None
54
+ )
55
+
56
+ self._n_frequent = n_frequent
57
+ self._n_extremes = n_extremes
58
+ self._outlier_irq_factor = outlier_irq_factor
59
+
60
+ self._target_type = target_type
61
+ self._seed = seed
62
+
63
+ self._collinear_threshold = collinear_threshold
64
+
65
+ self._test_size = test_size
66
+ self._shap_sample_limit = shap_sample_limit
67
+
68
+ self._n_clusters = n_clusters if n_clusters is not None else [4]
69
+
70
+ self._profile: TableProfile | None = None
71
+ self._corr_report: CorrelationReport | None = None
72
+ self._feature_importance: FeatureImportanceReport | None = None
73
+ self._cluster_report: list[ClusterReport] | None = None
74
+ self._diagnostics_report: ModelDiagnosticsReport = None
75
+
76
+ @property
77
+ def stats(self) -> TableProfile:
78
+ if self._profile is not None:
79
+ return self._profile
80
+
81
+ profiler = TabularDataProfiler(
82
+ self._df,
83
+ target=self._target,
84
+ columns=self._features,
85
+ n_frequent=self._n_frequent,
86
+ n_extremes=self._n_extremes,
87
+ outlier_iqr_factor=self._outlier_irq_factor,
88
+ )
89
+ self._profile = profiler.run()
90
+ return self._profile
91
+
92
+ @property
93
+ def correlation(self) -> CorrelationReport:
94
+ if self._corr_report is not None:
95
+ return self._corr_report
96
+
97
+ analyzer = FeatureTargetAnalyzer(
98
+ df=self._df,
99
+ target=self._target,
100
+ features=self._features,
101
+ collinear_threshold=self._collinear_threshold,
102
+ random_state=self._seed,
103
+ )
104
+
105
+ self._corr_report = analyzer.run()
106
+ return self._corr_report
107
+
108
+ @property
109
+ def feature_importance(self) -> FeatureImportanceReport:
110
+ if self._feature_importance is not None:
111
+ return self._feature_importance
112
+
113
+ analyzer = FeatureImportanceAnalyzer(
114
+ df=self._df,
115
+ features=self._features,
116
+ target=self._target,
117
+ target_type=self._target_type,
118
+ test_size=self._test_size,
119
+ shap_sample_limit=self._shap_sample_limit,
120
+ random_state=self._seed,
121
+ )
122
+
123
+ self._feature_importance = analyzer.run()
124
+ return self._feature_importance
125
+
126
+ @property
127
+ def clusters(self) -> list[ClusterReport]:
128
+ if self._cluster_report is not None:
129
+ return list(self._cluster_report.values())
130
+
131
+ prominent = self.select_prominent_features(top_n=8)
132
+ analyzer = TabularClustersCall(
133
+ df=self._df,
134
+ target=self._target,
135
+ features=prominent,
136
+ target_type=self._target_type
137
+ or (
138
+ "classification"
139
+ if self.correlation.target_type == "classification"
140
+ else "regression"
141
+ ),
142
+ n_clusters=self._n_clusters,
143
+ random_state=self._seed,
144
+ )
145
+ self._cluster_report = analyzer.run()
146
+ return list(self._cluster_report.values())
147
+
148
+ @property
149
+ def diagnostics(self) -> list[ModelDiagnosticsReport] | None:
150
+ if self._diagnostics_report is not None:
151
+ return list(self._diagnostics_report.values())
152
+
153
+ if self._diagnostics is None:
154
+ return None
155
+
156
+ fi = self.feature_importance
157
+ analyzer = ModelDiagnosticsCall(
158
+ df=self._df,
159
+ target=self._target,
160
+ inputs=self._diagnostics,
161
+ features=fi.feature_names,
162
+ target_type=self._target_type or fi.target_type,
163
+ preprocessed_X=fi.preprocessed_X,
164
+ random_state=self._seed,
165
+ )
166
+ self._diagnostics_report = analyzer.run()
167
+ return list(self._diagnostics_report.values())
168
+
169
+ def plot_partial_dependence(
170
+ self,
171
+ top_n: int = 6,
172
+ kind: str = "both",
173
+ deduplicate_collinear: bool = True,
174
+ ) -> str:
175
+ """Returns base64 Partial Dependence chart for the top N prominent features."""
176
+ fi = self.feature_importance
177
+ prominent = self.select_prominent_features(
178
+ top_n=top_n, deduplicate_collinear=deduplicate_collinear
179
+ )
180
+ print(prominent, type(prominent))
181
+ chart_engine = EDAChartGenerator()
182
+ return chart_engine.plot_partial_dependence(
183
+ model=fi.fitted_model,
184
+ X=fi.preprocessed_X,
185
+ feature_names=fi.feature_names,
186
+ features_to_plot=prominent,
187
+ target_type=fi.target_type,
188
+ kind=kind,
189
+ )
190
+
191
+ def analyze_all(
192
+ self,
193
+ ) -> tuple[TableProfile, CorrelationReport, FeatureImportanceReport]:
194
+ return self.stats, self.correlation, self.feature_importance
195
+
196
+ def select_prominent_features(
197
+ self,
198
+ top_n: int = 6,
199
+ include_auxiliary: bool = True,
200
+ ) -> list[str]:
201
+ """
202
+ Guarantees selection of all Tier 1 (Golden) and Tier 2 (Strong) features,
203
+ followed by Tier 3 (Auxiliary) features up to top_n.
204
+ """
205
+ stats, _corr, fi = self.analyze_all()
206
+
207
+ unhealthy = {
208
+ feat
209
+ for feat, p in stats.features.items()
210
+ if p.missing_percentage > 80.0 or (p.is_numeric and p.std_dev == 0)
211
+ }
212
+
213
+ golden_feats = [
214
+ m.feature for m in fi.importances if "Tier 1" in m.tier and m.feature not in unhealthy
215
+ ]
216
+ strong_feats = [
217
+ m.feature for m in fi.importances if "Tier 2" in m.tier and m.feature not in unhealthy
218
+ ]
219
+ aux_feats = [
220
+ m.feature for m in fi.importances if "Tier 3" in m.tier and m.feature not in unhealthy
221
+ ]
222
+ other_feats = [
223
+ m.feature
224
+ for m in fi.importances
225
+ if m.feature not in golden_feats
226
+ and m.feature not in strong_feats
227
+ and m.feature not in aux_feats
228
+ and m.feature not in unhealthy
229
+ ]
230
+
231
+ # 1. Always prioritize Golden and Strong features
232
+ selected = list(golden_feats)
233
+ for f in strong_feats:
234
+ if f not in selected:
235
+ selected.append(f)
236
+
237
+ # 2. Add Auxiliary features
238
+ if include_auxiliary:
239
+ for f in aux_feats:
240
+ if f not in selected:
241
+ selected.append(f)
242
+ if len(selected) >= max(top_n, len(golden_feats) + len(strong_feats) + 1):
243
+ break
244
+
245
+ # 3. Fill up to top_n if capacity remains
246
+ if len(selected) < top_n:
247
+ for f in other_feats:
248
+ if f not in selected:
249
+ selected.append(f)
250
+ if len(selected) == top_n:
251
+ break
252
+
253
+ return selected
254
+
255
+ def interactions(self, top_n: int = 6, max_pairs: int | None = None) -> InteractionReport:
256
+ prominent = self.select_prominent_features(top_n=top_n, include_auxiliary=True)
257
+ analyzer = FeatureInteractionAnalyzer(
258
+ df=self._df,
259
+ target=self._target,
260
+ features=prominent,
261
+ target_type=self._target_type
262
+ or (
263
+ "classification"
264
+ if self.correlation.target_type == "classification"
265
+ else "regression"
266
+ ),
267
+ max_pairs=max_pairs,
268
+ random_state=self._seed,
269
+ )
270
+ return analyzer.run()
271
+
272
+ def to_markdown(self, output: Path | str | None = None) -> str:
273
+ stats, corr, fi = self.analyze_all()
274
+ md_builder = EDAMarkdownReportBuilder(
275
+ table_profile=stats,
276
+ corr_report=corr,
277
+ importance_report=fi,
278
+ )
279
+ md_data = md_builder.generate(output)
280
+ return md_data
281
+
282
+ def to_html(
283
+ self,
284
+ output: Path | str | None = None,
285
+ top_n_features: int = 6,
286
+ top_n_interactions: int = 20,
287
+ ) -> str:
288
+ stats, corr, fi = self.analyze_all()
289
+ prominent = self.select_prominent_features(top_n=top_n_features, include_auxiliary=True)
290
+ interaction_rep = self.interactions(top_n=top_n_features, max_pairs=None)
291
+ cluster_rep = self.clusters
292
+ diag_rep = self.diagnostics
293
+
294
+ chart_engine = EDAChartGenerator()
295
+ html_builder = EDAHTMLDashboardBuilder(
296
+ table_profile=stats,
297
+ corr_report=corr,
298
+ importance_report=fi,
299
+ chart_generator=chart_engine,
300
+ df=self._df,
301
+ prominent_features=prominent,
302
+ interaction_report=interaction_rep,
303
+ cluster_report=cluster_rep,
304
+ diagnostics_report=diag_rep,
305
+ )
306
+ return html_builder.generate(
307
+ output_path=output,
308
+ max_interaction_cards=top_n_interactions,
309
+ max_table_rows=top_n_interactions,
310
+ )
311
+
312
+ def to_json(self, output: Path | str | None = None) -> dict[str, Any]:
313
+ stats, corr, fi = self.analyze_all()
314
+ cluster_rep = self.clusters
315
+ diagnostics = self.diagnostics
316
+ json_builder = StructuredDataExporter(
317
+ table_profile=stats,
318
+ corr_report=corr,
319
+ importance_report=fi,
320
+ cluster_report=cluster_rep,
321
+ diagnostic_report=diagnostics,
322
+ )
323
+ return json_builder.export_json(output)
324
+
325
+ # def to_pdf(self, output: Path | str | None = None) -> weasyprint.HTML:
326
+ # stats, corr, fi = self.analyze_all()
327
+ # chart_engine = EDAChartGenerator()
328
+ # pdf_builder = EDAPDFReportBuilder(
329
+ # table_profile=stats,
330
+ # corr_report=corr,
331
+ # importance_report=fi,
332
+ # chart_generator=chart_engine,
333
+ # )
334
+ # pdf_data = pdf_builder.generate(output)
335
+ # return pdf_data
336
+
337
+ def to_csv(self, output: Path | str | None = None) -> dict[str, Any]:
338
+ stats, corr, fi = self.analyze_all()
339
+ csv_builder = StructuredDataExporter(
340
+ table_profile=stats,
341
+ corr_report=corr,
342
+ importance_report=fi,
343
+ )
344
+ csv_data = csv_builder.export_csvs(output)
345
+ return csv_data
346
+
347
+ def to_notebook(self, top_n_features: int = 6, top_n_interactions: int = 6):
348
+ from IPython.display import display, HTML
349
+
350
+ html = self.to_html(
351
+ top_n_features=top_n_features,
352
+ top_n_interactions=top_n_interactions,
353
+ )
354
+ return display(HTML(html))