pysubgroup interoperability¶
pysubgroup is a popular Python library
for subgroup discovery — finding interpretable conditions (conjunctions of
selectors) under which a target behaves unusually. pycatdap exposes its
AIC-based interestingness as a pysubgroup-compatible quality function,
pycatdap.measures.AICMeasure, so you can drive pysubgroup's search with the
same ΔAIC criterion pycatdap's native discover_error_slices uses.
Status (what this page covers)
AICMeasure ships as of this release (H-0018, issue #31).
pysubgroup is an optional dependency. The measure is designed for
pysubgroup's BeamSearch / SimpleDFS; see
Scope for why Apriori / DFS are
out of scope.
Install¶
pysubgroup is not bundled as a pycatdap extra on purpose: it pins
numpy<2.0.0, and a packaging-level dependency would propagate that cap to
every pycatdap install (pycatdap targets numpy 2.x). Installing pysubgroup
explicitly keeps that constraint opt-in.
Importing pycatdap.measures never requires pysubgroup; only
pycatdap.measures.AICMeasure does. Accessing it without pysubgroup installed
raises a clear ImportError telling you to pip install pysubgroup.
Side-by-side example¶
import numpy as np
import pandas as pd
import pysubgroup as ps
import pycatdap
# A dataset where column `a` is informative about the binary target and
# column `b` is noise.
rng = np.random.RandomState(0)
n = 600
a = rng.choice(["x", "y"], n)
b = rng.choice(["p", "q"], n)
target = ((a == "x") & (rng.rand(n) < 0.85)) | ((a == "y") & (rng.rand(n) < 0.15))
df = pd.DataFrame({"a": a, "b": b, "target": target})
# --- pysubgroup search driven by AIC ---
task = ps.SubgroupDiscoveryTask(
df,
ps.BinaryTarget("target", True),
ps.create_selectors(df, ignore=["target"]),
result_set_size=5,
depth=2,
qf=pycatdap.measures.AICMeasure(), # <-- AIC as the quality function
)
result = ps.BeamSearch().execute(task)
for quality, subgroup in result.to_descriptions():
print(f"{quality:8.3f} {subgroup}")
# The informative column `a` tops the ranking; `b` only appears (weakly)
# in conjunctions.
The same signal, via pycatdap's native API:
from pycatdap.error import discover_error_slices
# Treat the binary outcome as an "error" label and discover the slices that
# concentrate it — driven by the same ΔAIC.
y_pred = np.zeros(n, dtype=int)
native = discover_error_slices(
df.drop(columns="target"), target.astype(int), y_pred, max_vars=1
)
for s in native.slices:
print(s.description, s.delta_aic)
Both routes surface column a as the most informative variable, because both
score candidate subgroups by ΔAIC.
How the bridge works¶
pysubgroup's binary-target quality functions receive, per candidate subgroup,
the dataset totals (N, P) (rows, positives) and the subgroup counts (n, p).
AICMeasure turns those into the 2×2 contingency table of target against
subgroup membership
| target = positive | target = negative | |
|---|---|---|
| in subgroup | p |
n − p |
| out subgroup | P − p |
(N − n) − (P − p) |
and scores it with pycatdap.measures.aic (ΔAIC of the two-way model against
the null model).
pysubgroup maximises quality, while ΔAIC is negative for an informative
split, so the returned quality is -ΔAIC — higher means more informative. An
empty subgroup yields nan (matching pysubgroup's own StandardQF).
Scope: BeamSearch / SimpleDFS, not Apriori¶
AICMeasure implements evaluate only; it deliberately does not provide
optimistic_estimate. ΔAIC has no sound optimistic upper bound (pycatdap's own
slice discovery prunes by support, not ΔAIC — see HISTORY H-0014 §C), so an
estimate would make pruning-based algorithms unsound.
| pysubgroup algorithm | uses optimistic_estimate? |
works with AICMeasure? |
|---|---|---|
BeamSearch |
no | ✅ yes |
SimpleDFS |
no | ✅ yes |
Apriori |
yes | ❌ no |
DFS |
yes | ❌ no |
Use BeamSearch (or SimpleDFS) — they evaluate every candidate and need no
bound.