Source code for graph_explain.core.explanation

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

import networkx as nx

from ..core.evaluation import evaluate_fidelity, evaluate_sparsity


[docs] @dataclass class Explanation: node_importance: Any | None = None edge_importance: Any | None = None feature_importance: Any | None = None subgraph: Any | None = None prediction_original: Any | None = None prediction_explanation: Any | None = None node_idx: int | None = None target_class: int | None = None mask_threshold: float = 0.5 metadata: dict = field(default_factory=dict)
[docs] def evaluate(self, metrics: list[str] | None = None, **kwargs) -> dict: metrics = metrics or ["fidelity", "sparsity"] results: dict = {} for name in metrics: name = name.lower() if name == "fidelity": results[name] = evaluate_fidelity(self) elif name in ("sparsity", "sparsity_ratio"): results[name] = evaluate_sparsity(self, **kwargs) else: raise ValueError(f"Métrica desconocida: {name}") return results
[docs] def to_networkx(self, threshold: float | None = None) -> nx.Graph: threshold = threshold if threshold is not None else self.mask_threshold backend = self.metadata.get("backend") data = self.metadata.get("backing_data") if backend is None or data is None: raise ValueError( "La explicación se creó sin backend/backing_data en metadata" ) G: nx.Graph = nx.Graph() keep_edges = [] if self.edge_importance is not None: edge_index = backend.edge_index(data) for e in range(self.edge_importance.shape[0]): if float(self.edge_importance[e]) >= threshold: u = int(edge_index[0, e]) v = int(edge_index[1, e]) keep_edges.append((u, v, float(self.edge_importance[e]))) if keep_edges: G.add_weighted_edges_from(keep_edges) nodes = {n for e in keep_edges for n in e[:2]} if self.node_importance is not None: for n in range(self.node_importance.shape[0]): if float(self.node_importance[n]) >= threshold: nodes.add(n) G.add_nodes_from(nodes) return G
def __repr__(self) -> str: parts = [] if self.node_importance is not None: parts.append(f"node_importance={tuple(self.node_importance.shape)}") if self.edge_importance is not None: parts.append(f"edge_importance={tuple(self.edge_importance.shape)}") if self.feature_importance is not None: parts.append(f"feature_importance={tuple(self.feature_importance.shape)}") return f"Explanation({', '.join(parts)})"