kdiagram.plot.feature_based.plot_feature_fingerprint¶
- kdiagram.plot.feature_based.plot_feature_fingerprint(importances, features=None, labels=None, normalize=True, fill=True, cmap='tab10', title='Feature Impact Fingerprint', figsize=None, show_grid=True, grid_props=None, acov='half_circle', ax=None, savefig=None, dpi=300)[source]¶
Create a radar chart visualizing feature-importance profiles.
This function draws a polar (radar) chart that compares how the importance of a common set of features varies across multiple groups/layers (e.g., different models, years, or spatial zones). Each group is drawn as a closed polygon, producing an interpretable “fingerprint” of relative influence across features (see also the dataset helper
make_fingerprint_data(); concept introduced in Kouadio[1].The angular position encodes the feature index, and the radius encodes its (optionally normalized) importance value. Normalization allows shape-only comparison across layers, independent of absolute scale.
- Parameters:
- importancesarray_like
ofshape(n_layers,n_features) The importance matrix. Each row corresponds to one layer/group and each column to a feature. Accepts a list of lists, a NumPy array, or a pandas DataFrame.
- features
listofstr,optional Names of the features (length must match the number of columns in
importances). IfNone, generic names['feature 1', ..., 'feature N']are generated.- labels
listofstr,optional Display names for layers (length should match
n_layers). IfNone, generic names['Layer 1', ..., 'Layer M']are generated. When counts mismatch, the function pads/truncates and issues a warning.- normalizebool, default=True
If
True, normalize each row to the unit interval via \(r'_{ij} = r_{ij}/\max_k r_{ik}\) (safe-dividing by zero yields zeros). This highlights shape differences across layers. IfFalse, raw magnitudes are plotted.- fillbool, default=True
If
True, fill each polygon with a translucent color; otherwise draw outlines only.- cmap
strorlist, default=’tab10’ Either a Matplotlib colormap name (e.g.,
'viridis','plasma','tab10') or an explicit list of colors. Lists shorter than the number of layers will cycle with a warning.- title
str, default=’FeatureImpactFingerprint’ Figure title.
- figsize
tupleof(float,float),optional Figure size in inches. If
None, a sensible default is used.- show_gridbool, default=True
Whether to show polar grid lines.
- savefig
str,optional Path to save the figure (e.g.,
'fingerprint.png'). If omitted, the plot is shown interactively.
- importancesarray_like
- Returns:
- ax
matplotlib.axes.Axes The polar axes containing the radar chart (useful for further customization).
- ax
- Parameters:
See also
kdiagram.datasets.make_fingerprint_dataGenerate a synthetic importance matrix suitable for this plot.
kdiagram.plot.relationship.plot_relationshipPolar scatter for true–predicted relationships.
matplotlib.pyplot.polarUnderlying polar plotting primitives.
Notes
Angular encoding. With \(N\) features, angular positions are equally spaced as:
(1)¶\[\theta_j \;=\; \frac{2\pi j}{N}, \qquad j = 0, \dots, N-1.\]Closing polygons. To draw closed fingerprints, the first vertex \((\theta_0, r_{i0})\) is appended again at \(2\pi\) for each layer \(i\).
Row-wise normalization (default). If
normalize=True, each row \(\mathbf r_i=(r_{i0},\dots,r_{i,N-1})\) is scaled to its maximum:(2)¶\[\begin{split}r'_{ij} \;=\; \begin{cases} \dfrac{r_{ij}}{\max_k r_{ik}}, & \max_k r_{ik} > 0,\\[6pt] 0, & \text{otherwise,} \end{cases}\end{split}\]which emphasizes shape differences between layers but removes absolute magnitude information. Set
normalize=Falseto compare magnitudes.Alternative min–max scaling (pre-processing). If you prefer values distributed over \([0,1]\) using the local range, apply this transformation per row before calling the function as:
(3)¶\[r''_{ij} \;=\; \frac{r_{ij} - \min_k r_{ik}} {\max_k r_{ik} - \min_k r_{ik} + \varepsilon},\]with a small \(\varepsilon>0\) to avoid division by zero.
Data assumptions. Importance values are expected to be non-negative. Rows with a non-positive maximum (all zeros or all negative) become zeros under the default normalization. If your data can be negative, either: (1) set
normalize=Falseand choose appropriate radial limits, or (2) shift/scale to non-negative values (e.g., min–max per row).Missing/invalid values.
NaNorinfentries propagate to the plot and may render gaps. Clean data beforehand, e.g.:import numpy as np X = np.asarray(importances, float) X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
Radial limits and ticks. The plot enforces a non-negative radius (
ax.set_ylim(bottom=0)). For unnormalized data, you may set a custom maximum:ax.set_rmax( np.nanmax(importances) )
Optionally add/readjust radial ticks for readability:
ax.set_yticks([0.25, 0.5, 0.75, 1.0]) ax.set_yticklabels(["0.25", "0.50", "0.75", "1.00"])
Feature order matters. The perceived shape depends on feature ordering around the circle. Keep a consistent, meaningful order across comparisons (e.g., domain grouping or sorted by average importance).
Many features or layers. With large \(N\), tick labels can overlap. Consider thinning labels or rotating them:
angles = ax.get_xticks() ax.set_xticks(angles[::2]) ax.set_xticklabels([lbl for i, lbl in enumerate(features) if i % 2 == 0], rotation=25, ha="right")
For many layers, prefer a discrete colormap and a multi-column legend or move it outside:
ax.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0), ncol=2)
Color and accessibility. Use colorblind-friendly palettes (e.g.,
'tab10','tab20') or pass an explicit color list. Avoid relying on color alone when printing in grayscale—consider distinct linestyles.Complexity. Runtime and memory scale as \(\mathcal O(MN)\) for \(M\) layers and \(N\) features. For very large inputs, down-select features or layers for clarity.
Utilities. Inputs are coerced to a numeric 2D array and feature names managed via lightweight helpers (e.g.,
ensure_2d,columns_manager). Name count mismatches are padded/truncated with a warning rather than raising.References
Examples
Generate random importances and plot with normalization and fills.
>>> import numpy as np >>> from kdiagram.plot.feature_based import plot_feature_fingerprint >>> rng = np.random.default_rng(42) >>> imp = rng.random((3, 6)) # 3 layers, 6 features >>> feats = [f'Feature {i+1}' for i in range(6)] >>> labels = ['Model A', 'Model B', 'Model C'] >>> ax = plot_feature_fingerprint( ... importances=imp, ... features=feats, ... labels=labels, ... title='Random Feature Importance Comparison', ... cmap='Set3', ... normalize=True, ... fill=True ... )
Year-over-year weights without normalization.
>>> features = ['rainfall', 'GWL', 'seismic', 'density', 'geo'] >>> weights = [ ... [0.2, 0.4, 0.1, 0.6, 0.3], # 2023 ... [0.3, 0.5, 0.2, 0.4, 0.4], # 2024 ... [0.1, 0.6, 0.2, 0.5, 0.3], # 2025 ... ] >>> years = ['2023', '2024', '2025'] >>> ax = plot_feature_fingerprint( ... importances=weights, ... features=features, ... labels=years, ... title='Feature Influence Over Years', ... cmap='tab10', ... normalize=False ... )