jhb-dataclean
Small, predictable functions to clean pandas DataFrames: column names, free text, data types, missing values, duplicates and outliers, plus reading and writing CSV, Excel and Parquet files.
Every function takes a DataFrame and returns a new one: your input is never modified, so the steps chain naturally.
Installation
pip install jhb-dataclean
CSV support comes with pandas. Excel and Parquet need an optional extra:
pip install "jhb-dataclean[excel]" # .xlsx files (openpyxl)
pip install "jhb-dataclean[parquet]" # .parquet files (pyarrow)
pip install "jhb-dataclean[all]" # both
Requires Python 3.11+ and pandas 2.2+ (pandas 3 is supported).
Quick start
>>> import pandas as pd
>>> import jhb_dataclean as dc
>>> raw = pd.DataFrame(
... {
... " Order ID ": [1, 2, 2, 3, 4],
... "City": [" Madrid ", "MÁLAGA", "MÁLAGA", "Barcleona", None],
... "orderDate": [
... "2024-01-15",
... "16/01/2024",
... "16/01/2024",
... "2024-01-17",
... "2024-01-18",
... ],
... "Amount": ["100.5", "250", "250", "N/A", "99000"],
... "Paid": ["yes", "no", "no", "SI", "1"],
... }
... )
>>> df = dc.standardize_columns(raw)
>>> df = dc.normalize_text(df, remove_accents=True, replacements={"barcleona": "barcelona"})
>>> df = dc.convert_types(
... df, {"order_date": "datetime", "amount": "numeric", "paid": "boolean"}
... )
>>> df = dc.remove_duplicates(df, ["order_id"])
>>> df = dc.handle_missing(df, {"city": "unknown", "amount": "median"})
>>> df = dc.detect_outliers(df, ["amount"])
>>> df.columns.tolist()
['order_id', 'city', 'order_date', 'amount', 'paid', 'amount_outlier']
>>> df["city"].tolist()
['madrid', 'malaga', 'barcelona', 'unknown']
>>> df["amount"].tolist()
[100.5, 250.0, 250.0, 99000.0]
>>> df["amount_outlier"].tolist()
[False, False, False, True]
Functions
| Function | What it does |
|---|---|
read_data |
Read a .csv, .xlsx or .parquet file, choosing the format from the extension |
standardize_columns |
Turn column names into snake_case without accents or symbols |
normalize_text |
Trim spaces, fix case, remove accents and replace known typos |
convert_types |
Convert columns to datetime, numeric, boolean or string |
handle_missing |
Drop rows or fill missing values with mean, median, mode or a value |
remove_duplicates |
Remove duplicated rows, choosing which copy to keep |
detect_outliers |
Flag outliers with the IQR or z-score method |
write_data |
Write a DataFrame to .csv, .xlsx or .parquet |
Every function has a full docstring: run help(dc.convert_types) to see all
its options.
standardize_columns
>>> df = pd.DataFrame(
... columns=[" Fecha Pedido ", "precioUnitario", "Categoría", "Precio (€)"]
... )
>>> dc.standardize_columns(df).columns.tolist()
['fecha_pedido', 'precio_unitario', 'categoria', 'precio']
It raises ValueError if two columns would end up with the same name.
normalize_text
By default it works on every text column: it trims and collapses spaces, lowercases, and turns empty strings into missing values.
>>> df = pd.DataFrame({"city": [" Málaga ", "MALAGA", "Barcleona", " "]})
>>> clean = dc.normalize_text(
... df, remove_accents=True, replacements={"barcleona": "barcelona"}
... )
>>> clean["city"].tolist()[:3]
['malaga', 'malaga', 'barcelona']
>>> clean["city"].isna().tolist()
[False, False, False, True]
Use case="upper", case="title" or case=None to change the case rule.
convert_types
Pass a mapping of column to target type: "datetime", "numeric",
"boolean" or "string". Values that cannot be converted become missing.
>>> df = pd.DataFrame(
... {
... "date": ["2024-01-15", "15/03/2024"],
... "amount": ["1500.50", "N/A"],
... "active": ["sí", "no"],
... "zip_code": [28001, 8001],
... }
... )
>>> types = {
... "date": "datetime",
... "amount": "numeric",
... "active": "boolean",
... "zip_code": "string",
... }
>>> result = dc.convert_types(df, types, zfill={"zip_code": 5})
>>> result["date"].dt.strftime("%Y-%m-%d").tolist()
['2024-01-15', '2024-03-15']
>>> result["active"].tolist()
[True, False]
>>> result["zip_code"].tolist()
['28001', '08001']
- Dates are read day first (
15/03/2024is 15 March); ISO dates like2024-04-10are always read correctly. Usedayfirst=Falsefor month-first data. - Booleans accept
si/yes/true/1andno/false/0, ignoring case and accents. Change them withtrue_valuesandfalse_values. zfillrestores leading zeros in codes such as zip codes.
handle_missing
Pass a mapping of column to strategy: "drop", "mean", "median", "mode"
or any other value to fill with. Rows are dropped before the statistics
are computed.
>>> df = pd.DataFrame(
... {
... "customer": ["Ana", "Carlos", None, "Pedro"],
... "purchases": [5, 12, 4, None],
... "city": ["Madrid", None, "Madrid", "Sevilla"],
... }
... )
>>> result = dc.handle_missing(
... df, {"customer": "drop", "purchases": "median", "city": "unknown"}
... )
>>> result["purchases"].tolist()
[5.0, 12.0, 8.5]
>>> result["city"].tolist()
['Madrid', 'unknown', 'Sevilla']
To fill with a value that is also a strategy name (for example the text
"median"), use fill_values={"level": "median"}.
remove_duplicates
By default only exact duplicates are removed. Pass the key columns to compare
only those, and order_by to choose which copy to keep:
>>> df = pd.DataFrame(
... {
... "order_id": [1, 2, 1],
... "amount": [100, 250, 120],
... "date": ["2024-01-20", "2024-01-16", "2024-01-15"],
... }
... )
>>> dc.remove_duplicates(df, ["order_id"], keep="last", order_by="date")["amount"].tolist()
[100, 250]
The result keeps the original row order and index.
detect_outliers
Rows are never removed: a boolean column <column>_outlier is added so you can
decide what to do.
>>> df = pd.DataFrame({"price": [25, 30, 28, 5000, 22, 31, 27]})
>>> dc.detect_outliers(df)["price_outlier"].tolist()
[False, False, False, True, False, False, False]
>>> int(dc.detect_outliers(df, method="zscore", threshold=2)["price_outlier"].sum())
1
The IQR method (default, threshold=1.5) works well with few rows. The
z-score method (threshold=3) needs more data: with 7 rows no value can
reach a z-score of 3.
read_data and write_data
df = dc.read_data("sales.csv", sep=";")
df = dc.read_data("sales.xlsx", sheet_name="2024")
dc.write_data(df, "sales_clean.parquet")
Extra arguments go straight to the pandas function (pd.read_csv,
df.to_excel...). The index is not written unless you pass index=True.
Things to know about file formats
Each format keeps types differently, so the same data may come back with
different types. Pass the result through convert_types to get the same
DataFrame from any format.
- CSV stores everything as text:
"N/A"and empty cells are read as missing, and codes like08001lose their leading zero unless you passdtype={"zip_code": str}. - Excel may return numbers and dates already typed, drops leading zeros, and does not keep a final row that is completely empty.
- Parquet keeps the original types.
Development
The project uses pixi to manage the environment:
pixi install
pixi run test # pytest
pixi run lint # ruff
License
Metadata
Release files for jhb-dataclean 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| jhb_dataclean-0.1.0.tar.gz | 27.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jhb_dataclean-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.0 kB
Release files / jhb_dataclean-0.1.0.tar.gz
| Download URL | jhb_dataclean-0.1.0.tar.gz |
|---|---|
| Size | 27.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
65fb21a53b51cc569ff1cf8b057071ca2f6d613263ec05004e476827146852aa
|
|
BLAKE2b-256 checksum How to use checksums |
181c7da946d85ec299ee1ef4512f254e81e07db9a6b7a4e17ee8ff1dfb164a1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / jhb_dataclean-0.1.0-py3-none-any.whl
| Download URL | jhb_dataclean-0.1.0-py3-none-any.whl |
|---|---|
| Size | 21.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
776dfc89cb34485cc6f85790f146330ee858ceb9a86cc7f4594267642ea53d18
|
|
BLAKE2b-256 checksum How to use checksums |
986d8b57f5dc1c10e8b7f4b78b8748e8d658c416bed7744fc821507891fab6a3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for jhb-dataclean 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for jhb_dataclean-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.