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.
- flasheda-0.4.0/MIT.License +21 -0
- flasheda-0.4.0/PKG-INFO +182 -0
- flasheda-0.4.0/README.md +117 -0
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/__init__.py +133 -92
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/__init__.py +3 -3
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/categorical.py +78 -75
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/correlation.py +94 -90
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/missing.py +53 -53
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/numeric.py +70 -70
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/analyzers/overview.py +65 -65
- flasheda-0.4.0/flasheda/analyzers/target.py +230 -0
- flasheda-0.4.0/flasheda/clean/__init__.py +48 -0
- flasheda-0.4.0/flasheda/clean/detectors.py +124 -0
- flasheda-0.4.0/flasheda/codegen/__init__.py +91 -0
- flasheda-0.4.0/flasheda/codegen/planner.py +170 -0
- flasheda-0.4.0/flasheda/codegen/steps.py +304 -0
- flasheda-0.4.0/flasheda/codegen/validate.py +107 -0
- flasheda-0.4.0/flasheda/codegen/writers/__init__.py +22 -0
- flasheda-0.4.0/flasheda/codegen/writers/base.py +86 -0
- flasheda-0.4.0/flasheda/codegen/writers/duckdb_writer.py +147 -0
- flasheda-0.4.0/flasheda/codegen/writers/pandas_writer.py +87 -0
- flasheda-0.4.0/flasheda/codegen/writers/polars_writer.py +126 -0
- flasheda-0.4.0/flasheda/codegen/writers/pyspark_writer.py +99 -0
- flasheda-0.4.0/flasheda/engine/__init__.py +23 -0
- flasheda-0.4.0/flasheda/engine/backends.py +67 -0
- flasheda-0.4.0/flasheda/engine/sampler.py +125 -0
- flasheda-0.4.0/flasheda/engine/source.py +110 -0
- flasheda-0.4.0/flasheda/features/__init__.py +169 -0
- flasheda-0.4.0/flasheda/modeling/__init__.py +230 -0
- flasheda-0.4.0/flasheda/modeling/script.py +138 -0
- flasheda-0.4.0/flasheda/modeling/specs.py +79 -0
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda/report.py +688 -521
- flasheda-0.4.0/flasheda/sampler.py +9 -0
- flasheda-0.4.0/flasheda.egg-info/PKG-INFO +182 -0
- flasheda-0.4.0/flasheda.egg-info/SOURCES.txt +44 -0
- flasheda-0.4.0/flasheda.egg-info/requires.txt +34 -0
- {flasheda-0.1.3 → flasheda-0.4.0}/pyproject.toml +2 -2
- {flasheda-0.1.3 → flasheda-0.4.0}/setup.cfg +4 -4
- flasheda-0.4.0/setup.py +52 -0
- flasheda-0.4.0/tests/test_clean_target.py +167 -0
- flasheda-0.4.0/tests/test_codegen.py +66 -0
- flasheda-0.4.0/tests/test_engine.py +77 -0
- flasheda-0.4.0/tests/test_features_model.py +138 -0
- flasheda-0.4.0/tests/test_writers.py +172 -0
- flasheda-0.1.3/PKG-INFO +0 -49
- flasheda-0.1.3/README.md +0 -21
- flasheda-0.1.3/flasheda/sampler.py +0 -124
- flasheda-0.1.3/flasheda.egg-info/PKG-INFO +0 -49
- flasheda-0.1.3/flasheda.egg-info/SOURCES.txt +0 -17
- flasheda-0.1.3/flasheda.egg-info/requires.txt +0 -7
- flasheda-0.1.3/setup.py +0 -27
- {flasheda-0.1.3 → flasheda-0.4.0}/flasheda.egg-info/dependency_links.txt +0 -0
- {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.
|
flasheda-0.4.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/flasheda/)
|
|
71
|
+
[](https://pypi.org/project/flasheda/)
|
|
72
|
+
[](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml)
|
|
73
|
+
[](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
|
flasheda-0.4.0/README.md
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/flasheda/)
|
|
6
|
+
[](https://pypi.org/project/flasheda/)
|
|
7
|
+
[](https://github.com/sjapanjots/Flash-EDA/actions/workflows/tests.yml)
|
|
8
|
+
[](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 .
|
|
11
|
-
from .
|
|
12
|
-
from .
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
|
|
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"]
|