edaprep
Transparent, leakage-safe EDA and ML preprocessing, with an explainable planner.
edaprep looks at a dataset, works out which preprocessing operations actually apply
to it, tells you what it intends to do and why, and then does it — fitting every
statistic on the training data alone.
import edaprep
pipe = edaprep.AutoPipeline(target="churn", model_family="tree", random_state=42)
pipe.fit(train_df)
pipe.explain()
X_train = pipe.transform(train_df)
X_test = pipe.transform(test_df)
income:
+ outliers_report - skew 3.22 is moderate (>= 1.0); IQR fence widened to k=3.0 for the asymmetry
+ impute_median - 2.0% missing; median rather than mean because it is unaffected by
the skew (3.22) and by outliers
+ transform_log1p - skew 3.22 is moderate and the column is non-negative (min 2576.27),
so log1p applies and is invertible
+ scale_robust - skew 3.22; robust scaling (median and IQR) rather than standard,
whose standard deviation is dominated by the tail
city:
+ group_rare_categories - 163 levels; those appearing in fewer than 5 rows (1.0%) are
grouped, since they cannot support a reliable estimate
+ encode_target - 163 levels exceeds the 50-level one-hot ceiling; target encoding is
used with 5-fold cross-fitting so no row is encoded using its own target
customer_id:
x dropped - identifier: 100.0% of values are distinct, so it cannot generalise beyond
the rows it was fitted on
Why it exists
Tabular ML notebooks converge on nearly the same workflow regardless of domain — and
they go wrong in the same places. docs/design-rationale.md
catalogues that workflow and names the failure modes; each one shaped a design decision
here:
- The dtype-based column split (
select_dtypes(include=['int64','float64'])) is the most-typed line in tabular data science and the largest single source of error: it sends a zip code and a temperature down the same path, and drops a numeric column stored as text entirely.edaprepinfers a semantic type and reports its confidence. - The IQR and z-score fences get rewritten in project after project, with the
multiplier drifting between 1.5 and 3.0 for no recorded reason. Both are now single
parameterised, named, reported operations — and the index-alignment bug that silently
flags the wrong rows when a column has any
NaNis fixed once, here. - Leakage is easy to introduce and hard to notice. Fitting a
StandardScaleron the full frame, writing the result to CSV, then splitting afterwards looks fine and poisons every downstream experiment.edaprepmakes it structurally impossible rather than merely discouraged. - Sophisticated pipelines end up routing, not sequencing — branching on skewness for
numerics and on the consuming model for categoricals, usually buried in a
make_preprocessorclosure. That became the planner, andmodel_family.
Installation
pip install edaprep # core: numpy, pandas, scipy
pip install "edaprep[visualization]" # + matplotlib
pip install "edaprep[advanced]" # + scikit-learn
pip install "edaprep[all]"
Python 3.9–3.13, tested on Linux, macOS and Windows.
What it does
Understand a dataset
profile = edaprep.profile(df, target="churn")
print(profile.summary())
Dataset
600 rows x 18 columns
300.3 KB in memory
628 missing cells (5.81%)
target: churn (classification, 2 classes, minority/majority ratio 0.232)
Semantic types
numeric 5
binary 4
categorical 3
...
Data-quality findings
[x] 1 column(s) are almost perfectly associated with the target (>= 0.98). This
usually means the column encodes the answer: 'leaky'.
[!] 2 column(s) contain placeholder strings that most likely mean 'missing' but are
not recognised as NaN: 'workclass', 'occupation'.
[!] 1 group(s) of identical columns: income=income_copy
[i] 1 column pair(s) go missing together, which usually means a shared cause:
income~income_copy (1.00)
Explore it
report = edaprep.EDA(df, target="churn").analyze("standard")
print(report.summary())
report.numerical # a DataFrame
report.to_html("eda.html")
Three levels that differ in work done, not just in what is shown: quick skips every
O(n log n) and O(p²) computation, standard adds moments, outliers, correlation and
target relationships, deep adds VIF and significance tests with a
Benjamini-Hochberg adjustment.
Prepare it
pipe = edaprep.AutoPipeline(target="churn", model_family="linear", random_state=42)
X_train = pipe.fit_transform(train_df)
X_test = pipe.transform(test_df)
pipe.plan_ # the decisions, serialisable and editable
pipe.report_ # what actually happened, with counts
pipe.transformations_ # one row per decision, as a DataFrame
pipe.statistics_ # every learned parameter
Or say exactly what should happen:
pipe = (
edaprep.Pipeline(target="churn")
.flag_missing()
.handle_outliers(strategy="clip")
.handle_missing()
.encode_categorical()
.scale_numeric()
)
Override anything
config = edaprep.Config(random_state=42)
config.column("age").imputation = "mean"
config.column("income").outlier_strategy = "clip"
config.column("city").encoding = "frequency"
config.column("zip").semantic_type = "categorical"
config.thresholds.skew_heavy = 4.0
pipe = edaprep.AutoPipeline(target="churn", config=config)
Overrides are tagged in the plan, so explain() marks them as yours rather than
presenting them as the planner's reasoning.
Design guarantees
No leakage, structurally. Learned state lives only in attributes written inside
fit; transform is a pure function of that state. The property is asserted directly:
a test transforms a frame whole and then row by row and requires identical output, which
fails immediately if anything recomputes a statistic at transform time.
Nothing silent. Dropped columns, imputed values, grouped categories, clipped rows
and unseen categories are all counted and reported. edaprep never calls
warnings.filterwarnings.
Everything explainable. Every automatic decision carries an English rationale naming the measurement behind it. The plan is inert, serialisable data — printable, diffable, storable next to a model artefact, and re-executable.
Reproducible. random_state seeds every stochastic step. The report records the
library version, the configuration, the seed, whether profiling sampled, and every
learned parameter.
Conservative. Outliers are reported, not deleted, by default. Duplicate rows are
reported, not removed — repeated observations are legitimate in transactional data.
Class imbalance is measured and reported; resampling is a modelling decision that
belongs after the split, so edaprep does not do it.
Performance
Measured, not asserted. See docs/performance.md.
| operation | edaprep | baseline |
|---|---|---|
Scaler (standard) |
6.9 ms | sklearn StandardScaler 26.7 ms |
MissingValueHandler (median) |
5.2 ms | sklearn SimpleImputer 17.0 ms |
OutlierHandler (IQR clip) |
25.2 ms | the usual IQR block 41.4 ms |
numeric_block_stats (20k × 300) |
578 ms | equivalent pandas loop 1056 ms |
AutoPipeline.transform |
240 ms / 35.6 MiB | ColumnTransformer 104 ms / 67.2 MiB |
100,000 rows unless stated. The most instructive result is one that went the other way:
a hand-written NumPy kernel in this library turned out to be 2.1× slower than the
pandas code it replaced, so it was deleted. That story is in docs/performance.md §1.
No native code. Nothing here is un-vectorisable, and the one place a hand-written kernel looked promising was slower than pandas.
Documentation
| Workflow mining | what 13 repositories revealed, and the 9 defects found |
| Architecture | package design, the planner, execution model |
| User guide | installation to production, with the train/test workflow |
| Performance | benchmarks, method, and what optimisation actually changed |
| Extending | custom transformers, rules and backends |
| Example | raw dataset to ML-ready, end to end |
Scope
In: dataset inspection, EDA, data quality, cleaning, missing values, duplicates, outliers, dtype inference, categorical encoding, numeric transformation, scaling, feature selection, datetime expansion, leakage-safe train/test preparation, pipelines, reporting.
Out, deliberately: model training, resampling, hyperparameter search, NLP,
forecasting, deep learning, distributed execution. Extension points exist for each
(docs/extending.md), and none is implemented in v1.
Contributing
Contributions are welcome, and CONTRIBUTING.md is written to make
the first one straightforward: it explains the architecture in a page, lists the rules
CI actually enforces, and points at issues scoped so that each one names the file to
change and the test to write.
Issues labelled good first issue
are a deliberate starting set. Comment to claim one.
pip install -e ".[dev]"
pytest # 358 tests, ~15s
ruff check src/ tests/ benchmarks/ examples/
python benchmarks/bench.py
Licence
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file edaprep-0.2.0.tar.gz.
File metadata
- Download URL: edaprep-0.2.0.tar.gz
- Upload date:
- Size: 165.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de363a8dce334920a9f50fff39949bf3738dc42d5b9b2091666325b6cb22a310
|
|
| MD5 |
5a9d5b4583ed9a9159da7514f8dd0df7
|
|
| BLAKE2b-256 |
aa67eb945efe553449ea8be88185e669721fe5fb754c6bd8df7f8b192aa1db7d
|
Provenance
The following attestation bundles were made for edaprep-0.2.0.tar.gz:
Publisher:
publish.yml on bijay-odyssey/edaprep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edaprep-0.2.0.tar.gz -
Subject digest:
de363a8dce334920a9f50fff39949bf3738dc42d5b9b2091666325b6cb22a310 - Sigstore transparency entry: 2597551905
- Sigstore integration time:
-
Permalink:
bijay-odyssey/edaprep@3fd9d8c1dad644e5c65f8337c93e633a71c34630 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/bijay-odyssey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fd9d8c1dad644e5c65f8337c93e633a71c34630 -
Trigger Event:
release
-
Statement type:
File details
Details for the file edaprep-0.2.0-py3-none-any.whl.
File metadata
- Download URL: edaprep-0.2.0-py3-none-any.whl
- Upload date:
- Size: 158.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a6e3a587ec08033e1933cf8d213c9ff9feb92b0616fbd2cc5c71af34c965725
|
|
| MD5 |
e915122392e94a5974345d37e952046f
|
|
| BLAKE2b-256 |
26bb964b89af810d502170e8938de5ef1638187db1c7b80ac57da5c051debcb9
|
Provenance
The following attestation bundles were made for edaprep-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on bijay-odyssey/edaprep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edaprep-0.2.0-py3-none-any.whl -
Subject digest:
9a6e3a587ec08033e1933cf8d213c9ff9feb92b0616fbd2cc5c71af34c965725 - Sigstore transparency entry: 2597552223
- Sigstore integration time:
-
Permalink:
bijay-odyssey/edaprep@3fd9d8c1dad644e5c65f8337c93e633a71c34630 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/bijay-odyssey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fd9d8c1dad644e5c65f8337c93e633a71c34630 -
Trigger Event:
release
-
Statement type: