Source code for secml.figure._plots.c_plot_metric

"""
.. module:: CPlotMetric
   :synopsis: Performance evaluation metrics plots.

.. moduleauthor:: Marco Melis <marco.melis@unica.it>
.. moduleauthor:: Ambra Demontis <ambra.demontis@unica.it>

"""
import itertools

from sklearn.metrics import confusion_matrix

from secml.figure._plots import CPlot
from secml.ml.peval.metrics import CRoc
from secml.array import CArray


[docs]class CPlotMetric(CPlot): """Plots of performance evaluation metrics. Currently parameters default for ROC plots: - show_legend: True - ylabel: 'True Positive Rate (%)' - xlabel: 'False Positive Rate (%)' - yticks: [0, 20, 40, 60, 80, 100] - yticklabels: see yticks - xticks: list. [0.1, 0.5, 1, 2, 5, 10, 20, 50, 100] - xticklabels: see xticks - ylim: (0.1, 100) - xlim: (0, 100) - grid: True See Also -------- .CPlot : basic subplot functions. .CFigure : creates and handle figures. """
[docs] def apply_params_roc(self): """Apply defined parameters to active subplot.""" fig_legend = self.get_legend() if self.show_legend is not False and fig_legend is not None: fig_legend.set_visible(True) self.grid(grid_on=True) if self._ylabel is None: self.ylabel('True Positive Rate (%)') if self._xlabel is None: self.xlabel('False Positive Rate (%)') if self._yticks is None: self.yticks([0, 20, 40, 60, 80, 100]) if self._yticklabels is None: self.yticklabels(['0', '20', '40', '60', '80', '100']) if self._xticks is None: self.xticks([0.1, 0.5, 1, 2, 5, 10, 20, 50, 100]) if self._xticklabels is None: self.xticklabels(['0.1', '0.5', '1', '2', '5', '10', '20', '50', '100']) # Limits have to applied after ticks to be effective if self._ylim is None: self.ylim(0, 100) if self._xlim is None: self.xlim(0.1, 100)
# TODO: REMOVE STYLE
[docs] def plot_roc(self, fpr, tpr, label=None, style=None, logx=True): """Plot a ROC curve given input fpr and tpr. Curves will be plotted inside the active figure or a new figure will be created using default parameters. Parameters ---------- fpr : CArray Array with False Positive Rats. tpr : CArray Array with False Positive Rates. label : str or None, optional Label to assign to the roc. style : str or None, optional Style of the roc plot. logx : bool, optional If True (default), logarithmic scale will be used for fpr axis. Returns ------- roc_plot : CFigure Figure after this plot session. """ if fpr.size != tpr.size: raise ValueError("input tpr and fpr arrays must have same length.") # Customizing figure self.apply_params_roc() # TODO: REMOVE AFTER COLORMAPS ARE IMPLEMENTED IN CFIGURE styles = ['go-', 'yp--', 'rs-.', 'bD--', 'c-.', 'm-', 'y-.'] plot_func = self.semilogx if logx is True else self.plot plot_func(fpr * 100, tpr * 100, styles[self.n_lines % len(styles)] if style is None else style, label=label, markevery=self.get_xticks_idx(fpr * 100)) if label is not None: # Legend on the lower right self.legend(loc=1, labelspacing=0.4, handletextpad=0.3) if logx is True: # xticks have been reset by semilogx, reassign them self.xticks(self._xticks) self.xticklabels(self._xticklabels)
# TODO: REMOVE STYLE
[docs] def plot_roc_mean(self, roc, label=None, invert_tpr=False, style=None, plot_std=False, logx=True): """Plot the mean of ROC curves. Curves will be plotted inside the active figure or a new figure will be created using default parameters. Parameters ---------- roc : CRoc Roc curves to plot. label : str or None, optional Label to assign to the roc. invert_tpr : bool True if 1 - tpr (False Negative Rates) should be plotted on y axis. Default False. style : str or None, optional Style of the roc plot. If a string, must follow the matplotlib convention: '[color][marker][line]'. plot_std : bool (default False) If True, standard deviation of True Positive Rates will be plotted. logx : bool, optional If True (default), logarithmic scale will be used for fpr axis. Returns ------- roc_plot : CFigure Figure after this plot session. """ if not isinstance(roc, CRoc): raise TypeError("input must be a `CRoc` instance.") if roc.has_mean is False: raise ValueError("average for input roc has not been computed. " "Use `CRoc.average()` first.") # Customizing figure self.apply_params_roc() # TODO: REMOVE AFTER COLORMAPS ARE IMPLEMENTED IN CFIGURE styles = ['go-', 'yp--', 'rs-.', 'bD--', 'c-.', 'm-', 'y-.'] # If std should be plotted each run plots 2 curvers n_lines = int(self.n_lines / 2) if plot_std is True else self.n_lines # Get indices of fpr @ xticks mkrs_idx = self.get_xticks_idx(roc.mean_fpr * 100) mean_tpr = roc.mean_tpr if invert_tpr is False else 1 - roc.mean_tpr plot_func = self.semilogx if logx is True else self.plot plot_func(roc.mean_fpr * 100, mean_tpr * 100, styles[n_lines % len(styles)] if style is None else style, label=label, markevery=mkrs_idx) if plot_std is True: if roc.has_std_dev is False: raise ValueError("roc object has no standard deviation for data.") self.errorbar(roc.mean_fpr[mkrs_idx] * 100, mean_tpr[mkrs_idx] * 100, ecolor=styles[n_lines % len(styles)][0] if style is None else style[0], fmt='None', yerr=roc.std_dev_tpr[mkrs_idx] * 100) if label is not None: # Legend on the lower right self.legend(loc=4 if invert_tpr is False else 1, labelspacing=0.4, handletextpad=0.3) if logx is True: # xticks have been reset by semilogx, reassign them self.xticks(self._xticks) self.xticklabels(self._xticklabels)
[docs] def plot_roc_reps(self, roc, label=None, invert_tpr=False, logx=True): """Plot all input ROC curves. Curves will be plotted inside the active figure or a new figure will be created using default parameters. Parameters ---------- roc : CRoc Roc curves to plot. label : str or None, optional Label to assign to the roc. Repetition number will be appended using the following convention: - If label is None -> "rep 'i'" - If label is not None -> "`label` (rep `i`)" invert_tpr : bool True if 1 - tpr (False Negative Rates) should be plotted on y axis. Default False. logx : bool, optional If True (default), logarithmic scale will be used for fpr axis. Returns ------- roc_plot : CFigure Figure after this plot session. """ def label_w_rep(l_str, i): """Format input label to show repetition number. Parameters ---------- l_str : str Original label. i : int Repetition index number. Returns ------- out_label : str Label formatted as following: 1) If label is '' -> "rep 'i'" 2) If label is not '' -> "`label` (rep `i`)" """ i_label = 'rep {:}'.format(i) if l_str is not None: i_label = l_str + ' (' + i_label + ')' return i_label if not isinstance(roc, CRoc): raise TypeError("input must be a `CRoc` instance.") # Customizing figure self.apply_params_roc() # TODO: REMOVE AFTER COLORMAPS ARE IMPLEMENTED IN CFIGURE styles = ['go-', 'yp--', 'rs-.', 'bD--', 'c-.', 'm-', 'y-.'] # Storing number of lines already plotted to chose style accordingly n_lines = self.n_lines plot_func = self.semilogx if logx is True else self.plot for rep_i in range(roc.n_reps): if roc.n_reps <= 1: # For one rep ROC is stored as CArray tpr = roc.tpr fpr = roc.fpr else: # For more than one rep ROC is stored as lists tpr = roc.tpr[rep_i] fpr = roc.fpr[rep_i] tpr = tpr if invert_tpr is False else 1 - tpr plot_func(fpr * 100, tpr * 100, styles[(n_lines + rep_i) % len(styles)], label=label_w_rep(label, rep_i), markevery=self.get_xticks_idx(fpr * 100)) if label is not None: # Legend on the lower right self.legend(loc=4 if invert_tpr is False else 1, labelspacing=0.4, handletextpad=0.3) if logx is True: # xticks have been reset by semilogx, reassign them self.xticks(self._xticks) self.xticklabels(self._xticklabels)
# FIXME: accept a CMetricConfusionMatrix object instead
[docs] def plot_confusion_matrix(self, y_true, y_pred, normalize=False, labels=None, title=None, cmap='Blues', colorbar=False): """Plot a confusion matrix. y_true : CArray True labels. y_pred : CArray Predicted labels. normalize : bool, optional If True, normalize the confusion matrix in 0/1. Default False. labels : list, optional List with the label of each class. title: str, optional Title of the plot. Default None. cmap: str or matplotlib.pyplot.cm, optional Colormap to use for plotting. Default 'Blues'. colorbar : bool, optional If True, show the colorbar side of the matrix. Default False. """ matrix = CArray(confusion_matrix( y_true.tondarray(), y_pred.tondarray())) if normalize: # min-max normalization matrix_min = matrix.min() matrix_max = matrix.max() matrix = (matrix - matrix.min()) / (matrix_max - matrix_min) ax = self.imshow(matrix, interpolation='nearest', cmap=cmap) self._sp.set_xticks(CArray.arange(matrix.shape[1]).tondarray()) self._sp.set_yticks(CArray.arange(matrix.shape[0]).tondarray()) if labels is not None: self._sp.set_xticklabels(labels) self._sp.set_yticklabels(labels) # Rotate the tick labels and set their alignment. import matplotlib.pyplot as plt plt.setp(self._sp.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") fmt = '.2f' if normalize else 'd' if colorbar is True: from mpl_toolkits.axes_grid1 import make_axes_locatable divider = make_axes_locatable(plt.gca()) cax = divider.append_axes("right", size="5%", pad=0.1) # TODO: set format -> cax.set_yticklabels self.colorbar(ax, cax=cax) if title is True: self.title(title) thresh = matrix.max() / 2. for i, j in itertools.product( range(matrix.shape[0]), range(matrix.shape[1])): self.text(j, i, format(matrix[i, j].item(), fmt), horizontalalignment="center", color="white" if matrix[i, j] > thresh else "black")