From 651f2e08222ec1a51fda8f476d3526a5cc0cc694 Mon Sep 17 00:00:00 2001 From: ejohnson643 Date: Thu, 4 Nov 2021 23:46:32 -0500 Subject: [PATCH] Added a bunch of plotting routines to the EMBEDR_sweep class... --- EMBEDR/embedr.py | 320 ++++-------- EMBEDR/plots/sweep_boxplots.py | 845 ++++++++++++++++++++++++++++++++ EMBEDR/plots/sweep_lineplots.py | 210 ++++++++ EMBEDR/plotting_utility.py | 36 +- EMBEDR/tsne.py | 3 + 5 files changed, 1175 insertions(+), 239 deletions(-) create mode 100644 EMBEDR/plots/sweep_boxplots.py create mode 100644 EMBEDR/plots/sweep_lineplots.py diff --git a/EMBEDR/embedr.py b/EMBEDR/embedr.py index e307c83..3db7f87 100644 --- a/EMBEDR/embedr.py +++ b/EMBEDR/embedr.py @@ -211,6 +211,7 @@ def _validate_parameters_without_data(self): self._keep_affmats = bool(self._keep_affmats) self.rs = check_random_state(self.rs) self._seed = self.rs.get_state()[1][0] + self._null_seed = self._seed try: self.verbose = float(self.verbose) except ValueError as e: @@ -560,8 +561,6 @@ def _fit(self, null_fit=False): self.n_components)) self.null_EES = np.zeros((self.n_null_embed, self.n_samples)) - self._null_seed = self._seed - for nNo in range(self.n_null_embed): if self.verbose >= 1: @@ -1153,7 +1152,7 @@ def get_tSNE_embedding(self, aff_mat = self._recalculate_P(aff_mat, kNN_graph) ## Initialize the t-SNE object. - embObj = self._initialize_tSNE_embed(aff_mat) + embObj = self._initialize_tSNE_embed(X, aff_mat) ## Get a locally-normed and asymmetric affinity matrix for EES... local_aff_mat = self._get_asym_local_affmat(X, kNN_graph=kNN_graph, @@ -1269,7 +1268,7 @@ def get_tSNE_embedding(self, return emb_Y, emb_EES - def _initialize_tSNE_embed(self, affObj): + def _initialize_tSNE_embed(self, X, affObj): if self.verbose >= 3: print(f"\nInitializing t-SNE Embedding...") @@ -1281,7 +1280,11 @@ def _initialize_tSNE_embed(self, affObj): verbose=self.verbose, **self.DRA_params) - embObj.initialize_embedding(affObj) + if 'initialization' in self.DRA_params: + if self.DRA_params['initialization'] == 'pca': + embObj.initialize_embedding(X) + else: + embObj.initialize_embedding(affObj) return embObj @@ -1305,7 +1308,7 @@ def load_tSNE_embedding(self, ## Initialize the embedding object. if embObj is None: - embObj = self._initialize_tSNE_embed(aff_mat) + embObj = self._initialize_tSNE_embed(X, aff_mat) ## Check for the existence of loaded kNN graphs. if "Embed_tSNE" not in self.project_hdr: @@ -2481,7 +2484,8 @@ def fit_samplewise_optimal(self): perp = opt_hp_vals else: knn = opt_hp_vals - optObj = EMBEDR(perplexity=perp, + optObj = EMBEDR(X=self.data_X, + perplexity=perp, n_neighbors=knn, kNN_metric=self.kNN_metric, kNN_alg=self.kNN_alg, @@ -2535,245 +2539,87 @@ def _get_hp_from_kEff(self, kEff): return interpolate(x_coords, y_coords, np.asarray(nn)).squeeze() - def plot_sweep(self, - fig=None, - gridspec=None, - box_widths=None, - box_positions=None, - box_notch=True, - box_bootstrap=100, - box_whiskers=(1, 99), - box_color='grey', - box_fliers=None, - box_props=None, - box_hl_color='grey', - box_hl_props=None, - values_2_highlight=None, - xLabel_idx=None, - xLabel=None, - xLabel_size=16, - xLim=None, - pVal_cmap=None, - fig_size=(12, 5), - fig_pad=0.4, - fig_ppad=0.01, - show_borders=False, - bot_wpad=0.0, - bot_hpad=0.0, - cax_ticklabels=None, - cax_width_frac=1.3, - cax_w2h_ratio=0.1): - """Generates scatter plot of embedded data colored by EMBEDR p-value - - Parameters - ---------- - """ - - import matplotlib.gridspec as gs - import matplotlib.pyplot as plt - - hp_array = np.sort(self.sweep_values) - - if box_fliers is None: - box_fliers = {'marker': ".", - 'markeredgecolor': box_color, - 'markersize': 2, - 'alpha': 0.5} - - if box_props is None: - box_props = {'alpha': 0.5, - 'color': box_color, - 'fill': True} - - if box_hl_props is None: - box_hl_props = box_props.copy() - box_hl_props.update({"alpha": 0.9, "color": box_hl_color}) - - if values_2_highlight is None: - values_2_highlight = [] - - box_patches = ['boxes', 'whiskers', 'fliers', 'caps', 'medians'] - - if fig is None: - fig = plt.figure(figsize=fig_size) - - if gridspec is None: - gridspec = fig.add_gridspec(1, 1) - - if pVal_cmap is None: - pVal_cmap = putl.CategoricalFadingCMap() - - ## Set up large axes - spine_alpha = 1 if show_borders else 0 - bot_ax = fig.add_subplot(gridspec[0]) - bot_ax = putl.make_border_axes(bot_ax, xticks=[], yticks=[], - spine_alpha=spine_alpha) - - ## Set up floating bottom gridspec - bot_gs = gs.GridSpec(nrows=1, ncols=1, - wspace=bot_wpad, hspace=bot_hpad) - - ax = putl.make_border_axes(fig.add_subplot(bot_gs[0]), - yticklabels=[], - yticks=-np.sort(pVal_cmap.change_points), - spine_alpha=1) - - putl.update_tight_bounds(fig, bot_gs, gridspec[0], w_pad=bot_wpad, - h_pad=bot_hpad, fig_pad=fig_pad) - - hl_boxes = {} - hl_idx = [] - for hpNo, hpVal in enumerate(hp_array): - - if box_widths is not None: - try: - box_wid = box_widths[hpNo] - except TypeError as err: - box_wid = box_widths - else: - box_wid = 0.8 - - if box_positions is not None: - try: - box_pos = [box_positions[hpNo]] - except TypeError as err: - box_pos = [box_positions] - else: - box_pos = [hpNo] + def sweep_boxplot(self, sweep_type='pvalues', **kwargs): + """Generates boxplots of specified values vs swept hyperparameter.""" + if sweep_type.lower() == 'pvalues': + from EMBEDR.plots.sweep_boxplots import SweepBoxplot_pValues + + plotObj = SweepBoxplot_pValues(np.sort(self.sweep_values), + self.pValues, + kEff_dict=self.kEff, + verbose=self.verbose > 3, + **kwargs) + axis = plotObj.make_plot() + + if sweep_type.lower() == 'ees': + from EMBEDR.plots.sweep_boxplots import SweepBoxplot_EES + + EES_dict = {'data':self.data_EES, 'null':self.null_EES} + plotObj = SweepBoxplot_EES(np.sort(self.sweep_values), + EES_dict, + kEff_dict=self.kEff, + verbose=self.verbose > 3, + **kwargs) + axis = plotObj.make_plot() + + return axis + + def sweep_lineplot(self, sweep_type='pvalues', metadata=None, labels=None, + params_2_highlight=None, fig=None, axis=None, + xticks=[], xticklabels=[], **kwargs): + """Generates lineplots of each cell vs swept hyperparameters.""" + + hp_values = np.sort(self.sweep_values) + n_hp = len(hp_values) + + if params_2_highlight is None: + hp_2_hl = [] + else: + hp_2_hl = params_2_highlight + hp_2_hl_idx = np.array([ii for ii, hp in enumerate(hp_values) + if hp in hp_2_hl]).astype(int) - if hpVal in values_2_highlight: - box_pps = box_hl_props.copy() - box_col = box_color - hl_idx.append(hpNo) + if len(xticks) < 1: + if len(hp_2_hl) > 0: + xticks = [0] + hp_2_hl_idx.tolist() + [n_hp - 1] else: - box_pps = box_props.copy() - box_col = box_hl_color - - box = ax.boxplot(np.log10(self.pValues[hpVal]), - widths=box_wid, - positions=box_pos, - notch=box_notch, - bootstrap=box_bootstrap, - patch_artist=True, - whis=box_whiskers, - boxprops=box_pps, - flierprops=box_fliers) - - for item in box_patches: - plt.setp(box[item], color=box_col) - - if hpVal in values_2_highlight: - hl_boxes[hpVal] = box['boxes'][0] - - if xLabel_idx is None: - if values_2_highlight: - xLabel_idx = [0] + hl_idx + [hpNo] - else: - if len(hp_array) <= 5: - xLabel_idx = np.arange(len(hp_array)) + if n_hp <= 5: + xticks = np.arange(n_hp) else: - xLabel_idx = np.linspace(0, len(hp_array), 5) - xLabel_idx = human_round(xLabel_idx) - xLabel_idx = np.asarray(xLabel_idx).astype(int) - - ax.set_xticks(xLabel_idx) - xticks = [f"{int(self.kEff[hp_array[idx]])}" for idx in xLabel_idx] - xticks = human_round(np.asarray(xticks).squeeze()) - ax.grid(which='major', axis='x', alpha=0) - ax.set_xticklabels(xticks) - - if xLim is None: - xLim = [-1, len(hp_array)] - - ax.set_xlabel(r"$ k_{Eff}$", fontsize=xLabel_size, labelpad=0) - ax.set_xlim(*xLim) - - # ax.set_yticks(-np.sort(pVal_cmap.change_points)) - # ax.set_yticklabels([]) - - ax.set_ylim(-pVal_cmap.change_points.max(), - -pVal_cmap.change_points.min()) - - ax.tick_params(pad=-3) - - ## Update the figure again... - putl.update_tight_bounds(fig, bot_gs, gridspec[0], w_pad=bot_wpad, - h_pad=bot_hpad, fig_pad=fig_pad) - - ## Colorbar parameters - if cax_ticklabels is None: - cax_ticklabels = [f"{10.**(-cp):.1e}" - for cp in pVal_cmap.change_points] - - inv_ax_trans = ax.transAxes.inverted() - fig_trans = fig.transFigure - - ## Convert from data to display - min_pVal = np.min([np.log10(self.pValues[hp].min()) - for hp in self.sweep_values]) - min_pVal = np.min([min_pVal, -pVal_cmap.change_points.max()]) - max_pVal = np.max([np.log10(self.pValues[hp].max()) - for hp in self.sweep_values]) - max_pVal = np.min([max_pVal, -pVal_cmap.change_points.min()]) - min_pVal_crds = ax.transData.transform([xLim[0], min_pVal]) - max_pVal_crds = ax.transData.transform([xLim[0], max_pVal]) + xticks = np.unique(np.linspace(0, n_hp, 5)) + xticks = np.clip(xticks, 0, n_hp - 1) + xticks = np.asarray(xticks).astype(int) - # print(f"min_pVal_crds: {min_pVal_crds}") - # print(f"max_pVal_crds: {max_pVal_crds}") + print(xticks) - ## Convert from display to figure coordinates - cFigX0, cFigY0 = fig.transFigure.inverted().transform(min_pVal_crds) - cFigX1, cFigY1 = fig.transFigure.inverted().transform(max_pVal_crds) + if len(xticklabels) == 0: + xtlabs = np.asarray([self.kEff[hp_values[idx]] + for idx in xticks]) + xticklabels = human_round(xtlabs).squeeze().astype(int) - # print(f"cFig0: {cFigX0:.4f}, {cFigY0:.4f}") - # print(f"cFig1: {cFigX1:.4f}, {cFigY1:.4f}") + if sweep_type.lower() == 'pvalues': + values_dict = self.pValues - cFig_height = np.abs(cFigY1 - cFigY0) - cFig_width = cax_w2h_ratio * cFig_height + if (metadata is None) or (labels is None): + from EMBEDR.plots.sweep_lineplots import sweep_lineplot - # print(f"The color bar will be {cFig_width:.4f} x {cFig_height:.4f}") + axis = sweep_lineplot(hp_values, + values_dict, + fig=fig, + axis=axis, + # xticks=xticks, + xticklabels=xticklabels, **kwargs) - cAxX0, cAxY0 = cFigX0 - cax_width_frac * cFig_width, cFigY0 - cAxX1, cAxY1 = cAxX0 + cFig_width, cFigY0 + cFig_height - - ## Convert from Figure back into Axes - [cAxX0, - cAxY0] = inv_ax_trans.transform(fig_trans.transform([cAxX0, cAxY0])) - [cAxX1, - cAxY1] = inv_ax_trans.transform(fig_trans.transform([cAxX1, cAxY1])) - - # print(f"cAx0: {cAxX0:.4f}, {cAxY0:.4f}") - # print(f"cAx1: {cAxX1:.4f}, {cAxY1:.4f}") - - cAx_height = np.abs(cAxY1 - cAxY0) - cAx_width = np.abs(cAxX1 - cAxX0) - - # print(f"The color bar will be {cAx_width:.4f} x {cAx_height:.4f}") - - caxIns = ax.inset_axes([cAxX0, cAxY0, cAx_width, cAx_height]) - caxIns = putl.make_border_axes(caxIns, spine_alpha=0) - - hax = plt.scatter([], [], c=[], s=[], cmap=pVal_cmap.cmap, - norm=pVal_cmap.cnorm) - cAx = fig.colorbar(hax, cax=caxIns, ticks=[], - boundaries=pVal_cmap.cnorm.boundaries) - cAx.ax.invert_yaxis() - - cAx.set_ticks(pVal_cmap.change_points) - cAx.set_ticklabels(cax_ticklabels) - cAx.ax.tick_params(length=0) - cAx.ax.yaxis.set_ticks_position('left') - - cAx.ax.set_ylabel(r"EMBEDR $p$-Value", - fontsize=xLabel_size, - labelpad=2) - cAx.ax.yaxis.set_label_position('left') - - ## Update the figure again... - putl.update_tight_bounds(fig, bot_gs, gridspec[0], w_pad=bot_wpad, - h_pad=bot_hpad, fig_pad=fig_pad) - - return ax + else: + from EMBEDR.plots.sweep_lineplots import sweep_lineplot_byCat + axes = sweep_lineplot_byCat(hp_values, + values_dict, + metadata, + labels, + fig=fig, + # xticks=xticks, + xticklabels=xticklabels, + **kwargs) diff --git a/EMBEDR/plots/sweep_boxplots.py b/EMBEDR/plots/sweep_boxplots.py new file mode 100644 index 0000000..97f05d8 --- /dev/null +++ b/EMBEDR/plots/sweep_boxplots.py @@ -0,0 +1,845 @@ +from EMBEDR.human_round import human_round +import EMBEDR.plotting_utility as putl +import matplotlib +import matplotlib.gridspec as gs +import matplotlib.pyplot as plt +import numpy as np + + +class SweepBoxplot(object): + + BOX_PATCHES = ['boxes', 'whiskers', 'fliers', 'caps', 'medians'] + + def __init__(self, + hyperparam_array, + values_dict, + kEff_dict=None, + fig=None, + fig_size=(12, 5), + gridspec=None, + gridspec_idx=(0, 0), + show_outer_border=False, + show_inner_border=True, + back_wpad=0.0, + back_hpad=0.0, + fig_pad=0.4, + params_2_highlight=None, + box_color=None, + box_fliers=None, + box_props=None, + box_hl_color=None, + box_hl_props=None, + box_widths=None, + box_positions=None, + box_notch=True, + box_bootstrap=100, + box_whiskers=(1, 99), + xticks=None, + xticklabels=None, + hp_2_xtick_map=None, + xlabel=None, + xlabel_size=16, + xlim=None, + yticks=None, + yticklabels=None, + ylabel=None, + ylabel_size=16, + ylim=None, + verbose=False, + **kwargs): + + self.hp_array = np.sort(hyperparam_array).squeeze() + self.n_hp = len(self.hp_array) + + self.values = values_dict + + if (kEff_dict is None) and (hp_2_xtick_map is None): + self.hp_2_xtick_map = {hp: hp for hp in self.hp_array} + elif kEff_dict is not None: + self.hp_2_xtick_map = kEff_dict + elif hp_2_xtick_map is not None: + self.hp_2_xtick_map = hp_2_xtick_map + else: + if kEff_dict != hp_2_xtick_map: + err_str = f"Values provided for both `kEff_dict` and" + err_str += f" `hp_2_xtick_map` but they do not match! Cannot" + err_srt += f" set hyperparameter to xticklabel map!" + raise ValueError(err_str) + else: + self.hp_2_xtick_map = kEff_dict + + if fig is None: + fig = plt.figure(figsize=fig_size) + self.fig = fig + + if gridspec is None: + gridspec = fig.add_gridspec(1, 1) + self.gridspec = gridspec + self.outer_gs = self.gridspec[gridspec_idx] + + self.back_wpad, self.back_hpad = back_wpad, back_hpad + self.fig_pad = fig_pad + + ## Set up large axes + spine_alpha = 1 if show_outer_border else 0 + self.back_axis = self.fig.add_subplot(self.outer_gs) + self.back_axis = putl.make_border_axes(self.back_axis, + xticks=[], yticks=[], + spine_alpha=spine_alpha) + + ## Set up floating gridspec + self.spine_alpha = 1 if show_inner_border else 0 + self.inner_gs = gs.GridSpec(nrows=1, ncols=1, + wspace=self.back_wpad, + hspace=self.back_hpad) + + if params_2_highlight is None: + self.hp_2_hl = [] + else: + self.hp_2_hl = params_2_highlight + self.hp_2_hl_idx = np.array([ii for ii, hp in enumerate(self.hp_array) + if hp in self.hp_2_hl]).astype(int) + + self.box_color = box_color + self.box_fliers = box_fliers + self.box_props = box_props + self.box_hl_color = box_hl_color + self.box_hl_props = box_hl_props + self.box_widths = box_widths + self.box_positions = box_positions + self.box_notch = box_notch + self.box_bootstrap = box_bootstrap + self.box_whiskers = box_whiskers + + self.xticks = [] if xticks is None else xticks + self.xticklabels = [] if xticklabels is None else xticklabels + self.xlabel = xlabel + self.xlab_s = xlabel_size + self.xlim = xlim + + if len(self.xticks) < 1: + if len(self.hp_2_hl) > 0: + self.xticks = [0] + self.hp_2_hl_idx.tolist() + [self.n_hp - 1] + else: + if self.n_hp <= 5: + self.xticks = np.arange(self.n_hp) + else: + self.xticks = np.unique(np.linspace(0, self.n_hp, 5)) + self.xticks = np.clip(self.xticks, 0, self.n_hp - 1) + self.xticks = np.asarray(self.xticks).astype(int) + + if len(self.xticklabels) == 0: + xtlabs = np.asarray([self.hp_2_xtick_map[self.hp_array[idx]] + for idx in self.xticks]) + self.xticklabels = human_round(xtlabs).squeeze().astype(int) + + if self.xlabel is None: + self.xlabel = r"$k_{\mathrm{Eff}}$" + + if self.xlim is None: + self.xlim = [-1, self.n_hp] + + self.yticks = [] if yticks is None else yticks + self.yticklabels = [] if yticklabels is None else yticklabels + self.ylabel = ylabel + self.ylab_s = ylabel_size + self.ylim = ylim + + self.verbose = verbose + return + + def update_tight_bounds(self): + putl.update_tight_bounds(self.fig, + self.inner_gs, + self.outer_gs, + w_pad=self.back_wpad, + h_pad=self.back_hpad, + fig_pad=self.fig_pad) + return + + def make_plot(self, **kwargs): + + self.axis = self.fig.add_subplot(self.inner_gs[0]) + self.axis = putl.make_border_axes(self.axis, + spine_alpha=self.spine_alpha) + + self.axis = self._make_plot(**kwargs) + + self.axis.grid(which='major', axis='x', alpha=0) + + self.axis.set_xticks(self.xticks) + self.axis.set_xticklabels(self.xticklabels) + self.axis.set_xlabel(self.xlabel, fontsize=self.xlab_s, labelpad=0) + self.axis.set_xlim(self.xlim) + + if len(self.yticks) > 0: + self.axis.set_yticks(self.yticks) + self.axis.set_yticklabels(self.yticklabels) + + if self.ylim is not None: + self.axis.set_ylim(self.ylim) + + self.axis.tick_params(pad=-3) + + self.update_tight_bounds() + + return self.axis + + def _make_plot(self, **kwargs): + + return self.axis + + +class SweepBoxplot_pValues(SweepBoxplot): + + def __init__(self, *args, **kwargs): + + try: + self.pVal_cmap = kwargs.pop('pVal_cmap') + except KeyError: + self.pVal_cmap = putl.CategoricalFadingCMap() + + try: + self.cax_ticklabels = kwargs.pop('cax_ticklabels') + except KeyError: + self.cax_ticklabels = [f"{10.**(-cp):.1e}" + for cp in self.pVal_cmap.change_points] + + try: + self.clabel_size = kwargs.pop('clabel_size') + except KeyError: + self.clabel_size = 16 + + try: + self.cax_width_frac = kwargs.pop('cax_width_frac') + except KeyError: + self.cax_width_frac = 1.3 + + try: + self.cax_w2h_ratio = kwargs.pop('cax_w2h_ratio') + except KeyError: + self.cax_w2h_ratio = 0.1 + + super(SweepBoxplot_pValues, self).__init__(*args, **kwargs) + + if self.yticks == []: + self.yticks = -np.sort(self.pVal_cmap.change_points) + + if self.ylim is None: + self.ylim = [-self.pVal_cmap.change_points.max(), + -self.pVal_cmap.change_points.min()] + + if self.box_color is None: + self.box_color = 'gray' + + if self.box_fliers is None: + self.box_fliers = {'marker': ".", + 'markeredgecolor': self.box_color, + 'markersize': 2, + 'alpha': 0.5} + + if self.box_props is None: + self.box_props = {'alpha': 0.5, + 'color': self.box_color, + 'fill': True} + + if self.box_hl_color is None: + self.box_hl_color = 'gray' + + if self.box_hl_props is None: + self.box_hl_props = self.box_props.copy() + self.box_hl_props.update({"alpha": 0.9, + "color": self.box_hl_color}) + + if self.box_widths is None: + self.box_widths = 0.8 + try: + _ = [el for el in self.box_widths] + except TypeError as err: + self.box_widths = np.ones((self.n_hp)) * self.box_widths + + if self.box_positions is None: + self.box_positions = np.arange(self.n_hp) + + return + + def make_plot(self, **kwargs): + + self.axis = super().make_plot(**kwargs) + + self._cAx = self._add_colorbar() + + self.update_tight_bounds() + + return self.axis + + def _make_plot(self, **kwargs): + + hl_boxes = {} + for hpNo, hpVal in enumerate(self.hp_array): + + box_wid = self.box_widths[hpNo] + box_pos = self.box_positions[hpNo] + + if hpVal in self.hp_2_hl: + box_pps = self.box_hl_props.copy() + box_col = self.box_hl_color + else: + box_pps = self.box_props.copy() + box_col = self.box_color + + vals = np.log10(self.values[hpVal]) + box = self.axis.boxplot(vals, + widths=box_wid, + positions=[box_pos], + notch=self.box_notch, + bootstrap=self.box_bootstrap, + patch_artist=True, + whis=self.box_whiskers, + boxprops=box_pps, + flierprops=self.box_fliers) + + for item in self.BOX_PATCHES: + plt.setp(box[item], color=box_col) + + if hpVal in self.hp_2_hl: + hl_boxes[hpVal] = box['boxes'][0] + + self._highlighted_boxes = hl_boxes + + return self.axis + + def _add_colorbar(self, **kwargs): + + ## Get the transfiguration objects + inv_ax_trans = self.axis.transAxes.inverted() + fig_trans = self.fig.transFigure + + ## Convert pValue bounds from data to display + min_pVal = np.min([np.log10(self.values[hp].min()) + for hp in self.hp_array]) + min_pVal = np.min([min_pVal, -self.pVal_cmap.change_points.max()]) + max_pVal = np.max([np.log10(self.values[hp].max()) + for hp in self.hp_array]) + max_pVal = np.min([max_pVal, -self.pVal_cmap.change_points.min()]) + + min_pVal_crds = self.axis.transData.transform([self.xlim[0], min_pVal]) + max_pVal_crds = self.axis.transData.transform([self.xlim[0], max_pVal]) + + if self.verbose: + print(f"min_pVal_crds: {min_pVal_crds}") + print(f"max_pVal_crds: {max_pVal_crds}") + + ## Convert from display to figure coordinates + cFigX0, cFigY0 = fig_trans.inverted().transform(min_pVal_crds) + cFigX1, cFigY1 = fig_trans.inverted().transform(max_pVal_crds) + + if self.verbose: + print(f"cFig0: {cFigX0:.4f}, {cFigY0:.4f}") + print(f"cFig1: {cFigX1:.4f}, {cFigY1:.4f}") + + ## Get the height and width of the colorbar in Figure coordinates. + cFig_height = np.abs(cFigY1 - cFigY0) + cFig_width = self.cax_w2h_ratio * cFig_height + + if self.verbose: + print(f"The color bar will be {cFig_width:.4f} x" + f" {cFig_height:.4f}") + + ## Get the colorbar corners + cAxX0, cAxY0 = cFigX0 - self.cax_width_frac * cFig_width, cFigY0 + cAxX1, cAxY1 = cAxX0 + cFig_width, cFigY0 + cFig_height + + ## Convert from Figure back into Axes + [cAxX0, + cAxY0] = inv_ax_trans.transform(fig_trans.transform([cAxX0, cAxY0])) + [cAxX1, + cAxY1] = inv_ax_trans.transform(fig_trans.transform([cAxX1, cAxY1])) + + if self.verbose: + print(f"cAx0: {cAxX0:.4f}, {cAxY0:.4f}") + print(f"cAx1: {cAxX1:.4f}, {cAxY1:.4f}") + + ## Get the colorbar height and width in axes coordinates + cAx_height = np.abs(cAxY1 - cAxY0) + cAx_width = np.abs(cAxX1 - cAxX0) + + if self.verbose: + print(f"The color bar will be {cAx_width:.4f} x {cAx_height:.4f}") + + ## Make an inset axis. + caxIns = self.axis.inset_axes([cAxX0, cAxY0, cAx_width, cAx_height]) + caxIns = putl.make_border_axes(caxIns, spine_alpha=0) + + ## Create a dummy mappable then the colorbar. + hax = self.axis.scatter([], [], c=[], s=[], cmap=self.pVal_cmap.cmap, + norm=self.pVal_cmap.cnorm) + cAx = self.fig.colorbar(hax, cax=caxIns, ticks=[], + boundaries=self.pVal_cmap.cnorm.boundaries) + + cAx.set_ticks(self.pVal_cmap.change_points) + cAx.set_ticklabels(self.cax_ticklabels) + cAx.ax.tick_params(length=0) + + cAx.ax.invert_yaxis() + cAx.ax.yaxis.set_ticks_position('left') + cAx.ax.set_ylabel(r"EMBEDR $p$-Value", + fontsize=self.clabel_size, + labelpad=2) + cAx.ax.yaxis.set_label_position('left') + + return cAx + + +class SweepBoxplot_EES(SweepBoxplot): + + def __init__(self, *args, **kwargs): + + super(SweepBoxplot_EES, self).__init__(*args, **kwargs) + + if self.ylabel is None: + self.ylabel = r"Cell-Wise $D_{KL}$ (log scale)" + + if self.box_color is None: + self.box_color = {'data': 'C0', + 'null': 'C1'} + + if self.box_fliers is None: + self.box_fliers = {'marker': ".", + 'markersize': 2, + 'alpha': 0.5} + + if self.box_props is None: + self.box_props = {'alpha': 0.5, + 'fill': True} + + if self.box_hl_color is None: + self.box_hl_color = {'data': 'C0', + 'null': 'C1'} + + if self.box_hl_props is None: + self.box_hl_props = self.box_props.copy() + self.box_hl_props.update({"alpha": 0.9}) + + if self.box_widths is None: + self.box_widths = 0.8 + try: + _ = [el for el in self.box_widths] + except TypeError as err: + self.box_widths = np.ones((self.n_hp)) * self.box_widths + + if self.box_positions is None: + self.box_positions = np.arange(self.n_hp) + + def make_plot(self, **kwargs): + + self.axis = super().make_plot(**kwargs) + + self.update_tight_bounds() + + return self.axis + + def _make_plot(self, **kwargs): + + min_pVal, max_pVal = np.inf, -np.inf + hl_boxes = {} + legend_boxes = {} + conditions = ['data', 'null'] + for condition in conditions: + hl_boxes[condition] = {} + + for hpNo, hpVal in enumerate(self.hp_array): + + box_wid = self.box_widths[hpNo] / 2 + if condition == 'data': + box_pos = self.box_positions[hpNo] - box_wid / 2 + else: + box_pos = self.box_positions[hpNo] + box_wid / 2 + + if hpVal in self.hp_2_hl: + box_col = self.box_hl_color[condition] + box_pps = self.box_hl_props.copy() + else: + box_pps = self.box_props.copy() + box_col = self.box_color[condition] + box_pps['color'] = box_col + + vals = np.log10(self.values[condition][hpVal]).ravel() + + if vals.min() < min_pVal: + min_pVal = vals.min() + if vals.max() > max_pVal: + max_pVal = vals.max() + + box = self.axis.boxplot(vals, + widths=box_wid, + positions=[box_pos], + notch=self.box_notch, + bootstrap=self.box_bootstrap, + patch_artist=True, + whis=self.box_whiskers, + boxprops=box_pps, + flierprops=self.box_fliers) + + for item in self.BOX_PATCHES: + plt.setp(box[item], color=box_col) + + if hpVal in self.hp_2_hl: + hl_boxes[condition][hpVal] = box['boxes'][0] + + box_pps = self.box_props.copy() + box_pps['color'] = self.box_color[condition] + dummy_box = self.axis.boxplot([], widths=0., + notch=self.box_notch, + bootstrap=self.box_bootstrap, + patch_artist=True, + whis=self.box_whiskers, + boxprops=box_pps, + flierprops=self.box_fliers) + + for item in self.BOX_PATCHES: + plt.setp(dummy_box[item], color=self.box_color[condition]) + + legend_boxes[condition] = dummy_box['boxes'][0] + + self.axis.legend(list(legend_boxes.values()), + [cond.title() for cond in conditions]) + + self.yticks = np.linspace(min_pVal, max_pVal, 7) + self.yticklabels = [f"{10**yt:.2f}" for yt in self.yticks] + self.axis.set_yticks(self.yticks) + self.axis.set_yticklabels(self.yticklabels) + self.axis.set_ylabel(self.ylabel, fontsize=self.ylab_s) + + self._highlighted_boxes = hl_boxes + + self.update_tight_bounds() + + return self.axis + + + + + + + + +# def sweep_boxplots(hp_array, +# values_2_sweep, +# sweep_type, +# kEff_array, +# fig=None, +# fig_size=(12, 5), +# gridspec=None, +# show_borders=False, +# back_wpad=0.0, +# back_hpad=0.0, +# fig_pad=0.4, +# categ_cmap=None, +# values_2_highlight=[], +# box_color='grey', +# box_fliers=None, +# box_props=None, +# box_hl_color='grey', +# box_hl_props=None, +# box_widths=None, +# box_positions=None, +# box_notch=True, +# box_bootstrap=100, +# box_whiskers=(1, 99), +# xticks=None, +# xlabel=None, +# xlabel_size=16, +# xlim=None, +# ylim=None, +# cax_ticklabels=None, +# cax_width_frac=1.3, +# cax_w2h_ratio=0.1, +# verbose=False): + +# if fig is None: +# fig = plt.figure(figsize=fig_size) + +# if gridspec is None: +# gridspec = fig.add_gridspec(1, 1) + +# ## Set up large axes +# spine_alpha = 1 if show_borders else 0 +# back_axis = fig.add_subplot(gridspec[0]) +# back_axis = putl.make_border_axes(back_axis, xticks=[], yticks=[], +# spine_alpha=spine_alpha) + +# ## Set up floating gridspec +# back_gs = gs.GridSpec(nrows=1, ncols=1, +# wspace=back_wpad, hspace=back_hpad) + +# def tight_bounds_updater(): +# putl.update_tight_bounds(fig, back_gs, gridspec[0], w_pad=back_wpad, +# h_pad=back_hpad, fig_pad=fig_pad) + +# if sweep_type.lower() == 'pvalues': +# axis = sweep_boxplots_pvalues(hp_array, +# values_2_sweep, +# fig, gridspec, +# back_axis, back_gs, +# tight_bounds_updater, +# pVal_cmap=categ_cmap, +# values_2_highlight=values_2_highlight, +# box_color=box_color, +# box_fliers=box_fliers, +# box_props=box_props, +# box_hl_color=box_hl_color, +# box_hl_props=box_hl_props, +# box_widths=box_widths, +# box_positions=box_positions, +# box_notch=box_notch, +# box_bootstrap=box_bootstrap, +# box_whiskers=box_whiskers, +# cax_ticklabels=cax_ticklabels, +# cax_width_frac=cax_width_frac, +# cax_w2h_ratio=cax_w2h_ratio, +# xlim=xlim, +# ylim=ylim, +# label_size=xlabel_size, +# verbose=verbose) + +# elif sweep_type.lower() == 'ees': +# axis = sweep_boxplots_EES(hp_array, +# values_2_sweep, +# fig, gridspec, +# back_axis, back_gs, +# tight_bounds_updater, +# pVal_cmap=categ_cmap, +# values_2_highlight=values_2_highlight, +# box_color=box_color, +# box_fliers=box_fliers, +# box_props=box_props, +# box_hl_color=box_hl_color, +# box_hl_props=box_hl_props, +# box_widths=box_widths, +# box_positions=box_positions, +# box_notch=box_notch, +# box_bootstrap=box_bootstrap, +# box_whiskers=box_whiskers, +# cax_ticklabels=cax_ticklabels, +# cax_width_frac=cax_width_frac, +# cax_w2h_ratio=cax_w2h_ratio, +# xlim=xlim, +# ylim=ylim, +# label_size=xlabel_size, +# verbose=verbose) + +# if xticks is None: +# if values_2_highlight: +# xticks = [0] + hl_idx + [hpNo] +# else: +# if len(hp_array) <= 5: +# xticks = np.arange(len(hp_array)) +# else: +# xticks = np.unique(np.linspace(0, len(hp_array), 5)) +# xticks = np.clip(xticks, 0, len(hp_array) - 1) +# xticks = np.asarray(xticks).astype(int) + +# axis.set_xticks(xticks) + +# xticklabels = [f"{int(kEff_array[hp_array[idx]])}" for idx in xticks] +# xticklabels = human_round(np.asarray(xticklabels).squeeze()).astype(int) +# axis.grid(which='major', axis='x', alpha=0) +# axis.set_xticklabels(xticklabels) + +# if xlabel is None: +# xlabel = r"$k_{\mathrm{Eff}}$" +# axis.set_xlabel(xlabel, fontsize=xlabel_size, labelpad=0) + +# tight_bounds_updater() + +# return axis + + +# def sweep_boxplots_pvalues(hp_array, +# pValues, +# fig, +# gridspec, +# back_axis, +# back_gs, +# tight_bounds_updater, +# pVal_cmap=None, +# values_2_highlight=[], +# box_color='grey', +# box_fliers=None, +# box_props=None, +# box_hl_color='grey', +# box_hl_props=None, +# box_widths=None, +# box_positions=None, +# box_notch=True, +# box_bootstrap=100, +# box_whiskers=(1, 99), +# cax_ticklabels=None, +# cax_width_frac=1.3, +# cax_w2h_ratio=0.1, +# xlim=None, +# ylim=None, +# label_size=16, +# verbose=False): + +# if pVal_cmap is None: +# pVal_cmap = putl.CategoricalFadingCMap() + +# axis = putl.make_border_axes(fig.add_subplot(back_gs[0]), +# yticklabels=[], +# yticks=-np.sort(pVal_cmap.change_points), +# spine_alpha=1) + +# if box_fliers is None: +# box_fliers = {'marker': ".", +# 'markeredgecolor': box_color, +# 'markersize': 2, +# 'alpha': 0.5} + +# if box_props is None: +# box_props = {'alpha': 0.5, +# 'color': box_color, +# 'fill': True} + +# if box_hl_props is None: +# box_hl_props = box_props.copy() +# box_hl_props.update({"alpha": 0.9, "color": box_hl_color}) + +# if values_2_highlight is None: +# values_2_highlight = [] + +# hl_boxes = {} +# hl_idx = [] +# for hpNo, hpVal in enumerate(hp_array): + +# if box_widths is not None: +# try: +# box_wid = box_widths[hpNo] +# except TypeError as err: +# box_wid = box_widths +# else: +# box_wid = 0.8 + +# if box_positions is not None: +# try: +# box_pos = [box_positions[hpNo]] +# except TypeError as err: +# box_pos = [box_positions] +# else: +# box_pos = [hpNo] + +# if hpVal in values_2_highlight: +# box_pps = box_hl_props.copy() +# box_col = box_hl_color +# hl_idx.append(hpNo) +# else: +# box_pps = box_props.copy() +# box_col = box_color + +# box = axis.boxplot(np.log10(pValues[hpVal]), +# widths=box_wid, +# positions=box_pos, +# notch=box_notch, +# bootstrap=box_bootstrap, +# patch_artist=True, +# whis=box_whiskers, +# boxprops=box_pps, +# flierprops=box_fliers) + +# for item in BOX_PATCHES: +# plt.setp(box[item], color=box_col) + +# if hpVal in values_2_highlight: +# hl_boxes[hpVal] = box['boxes'][0] + +# if xlim is None: +# xlim = [-1, len(hp_array)] +# axis.set_xlim(*xlim) + +# if ylim is None: +# ylim = [-pVal_cmap.change_points.max(), -pVal_cmap.change_points.min()] +# axis.set_ylim(*ylim) + +# axis.tick_params(pad=-3) + +# tight_bounds_updater() + +# ## Colorbar parameters +# if cax_ticklabels is None: +# cax_ticklabels = [f"{10.**(-cp):.1e}" +# for cp in pVal_cmap.change_points] + +# inv_ax_trans = axis.transAxes.inverted() +# fig_trans = fig.transFigure + +# ## Convert pValue bounds from data to display +# min_pVal = np.min([np.log10(pValues[hp].min()) for hp in hp_array]) +# min_pVal = np.min([min_pVal, -pVal_cmap.change_points.max()]) +# max_pVal = np.max([np.log10(pValues[hp].max()) for hp in hp_array]) +# max_pVal = np.min([max_pVal, -pVal_cmap.change_points.min()]) +# min_pVal_crds = axis.transData.transform([xlim[0], min_pVal]) +# max_pVal_crds = axis.transData.transform([xlim[0], max_pVal]) + +# if verbose: +# print(f"min_pVal_crds: {min_pVal_crds}") +# print(f"max_pVal_crds: {max_pVal_crds}") + +# ## Convert from display to figure coordinates +# cFigX0, cFigY0 = fig_trans.inverted().transform(min_pVal_crds) +# cFigX1, cFigY1 = fig_trans.inverted().transform(max_pVal_crds) + +# if verbose: +# print(f"cFig0: {cFigX0:.4f}, {cFigY0:.4f}") +# print(f"cFig1: {cFigX1:.4f}, {cFigY1:.4f}") + +# ## Get the height and width of the colorbar in Figure coordinates. +# cFig_height = np.abs(cFigY1 - cFigY0) +# cFig_width = cax_w2h_ratio * cFig_height + +# if verbose: +# print(f"The color bar will be {cFig_width:.4f} x {cFig_height:.4f}") + +# ## Get the colorbar corners +# cAxX0, cAxY0 = cFigX0 - cax_width_frac * cFig_width, cFigY0 +# cAxX1, cAxY1 = cAxX0 + cFig_width, cFigY0 + cFig_height + +# ## Convert from Figure back into Axes +# [cAxX0, +# cAxY0] = inv_ax_trans.transform(fig_trans.transform([cAxX0, cAxY0])) +# [cAxX1, +# cAxY1] = inv_ax_trans.transform(fig_trans.transform([cAxX1, cAxY1])) + +# if verbose: +# print(f"cAx0: {cAxX0:.4f}, {cAxY0:.4f}") +# print(f"cAx1: {cAxX1:.4f}, {cAxY1:.4f}") + +# ## Get the colorbar height and width in axes coordinates +# cAx_height = np.abs(cAxY1 - cAxY0) +# cAx_width = np.abs(cAxX1 - cAxX0) + +# if verbose: +# print(f"The color bar will be {cAx_width:.4f} x {cAx_height:.4f}") + +# ## Make an inset axis. +# caxIns = axis.inset_axes([cAxX0, cAxY0, cAx_width, cAx_height]) +# caxIns = putl.make_border_axes(caxIns, spine_alpha=0) + +# ## Create a dummy mappable then the colorbar. +# hax = plt.scatter([], [], c=[], s=[], cmap=pVal_cmap.cmap, +# norm=pVal_cmap.cnorm) +# cAx = fig.colorbar(hax, cax=caxIns, ticks=[], +# boundaries=pVal_cmap.cnorm.boundaries) + +# cAx.set_ticks(pVal_cmap.change_points) +# cAx.set_ticklabels(cax_ticklabels) +# cAx.ax.tick_params(length=0) + +# cAx.ax.invert_yaxis() +# cAx.ax.yaxis.set_ticks_position('left') +# cAx.ax.set_ylabel(r"EMBEDR $p$-Value", +# fontsize=label_size, +# labelpad=2) +# cAx.ax.yaxis.set_label_position('left') + +# return axis diff --git a/EMBEDR/plots/sweep_lineplots.py b/EMBEDR/plots/sweep_lineplots.py new file mode 100644 index 0000000..f5d7708 --- /dev/null +++ b/EMBEDR/plots/sweep_lineplots.py @@ -0,0 +1,210 @@ +from EMBEDR.human_round import human_round +import EMBEDR.plotting_utility as putl +import matplotlib +import matplotlib.gridspec as gs +import matplotlib.pyplot as plt +import numpy as np + +def sweep_lineplot(hyperparam_array, + values, + log_hp=True, + log_values=True, + fig=None, + fig_size=(12, 5), + axis=None, + show_border=True, + threshold=-3, + threshold_color='lightgrey', + threshold_width=3, + line_color='k', + line_width=None, + line_alpha=None, + plot_median=True, + median_kws=None, + plot_percentiles=[90], + perc_kws=None, + xticks=None, + xticklabels=None, + xlabel=r"$k_{\mathrm{Eff}}$", + xlabel_size=16, + xlim=None, + yticks=None, + yticklabels=None, + ylabel=r"EMBEDR $p$-Value", + ylabel_size=16, + ylim=None, + title=r"EMBEDR Sweep: per-sample $p$-Value", + title_size=16, + title_pad=None): + + if isinstance(values, dict): + values = np.asarray([values[key] for key in hyperparam_array]) + n_hp, n_samples = values.shape + + if xticks is None: + xticks = hyperparam_array + + if yticks is None: + yticks = -1 * np.array([0, 2, 3, 4, 5]) + + if log_hp: + hyperparam_array = np.log10(hyperparam_array) + + xticks = np.log10(xticks) + if xticklabels is None: + xticklabels = [int(human_round(10**xt)) for xt in xticks] + + if log_values: + values = np.log10(values) + + if yticklabels is None: + yticklabels = [f"{10.**yt:.1g}" for yt in yticks] + + if fig is None: + fig = plt.figure(figsize=fig_size) + + if axis is None: + axis = fig.add_subplot(111) + spine_alpha = 1 if show_border else 0 + axis = putl.make_border_axes(axis, spine_alpha=spine_alpha) + + if line_width is None: + line_width = 0.2 + 10 / n_samples + if line_alpha is None: + line_alpha = 0.2 + 10 / n_samples + + _ = axis.plot(hyperparam_array, values, color=line_color, + lw=line_width, alpha=line_alpha) + + ylim = axis.get_ylim() + + _ = axis.axhline(threshold, lw=threshold_width, + color=threshold_color, zorder=-10) + + axis.set_ylim(ylim) + + if plot_median: + med_val = np.median(values, axis=1) + if median_kws is None: + median_kws = {'marker': 's', + 'markersize': 6, + 'markeredgecolor': 'w', + 'color': line_color, + 'lw': 1} + _ = axis.plot(hyperparam_array, med_val, **median_kws) + + if plot_percentiles: + perc_val = np.percentile(values, plot_percentiles, axis=1).T.squeeze() + + if perc_kws is None: + perc_kws = {'color': line_color, 'lw': 2} + _ = axis.plot(hyperparam_array, perc_val, **perc_kws) + + axis.set_xticks(xticks) + if xticklabels is not None: + axis.set_xticklabels(xticklabels) + else: + axis.set_xticklabels(xticklabels) + + axis.set_yticks(yticks) + if yticklabels is not None: + axis.set_yticklabels(yticklabels) + else: + axis.set_yticklabels(yticklabels) + + if xlabel is not None: + axis.set_xlabel(xlabel, fontsize=xlabel_size) + if ylabel is not None: + axis.set_ylabel(ylabel, fontsize=ylabel_size) + + if title is not None: + axis.set_title(title, fontsize=title_size, pad=title_pad) + + return axis + + +def sweep_lineplot_byCat(hyperparam_array, + values, + metadata, + label, + label_cmap='husl', + labels_2_show='all', + n_cols=3, + n_rows=None, + fig=None, + fig_size=(4, 2), + axes_sharey=True, + show_border=True, + xticks=None, + xticklabels=None, + xlabel=r"$k_{\mathrm{Eff}}$", + xlabel_size=16, + xlim=None, + yticks=None, + yticklabels=None, + ylabel=r"EMBEDR $p$-Value", + ylabel_size=16, + ylim=None, + verbose=False, + **kwargs): + + if isinstance(values, dict): + values = np.asarray([values[key] for key in hyperparam_array]) + n_hp, n_samples = values.shape + + [labels, + label_counts, + long_labels, + lab_2_idx_map, + label_cmap] = putl.process_categorical_label(metadata, label, + cmap=label_cmap) + + n_labels = len(label_counts) + + if n_rows is None: + n_rows = int(np.ceil(n_labels / n_cols)) + + if fig is None: + fig_size = (n_cols * fig_size[0], n_rows * fig_size[1]) + fig, axes = plt.subplots(n_rows, n_cols, + figsize=(n_cols * 4, n_rows * 2), + sharey=axes_sharey) + + for rNo, axes_row in enumerate(axes): + for cNo, axis in enumerate(axes_row): + spine_alpha=None + if rNo * n_cols + cNo >= n_labels: + spine_alpha = 1 if show_border else 0 + axes[rNo, cNo] = putl.make_border_axes(axis, + spine_alpha=spine_alpha) + + for lNo, label in enumerate(label_counts.index): + rowNo = int(lNo / n_cols) + colNo = lNo % n_cols + axis = axes[rowNo, colNo] + + good_idx = labels == label + n_labs = sum(good_idx) + if verbose: + print(f"There are {n_labs} samples with label = {label}") + + title = long_labels[lNo].title() + if "Multipotent" in title: + title = " ".join(title.split(" Multipotent ")) + + axis = sweep_lineplot(hyperparam_array, + values[:, good_idx], + fig=fig, + axis=axis, + line_color=label_cmap[lab_2_idx_map[label]], + xlabel_size=12, + ylabel_size=12, + title=title, + title_size=10, + title_pad=-8) + + fig.tight_layout() + + return axes + + \ No newline at end of file diff --git a/EMBEDR/plotting_utility.py b/EMBEDR/plotting_utility.py index b1fafde..a00c28a 100644 --- a/EMBEDR/plotting_utility.py +++ b/EMBEDR/plotting_utility.py @@ -138,13 +138,13 @@ def make_border_axes(axis, axis.set_xticks(xticks) if xticklabels is not None: - axis.set_xticks(xticklabels) + axis.set_xticklabels(xticklabels) if yticks is not None: axis.set_yticks(yticks) if yticklabels is not None: - axis.set_yticks(yticklabels) + axis.set_yticklabels(yticklabels) axis.set_visible(visible) @@ -219,6 +219,38 @@ def add_panel_number(axis, return axis +############################################################################### +## Functions for 3D Figures +############################################################################### + +def generate_rotations(fig, + axis, + angles=range(360), + file_name="3DAnimation_", + elevation=15): + + files = [] + for ii, angle in enumerate(angles): + if ii % 30 == 0: + print(f"{ii}: Angle = {angle}") + ax.view_init(elev = elevation, azim=angle) + fname = f'{file_name}_{ii}.jpeg' + fig.savefig(fname) + files.append(fname) + + return files + + +def animate_gif(files, output, delay=3, repeat=True): + """ + Uses imageMagick to produce an animated .gif from a list of + picture files. + """ + loop = -1 if repeat else 0 + file_list = " ".join(files) + os.system(f'convert -delay {delay} -loop {loop} {file_list} {output}') + + ############################################################################### ## Functions for Figure Aesthetics ############################################################################### diff --git a/EMBEDR/tsne.py b/EMBEDR/tsne.py index aa415dc..b48c43e 100644 --- a/EMBEDR/tsne.py +++ b/EMBEDR/tsne.py @@ -446,6 +446,9 @@ def optimize(self, ## ... fix the affinity matrix... if exaggeration != 1: P /= exaggeration + ## ... report the runtime... + if self.verbose >= 1: + timer.__exit__() ## ... and quit the loop! raise OptimizationInterrupt(error=d_kl, final_embedding=self.embedding)