Skip to main content

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/2024 is 15 March); ISO dates like 2024-04-10 are always read correctly. Use dayfirst=False for month-first data.
  • Booleans accept si/yes/true/1 and no/false/0, ignoring case and accents. Change them with true_values and false_values.
  • zfill restores 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 like 08001 lose their leading zero unless you pass dtype={"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

Apache-2.0

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)

Source distribution for jhb-dataclean 0.1.0
File Size Uploaded Provenance
jhb_dataclean-0.1.0.tar.gz 27.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for jhb-dataclean 0.1.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page