Bivariate association analysis with plot_pair, aic_heatmap, association_matrix, association_plot¶
Once you can drill into a single (target, explanatory) pair
with target_summary and plot_target, these
bivariate association APIs add the four remaining symmetric / multi-variable views — useful
when you do not yet have a single target in mind, or when you want a
one-glance map of all pairwise associations:
| API | Question it answers |
|---|---|
plot_pair(df, x, y) |
"Plot these two variables — let pycatdap pick which side is the response." |
aic_heatmap(catdap1_result) |
"Visualize the ΔAIC matrix from a CATDAP-01 run." |
association_matrix(df) |
"Give me the full m × m ΔAIC matrix for all column pairs." |
association_plot(table) |
"Show me which cells of the contingency table drive the relationship (vcd style)." |
This notebook walks through them on the Titanic dataset.
Note on package requirements: requires
pycatdap[tutorial]for the Titanic loader and plotly extras (same as notebook 06).
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import pycatdap
sns.set_theme(style="whitegrid")
df = sns.load_dataset("titanic")[
["survived", "pclass", "sex", "age", "fare", "embarked"]
].dropna()
df["survived"] = df["survived"].astype(bool)
df["pclass"] = df["pclass"].astype("category")
df.head()
1. plot_pair — when the response is implicit¶
In real EDA you often want to plot two columns and let the library decide
which axis is the response. plot_pair is a symmetric wrapper around
plot_target: it inspects the dtypes of x and y and picks the response
side per a simple rule:
x dtype |
y dtype |
response | rationale |
|---|---|---|---|
| discrete × discrete | y |
seaborn / vcd y ~ x convention |
|
| discrete × continuous | discrete side | Pearson residual interpretation favors discrete response | |
| continuous × continuous | y |
continuous regression mode (y is the target) |
Once decided, plot_pair delegates entirely to plot_target, so every
plot_target improvement is automatically inherited.
ax = pycatdap.plot_pair(df, "sex", "survived")
ax.set_title("plot_pair(df, 'sex', 'survived') — y='survived' chosen as target")
plt.show()
# Continuous explanatory + categorical target: discrete side wins.
ax = pycatdap.plot_pair(df, "age", "survived")
ax.set_title(
"plot_pair(df, 'age', 'survived') — y='survived' (discrete) chosen as target"
)
plt.show()
# Both continuous: H-0005 regression mode kicks in via plot_target(kind='scatter').
ax = pycatdap.plot_pair(df, "age", "fare")
ax.set_title("plot_pair(df, 'age', 'fare') — H-0005 regression scatter")
plt.show()
2. aic_heatmap — one-glance view of a CATDAP-01 run¶
When you already have a Catdap1Result (CATDAP-01 pairwise scan), the
m × m ΔAIC DataFrame is informative but hard to read as a table.
aic_heatmap paints it as a diverging heatmap centered at zero:
- green cells (ΔAIC < 0) → the explanatory is informative for that response
- red cells (ΔAIC > 0) → not informative (worse than the null model)
- diagonal NaN → self-association is undefined (rendered transparent)
*overlay → cells below thethresholdparameter (default0.0)
result = pycatdap.catdap1(df, response_names=["survived", "embarked"])
result.aic.round(2)
ax = pycatdap.aic_heatmap(result)
plt.show()
3. association_matrix — full m × m ΔAIC sweep¶
CATDAP-01 lets you specify one or more response variables explicitly;
association_matrix instead computes ΔAIC for every ordered pair
of columns — useful when you have not picked a response yet.
The returned matrix is asymmetric by design: M.loc[i, j] measures
"how much does j explain i (treating i as the response)", which is
different from M.loc[j, i]. The asymmetry itself is information — a
column that is well predicted by another but does not predict it back is
a hint of directional dependence.
matrix = pycatdap.association_matrix(df)
matrix.round(2)
# Feed the matrix directly into aic_heatmap — Catdap1Result is not required.
ax = pycatdap.aic_heatmap(matrix, threshold=-5.0)
ax.set_title("association_matrix(df) → aic_heatmap (cells with ΔAIC < -5 starred)")
plt.show()
4. association_plot — vcd-style Pearson residuals¶
ΔAIC summarizes the overall informativeness of a pair, but does not
tell you which cells of the contingency table drive the association.
association_plot is the vcd assoc(shade=TRUE) analogue: it plots the
Pearson standardized residuals
$$r_{ij} = \frac{O_{ij} - E_{ij}}{\sqrt{E_{ij}}}$$
with a diverging colormap (blue=negative, red=positive). Cells with
|residual| > threshold (default 2.0, the conventional "strong"
association cutoff) get a * overlay.
You can feed it either a TargetSummary (residuals come from
.pearson_residuals) or a raw pd.DataFrame crosstab (computed internally).
pair = pycatdap.target_summary(df, target="survived", explanatory="pclass")
ax = pycatdap.association_plot(pair)
plt.show()
# Raw crosstab path — handy when you already have a pd.crosstab table.
table = pd.crosstab(df["pclass"], df["embarked"])
ax = pycatdap.association_plot(table, threshold=1.5)
plt.show()
Limitation: continuous targets¶
association_plot operates on contingency tables, so it does not accept
RegressionTargetSummary (the continuous-target summary). Continuous targets have no Pearson
residual analogue at the cell level. For those cases use
plot_target(df, target, explanatory, kind="scatter") instead. The
following deliberately fails with an explicit pointer:
try:
reg_summary = pycatdap.target_summary(df, target="fare", explanatory="pclass")
pycatdap.association_plot(reg_summary)
except TypeError as exc:
print(exc)
5. Picking the right bivariate association API¶
| Situation | Reach for |
|---|---|
| "I want a plot of these two columns." | plot_pair |
"I just ran catdap1 and want to see the ΔAIC matrix at a glance." |
aic_heatmap(result) |
| "I have no target yet — give me everything." | association_matrix(df) then aic_heatmap(matrix) |
| "ΔAIC says this pair is informative — which cells matter?" | association_plot(target_summary(df, t, e)) |
Cross-references¶
- Single-pair drill-down: Notebook 06
- Univariate EDA (
describe,plot_variable,plot_missing): Notebook 02 - AIC-optimal binning of continuous variables: Notebook 03
- Multi-response CATDAP-01 + subset search via
catdap2: Notebook 04