pandas + Pandera DataFrame schema validation: working versions (tested 2026-10-15)
Validate pandas DataFrames with Pandera DataFrameModel schemas: dtypes, coercion, value checks, custom dataframe checks and lazy error reports.
passed3 passed · 0 failed2026-10-15 11:55 UTC- Status
- passed
- Last passed
- 2026-10-15 11:55 UTC
- Tests
- 3 / 3
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("pandas-pandera-validation") (install), or see them on GitHub.
pandas==3.0.6 pandera==0.34.1 pytest==9.1.1
import pandas as pd
import pandera.pandas as pa
from pandera.typing.pandas import Series
class Orders(pa.DataFrameModel):
order_id: Series[int] = pa.Field(unique=True, ge=1)
email: Series[str] = pa.Field(str_matches=r"^[^@]+@[^@]+$")
quantity: Series[int] = pa.Field(ge=1, le=1000)
unit_price: Series[float] = pa.Field(gt=0)
status: Series[str] = pa.Field(isin=["new", "paid", "shipped"])
class Config:
coerce = True # "3" -> 3, etc.
strict = True # unknown columns are errors
@pa.dataframe_check
def shipped_orders_are_small(cls, df: pd.DataFrame) -> Series[bool]:
return ~((df["status"] == "shipped") & (df["quantity"] > 500))
def clean_orders(df: pd.DataFrame) -> pd.DataFrame:
return Orders.validate(df, lazy=True) # lazy=True collects every failure
import pandas as pd
import pandera.errors as pe
import pytest
from schemas import clean_orders
def frame(**over):
base = {"order_id": [1, 2], "email": ["a@x.io", "b@x.io"], "quantity": ["3", "4"], "unit_price": [9.5, 1.0], "status": ["new", "paid"]}
base.update(over)
return pd.DataFrame(base)
def test_valid_and_coerced():
out = clean_orders(frame())
assert out["quantity"].tolist() == [3, 4]
assert str(out["quantity"].dtype).startswith("int")
def test_lazy_collects_all_errors():
with pytest.raises(pe.SchemaErrors) as e:
clean_orders(frame(email=["bad", "b@x.io"], status=["new", "lost"], order_id=[1, 1]))
cols = set(e.value.failure_cases["column"].dropna())
assert {"email", "status", "order_id"} <= cols
def test_strict_and_dataframe_check():
with pytest.raises(pe.SchemaErrors):
clean_orders(frame(extra=[1, 2]))
with pytest.raises(pe.SchemaErrors):
clean_orders(frame(status=["shipped", "new"], quantity=[600, 1]))
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
pandas DataFrame to formatted Excel report with openpyxl styles and roundtrip read
Write a DataFrame to a styled .xlsx with openpyxl (header styles, number formats, widths, freeze panes, filters), then read it back with pandas and verify, fully offline.
Pandas pyarrow-backed dtypes with partitioned Parquet round trips
Write and read partitioned Parquet with pyarrow, enforce a pa.schema on write, and keep nullable ints intact using dtype_backend=pyarrow, tested offline on tmp_path.
Polars DataFrame validation with pandera, collecting all errors
Validate Polars DataFrames with a pandera.polars DataFrameModel, collect every failing check with lazy validation, and test the schema offline with in-memory frames.
scikit-learn Pipeline with ColumnTransformer on pandas, GridSearchCV and joblib
A scikit-learn Pipeline that scales numeric columns and one-hot encodes categories inside a ColumnTransformer on a pandas DataFrame, tuned with GridSearchCV, then saved and reloaded with joblib. Tested offline on small in-memory data.