flasheda 0.1.3__tar.gz → 0.4.0__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.
Files changed (53) hide show
  1. flasheda-0.4.0/MIT.License +21 -0
  2. flasheda-0.4.0/PKG-INFO +182 -0
  3. flasheda-0.4.0/README.md +117 -0
  4. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/__init__.py +133 -92
  5. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/__init__.py +3 -3
  6. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/categorical.py +78 -75
  7. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/correlation.py +94 -90
  8. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/missing.py +53 -53
  9. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/numeric.py +70 -70
  10. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/overview.py +65 -65
  11. flasheda-0.4.0/flasheda/analyzers/target.py +230 -0
  12. flasheda-0.4.0/flasheda/clean/__init__.py +48 -0
  13. flasheda-0.4.0/flasheda/clean/detectors.py +124 -0
  14. flasheda-0.4.0/flasheda/codegen/__init__.py +91 -0
  15. flasheda-0.4.0/flasheda/codegen/planner.py +170 -0
  16. flasheda-0.4.0/flasheda/codegen/steps.py +304 -0
  17. flasheda-0.4.0/flasheda/codegen/validate.py +107 -0
  18. flasheda-0.4.0/flasheda/codegen/writers/__init__.py +22 -0
  19. flasheda-0.4.0/flasheda/codegen/writers/base.py +86 -0
  20. flasheda-0.4.0/flasheda/codegen/writers/duckdb_writer.py +147 -0
  21. flasheda-0.4.0/flasheda/codegen/writers/pandas_writer.py +87 -0
  22. flasheda-0.4.0/flasheda/codegen/writers/polars_writer.py +126 -0
  23. flasheda-0.4.0/flasheda/codegen/writers/pyspark_writer.py +99 -0
  24. flasheda-0.4.0/flasheda/engine/__init__.py +23 -0
  25. flasheda-0.4.0/flasheda/engine/backends.py +67 -0
  26. flasheda-0.4.0/flasheda/engine/sampler.py +125 -0
  27. flasheda-0.4.0/flasheda/engine/source.py +110 -0
  28. flasheda-0.4.0/flasheda/features/__init__.py +169 -0
  29. flasheda-0.4.0/flasheda/modeling/__init__.py +230 -0
  30. flasheda-0.4.0/flasheda/modeling/script.py +138 -0
  31. flasheda-0.4.0/flasheda/modeling/specs.py +79 -0
  32. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/report.py +688 -521
  33. flasheda-0.4.0/flasheda/sampler.py +9 -0
  34. flasheda-0.4.0/flasheda.egg-info/PKG-INFO +182 -0
  35. flasheda-0.4.0/flasheda.egg-info/SOURCES.txt +44 -0
  36. flasheda-0.4.0/flasheda.egg-info/requires.txt +34 -0
  37. {flasheda-0.1.3 → flasheda-0.4.0}/pyproject.toml +2 -2
  38. {flasheda-0.1.3 → flasheda-0.4.0}/setup.cfg +4 -4
  39. flasheda-0.4.0/setup.py +52 -0
  40. flasheda-0.4.0/tests/test_clean_target.py +167 -0
  41. flasheda-0.4.0/tests/test_codegen.py +66 -0
  42. flasheda-0.4.0/tests/test_engine.py +77 -0
  43. flasheda-0.4.0/tests/test_features_model.py +138 -0
  44. flasheda-0.4.0/tests/test_writers.py +172 -0
  45. flasheda-0.1.3/PKG-INFO +0 -49
  46. flasheda-0.1.3/README.md +0 -21
  47. flasheda-0.1.3/flasheda/sampler.py +0 -124
  48. flasheda-0.1.3/flasheda.egg-info/PKG-INFO +0 -49
  49. flasheda-0.1.3/flasheda.egg-info/SOURCES.txt +0 -17
  50. flasheda-0.1.3/flasheda.egg-info/requires.txt +0 -7
  51. flasheda-0.1.3/setup.py +0 -27
  52. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda.egg-info/dependency_links.txt +0 -0
  53. {flasheda-0.1.3 → flasheda-0.4.0}/flasheda.egg-info/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: flasheda
3
+ Version: 0.4.0
4
+ Summary: Fixed-time EDA for any dataset size, plus validated cleaning, feature and training code for pandas, polars, DuckDB and PySpark.
5
+ Home-page: https://github.com/sjapanjots/Flash-EDA
6
+ Author: Japanjot Singh
7
+ Author-email: sjapanjots@gmail.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/sjapanjots/Flash-EDA
10
+ Project-URL: Changelog, https://github.com/sjapanjots/Flash-EDA/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/sjapanjots/Flash-EDA/issues
12
+ Keywords: eda,data-cleaning,pandas,polars,duckdb,pyspark,code-generation,feature-engineering,automl
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: MIT.License
22
+ Requires-Dist: pandas>=2.0
23
+ Requires-Dist: numpy>=1.22
24
+ Requires-Dist: scipy>=1.7.0
25
+ Requires-Dist: rich>=12.0.0
26
+ Requires-Dist: jinja2>=3.0.0
27
+ Requires-Dist: matplotlib>=3.4.0
28
+ Requires-Dist: fpdf2>=2.7.0
29
+ Provides-Extra: fast
30
+ Requires-Dist: duckdb>=0.10; extra == "fast"
31
+ Requires-Dist: polars>=0.20; extra == "fast"
32
+ Requires-Dist: pyarrow>=12; extra == "fast"
33
+ Provides-Extra: spark
34
+ Requires-Dist: pyspark>=3.4; extra == "spark"
35
+ Provides-Extra: ml
36
+ Requires-Dist: scikit-learn>=1.2; extra == "ml"
37
+ Requires-Dist: pyarrow>=12; extra == "ml"
38
+ Provides-Extra: all
39
+ Requires-Dist: duckdb>=0.10; extra == "all"
40
+ Requires-Dist: polars>=0.20; extra == "all"
41
+ Requires-Dist: pyarrow>=12; extra == "all"
42
+ Requires-Dist: scikit-learn>=1.2; extra == "all"
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=7; extra == "dev"
45
+ Requires-Dist: duckdb>=0.10; extra == "dev"
46
+ Requires-Dist: polars>=0.20; extra == "dev"
47
+ Requires-Dist: pyarrow>=12; extra == "dev"
48
+ Requires-Dist: scikit-learn>=1.2; extra == "dev"
49
+ Requires-Dist: build; extra == "dev"
50
+ Requires-Dist: twine; extra == "dev"
51
+ Dynamic: author
52
+ Dynamic: author-email
53
+ Dynamic: classifier
54
+ Dynamic: description
55
+ Dynamic: description-content-type
56
+ Dynamic: home-page
57
+ Dynamic: keywords
58
+ Dynamic: license
59
+ Dynamic: license-file
60
+ Dynamic: project-url
61
+ Dynamic: provides-extra
62
+ Dynamic: requires-dist
63
+ Dynamic: requires-python
64
+ Dynamic: summary
65
+
66
+ <img width="1254" height="1254" alt="image" src="https://github.com/user-attachments/assets/a87d02e0-7a18-41e0-844d-a2b9c3d6f769" />
67
+
68
+ # FlashEDA
69
+
70
+ [![PyPI](https://img.shields.io/pypi/v/flasheda)](https://pypi.org/project/flasheda/)
71
+ [![Python](https://img.shields.io/pypi/pyversions/flasheda)](https://pypi.org/project/flasheda/)
72
+ [![Tests](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml/badge.svg)](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml)
73
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](MIT.License)
74
+
75
+ **Current version: 0.4.0** · [Changelog](CHANGELOG.md)
76
+
77
+ Analyse any dataset in a few seconds, then get **validated cleaning, feature and training code** that runs on the full data, in the engine that fits its size: pandas, polars, DuckDB or PySpark.
78
+
79
+ FlashEDA analyses a fixed-size random sample, so analysis time barely changes between 100 thousand and 100 million rows.
80
+
81
+ ## Install
82
+
83
+ ```bash
84
+ pip install flasheda # core
85
+ pip install "flasheda[fast]" # + polars, DuckDB, pyarrow (recommended for big files)
86
+ pip install "flasheda[ml]" # + scikit-learn, for flasheda.train
87
+ pip install "flasheda[spark]" # + PySpark
88
+ pip install "flasheda[all]" # everything except PySpark
89
+ ```
90
+
91
+ ## Quick start
92
+
93
+ ```python
94
+ import flasheda as fe
95
+
96
+ # 1. Analyse: any size, a few seconds
97
+ report = fe.analyze("orders.csv", target="churn", save_pdf=False)
98
+ report.show() # console report
99
+ report.save_html("report.html") # or report.save_pdf("report.pdf")
100
+
101
+ # 2. Clean: get a script for the full data, already checked on the sample
102
+ code = fe.clean("orders.csv", target="churn", engine="polars")
103
+ code.save("clean_orders.py") # then: python clean_orders.py
104
+
105
+ # 3. Model: compare models on the sample, get prep + training scripts
106
+ result = fe.train("orders.csv", target="churn", engine="polars")
107
+ result.show() # leaderboard
108
+ result.save("prep.py", "train.py") # python prep.py && python train.py → model.joblib
109
+ ```
110
+
111
+ ## What it does
112
+
113
+ | | |
114
+ |---|---|
115
+ | **Overview** | true row count (read from file metadata, no full load), types, memory, duplicates |
116
+ | **Data quality** | missing values, outliers, skew, likely ID columns, numbers stored as text, spelling variants, rare categories |
117
+ | **Target analysis** | task type, class balance, per-feature predictive power, **possible leakage**, recommended metric and validation split |
118
+ | **Correlations** | Pearson (numeric) and Cramér's V (categorical) |
119
+ | **Reports** | console, HTML, PDF, JSON |
120
+ | **Code generation** | one cleaning plan → pandas, polars (lazy, streaming), DuckDB SQL (larger than memory) or PySpark |
121
+ | **Features** | date parts, one-hot / frequency encoding, ratios that beat their inputs |
122
+ | **Models** | baseline, linear, random forest, gradient boosting compared with the right metric and split; training script for the winner |
123
+ | **Validation** | generated code is run on the sample and compared with a reference result before you get it |
124
+
125
+ ### Cleaning steps
126
+ Each step in the generated code has a comment saying why it was added:
127
+
128
+ - drop likely-ID, mostly-empty, constant and leaky columns
129
+ - drop rows where the target is missing, and drop duplicate rows
130
+ - convert numbers stored as text (`"12.5"`, `"N/A"`) to numbers
131
+ - merge spelling variants (`"Delhi"`, `" delhi"`, `"DELHI"`)
132
+ - group rare categories into `other`
133
+ - add `<col>_was_missing` flags when missingness predicts the target
134
+ - fill nulls (median / mode), log-transform skewed columns, clip outliers
135
+
136
+ ### Features and models
137
+ `fe.train()` (or `report.train()`) adds features on top of the cleaning steps, compares models on the sample, and writes two scripts:
138
+
139
+ - **`prep.py`**: cleaning + features for the full data, in the chosen engine
140
+ - **`train.py`**: scikit-learn training with cross-validation, using the same metric and split, that saves `model.joblib`
141
+
142
+ The metric and split follow the target: PR-AUC for imbalanced binary targets, MAE for skewed regression targets, time-based splits when there's a date column, and so on.
143
+
144
+ ### Engine choice
145
+
146
+ | Rows | Engine |
147
+ |---|---|
148
+ | < 1M | pandas |
149
+ | 1M – 200M | polars or DuckDB |
150
+ | > 200M | PySpark (if installed) |
151
+
152
+ Override with `fe.analyze(..., engine="duckdb")` or `report.codegen("duckdb")`.
153
+
154
+ ## Performance
155
+
156
+ Measured on synthetic messy data:
157
+
158
+ | Task | Time |
159
+ |---|---|
160
+ | Analyse a 5.1M-row Parquet file | 0.56 s |
161
+ | Sample a 1M-row CSV (0.1.x → 0.2.0) | 37.3 s → 0.65 s |
162
+ | Analyse + plan + generate + validate, 1M-row CSV with target | 2.5 s |
163
+ | Run the generated polars script on 1M rows | 0.9 s |
164
+ | `fe.train()` on a 1M-row CSV (plan, features, 4 models, checks) | 6.7 s |
165
+ | Generated `prep.py` + `train.py` on 1M rows | 1.3 s + 20 s |
166
+
167
+ ## Examples
168
+
169
+ - [`examples/phase1_demo.py`](examples/phase1_demo.py): engine choice and code generation at 0.1M / 1M / 5M rows
170
+ - [`examples/phase2_demo.py`](examples/phase2_demo.py): target analysis and cleaning on a 1M-row churn CSV
171
+ - [`examples/phase3_demo.py`](examples/phase3_demo.py): features, model comparison and training on 1M rows
172
+
173
+ ## Development
174
+
175
+ ```bash
176
+ pip install -e ".[dev]"
177
+ pytest
178
+ ```
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1,117 @@
1
+ <img width="1254" height="1254" alt="image" src="https://github.com/user-attachments/assets/a87d02e0-7a18-41e0-844d-a2b9c3d6f769" />
2
+
3
+ # FlashEDA
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/flasheda)](https://pypi.org/project/flasheda/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/flasheda)](https://pypi.org/project/flasheda/)
7
+ [![Tests](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml/badge.svg)](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](MIT.License)
9
+
10
+ **Current version: 0.4.0** · [Changelog](CHANGELOG.md)
11
+
12
+ Analyse any dataset in a few seconds, then get **validated cleaning, feature and training code** that runs on the full data, in the engine that fits its size: pandas, polars, DuckDB or PySpark.
13
+
14
+ FlashEDA analyses a fixed-size random sample, so analysis time barely changes between 100 thousand and 100 million rows.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install flasheda # core
20
+ pip install "flasheda[fast]" # + polars, DuckDB, pyarrow (recommended for big files)
21
+ pip install "flasheda[ml]" # + scikit-learn, for flasheda.train
22
+ pip install "flasheda[spark]" # + PySpark
23
+ pip install "flasheda[all]" # everything except PySpark
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ import flasheda as fe
30
+
31
+ # 1. Analyse: any size, a few seconds
32
+ report = fe.analyze("orders.csv", target="churn", save_pdf=False)
33
+ report.show() # console report
34
+ report.save_html("report.html") # or report.save_pdf("report.pdf")
35
+
36
+ # 2. Clean: get a script for the full data, already checked on the sample
37
+ code = fe.clean("orders.csv", target="churn", engine="polars")
38
+ code.save("clean_orders.py") # then: python clean_orders.py
39
+
40
+ # 3. Model: compare models on the sample, get prep + training scripts
41
+ result = fe.train("orders.csv", target="churn", engine="polars")
42
+ result.show() # leaderboard
43
+ result.save("prep.py", "train.py") # python prep.py && python train.py → model.joblib
44
+ ```
45
+
46
+ ## What it does
47
+
48
+ | | |
49
+ |---|---|
50
+ | **Overview** | true row count (read from file metadata, no full load), types, memory, duplicates |
51
+ | **Data quality** | missing values, outliers, skew, likely ID columns, numbers stored as text, spelling variants, rare categories |
52
+ | **Target analysis** | task type, class balance, per-feature predictive power, **possible leakage**, recommended metric and validation split |
53
+ | **Correlations** | Pearson (numeric) and Cramér's V (categorical) |
54
+ | **Reports** | console, HTML, PDF, JSON |
55
+ | **Code generation** | one cleaning plan → pandas, polars (lazy, streaming), DuckDB SQL (larger than memory) or PySpark |
56
+ | **Features** | date parts, one-hot / frequency encoding, ratios that beat their inputs |
57
+ | **Models** | baseline, linear, random forest, gradient boosting compared with the right metric and split; training script for the winner |
58
+ | **Validation** | generated code is run on the sample and compared with a reference result before you get it |
59
+
60
+ ### Cleaning steps
61
+ Each step in the generated code has a comment saying why it was added:
62
+
63
+ - drop likely-ID, mostly-empty, constant and leaky columns
64
+ - drop rows where the target is missing, and drop duplicate rows
65
+ - convert numbers stored as text (`"12.5"`, `"N/A"`) to numbers
66
+ - merge spelling variants (`"Delhi"`, `" delhi"`, `"DELHI"`)
67
+ - group rare categories into `other`
68
+ - add `<col>_was_missing` flags when missingness predicts the target
69
+ - fill nulls (median / mode), log-transform skewed columns, clip outliers
70
+
71
+ ### Features and models
72
+ `fe.train()` (or `report.train()`) adds features on top of the cleaning steps, compares models on the sample, and writes two scripts:
73
+
74
+ - **`prep.py`**: cleaning + features for the full data, in the chosen engine
75
+ - **`train.py`**: scikit-learn training with cross-validation, using the same metric and split, that saves `model.joblib`
76
+
77
+ The metric and split follow the target: PR-AUC for imbalanced binary targets, MAE for skewed regression targets, time-based splits when there's a date column, and so on.
78
+
79
+ ### Engine choice
80
+
81
+ | Rows | Engine |
82
+ |---|---|
83
+ | < 1M | pandas |
84
+ | 1M – 200M | polars or DuckDB |
85
+ | > 200M | PySpark (if installed) |
86
+
87
+ Override with `fe.analyze(..., engine="duckdb")` or `report.codegen("duckdb")`.
88
+
89
+ ## Performance
90
+
91
+ Measured on synthetic messy data:
92
+
93
+ | Task | Time |
94
+ |---|---|
95
+ | Analyse a 5.1M-row Parquet file | 0.56 s |
96
+ | Sample a 1M-row CSV (0.1.x → 0.2.0) | 37.3 s → 0.65 s |
97
+ | Analyse + plan + generate + validate, 1M-row CSV with target | 2.5 s |
98
+ | Run the generated polars script on 1M rows | 0.9 s |
99
+ | `fe.train()` on a 1M-row CSV (plan, features, 4 models, checks) | 6.7 s |
100
+ | Generated `prep.py` + `train.py` on 1M rows | 1.3 s + 20 s |
101
+
102
+ ## Examples
103
+
104
+ - [`examples/phase1_demo.py`](examples/phase1_demo.py): engine choice and code generation at 0.1M / 1M / 5M rows
105
+ - [`examples/phase2_demo.py`](examples/phase2_demo.py): target analysis and cleaning on a 1M-row churn CSV
106
+ - [`examples/phase3_demo.py`](examples/phase3_demo.py): features, model comparison and training on 1M rows
107
+
108
+ ## Development
109
+
110
+ ```bash
111
+ pip install -e ".[dev]"
112
+ pytest
113
+ ```
114
+
115
+ ## License
116
+
117
+ MIT
@@ -1,93 +1,134 @@
1
- from __future__ import annotations
2
-
3
- import time
4
- from concurrent.futures import ThreadPoolExecutor, as_completed
5
- from pathlib import Path
6
- from typing import Union, Optional
7
-
8
- import pandas as pd
9
-
10
- from .sampler import sample, get_sample_size, set_sample_size
11
- from .analyzers import overview, missing, numeric, categorical, correlation
12
- from .report import EDAReport
13
-
14
- __version__ = "0.1.2"
15
- __all__ = ["analyze", "set_sample_size", "get_sample_size", "EDAReport"]
16
-
17
-
18
- def analyze(
19
- source: Union[pd.DataFrame, str, Path],
20
- n: Optional[int] = None,
21
- *,
22
- show: bool = False,
23
- random_state: int = 42,
24
- save_pdf: bool = True,
25
- pdf_path: str = "flasheda_report.pdf",
26
- ) -> EDAReport:
27
-
28
- t0 = time.perf_counter()
29
- n = n or get_sample_size()
30
-
31
- if isinstance(source, pd.DataFrame):
32
- original_shape = source.shape
33
- else:
34
- original_shape = (-1, -1)
35
-
36
- sampled = sample(source, n=n, random_state=random_state)
37
-
38
- if original_shape == (-1, -1):
39
- original_shape = sampled.shape
40
-
41
- tasks = {
42
- "overview": lambda: overview.analyze(sampled, original_shape),
43
- "missing": lambda: missing.analyze(sampled),
44
- "numeric": lambda: numeric.analyze(sampled),
45
- "categorical": lambda: categorical.analyze(sampled),
46
- "correlation": lambda: correlation.analyze(sampled),
47
- }
48
-
49
- results: dict = {}
50
- with ThreadPoolExecutor(max_workers=5) as pool:
51
- future_map = {pool.submit(fn): name for name, fn in tasks.items()}
52
- for future in as_completed(future_map):
53
- name = future_map[future]
54
- try:
55
- results[name] = future.result()
56
- except Exception as exc:
57
- results[name] = {"error": str(exc)}
58
-
59
- warnings: list[str] = []
60
- miss = results.get("missing", {})
61
- if miss.get("critical_columns"):
62
- warnings.append(f"Columns with >50% missing: {', '.join(miss['critical_columns'])}")
63
- num = results.get("numeric", {})
64
- if num.get("skewed_columns"):
65
- warnings.append(f"Highly skewed columns: {', '.join(num['skewed_columns'])}")
66
- cat = results.get("categorical", {})
67
- if cat.get("likely_id_columns"):
68
- warnings.append(f"Likely ID columns: {', '.join(cat['likely_id_columns'])}")
69
- corr = results.get("correlation", {})
70
- if corr.get("numeric_strong_pairs"):
71
- warnings.append(f"{len(corr['numeric_strong_pairs'])} strongly correlated pair(s) found")
72
-
73
- elapsed = round(time.perf_counter() - t0, 3)
74
-
75
- report = EDAReport(
76
- overview=results.get("overview", {}),
77
- missing=results.get("missing", {}),
78
- numeric=results.get("numeric", {}),
79
- categorical=results.get("categorical", {}),
80
- correlation=results.get("correlation", {}),
81
- sample_size=len(sampled),
82
- elapsed_seconds=elapsed,
83
- original_shape=original_shape,
84
- warnings=warnings,
85
- )
86
-
87
- if show:
88
- report.show()
89
-
90
- if save_pdf:
91
- report.save_pdf(pdf_path)
92
-
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from pathlib import Path
6
+ from typing import Union, Optional
7
+
8
+ import pandas as pd
9
+
10
+ from . import codegen, engine
11
+ from .clean import clean
12
+ from .modeling import train
13
+ from .engine import available_engines, choose_engine, inspect_source, sample, get_sample_size, set_sample_size
14
+ from .analyzers import overview, missing, numeric, categorical, correlation
15
+ from .analyzers import target as target_analyzer
16
+ from .report import EDAReport
17
+
18
+ __version__ = "0.4.0"
19
+ __all__ = [
20
+ "analyze",
21
+ "available_engines",
22
+ "clean",
23
+ "codegen",
24
+ "engine",
25
+ "get_sample_size",
26
+ "inspect_source",
27
+ "set_sample_size",
28
+ "train",
29
+ "EDAReport",
30
+ ]
31
+
32
+
33
+ def analyze(
34
+ source: Union[pd.DataFrame, str, Path],
35
+ n: Optional[int] = None,
36
+ *,
37
+ show: bool = False,
38
+ random_state: int = 42,
39
+ save_pdf: bool = True,
40
+ pdf_path: str = "flasheda_report.pdf",
41
+ engine: Optional[str] = None,
42
+ target: Optional[str] = None,
43
+ ) -> EDAReport:
44
+ """
45
+ Analyse a fixed-size sample of `source` (DataFrame, CSV/TSV or Parquet path).
46
+
47
+ `target` names the column you want to predict; it adds task detection,
48
+ class balance, per-feature predictive power and leakage checks.
49
+
50
+ `engine` forces the engine recommended for full-size work; by default it
51
+ is chosen from the dataset's row count.
52
+ """
53
+ t0 = time.perf_counter()
54
+ n = n or get_sample_size()
55
+
56
+ info = inspect_source(source)
57
+ if target is not None and target not in info.columns:
58
+ raise KeyError(f"Target column {target!r} not found. Columns: {info.columns}")
59
+ sampled = sample(source, n=n, random_state=random_state)
60
+ original_shape = info.shape
61
+
62
+ tasks = {
63
+ "overview": lambda: overview.analyze(sampled, original_shape),
64
+ "missing": lambda: missing.analyze(sampled),
65
+ "numeric": lambda: numeric.analyze(sampled),
66
+ "categorical": lambda: categorical.analyze(sampled),
67
+ "correlation": lambda: correlation.analyze(sampled),
68
+ }
69
+ if target is not None:
70
+ tasks["target"] = lambda: target_analyzer.analyze(sampled, target, random_state)
71
+
72
+ results: dict = {}
73
+ with ThreadPoolExecutor(max_workers=5) as pool:
74
+ future_map = {pool.submit(fn): name for name, fn in tasks.items()}
75
+ for future in as_completed(future_map):
76
+ name = future_map[future]
77
+ try:
78
+ results[name] = future.result()
79
+ except Exception as exc:
80
+ results[name] = {"error": str(exc)}
81
+
82
+ warnings: list[str] = []
83
+ miss = results.get("missing", {})
84
+ if miss.get("critical_columns"):
85
+ warnings.append(f"Columns with >50% missing: {', '.join(miss['critical_columns'])}")
86
+ num = results.get("numeric", {})
87
+ if num.get("skewed_columns"):
88
+ warnings.append(f"Highly skewed columns: {', '.join(num['skewed_columns'])}")
89
+ cat = results.get("categorical", {})
90
+ if cat.get("likely_id_columns"):
91
+ warnings.append(f"Likely ID columns: {', '.join(cat['likely_id_columns'])}")
92
+ corr = results.get("correlation", {})
93
+ if corr.get("numeric_strong_pairs"):
94
+ warnings.append(f"{len(corr['numeric_strong_pairs'])} strongly correlated pair(s) found")
95
+
96
+ tgt = results.get("target", {})
97
+ if "error" in tgt:
98
+ raise RuntimeError(f"Target analysis failed: {tgt['error']}")
99
+ if tgt.get("leakage_columns"):
100
+ cols = ", ".join(str(l["column"]) for l in tgt["leakage_columns"])
101
+ warnings.append(f"Possible target leakage: {cols}")
102
+ if tgt.get("imbalanced"):
103
+ warnings.append(
104
+ f"Imbalanced target: minority class is {tgt['minority_pct']}% "
105
+ f"(ratio {tgt['imbalance_ratio']}:1)"
106
+ )
107
+ if tgt.get("null_count"):
108
+ warnings.append(f"Target {target!r} is null in {tgt['null_pct']}% of sampled rows")
109
+
110
+ elapsed = round(time.perf_counter() - t0, 3)
111
+
112
+ report = EDAReport(
113
+ overview=results.get("overview", {}),
114
+ missing=results.get("missing", {}),
115
+ numeric=results.get("numeric", {}),
116
+ categorical=results.get("categorical", {}),
117
+ correlation=results.get("correlation", {}),
118
+ sample_size=len(sampled),
119
+ elapsed_seconds=elapsed,
120
+ original_shape=original_shape,
121
+ warnings=warnings,
122
+ target=tgt,
123
+ source=str(info.path) if info.path else None,
124
+ recommended_engine=choose_engine(info.n_rows, prefer=engine),
125
+ sample=sampled,
126
+ )
127
+
128
+ if show:
129
+ report.show()
130
+
131
+ if save_pdf:
132
+ report.save_pdf(pdf_path)
133
+
93
134
  return report
@@ -1,3 +1,3 @@
1
- from . import overview, missing, numeric, categorical, correlation
2
-
3
- __all__ = ["overview", "missing", "numeric", "categorical", "correlation"]
1
+ from . import overview, missing, numeric, categorical, correlation, target
2
+
3
+ __all__ = ["overview", "missing", "numeric", "categorical", "correlation", "target"]