Target analysis & quality suite — target_analysis, quality_report, measures, suite¶
This notebook covers four target-driven APIs that round out the EDA
layer and complement the one-call profile() flagship:
| API | Purpose | When to use |
|---|---|---|
quality_report(df) |
Focused data-quality scan | CI gate — fast, no catdap2 / association_matrix |
target_analysis(df, response) |
ΔAIC ranking + top-K TargetSummary | Drill into a chosen response without the full profile sweep |
pycatdap.measures.* |
Pluggable association measures (aic, cramers_v, mutual_info, register) | association_matrix(measure=...) and downstream interop (pysubgroup / DivExplorer) |
pycatdap.suite.AICIndependenceSuite |
deepchecks-style CI suite | assert suite_result.passed, suite_result.summary() in pytest |
This notebook walks through all four on the Titanic dataset.
import seaborn as sns
import pycatdap
df = sns.load_dataset("titanic")
print(df.shape, "columns:", list(df.columns))
1. quality_report(df) — fast quality scan¶
quality_report shares the warning logic with profile() but skips
the heavy association_matrix / catdap2 passes, so it stays linear
in n_rows × n_cols. The result exposes a .passed boolean designed
for assert qr.passed in pytest / CI.
qr = pycatdap.quality_report(df)
print("passed:", qr.passed)
print("warnings:", len(qr.warnings))
qr.show()
# Tighten thresholds to flip the report and surface findings.
strict = pycatdap.quality_report(
df,
quality_thresholds={"high_missing": 0.1},
)
print("passed:", strict.passed)
strict.by_kind()
2. target_analysis(df, response) — ranked ΔAIC + top-K¶
target_analysis is the target-driven counterpart of profile(). It
ranks every non-response column by ΔAIC against response and keeps
the full TargetSummary for the top-K most informative columns —
perfect when you already know the response of interest.
ta = pycatdap.target_analysis(df, response="survived", top_k=3)
ta.ranking
# The top-K summaries carry full cross-tabulation, not just ΔAIC.
top = next(iter(ta.top_summaries))
summary = ta.top_summaries[top]
print(f"Top explanatory: {top} (ΔAIC = {summary.delta_aic:.2f})")
summary.counts
# response_card describes the response column itself
# (kind, cardinality, top value, etc.).
ta.response_card
3. pycatdap.measures — pluggable association measures¶
The measures module ships aic, cramers_v, and mutual_info with a
uniform signature Callable[[np.ndarray], float] (cross-frequency
table → scalar). Custom measures register at import time via
pycatdap.measures.register(name, fn) for pysubgroup / DivExplorer
interop (Issues #31 / #32).
import numpy as np
# Same input → three different association scores.
cf = np.array([[100.0, 20.0], [10.0, 90.0]])
print(f"aic = {pycatdap.measures.aic(cf):.3f}")
print(f"cramers_v = {pycatdap.measures.cramers_v(cf):.3f}")
print(f"mutual_info = {pycatdap.measures.mutual_info(cf):.3f} (nats)")
print()
print("Registered measures:", pycatdap.measures.list_measures())
association_matrix(measure=...) extension¶
The same measure names drop straight into association_matrix. The
default measure="aic" keeps its existing behavior (continuous
targets still work via regression AIC); non-AIC measures
use a generic crosstab path that bins continuous columns via
pd.qcut.
small = df[["survived", "sex", "pclass", "embarked"]]
m_aic = pycatdap.association_matrix(small, measure="aic")
m_cv = pycatdap.association_matrix(small, measure="cramers_v")
print("ΔAIC matrix:")
print(m_aic.round(2))
print()
print("Cramér's V matrix:")
print(m_cv.round(3))
4. pycatdap.suite.AICIndependenceSuite — CI integration¶
The suite is the deepchecks-style API per Issue #15. Every Check is
a @dataclass(frozen=True) — there is no eval() / string DSL,
so the suite is safe to run in CI against untrusted DataFrames.
The standard AICIndependenceSuite bundles four checks:
ConstantColumnCheck— single-value columns (warning severity)HighCardinalityCheck— > 50 unique AND > 50 % of rows (info)IndependenceCheck— ΔAIC > 0 vs response (warning)PoolingSuggestionCheck— continuous columns where optimal binning beats fixedbins=4by > 5 ΔAIC (info)
SuiteResult.passed is False only when a warning-severity check
fails — info findings stay advisory.
suite = pycatdap.suite.AICIndependenceSuite(df, response="survived")
result = suite.run()
print("passed:", result.passed)
print(result.summary())
result.show()
The CI assertion pattern¶
In a pytest test, the entire data-quality contract for a dataset collapses to one line:
def test_titanic_passes_aic_independence_suite() -> None:
df = sns.load_dataset("titanic")
result = pycatdap.suite.AICIndependenceSuite(df, response="survived").run()
assert result.passed, result.summary()
The assertion message embeds the failing check names, so a CI failure points at the exact reason without forcing the developer to open the HTML report.
Individual checks¶
Each Check is reusable on its own — no suite required.
from pycatdap.suite import HighCardinalityCheck
check = HighCardinalityCheck(max_categories=10, max_ratio=0.2)
r = check.run(df)
print(f"{r.name}: passed={r.passed} affected={r.affected_columns}")
print(r.message)
5. Generate the HTML report¶
All four target-driven result types (QualityReport, TargetAnalysisResult,
SuiteResult) match the ProfileResult contract:
.show / .to_html(path) / .to_dict / .to_plotly_json. The HTML is a
single self-contained file with inline Plotly figures where
applicable — emailable, attachable to Confluence, viewable offline.
import pathlib
import tempfile
with tempfile.TemporaryDirectory() as tmp:
out_dir = pathlib.Path(tmp)
sizes = {}
for name, html in (
("quality_report", qr.to_html()),
("target_analysis", ta.to_html()),
("suite_result", result.to_html()),
):
p = out_dir / f"{name}.html"
p.write_text(html, encoding="utf-8")
sizes[name] = p.stat().st_size / 1024
for k, v in sizes.items():
print(f"{k:18s} {v:6.0f} KiB")
When to use which¶
| You want | Use |
|---|---|
| Full EDA report (overview + variables + association + top subsets) | pycatdap.profile(df, response=...) |
| Just quality warnings, fast (no catdap2) | pycatdap.quality_report(df) |
| ΔAIC ranking of all columns vs a chosen response + top-K cross-tabs | pycatdap.target_analysis(df, response=...) |
| A specific (target, explanatory) pair with cross-tabs and residuals | pycatdap.target_summary(df, target, explanatory) |
| ΔAIC matrix across all column pairs | pycatdap.association_matrix(df) |
| Cramér's V / mutual-info matrix across all column pairs | pycatdap.association_matrix(df, measure="cramers_v") |
assert data quality in CI |
pycatdap.suite.AICIndependenceSuite(df, response=...).run().passed |
| Custom interestingness measure (pysubgroup interop) | pycatdap.measures.register("name", fn) then use measure="name" |
Cross-references¶
- One-call EDA report: Notebook 08
- Bivariate APIs (
plot_pair,aic_heatmap,association_matrix,association_plot): Notebook 07 - Single (target, explanatory) pair: Notebook 06
- Multivariate subset search (
catdap2): Notebook 04