Skip to content

Coherence

coherence_importance

coherence_importance(graph: ConceptGraph, X: DataFrame, feature_names: Sequence[str], importances: ndarray, *, method: CorrelationMethod = 'spearman', coherence_threshold: float | None = None, importance_threshold: float | None = None) -> DataFrame

Per-concept coherence × importance table.

PARAMETER DESCRIPTION
graph

ConceptGraph.

TYPE: ConceptGraph

X

Feature matrix used to compute within-block correlation.

TYPE: DataFrame

feature_names

Inputs to :func:importance_sum. Per-sample (N, F) or per-feature (F,).

TYPE: Sequence[str]

importances

Inputs to :func:importance_sum. Per-sample (N, F) or per-feature (F,).

TYPE: Sequence[str]

method

Correlation method passed to :func:feature_correlation.

TYPE: CorrelationMethod DEFAULT: 'spearman'

coherence_threshold

Quadrant boundary on the coherence axis. Defaults to the median across concepts.

TYPE: float | None DEFAULT: None

importance_threshold

Quadrant boundary on the importance axis. Defaults to the median across concepts.

TYPE: float | None DEFAULT: None

RETURNS DESCRIPTION
DataFrame

One row per concept (root included). Columns include coherence, importance_sum, quadrant, plus all the structural columns from :func:empty_concept_frame.

Source code in src/concept_graph_xai/metrics/coherence.py
def coherence_importance(
    graph: ConceptGraph,
    X: pd.DataFrame,
    feature_names: Sequence[str],
    importances: np.ndarray,
    *,
    method: CorrelationMethod = "spearman",
    coherence_threshold: float | None = None,
    importance_threshold: float | None = None,
) -> pd.DataFrame:
    """Per-concept coherence × importance table.

    Parameters
    ----------
    graph:
        ConceptGraph.
    X:
        Feature matrix used to compute within-block correlation.
    feature_names, importances:
        Inputs to :func:`importance_sum`. Per-sample (N, F) or per-feature (F,).
    method:
        Correlation method passed to :func:`feature_correlation`.
    coherence_threshold:
        Quadrant boundary on the coherence axis. Defaults to the median across
        concepts.
    importance_threshold:
        Quadrant boundary on the importance axis. Defaults to the median across
        concepts.

    Returns
    -------
    pandas.DataFrame
        One row per concept (root included). Columns include ``coherence``,
        ``importance_sum``, ``quadrant``, plus all the structural columns from
        :func:`empty_concept_frame`.
    """

    corr = feature_correlation(graph, X, method=method)
    block_lookup = corr.block_stats.set_index("concept_path")["mean_abs"].to_dict()

    imp_df = importance_sum(graph, feature_names, importances).copy()
    coherence: list[float] = []
    for path in imp_df.index:
        coherence.append(float(block_lookup.get(path, np.nan)))
    imp_df["coherence"] = coherence

    coh = np.asarray(imp_df["coherence"], dtype=float)
    imp = np.asarray(imp_df["importance_sum"], dtype=float)
    valid_coh = coh[~np.isnan(coh)]
    coh_thr = (
        float(np.median(valid_coh))
        if coherence_threshold is None and valid_coh.size > 0
        else float(coherence_threshold or 0.0)
    )
    valid_imp = imp[~np.isnan(imp)]
    imp_thr = (
        float(np.median(valid_imp))
        if importance_threshold is None and valid_imp.size > 0
        else float(importance_threshold or 0.0)
    )

    quadrants: list[str] = []
    for c, i in zip(coh, imp, strict=True):
        if np.isnan(c) or np.isnan(i):
            quadrants.append("undefined")
        elif c >= coh_thr and i >= imp_thr:
            quadrants.append("well_designed")
        elif c < coh_thr and i >= imp_thr:
            quadrants.append("kitchen_sink")
        elif c >= coh_thr and i < imp_thr:
            quadrants.append("redundant")
        else:
            quadrants.append("noise")
    imp_df["quadrant"] = quadrants
    imp_df.attrs["coherence_threshold"] = coh_thr
    imp_df.attrs["importance_threshold"] = imp_thr
    imp_df.attrs["method"] = method
    return imp_df