Skip to content

catdap1

pycatdap.catdap1

CATDAP-01: Pairwise AIC evaluation of categorical variable associations.

Evaluates every pair of categorical variables in a DataFrame using AIC, ranking explanatory variables by their informativeness for each response.

Catdap1Result dataclass

Catdap1Result(
    tway_tables: Mapping[tuple[str, str], DataFrame],
    aic: DataFrame,
    aic_order: Mapping[str, list[str]],
    total: Series,
)

Result container for :func:catdap1.

Attributes:

Name Type Description
tway_tables Mapping[tuple[str, str], DataFrame]

Two-way frequency tables keyed by (response, explanatory). These tables reflect NaN-dropped data (consistent with AIC).

aic DataFrame

ΔAIC values. Rows are response variables, columns are all variables in the original column order. Self-entries are NaN.

aic_order Mapping[str, list[str]]

For each response variable, explanatory variables sorted by ΔAIC ascending (best first). Read-only mapping.

total Series

Per-column non-null count from the input data. Note: pairwise observation counts used in AIC may differ when NaN values exist.

to_plotly_json

to_plotly_json() -> dict[str, object]

Return a Plotly-Figure-compatible JSON spec for this result.

Produces a horizontal bar chart of ΔAIC values per explanatory variable for the first response in :attr:aic. Suitable for direct consumption by react-plotly.js or any Plotly renderer.

Returns:

Type Description
dict

Plotly figure spec with data and layout keys.

Notes

Implements the contract from BLUEPRINT.md §5.7 / DP-4 (LizyStudio integration). Multi-response handling and richer payloads will be added in v0.3+ (Issue #12).

Source code in src/pycatdap/catdap1.py
def to_plotly_json(self) -> dict[str, object]:
    """Return a Plotly-Figure-compatible JSON spec for this result.

    Produces a horizontal bar chart of ΔAIC values per explanatory
    variable for the first response in :attr:`aic`. Suitable for
    direct consumption by ``react-plotly.js`` or any Plotly renderer.

    Returns
    -------
    dict
        Plotly figure spec with ``data`` and ``layout`` keys.

    Notes
    -----
    Implements the contract from BLUEPRINT.md §5.7 / DP-4 (LizyStudio
    integration). Multi-response handling and richer payloads will be
    added in v0.3+ (Issue #12).
    """
    response = str(self.aic.index[0])
    row = self.aic.loc[response].dropna()
    variables = [str(v) for v in row.index]
    values = [float(v) for v in row.to_numpy()]
    colors = ["#2ca02c" if v < 0 else "#d62728" for v in values]
    return {
        "data": [
            {
                "type": "bar",
                "orientation": "h",
                "x": values,
                "y": variables,
                "marker": {"color": colors},
                "name": "ΔAIC",
            }
        ],
        "layout": {
            "title": f"AIC Comparison (response: {response})",
            "xaxis": {"title": "ΔAIC", "zeroline": True},
            "yaxis": {"title": "Variable", "automargin": True},
        },
    }

catdap1

catdap1(
    data: DataFrame, response_names: list[str] | None = None
) -> Catdap1Result

Evaluate pairwise associations between categorical variables using AIC.

For each response variable, computes ΔAIC for every other variable as explanatory, then ranks them by informativeness.

Parameters:

Name Type Description Default
data DataFrame

DataFrame of categorical variables. Not modified.

required
response_names list[str] or None

Column names to use as response variables. If None, all columns are used as response variables in turn.

None

Returns:

Type Description
Catdap1Result

Container with two-way tables, AIC values, rankings, and totals.

Raises:

Type Description
ValueError

If data is empty, has fewer than 2 columns, or a response name is missing.

Source code in src/pycatdap/catdap1.py
def catdap1(
    data: pd.DataFrame,
    response_names: list[str] | None = None,
) -> Catdap1Result:
    """Evaluate pairwise associations between categorical variables using AIC.

    For each response variable, computes ΔAIC for every other variable
    as explanatory, then ranks them by informativeness.

    Parameters
    ----------
    data : DataFrame
        DataFrame of categorical variables.  Not modified.
    response_names : list[str] or None
        Column names to use as response variables.  If ``None``, all
        columns are used as response variables in turn.

    Returns
    -------
    Catdap1Result
        Container with two-way tables, AIC values, rankings, and totals.

    Raises
    ------
    ValueError
        If *data* is empty, has fewer than 2 columns, or a response
        name is missing.
    """
    responses = _validate_input(data, response_names)
    all_cols = list(data.columns)

    tway_tables: dict[tuple[str, str], pd.DataFrame] = {}
    aic_records: dict[str, dict[str, float]] = {}
    aic_order: dict[str, list[str]] = {}

    for resp in responses:
        explanatory_cols = [c for c in all_cols if c != resp]
        row: dict[str, float] = {}

        for expl in explanatory_cols:
            cross, marg_e, marg_f, n = build_crosstab(data, resp, expl)
            delta = compute_delta_aic(cross, marg_e, marg_f, n)
            row[expl] = delta

            # Store NaN-dropped crosstab consistent with AIC computation
            subset = data[[resp, expl]].dropna()
            tway_tables[(resp, expl)] = pd.crosstab(subset[resp], subset[expl])

        aic_records[resp] = row
        aic_order[resp] = sorted(row, key=lambda col: row[col])

    aic_df = pd.DataFrame.from_dict(aic_records, orient="index")
    aic_df = aic_df.reindex(columns=all_cols)
    aic_df.index.name = "response"

    total = data.count()

    return Catdap1Result(
        tway_tables=types.MappingProxyType(tway_tables),
        aic=aic_df.copy(),
        aic_order=types.MappingProxyType(aic_order),
        total=total.copy(),
    )