Skip to content

Commit 3cb3529

Browse files
authored
Merge pull request #1 from data-centt/main
Update
2 parents 56c1f9b + aa2785a commit 3cb3529

18 files changed

Lines changed: 3266 additions & 217 deletions

.github/workflows/docs.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Deploy Docs
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
deploy:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Configure Git Credentials
17+
run: |
18+
git config user.name "github-actions[bot]"
19+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
20+
21+
- uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.x"
24+
25+
- name: Cache MkDocs build
26+
uses: actions/cache@v4
27+
with:
28+
key: mkdocs-material-${{ github.sha }}
29+
path: .cache
30+
restore-keys: |
31+
mkdocs-material-
32+
33+
- name: Install docs dependencies
34+
run: pip install mkdocs-material
35+
36+
- name: Build and deploy to gh-pages
37+
run: mkdocs gh-deploy --force

.github/workflows/python-app.yml

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,54 @@ permissions:
1010
contents: read
1111

1212
jobs:
13-
build:
13+
lint:
1414
runs-on: ubuntu-latest
15-
1615
steps:
1716
- uses: actions/checkout@v4
18-
1917
- name: Set up Python
2018
uses: actions/setup-python@v5
2119
with:
22-
python-version: "3.10"
23-
24-
- name: Install deps and package
25-
run: |
26-
python -m pip install --upgrade pip
27-
python -m pip install -e . pytest flake8
28-
20+
python-version: "3.11"
21+
- name: Install flake8
22+
run: python -m pip install --upgrade pip flake8
2923
- name: Lint with flake8
3024
run: |
3125
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
3226
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3327
28+
test:
29+
runs-on: ubuntu-latest
30+
name: test (py${{ matrix.python-version }}, pandas ${{ matrix.pandas }})
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
include:
35+
# Python version coverage, on the latest dependencies.
36+
- { python-version: "3.10", pandas: latest, pin: "" }
37+
- { python-version: "3.11", pandas: latest, pin: "" }
38+
- { python-version: "3.12", pandas: latest, pin: "" }
39+
- { python-version: "3.13", pandas: latest, pin: "" }
40+
# pandas version coverage, on a stable Python.
41+
- { python-version: "3.11", pandas: "2.0.3", pin: "pandas==2.0.3 numpy==1.26.4" }
42+
- { python-version: "3.11", pandas: "2.1.4", pin: "pandas==2.1.4 numpy==1.26.4" }
43+
steps:
44+
- uses: actions/checkout@v4
45+
46+
- name: Set up Python ${{ matrix.python-version }}
47+
uses: actions/setup-python@v5
48+
with:
49+
python-version: "${{ matrix.python-version }}"
50+
51+
- name: Upgrade pip
52+
run: python -m pip install --upgrade pip
53+
54+
- name: Pin dependency versions
55+
if: matrix.pin != ''
56+
run: python -m pip install ${{ matrix.pin }}
57+
58+
- name: Install package and test tools
59+
# polars requires Python 3.10+; pyarrow powers the polars <-> pandas bridge.
60+
run: python -m pip install -e . pytest polars pyarrow
61+
3462
- name: Test with pytest
35-
run: |
36-
pytest -q
63+
run: pytest -q

README.md

Lines changed: 74 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,112 @@
1-
# Percentify
21

3-
[![Downloads](https://static.pepy.tech/badge/percentify)](https://pepy.tech/project/percentify )
2+
<p align="center">
3+
<img src="https://raw.githubusercontent.com/data-centt/percentify/main/asset/log.png" alt="Percentify logo" height="150">
4+
</p>
5+
46
[![PyPI version](https://img.shields.io/pypi/v/percentify.svg?style=flat&color=blue)](https://pypi.org/project/percentify/)
5-
[![Python Versions](https://img.shields.io/pypi/pyversions/percentify.svg?style=flat&color=green)](https://pypi.org/project/percentify/)
7+
[![Python Version](https://img.shields.io/badge/python-%3E%3D3.10-green?style=flat)](https://pypi.org/project/percentify/)
68
[![License](https://img.shields.io/pypi/l/percentify.svg?style=flat&color=orange)](LICENSE)
9+
[![Docs](https://img.shields.io/badge/docs-percentify-14b8a6)](https://data-centt.github.io/percentify/)
710
[![Build Status](https://github.com/data-centt/percentify/actions/workflows/python-app.yml/badge.svg)](https://github.com/data-centt/percentify/actions/workflows/python-app.yml)
11+
[![Polars](https://img.shields.io/badge/Polars-supported-cd792c?style=flat)](https://data-centt.github.io/percentify/)
812

9-
**Percentify is a niche data science library for practitioners and learners alike, drawing its main dependencies from pandas and numpy, and including everyday statistics.**
10-
11-
Following the **Pareto principle**, Percentify brings the 20% of operations that make up 80% of daily data work to the forefront, each as a single, readable function call. No more digging through six-line recipes and hard-to-remember import paths for the checks you run on every dataset.
12-
13-
Percentify **does not aim to compete** with pandas, scipy, statsmodels, or scikit-learn; it stands on their shoulders and works *alongside* them. The goal is to make the core concepts easy to learn, quick to use, and simple to remember. Every function names the underlying library it draws from, so the moment you need the full, configurable version, you know exactly where to go.
14-
15-
Every function takes a pandas `DataFrame` (or `Series`) and hands back a clean `DataFrame` you can read, sort, or feed straight into the next step.
16-
17-
---
18-
19-
## 📦 Installation
20-
```
21-
pip install percentify
22-
```
23-
24-
Requires `numpy` and `pandas`.
13+
**80% of the checks you run on every dataset. 20% of the code.**
2514

26-
---
15+
Exploratory stats and data-quality diagnostics for pandas and **Polars** DataFrames. One call each.
2716

28-
## 📊 The Toolkit
17+
> [!TIP]
18+
> **⚡ Polars is first-class, not an afterthought.** Pass a Polars DataFrame or Series and get the same kind straight back, with no flag and no manual conversion. Every function works on both backends, which is what sets Percentify apart from the pandas-only tools.
2919
30-
### `change`: Percentage Change
31-
Two numbers, two columns, or a whole series at once.
32-
```python
33-
from percentify import change
20+
Where a function wraps an existing library (pandas, scipy, statsmodels, scikit-learn), it names it, so you always know where to dig deeper.
3421

35-
change(100, 150) # → 50.0 (a 50% increase)
22+
## `profiler`
3623

37-
change(df["forecast"], df["actual"]) # element-wise % change between two columns
24+
**pandas `.describe()` tells you what your data _is_. `profiler()` tells you what to _do_ about it:** every issue ranked worst-first, each with its fix.
3825

39-
change(df["revenue"]) # period-over-period % change down the column
40-
# 0 NaN
41-
# 1 50.0
42-
# 2 -40.0
43-
44-
change(df) # every numeric column at once
45-
```
46-
47-
### `vif`: Variance Inflation Factor (Multicollinearity)
48-
The classic multicollinearity check, without the six-line loop.
49-
> _Similar Concept:_ `statsmodels.stats.outliers_influence.variance_inflation_factor`
5026
```python
51-
from percentify import vif
27+
from percentify import profiler
5228

53-
vif(df)
54-
# feature VIF
55-
# 0 income 8.40
56-
# 1 debt 7.90
57-
# 2 age 1.20
29+
report = profiler(df, target="churn")
5830

59-
vif(df, flag=5.0) # only rows above the threshold (your problem columns)
31+
report.to_frame() # every finding, ranked worst-first, with a suggested fix
32+
report.errors # just the blocking issues
33+
report.health # a 0 to 100 data-health score
34+
assert not report.errors # drop it straight into a CI data-quality gate
6035
```
6136

62-
### `missing`: Missing Data Profiling
63-
No more `df.isnull().sum() / len(df) * 100`.
64-
> _Underlying library:_ `pandas.DataFrame.isna`
65-
```python
66-
from percentify import missing
67-
68-
missing(df)
69-
# column missing_pct
70-
# 0 salary 12.40
71-
# 1 age 3.10
72-
# 2 name 0.00
73-
```
37+
Point it at any messy DataFrame, pandas or Polars, and see what it flags before you model. [Try it on your own data →](https://data-centt.github.io/percentify/documentation/#profiler)
7438

75-
### `cv`: Coefficient of Variation
76-
Relative variability (std ÷ mean), as a percentage.
77-
> _Underlying library:_ `scipy.stats.variation`
78-
```python
79-
from percentify import cv
39+
## 📖 Documentation
8040

81-
cv(df["salary"]) # → 34.2 (a single Series returns a number)
82-
cv(df) # → DataFrame of every numeric column, most variable first
83-
```
41+
**Full guide, every function, and live examples → [data-centt.github.io/percentify](https://data-centt.github.io/percentify/)**
8442

85-
### `outliers`: Percentage of Outliers (IQR Method)
86-
Stop rewriting the IQR bounds from scratch.
87-
> _Similar Concept:_ `scipy.stats.iqr`
88-
```python
89-
from percentify import outliers
43+
## 📦 Installation
9044

91-
outliers(df["salary"]) # → 4.7
92-
outliers(df) # → DataFrame of every numeric column
45+
```bash
46+
pip install percentify
9347
```
9448

95-
### `r_squared`: R-Squared
96-
```python
97-
from percentify import r_squared
49+
Requires Python 3.10+, `numpy`, and `pandas` 2.0+.
9850

99-
r_squared(y_true, y_pred) # → 87.3
100-
```
101-
> _Similar Concepts:_ `sklearn.metrics.r2_score`
51+
## Quick example
10252

103-
### `pca_variance`: PCA Variance Breakdown
104-
Columns are standardized by default, so a feature measured in large units (e.g.
105-
dollars) can't dominate the result just because of its scale. Pass
106-
`standardize=False` for covariance-based PCA on the raw values.
107-
> _Similar Concept:_ `sklearn.decomposition.PCA` (`.explained_variance_ratio_`)
10853
```python
109-
from percentify import pca_variance
54+
import pandas as pd
55+
from percentify import missing
11056

111-
pca_variance(df)
112-
# component variance_explained cumulative
113-
# 0 PC1 45.2 45.2
114-
# 1 PC2 23.1 68.3
115-
# 2 PC3 12.8 81.1
57+
df = pd.DataFrame({
58+
"salary": [50000, None, 60000, None],
59+
"age": [25, 30, None, 40],
60+
"city": ["NY", "LA", "SF", "LA"],
61+
})
11662

117-
pca_variance(df, standardize=False) # covariance-based (scale-sensitive)
63+
missing(df)
64+
# column missing_pct
65+
# 0 salary 50.0
66+
# 1 age 25.0
67+
# 2 city 0.0
11868
```
11969

120-
---
70+
One import, one line. A clean, sorted DataFrame you can read or feed into the next step.
71+
72+
## What's inside
73+
74+
| Function | What it answers |
75+
|---|---|
76+
| `profiler` | What is wrong with this dataset, and how do I fix it? |
77+
| `change` | Growth as numbers, columns, or a whole series |
78+
| `vif` | Which features are collinear? |
79+
| `missing` | How much of each column is missing? |
80+
| `cv` | How variable is each column, relative to its mean? |
81+
| `outliers` | What percentage of each column are outliers? |
82+
| `pca_variance` | How much variance does each principal component explain? |
83+
| `pca_loadings` | What does each principal component consist of? |
84+
| `imbalance` | How skewed are the classes in a target column? |
85+
| `correlate` | Which features move together, and is it significant? |
86+
| `skew_report` | How skewed is each column, and what transform helps? |
87+
| `bootstrap_ci` | What is the confidence interval for a statistic? |
88+
| `permutation_test` | Are two groups really different? (a p-value) |
89+
| `effect_size` | How big is the difference, not just whether it is significant? |
90+
| `difference` | How far apart are two values or columns? |
91+
| `split` | How does a total divide across weights or groups? |
92+
| `display` | Format numbers or a column as clean "%" strings |
93+
94+
→ See the **[documentation](https://data-centt.github.io/percentify/)** for a worked, real-output example of every function.
12195

12296
## 🛟 Friendly by design
12397

124-
Built for early-career analysts as much as seasoned ones:
125-
126-
- **No cryptic tracebacks.** Hand it a text column where numbers are needed and you get a clear `PercentifyWarning` ("numeric columns required") plus an empty result — not an Arrow/NumPy stack trace.
127-
- **Sensible defaults.** Results come back sorted worst-first (most collinear, most missing, most variable), and PCA is standardized out of the box.
128-
- **DataFrames everywhere**, so the output drops straight into your notebook, your next filter, or your model.
129-
130-
Catch or silence the warnings like any other:
131-
```python
132-
import warnings
133-
from percentify import PercentifyWarning
134-
135-
warnings.filterwarnings("ignore", category=PercentifyWarning)
136-
```
98+
- **No cryptic tracebacks**; Hand a function a text column where numbers are needed and you get a clear PercentifyWarning, not an Arrow/NumPy stack trace.
99+
- **Sensible defaults**; Results come back sorted worst-first, and PCA is standardized out of the box.
100+
- **DataFrames everywhere**; so the output drops straight into your notebook, your next filter, or your model.
101+
- **Pandas or polars**; pass either a pandas or polars object and you get the same kind back, no flag needed.
137102

138-
---
139103

140104
## 🤝 Contributing
141105

142-
Contributions are welcome — but they must follow the repo's guiding principle:
106+
Contributions are welcome but they must follow the repo's guiding principle:
107+
> Keep each method as direct-to-output as possible. A percentify function should return the single most common answer in one line, and point users to the underlying library (pandas, scipy, statsmodels, scikit-learn) for the full, configurable version when the simplest output isn't what they're after.
143108
144-
> **Keep each method as direct-to-output as possible.** A percentify function should return the single most common answer in one line, and point users to the underlying library (pandas, scipy, statsmodels, scikit-learn) for the full, configurable version when the simplest output isn't what they're after.
109+
**It must support polars.** Every function accepts both pandas and polars objects (via the `@_backend_aware` decorator) and returns the same kind, so any new contribution must keep that parity.
145110

146111
If your idea keeps things that simple and direct:
147112
- Open an issue first to discuss it
@@ -150,4 +115,5 @@ If your idea keeps things that simple and direct:
150115
- Commit your changes
151116
- Open a pull request
152117

153-
> Anything that adds knobs and options for their own sake, or duplicates what the parent libraries already do well, is out of scope please those cases should point to the source library instead.
118+
> Anything that adds knobs and options for their own sake, or duplicates what the parent libraries already do well, is out of scope; those cases should point to the source library instead.
119+
I try to keep it compact on purpose, to discuss big new features first.

asset/favicon.png

8.38 KB
Loading

asset/log.png

78.7 KB
Loading

docs/asset/favicon.png

8.38 KB
Loading

docs/asset/log.png

78.7 KB
Loading

0 commit comments

Comments
 (0)