def add_custom_ticks(ax: plt.Axes, tick_interp: np.ndarray): # I'm playing some tricks here, and just relabeling the ticks on the x-axis xticks = np.array(ax.get_xticks(), dtype=np.int) # This catch is necessary because sometimes the last tick is out of bonds for our # interpreted temp data if xticks[-1] > tick_interp[-1].size: xticks = xticks[:-1] ax.set_xticklabels(["{:.0f}".format(t) for t in tick_interp[xticks]])
def update_disc_plot(ax: plt.Axes, d_x, d_g_z, update_interval): clear_line(ax, 'dx') clear_line(ax, 'dgz') x = np.arange(1, len(d_x) + 1) ax.plot(x, d_x, color='#308862', label='D(x)', gid='dx') ax.plot(x, d_g_z, color='#B23F62', label='D(G(z))', gid='dgz', alpha=0.9) ax.legend(loc='upper right', framealpha=0.75, bbox_to_anchor=(1.008, 1.136), ncol=5) ax.set_xlim(left=1, right=len(x) + 0.01) ax.set_ylim(0, 1.0) ax.set_yticks(np.arange(0, 1, 0.1)) ticks = ax.get_xticks() ax.set_xticklabels([f'{t:.0f}' for t in ticks * update_interval])
def update_loss_plot(ax: plt.Axes, d_loss, d_fake_loss, g_loss, update_interval, separate=False): clear_line(ax, 'd_loss') clear_line(ax, 'g_loss') x = np.arange(1, len(d_loss) + 1) if separate: ax.plot(x, np.add(d_loss, d_fake_loss), color='dodgerblue', label='D Loss', gid='d_loss') clear_line(ax, 'd_real_loss') ax.plot(x, d_loss, color='lightseagreen', label='D Loss(Real)', gid='d_real_loss') clear_line(ax, 'd_fake_loss') ax.plot(x, d_fake_loss, color='mediumpurple', label='D Loss(Fake)', gid='d_fake_loss') else: ax.plot(x, d_loss, color='dodgerblue', label='D Loss', gid='d_loss') ax.plot(x, g_loss, color='coral', label='G Loss', gid='g_loss', alpha=0.9) ax.legend(loc='upper right', framealpha=0.75, bbox_to_anchor=(1.008, 1.136), ncol=5) ax.set_xlim(left=1, right=len(x) + 0.01) ticks = ax.get_xticks() ax.set_xticklabels([f'{t:.0f}' for t in ticks * update_interval])
def add_percent_axis(ax: plt.Axes, data_size, flip_axis: bool = False) -> plt.Axes: """ Adds a twin axis with percentages to a count plot. Args: ax: Plot axes figure to add percentage axis to data_size: Total count to use to normalize percentages flip_axis: Whether the countplot had its axes flipped Returns: Twin axis that percentages were added to """ if flip_axis: ax_perc = ax.twiny() ax_perc.set_xticks(100 * ax.get_xticks() / data_size) ax_perc.set_xlim( ( 100.0 * (float(ax.get_xlim()[0]) / data_size), 100.0 * (float(ax.get_xlim()[1]) / data_size), ) ) ax_perc.xaxis.set_major_formatter(mtick.PercentFormatter()) ax_perc.xaxis.set_tick_params(labelsize=10) else: ax_perc = ax.twinx() ax_perc.set_yticks(100 * ax.get_yticks() / data_size) ax_perc.set_ylim( ( 100.0 * (float(ax.get_ylim()[0]) / data_size), 100.0 * (float(ax.get_ylim()[1]) / data_size), ) ) ax_perc.yaxis.set_major_formatter(mtick.PercentFormatter()) ax_perc.yaxis.set_tick_params(labelsize=10) ax_perc.grid(False) return ax_perc
def draw_categorical( plot_type: str, ax: plt.Axes, data: Union[list, np.ndarray, to.Tensor, pd.DataFrame], x_label: Optional[Union[str, Sequence[str]]], y_label: Optional[str], vline_level: float = None, vline_label: str = "approx. solved", palette=None, title: str = None, show_legend: bool = True, legend_kwargs: dict = None, plot_kwargs: dict = None, ) -> plt.Figure: """ Create a box or violin plot for a list of data arrays or a pandas DataFrame. The plot is neither shown nor saved. If you want to order the 4th element to the 2nd position in terms of colors use .. code-block:: python palette.insert(1, palette.pop(3)) .. note:: If you want to have a tight layout, it is best to pass axes of a figure with `tight_layout=True` or `constrained_layout=True`. :param plot_type: tye of categorical plot, pass box or violin :param ax: axis of the figure to plot on :param data: list of data sets to plot as separate boxes :param x_label: labels for the categories on the x-axis, if `data` is not given as a `DataFrame` :param y_label: label for the y-axis, pass `None` to set no label :param vline_level: if not `None` (default) add a vertical line at the given level :param vline_label: label for the vertical line :param palette: seaborn color palette, pass `None` to use the default palette :param show_legend: if `True` the legend is shown, useful when handling multiple subplots :param title: title displayed above the figure, set to None to suppress the title :param legend_kwargs: keyword arguments forwarded to pyplot's `legend()` function, e.g. `loc='best'` :param plot_kwargs: keyword arguments forwarded to seaborn's `boxplot()` or `violinplot()` function :return: handle to the resulting figure """ plot_type = plot_type.lower() if plot_type not in ["box", "violin"]: raise pyrado.ValueErr(given=plot_type, eq_constraint="box or violin") if not isinstance(data, (list, to.Tensor, np.ndarray, pd.DataFrame)): raise pyrado.TypeErr( given=data, expected_type=[list, to.Tensor, np.ndarray, pd.DataFrame]) # Set defaults which can be overwritten plot_kwargs = merge_dicts([dict(alpha=1), plot_kwargs]) # by default no transparency alpha = plot_kwargs.pop( "alpha") # can't pass the to the seaborn plotting functions legend_kwargs = dict() if legend_kwargs is None else legend_kwargs palette = sns.color_palette() if palette is None else palette # Preprocess if isinstance(data, pd.DataFrame): df = data else: if isinstance(data, list): data = np.array(data) elif isinstance(data, to.Tensor): data = data.detach().cpu().numpy() if x_label is not None and not len(x_label) == data.shape[1]: raise pyrado.ShapeErr(given=data, expected_match=x_label) df = pd.DataFrame(data, columns=x_label) if data.shape[0] < data.shape[1]: print_cbt( f"Less data samples {data.shape[0]} then data dimensions {data.shape[1]}", "y", bright=True) # Plot if plot_type == "box": ax = sns.boxplot(data=df, ax=ax, **plot_kwargs) elif plot_type == "violin": plot_kwargs = merge_dicts([ dict(alpha=0.3, scale="count", inner="box", bw=0.3, cut=0), plot_kwargs ]) ax = sns.violinplot(data=df, ax=ax, palette=palette, **plot_kwargs) # Plot larger circles for medians (need to memorize the limits) medians = df.median().to_numpy() left, right = ax.get_xlim() locs = ax.get_xticks() ax.scatter(locs, medians, marker="o", s=30, zorder=3, color="white", edgecolors="black") ax.set_xlim((left, right)) # Postprocess if alpha < 1 and plot_type == "box": for patch in ax.artists: r, g, b, a = patch.get_facecolor() patch.set_facecolor((r, g, b, alpha)) elif alpha < 1 and plot_type == "violin": for violin in ax.collections[::2]: violin.set_alpha(alpha) if vline_level is not None: # Add dashed line to mark a threshold ax.axhline(vline_level, c="k", ls="--", lw=1.0, label=vline_label) if x_label is None: ax.get_xaxis().set_ticks([]) if y_label is not None: ax.set_ylabel(y_label) if show_legend: ax.legend(**legend_kwargs) if title is not None: ax.set_title(title) return plt.gcf()
def break_axis( amin, amax=None, xy='x', ax: plt.Axes = None, fun_draw: Callable = None, margin=0.05, ) -> (plt.Axes, plt.Axes): """ :param amin: data coordinate to start breaking from :param amax: data coordinate to end breaking at :param xy: 'x' or 'y' :param fun_draw: if not None, fun_draw(ax1) and fun_draw(ax2) will be run to recreate ax. Use the same function as that was called for with ax. Use, e.g., fun_draw=lambda ax: ax.plot(x, y) :return: axs: a list of axes created """ if amax is None: amax = amin if ax is None: ax = plt.gca() if xy == 'x': rect = ax.get_position().bounds lim = ax.get_xlim() prop_min = (amin - lim[0]) / (lim[1] - lim[0]) prop_max = (amax - lim[0]) / (lim[1] - lim[0]) rect1 = np.array([rect[0], rect[1], rect[2] * prop_min, rect[3]]) rect2 = [ rect[0] + rect[2] * prop_max, rect[1], rect[2] * (1 - prop_max), rect[3] ] fig = ax.figure # type: plt.Figure ax1 = fig.add_axes(plt.Axes(fig=fig, rect=rect1)) ax1.update_from(ax) if fun_draw is not None: fun_draw(ax1) ax1.set_xticks(ax.get_xticks()) ax1.set_xlim(lim[0], amin) ax1.spines['right'].set_visible(False) ax2 = fig.add_axes(plt.Axes(fig=fig, rect=rect2)) ax2.update_from(ax) if fun_draw is not None: fun_draw(ax2) ax2.set_xticks(ax.get_xticks()) ax2.set_xlim(amax, lim[1]) ax2.spines['left'].set_visible(False) ax2.set_yticks([]) ax.set_visible(False) # plt.show() # CHECKED axs = [ax1, ax2] elif xy == 'y': rect = ax.get_position().bounds lim = ax.get_ylim() prop_all = ((amin - lim[0]) + (lim[1] - amax)) / (1 - margin) prop_min = (amin - lim[0]) / prop_all prop_max = (lim[1] - amax) / prop_all rect1 = np.array([rect[0], rect[1], rect[2], rect[3] * prop_min]) rect2 = [ rect[0], rect[1] + rect[3] * (1 - prop_max), rect[2], rect[3] * (1 - prop_max) ] fig = ax.figure # type: plt.Figure ax1 = fig.add_axes(plt.Axes(fig=fig, rect=rect1)) ax1.update_from(ax) if fun_draw is not None: fun_draw(ax1) ax1.set_yticks(ax.get_yticks()) ax1.set_ylim(lim[0], amin) ax1.spines['top'].set_visible(False) ax2 = fig.add_axes(plt.Axes(fig=fig, rect=rect2)) ax2.update_from(ax) if fun_draw is not None: fun_draw(ax2) ax2.set_yticks(ax.get_yticks()) ax2.set_ylim(amax, lim[1]) ax2.spines['bottom'].set_visible(False) ax2.set_xticks([]) ax.set_visible(False) # plt.show() # CHECKED axs = [ax1, ax2] else: raise ValueError() return axs
def cases_and_deaths( data: pd.DataFrame, dates: bool = False, ax: plt.Axes = None, smooth: bool = True, cases: str = "cases", deaths: str = "deaths", tight_layout=False, **kwargs, ) -> plt.Axes: """ A simple chart showing observed new cases cases as vertical bars and a smoothed out prediction of this curve. Args: data: A dataframe with ["cases", "deaths"] columns. dates: If True, show dates instead of days in the x-axis. ax: An explicit matplotlib axes. smooth: If True, superimpose a plot of a smoothed-out version of the cases curve. cases: deaths: Name of the cases/deaths columns in the dataframe. """ if not dates: data = data.reset_index(drop=True) # Smoothed data col_names = {cases: _("Cases"), deaths: _("Deaths")} if smooth: from pydemic import fitting as fit smooth = pd.DataFrame( { _("{} (smooth)").format(col_names[cases]): fit.smoothed_diff(data[cases]), _("{} (smooth)").format(col_names[deaths]): fit.smoothed_diff(data[deaths]), }, index=data.index, ) ax = smooth.plot(legend=False, lw=2, ax=ax) # Prepare cases dataframe and plot it kwargs.setdefault("alpha", 0.5) new_cases = data.diff().fillna(0) new_cases = new_cases.rename(col_names, axis=1) if "ylim" not in kwargs: deaths = new_cases.iloc[:, 1] exp = np.log10(deaths[deaths > 0]).mean() exp = min(10, int(exp / 2)) kwargs["ylim"] = (10**exp, None) ax: plt.Axes = new_cases.plot.bar(width=1.0, ax=ax, **kwargs) # Fix xticks periods = 7 if dates else 10 xticks = ax.get_xticks() labels = ax.get_xticklabels() ax.set_xticks(xticks[::periods]) ax.set_xticklabels(labels[::periods]) ax.tick_params("x", rotation=0) ax.set_ylim(1, None) if tight_layout: fig = ax.get_figure() fig.tight_layout() return ax
def plot_map(self, im: np.array, title: str = '', cbar_unit: str = None, tag: str = None, meta: dict = None, cmap: str = 'viridis', view_extent: np.array = None, outline: bool = False, points: bool = False, point_color: bool = False, rectangle: bool = False, labels: bool = False, ticks: bool = True, scamap: bool = None, ax: plt.Axes = None, hillshade: bool = False, scale_dict: dict = None, grid: bool = True, alpha: float = 1, showplot: bool = False, sci: bool = False, figsize: tuple = None, ashape: bool = None): # Main plotting function. Ensures that all other plots have the same parameters """ Inputs: im: The 3D np array to be plotted. Can be the DEM, the thickness, the error, etc. title: The title of the plot cbar_unit: The units of the color bar tag: The tag of the plot with which it will be saved cmap: The colormap wanted for the plot, defaults to viridis view_extent: The extent wanted for the plot. Defaults to None which will set the extent to the whole map. Takes as input a numpy array, like the self.point_extent array Outline: Boolean value, defaults to False. Set to true if you want the outline plotted on the map. points: Boolean value, defaults to False. Set to true if you want the points plotted on the map. point_color: Boolean value, defaults to False. Set to true if you want the points colored by the thickness. Could be changed in the future to a scalar map instead of a boolean. rectangle: Boolean value, defaults to False. Set to true if you want a rectangle outlining the points' extent in the map. CURRENTLY DOESN'T WORK PROPERLY labels: Boolean value, defaults to False. Set to true if you want the x and y labels plotted. ticks: Boolean value, defaults to True. Set to true if you want the x and y ticks plotted. scamap: Scalarmap object, defaults to None. Set if you want a specific colormap scale. Useful for subplots. ax: matplotlib Ax object, defaults to None. Set if you want to specify on which ax to plot. Useful for subplots. hillshade: Boolean value, defaults to False. Set for a hillshade effect, especially on DEMs scale: Dictionary, defaults to None. Parameters to add a scale to the map alpha: Float defaults to 1. Sets the transparency of the main map image showplot: Boolean, defaults to False. Set to true if you want to see the figure. Only pops up if no ax object is provided. """ # Gets the two last dimensions of the 3D array. Needed because rasters from rasterio are of (1, m, n) size if len(im.shape) == 3: im = im[0] if meta is None: meta = self.meta # Manages the extent array. The order needed here is different than given from the shapely format b = [0, 2, 1, 3] extent = [ rasterio.transform.array_bounds(*im.shape, meta['transform'])[i] for i in b ] xmin, xmax, ymin, ymax = [self.outline.total_bounds[i] for i in b] if view_extent is not None: view_extent = [view_extent[i] for i in b] xmin, xmax, ymin, ymax = view_extent fig = None if ax is None: fig = plt.figure(figsize=figsize, tight_layout=True) ax = plt.gca() # Plot the image and add a colorbar if scamap is None: norm = Normalize(np.nanmin(im), np.nanmax(im)) scamap = plt.cm.ScalarMappable(cmap=cmap, norm=norm) if hillshade: hills = im_to_hillshade(self.dem_im[0], 225, 40) ax.imshow(hills, extent=[self.extent[i] for i in b], cmap='Greys') img = ax.imshow(im, cmap=cmap, extent=extent, alpha=alpha) if cbar_unit is not None: divider = make_axes_locatable(ax) cax = divider.append_axes('right', size=0.2, pad=0.1) cbar = plt.colorbar(img, cax=cax, orientation='vertical') cbar.ax.get_yaxis().labelpad = 15 cbar.ax.set_title(f'{cbar_unit}') c = 'black' e = None if point_color: c = scamap.to_rgba(self.gpr.iloc[:, 2]) e = 'black' # Plots various map accessories if asked if points: lw = 0.1 if len(self.gpr) > 1000: lw = 0 ax.scatter(self.gpr.geometry.x, self.gpr.geometry.y, c=c, cmap=cmap, edgecolors=e, linewidths=lw) if outline: self.outline.plot(ax=ax, facecolor='None', edgecolor='black') if rectangle: rec = self.create_rectangle(color='red', lw=1) ax.add_patch(rec) if ashape: x, y = self.ashape.unary_union.exterior.xy ax.plot(x, y, color='red', lw=1) if scale_dict is not None: scale_dict = parse_scale_dict(scale_dict) y_offset = (ymax - ymin) * scale_dict['y_offset'] x_offset = (xmax - xmin) * scale_dict['x_offset'] length = scale_dict['length'] label_length = length if scale_dict['units'] == 'km': length *= 1000 xs = [xmin + x_offset, xmin + x_offset + length] ys = [ymin + y_offset, ymin + y_offset] bar = ax.plot(xs, ys, linewidth=5, color=scale_dict['color']) txt = ax.text(np.mean(xs), np.mean(ys) + y_offset * (1 + 0.05 / scale_dict['y_offset']), f'{label_length} {scale_dict["units"]}', ha='center', va='center', color=scale_dict['color']) # txt.set_path_effects([pe.withStroke(linewidth=5, foreground='w')]) # Customises the map ax.set_title(f'{title}') ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_aspect('equal') if labels: ax.set_xlabel('Eastings [m]') ax.set_ylabel('Northing [m]') if ticks: if sci: ax.ticklabel_format(axis='both', style='sci', scilimits=(0, 0)) else: ax.set_yticklabels( ['{:,.0f}'.format(y) for y in ax.get_yticks().tolist()]) ax.set_xticklabels( ['{:,.0f}'.format(x) for x in ax.get_xticks().tolist()], rotation=-45) else: ax.set_yticklabels([]) ax.set_xticklabels([]) ax.set_xticks([]) ax.set_yticks([]) if grid: ax.grid() # Defines a custom legend for the outline due to a geopandas bug # Currently no legend, need to figure out something for every model and dem source """handles, labels = ax.get_legend_handles_labels() handles.append(mlines.Line2D([], [], color='black', label=data['outline'])) handles.append(matplotlib.patches.Patch (color='none', label=crs)) ax.legend(handles=handles, bbox_to_anchor=(0.5, data['leg_pos']), loc="lower center", bbox_transform=fig.transFigure, ncol=len(handles), frameon=True)""" if fig is not None: if showplot: plt.show() if tag is not None: fig.savefig(f'{self.img_folder}/{tag}.png', bbox_inches='tight') return fig, ax else: return img