def post_process_plt(df: pd.DataFrame, ax: plt.axes.Axes, percent: bool = False) -> plt.figure.Figure: """Post-process the Matplotlib Axes instance produced by :func:`pre_process_plt`. The post-processing invovles further formatting of the legend, the x-axis and the y-axis. Parameters ---------- df : :class:`pandas.DataFrame` A DataFrame holding the accumulated SBU usage. See :func:`pre_process_df` and :func:`.get_agregated_sbu`. ax : :class:`matplotlib.Axes<matplotlib.axes.Axes>` An Axes instance produced by :func:`pre_process_plt`. percent : class`bool` If ``True``, apply additional formatting for handling percentages. Returns ------- :class:`matplotlib.figure.Figure`: A Matplotlib Figure constructed from **ax**. """ df_max = df.max().max() decimals = 1 - len(str(int(df.values.max()))) y_max = round(df_max, decimals) + 10**-decimals # Format the y-axis ax.yaxis.set_major_formatter(plt.ticker.StrMethodFormatter('{x:,.0f}')) ax.set(ylim=(0, y_max)) # Format the x-axis i = len(df.index) // 6 or 1 ax.set(xticks=df.index[0::i]) today = date.today().strftime('%d %b %Y') if percent: ax.set_ylabel('SBUs (System Billing Units) / %') ax.set_title('Accumulated % SBU usage: {}'.format(today), fontdict={'fontsize': 18}) ax.legend_.set_title('Project (PI): % SBU') else: ax.set_ylabel('SBUs (System Billing Units) / hours') ax.set_title('Accumulated SBU usage: {}'.format(today), fontdict={'fontsize': 18}) ax.legend_.set_title('Project (PI): SBU') return ax.get_figure()
def get_textobj_dimensions(axes: _matplotlib.axes.Axes, texts: list) -> _numpy.ndarray: """ get the dimensions (width, height) of a list of text objects """ if not isinstance(axes, _matplotlib.axes.Axes): raise TypeError("bad type of 'axes'") # get info from axes inv_trans = axes.transData.inverted() # text width/height must be calculated after rendering renderer = axes.get_figure().canvas.get_renderer() whs = _numpy.empty((len(texts), 2), dtype=float) for wh, t_obj in zip(whs, texts): wh[:] = _render_textobj_wh(t_obj, renderer, inv_trans) return whs
def send_plot( user: User, context, plot: matplotlib.axes.Axes, caption: str = "" ): """Send a pandas plot to the specified chat""" fig = plot.get_figure() fig.tight_layout() image = io.BytesIO() fig.savefig(image, format="png", dpi=300) plt.close(fig) image.seek(0) context.bot.send_photo( chat_id=user.telegram_id, photo=image, parse_mode="Markdown", caption=caption, ) pass
def plot_lookahead_final_acceptance_fractions( sampler_df: Union[pd.DataFrame, str], population_sizes: Union[np.ndarray, History], relative: bool = False, fill: bool = False, alpha: float = None, t_min: int = 0, title: str = "Composition of final acceptances", size: tuple = None, ax: mpl.axes.Axes = None): """Plot fraction of look-ahead samples in final acceptances, over generations. Parameters ---------- sampler_df: Dataframe or file as generated via `RedisEvalParallelSampler(log_file=...)`. population_sizes: The sizes of the populations of accepted particles. If a History is passed, those values are extracted automatically, otherwise should be for the same time values as `sampler_df`. relative: Whether to normalize the total evaluations for each generation to 1. fill: If True, instead of lines, filled areas are drawn that sum up to the totals. alpha: Alpha value for lines or areas. t_min: The minimum generation to show. E.g. a value of 1 omits the first generation. title: Plot title. size: The size of the plot in inches. ax: The axis object to use. Returns ------- ax: Axis of the generated plot. """ # process input if isinstance(sampler_df, str): sampler_df = pd.read_csv(sampler_df, sep=',') if alpha is None: alpha = 0.7 if fill else 1.0 # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # get numbers of final acceptances if isinstance(population_sizes, History): pop = population_sizes.get_all_populations() population_sizes = np.array( [pop.loc[pop.t == t, 'particles'] for t in sampler_df.t], dtype=float).flatten() # restrict to t >= 0 population_sizes = population_sizes[sampler_df.t >= t_min] sampler_df = sampler_df[sampler_df.t >= t_min] # extract variables t = sampler_df.t n_la_acc = sampler_df.n_lookahead_accepted # actual look-ahead acceptances cannot be more than requested n_la_acc = np.minimum(n_la_acc, population_sizes) # actual acceptances are the remaining ones, as these are always later n_act_acc = population_sizes - n_la_acc # normalize if relative: n_la_acc /= population_sizes n_act_acc /= population_sizes population_sizes /= population_sizes # plot if fill: ax.fill_between(t, n_la_acc, population_sizes, alpha=alpha, label="Actual") ax.fill_between(t, 0, n_la_acc, alpha=alpha, label="Look-ahead") else: ax.plot(t, population_sizes, linestyle='--', marker='o', color='black', alpha=alpha, label="Population size") ax.plot(t, n_act_acc, marker='o', alpha=alpha, label="Actual") ax.plot(t, n_la_acc, marker='o', alpha=alpha, label="Look-ahead") # prettify plot ax.legend() ax.set_title(title) ax.set_xlabel("Population index") ax.set_ylabel("Final acceptances") ax.set_ylim(bottom=0) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) if size is not None: fig.set_size_inches(size) return ax
def plot_lookahead_evaluations(sampler_df: Union[pd.DataFrame, str], relative: bool = False, fill: bool = False, alpha: float = None, t_min: int = 0, title: str = "Total evaluations", size: tuple = None, ax: mpl.axes.Axes = None): """Plot total vs look-ahead evaluations over the generations. Parameters ---------- sampler_df: Dataframe or file as generated via `RedisEvalParallelSampler(log_file=...)`. relative: Whether to normalize the total evaluations for each generation to 1. fill: If True, instead of lines, filled areas are drawn that sum up to the totals. alpha: Alpha value for lines or areas. t_min: The minimum generation to show. E.g. a value of 1 omits the first generation. title: Plot title. size: The size of the plot in inches. ax: The axis object to use. Returns ------- ax: Axis of the generated plot. """ # process input if isinstance(sampler_df, str): sampler_df = pd.read_csv(sampler_df, sep=',') if alpha is None: alpha = 0.7 if fill else 1.0 # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # restrict to t >= 0 sampler_df = sampler_df[sampler_df.t >= t_min] # extract variables t = sampler_df.t n_la = sampler_df.n_lookahead n_eval = sampler_df.n_evaluated n_act = n_eval - n_la # normalize if relative: n_la /= n_eval n_act /= n_eval n_eval /= n_eval # plot if fill: ax.fill_between(t, n_la, n_eval, alpha=alpha, label="Actual") ax.fill_between(t, 0, n_la, alpha=alpha, label="Look-ahead") else: ax.plot(t, n_eval, linestyle='--', marker='o', color='black', alpha=alpha, label="Total") ax.plot(t, n_act, marker='o', alpha=alpha, label="Actual") ax.plot(t, n_la, marker='o', alpha=alpha, label="Look-ahead") # prettify plot ax.legend() ax.set_title(title) ax.set_xlabel("Population index") ax.set_ylabel("Evaluations") ax.set_ylim(bottom=0) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) if size is not None: fig.set_size_inches(size) return ax
def plot_acceptance_rates_trajectory(histories: Union[List, History], labels: Union[List, str] = None, title: str = "Acceptance rates", yscale: str = 'lin', size: tuple = None, ax: mpl.axes.Axes = None, colors: List[str] = None, normalize_by_ess: bool = False): """ Plot of acceptance rates over all iterations, i.e. one trajectory per history. Parameters ---------- histories: The histories to plot from. History ids must be set correctly. labels: Labels corresponding to the histories. If None are provided, indices are used as labels. title: Title for the plot. yscale: The scale on which to plot the counts. Can be one of 'lin', 'log' (basis e) or 'log10' size: The size of the plot in inches. ax: The axis object to use. normalize_by_ess: bool, optional (default = False) Indicator to use effective sample size for the acceptance rate in place of the population size. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) if colors is None: colors = [None] * len(histories) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract sample numbers times = [] samples = [] pop_sizes = [] for history in histories: # note: the first entry of time -1 is trivial and is thus ignored here h_info = history.get_all_populations() times.append(np.array(h_info['t'])[1:]) if normalize_by_ess: ess = np.zeros(len(h_info['t']) - 1) for t in np.array(h_info['t'])[1:]: w = history.get_weighted_distances(t=t)['w'] ess[t - 1] = effective_sample_size(w) pop_sizes.append(ess) else: pop_sizes.append( np.array(history.get_nr_particles_per_population().values[1:])) samples.append(np.array(h_info['samples'])[1:]) # compute acceptance rates rates = [] for sample, pop_size in zip(samples, pop_sizes): rates.append(pop_size / sample) # apply scale ylabel = "Acceptance rate" if yscale == 'log': rates = [np.log(rate) for rate in rates] ylabel = "log(" + ylabel + ")" elif yscale == 'log10': rates = [np.log10(rate) for rate in rates] ylabel = "log10(" + ylabel + ")" # plot for t, rate, label, color in zip(times, rates, labels, colors): ax.plot(t, rate, 'x-', label=label, color=color) # add labels ax.legend() ax.set_title(title) ax.set_ylabel(ylabel) ax.set_xlabel("Population index $t$") # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_sample_numbers_trajectory(histories: Union[List, History], labels: Union[List, str] = None, rotation: int = 0, title: str = "Required samples", yscale: str = 'lin', size: tuple = None, ax: mpl.axes.Axes = None): """ Plot of required sample number over all iterations, i.e. one trajectory per history. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. rotation: int, optional (default = 0) Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: str, optional (default = "Required samples") Title for the plot. yscale: str, optional (default = 'lin') The scale on which to plot the counts. Can be one of 'lin', 'log' (basis e) or 'log10' size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract sample numbers times = [] samples = [] for history in histories: # note: the first entry corresponds to the calibration and should # be included here to be fair against methods not requiring # calibration h_info = history.get_all_populations() times.append(np.array(h_info['t'])) samples.append(np.array(h_info['samples'])) # apply scale ylabel = "Samples" if yscale == 'log': samples = [np.log(sample) for sample in samples] ylabel = "log(" + ylabel + ")" elif yscale == 'log10': samples = [np.log10(sample) for sample in samples] ylabel = "log10(" + ylabel + ")" # plot for t, sample, label in zip(times, samples, labels): ax.plot(t, sample, 'x-', label=label) # add labels if any(lab is not None for lab in labels): ax.legend() ax.set_title(title) ax.set_ylabel(ylabel) ax.set_xlabel("Population index $t$") # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_sample_numbers(histories: Union[List, History], labels: Union[List, str] = None, rotation: int = 0, title: str = "Required samples", size: tuple = None, ax: mpl.axes.Axes = None): """ Stacked bar plot of required numbers of samples over all iterations. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. rotation: int, optional (default = 0) Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: str, optional (default = "Total required samples") Title for the plot. size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() n_run = len(histories) # extract sample numbers samples = [] for history in histories: # note: the first entry corresponds to the calibration and should # be included here to be fair against methods not requiring # calibration samples.append(np.array(history.get_all_populations()['samples'])) # create matrix n_pop = max(len(sample) for sample in samples) matrix = np.zeros((n_pop, n_run)) for i_sample, sample in enumerate(samples): matrix[:len(sample), i_sample] = sample # plot bars for i_pop in reversed(range(n_pop)): ax.bar(x=np.arange(n_run), height=matrix[i_pop, :], bottom=np.sum(matrix[:i_pop, :], axis=0), label=f"Generation {i_pop-1}") # add labels ax.set_xticks(np.arange(n_run)) ax.set_xticklabels(labels, rotation=rotation) ax.set_title(title) ax.set_ylabel("Samples") ax.set_xlabel("Run") ax.legend() # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_distance_weights( log_files: Union[List[str], str], ts: Union[List[int], List[str], int, str] = "last", labels: Union[List[str], str] = None, colors: Union[List[Any], Any] = None, linestyles: Union[List[str], str] = None, keys_as_labels: bool = True, keys: List[str] = None, xticklabel_rotation: float = 0, normalize: bool = True, size: Tuple[float, float] = None, xlabel: str = "Summary statistic", ylabel: str = "Weight", title: str = None, ax: mpl.axes.Axes = None, **kwargs, ) -> mpl.axes.Axes: """Plot distance weights, one curve per argument. Assumes that the weights to be plotted from each file and timepoint have the same keys. Parameters ---------- log_files: The weight log files, as passed to the distance functions, e.g. as "scale_log_file" or "info_log_file". ts: Time points to plot. Defaults to the last entry in each file. labels: A label for each log file. colors: A color for each log file. linestyles: Linestyles to apply. keys_as_labels: Whether to use the summary statistic keys as x tick labels. keys: Data keys to plot. xticklabel_rotation: Angle by which to rotate x tick labels. normalize: Whether to normalize the weights to sum 1. size: Figure size in inches, (width, height). xlabel: x-axis label. ylabel: y-axis label. title: Plot title. ax: Axis object to use. **kwargs: Additional keyword arguments are passed on to `plt.plot()` when plotting lines. Returns ------- The used axis object. """ log_files, ts, colors, linestyles = to_lists(log_files, ts, colors, linestyles) labels = get_labels(labels, len(log_files)) # default keyword arguments if "marker" not in kwargs: kwargs["marker"] = "x" n_run = len(log_files) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # add a line per file for log_file, t, label, color, linestyle in zip(log_files, ts, labels, colors, linestyles): weights = load_dict_from_json(log_file) if t == "last": t = max(weights.keys()) weights = weights[t] if keys is None: keys = list(weights.keys()) weights = np.array([weights[key] for key in keys]) if normalize: weights /= weights.sum() ax.plot( weights, label=label, color=color, linestyle=linestyle, **kwargs, ) # add labels if n_run > 1: ax.legend() # x axis ticks if keys_as_labels: ax.set_xticks(np.arange(len(keys))) ax.set_xticklabels(keys, rotation=xticklabel_rotation) else: # enforce integer labels ax.xaxis.set_major_locator(MaxNLocator(integer=True)) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) if size is not None: fig.set_size_inches(size) return ax
def plot_eps_walltime_lowlevel(end_times: List, eps: List, labels: Union[List, str] = None, unit: str = 's', xscale: str = 'linear', yscale: str = 'log', title: str = "Epsilon over walltime", size: tuple = None, ax: mpl.axes.Axes = None) -> mpl.axes.Axes: """Low-level access to `plot_eps_walltime`. Directly define `end_times` and `eps`. Note that both should be arrays of the same length and at the beginning include a value for the calibration iteration. This is just what `pyabc.History.get_all_populations()` returns. The first time is used as the base time differences to which are plotted. The first epsilon is ignored. """ # preprocess input end_times = to_lists(end_times) labels = get_labels(labels, len(end_times)) n_run = len(end_times) # check time unit if unit not in TIME_UNITS: raise AssertionError(f"`unit` must be in {TIME_UNITS}") # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract relative walltimes walltimes = [] for end_ts in end_times: # compute differences to base diffs = end_ts[1:] - end_ts[0] # as seconds diffs = [diff.total_seconds() for diff in diffs] # append walltimes.append(diffs) # disregard calibration epsilon (inf) eps = [ep[1:] for ep in eps] for wt, ep, label in zip(walltimes, eps, labels): wt = np.asarray(wt) # apply time unit if unit == MINUTE: wt /= 60 elif unit == HOUR: wt /= (60 * 60) elif unit == DAY: wt /= (60 * 60 * 24) # plot ax.plot(wt, ep, label=label, marker='o') # prettify plot if n_run > 1: ax.legend() ax.set_title(title) ax.set_xlabel(f"Time [{unit}]") ax.set_ylabel("Epsilon") ax.set_xscale(xscale) ax.set_yscale(yscale) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_total_walltime(histories: Union[List[History], History], labels: Union[List, str] = None, unit: str = 's', rotation: int = 0, title: str = "Total walltimes", size: tuple = None, ax: mpl.axes.Axes = None) -> mpl.axes.Axes: """Plot total walltimes, for each history one single-color bar. Parameters ---------- histories: The histories to plot from. History ids must be set correctly. labels: Labels corresponding to the histories. If None are provided, indices are used as labels. unit: Time unit to use ('s', 'm', 'h', 'd' as seconds, minutes, hours, days). rotation: Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: Title for the plot. size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) n_run = len(histories) # check time unit if unit not in TIME_UNITS: raise AssertionError(f"`unit` must be in {TIME_UNITS}") # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract total walltimes walltimes = [] for h in histories: abc = h.get_abc() walltimes.append((abc.end_time - abc.start_time).total_seconds()) walltimes = np.asarray(walltimes) # apply time unit if unit == MINUTE: walltimes /= 60 elif unit == HOUR: walltimes /= (60 * 60) elif unit == DAY: walltimes /= (60 * 60 * 24) # plot bars ax.bar(x=np.arange(n_run), height=walltimes, label=labels) # prettify plot ax.set_xticks(np.arange(n_run)) ax.set_xticklabels(labels, rotation=rotation) ax.set_title(title) ax.set_xlabel("Run") ax.set_ylabel(f"Time [{unit}]") if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_walltime_lowlevel(end_times: List, start_times: Union[List, None] = None, labels: Union[List, str] = None, show_calibration: bool = None, unit: str = 's', rotation: int = 0, title: str = "Walltime by generation", size: tuple = None, ax: mpl.axes.Axes = None) -> mpl.axes.Axes: """Low-level access to `plot_walltime`. Directly define `end_times` and `start_times`.""" # preprocess input end_times = to_lists(end_times) labels = get_labels(labels, len(end_times)) n_run = len(end_times) # check start times if start_times is None: if show_calibration: raise AssertionError( "To plot the calibration iteration, start times are needed.") # fill in dummy times which will not be used anyhow start_times = [datetime.datetime.now() for _ in range(n_run)] # check time unit if unit not in TIME_UNITS: raise AssertionError(f"`unit` must be in {TIME_UNITS}") # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract relative walltimes walltimes = [] for start_t, end_ts in zip(start_times, end_times): times = [start_t, *end_ts] # compute stacked differences diffs = [end - start for start, end in zip(times[:-1], times[1:])] # as seconds diffs = [diff.total_seconds() for diff in diffs] # append walltimes.append(diffs) walltimes = np.asarray(walltimes) # create matrix n_pop = max(len(wt) for wt in walltimes) matrix = np.zeros((n_pop, n_run)) for i_run, wt in enumerate(walltimes): matrix[:len(wt), i_run] = wt if not show_calibration: matrix = matrix[1:, :] # apply time unit if unit == MINUTE: matrix /= 60 elif unit == HOUR: matrix /= (60 * 60) elif unit == DAY: matrix /= (60 * 60 * 24) # plot bars for i_pop in reversed(range(matrix.shape[0])): pop_ix = i_pop - 1 if not show_calibration: pop_ix = i_pop ax.bar(x=np.arange(n_run), height=matrix[i_pop, :], bottom=np.sum(matrix[:i_pop, :], axis=0), label=f"Generation {pop_ix}") # prettify plot ax.set_xticks(np.arange(n_run)) ax.set_xticklabels(labels, rotation=rotation) ax.set_title(title) ax.set_xlabel("Run") ax.set_ylabel(f"Time [{unit}]") ax.legend() if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_epsilons(histories: Union[List, History], labels: Union[List, str] = None, colors: List = None, scale: str = None, title: str = "Epsilon values", size: tuple = None, ax: mpl.axes.Axes = None): """ Plot epsilon trajectory. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. colors: List, optional Colors to use for the lines. If None, then the matplotlib default values are used. scale: str, optional (default='lin') Scaling to apply to the y axis. Must be one of 'lin', 'log', 'log10'. title: str, optional (default = "Epsilon values") Title for the plot. size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. A new one is created if None. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories, labels = to_lists_or_default(histories, labels) if colors is None: colors = [None for _ in range(len(histories))] if scale is None: scale = 'lin' # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract epsilons eps = [] for history in histories: # note: first entry is from calibration and thus translates to inf, # thus must be discarded eps.append(np.array(history.get_all_populations()['epsilon'][1:])) # scale eps = _apply_scale(eps, scale) # plot for ep, label, color in zip(eps, labels, colors): ax.plot(ep, 'x-', label=label, color=color) # format ax.set_xlabel("Population index") ax.set_ylabel(_get_ylabel(scale)) ax.legend() ax.set_title(title) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_polar_histogram( values: np.ndarray, r_edges: np.ndarray, theta_edges: np.ndarray, ax: mpl.axes.Axes = None, # Not sure how to type this (not optional # but hard to find default) cmap: Optional[str] = None, vmin: Optional[float] = None, vmax: Optional[float] = None, symmetric_color_limits: bool = False, interpolation_factor: float = 5, angle_convention: str = "clock", ): """Plot color polar plot :param values: Values to color-code :param r_edges: Edges in radius :param theta_edges: Edges in angle :param ax: matplotlib.axes.Axes. Needs to be `polar=True` :param cmap: matplotlib colormap :param vmin: lower limit of colorbar range. If None, from data :param vmax: upper limit of colorbar range. If None, from data :param symmetric_color_limits: How to obtain lower and upper limits of colormap from data. If False, limits are (min, max). If True, limits are (-max(abs), max(abs)) :param interpolation_factor: If None or 1, no interpolation is performed. If int > 1, each sector is broken in interpolation_factor parts, so they look more circular and less polygonal :param angle_convention: If "clock", angles increase clockwise and are 0 for positive y axis. If "math", angles increase counterclockwise and are 0 for positive x axis """ if interpolation_factor is not None: values, theta_edges, r_edges = interpolate_polarmap_angles( values, theta_edges, r_edges, factor=interpolation_factor) theta, r = np.meshgrid(theta_edges, r_edges) # Select color limits: if symmetric_color_limits: vmax_ = np.nanmax(np.abs(values)) vmin_ = -vmax_ else: vmax_ = np.nanmax(values) vmin_ = np.nanmin(values) # Only use them if not specified in input if vmin is None: vmin = vmin_ if vmax is None: vmax = vmax_ # Plot histogram/map fig = ax.get_figure() im = ax.pcolormesh(theta, r, values, cmap=cmap, vmin=vmin, vmax=vmax) fig.colorbar(im, ax=ax, cmap=cmap) if angle_convention == "clock": # Adjusting axis and sense of rotation to make it compatible # with [2]: Direction of movement is y axis, angles increase clockwise ax.set_theta_zero_location("N") ax.set_theta_direction(-1)
def plot_acceptance_rates_trajectory(histories: Union[List, History], labels: Union[List, str] = None, rotation: int = 0, title: str = "Acceptance rates", yscale: str = 'lin', size: tuple = None, ax: mpl.axes.Axes = None, colors: List[str] = None): """ Plot of acceptance rates over all iterations, i.e. one trajectory per history. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. rotation: int, optional (default = 0) Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: str, optional (default = "Acceptance rates") Title for the plot. yscale: str, optional (default = 'lin') The scale on which to plot the counts. Can be one of 'lin', 'log' (basis e) or 'log10' size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories, labels = to_lists_or_default(histories, labels) if colors is None: colors = [None] * len(histories) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract sample numbers times = [] samples = [] pop_sizes = [] for history in histories: # note: the first entry of time -1 is trivial and is thus ignored here h_info = history.get_all_populations() times.append(np.array(h_info['t'])[1:]) samples.append(np.array(h_info['samples'])[1:]) pop_sizes.append( np.array(history.get_nr_particles_per_population().values[1:])) # compute acceptance rates rates = [] for sample, pop_size in zip(samples, pop_sizes): rates.append(pop_size / sample) # apply scale ylabel = "Acceptance rate" if yscale == 'log': rates = [np.log(rate) for rate in rates] ylabel = "log(" + ylabel + ")" elif yscale == 'log10': rates = [np.log10(rate) for rate in rates] ylabel = "log10(" + ylabel + ")" # plot for t, rate, label, color in zip(times, rates, labels, colors): ax.plot(t, rate, 'x-', label=label, color=color) # add labels ax.legend() ax.set_title(title) ax.set_ylabel(ylabel) ax.set_xlabel("Population index $t$") # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_lookahead_acceptance_rates(sampler_df: Union[pd.DataFrame, str], t_min: int = 0, title: str = "Acceptance rates", size: tuple = None, ax: mpl.axes.Axes = None): """Plot acceptance rates for look-ahead vs ordinary samples. The ratios are relative to all accepted particles, including eventually discarded ones. Parameters ---------- sampler_df: Dataframe or file as generated via `RedisEvalParallelSampler(log_file=...)`. t_min: The minimum generation to show. E.g. a value of 1 omits the first generation. title: Plot title. size: The size of the plot in inches. ax: The axis object to use. Returns ------- ax: Axis of the generated plot. """ # process input if isinstance(sampler_df, str): sampler_df = pd.read_csv(sampler_df, sep=',') # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # restrict to t >= 0 sampler_df = sampler_df[sampler_df.t >= t_min] # extract variables # time t = sampler_df.t # look-ahead acceptances and samples n_la_acc = sampler_df.n_lookahead_accepted n_la = sampler_df.n_lookahead # total acceptances and samples n_all_acc = sampler_df.n_accepted n_all = sampler_df.n_evaluated # difference (actual proposal) n_act_acc = n_all_acc - n_la_acc n_act = n_all - n_la # plot ax.plot(t, n_all_acc / n_all, linestyle='--', marker='o', color='black', label="Combined") ax.plot(t, n_act_acc / n_act, marker='o', label="Actual") ax.plot(t, n_la_acc / n_la, marker='o', label="Look-ahead") # prettify plot ax.legend() ax.set_title(title) ax.set_xlabel("Population index") ax.set_ylabel("Acceptance rate") ax.set_ylim(bottom=0) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) if size is not None: fig.set_size_inches(size) return ax
def plot_total_sample_numbers(histories: Union[List, History], labels: Union[List, str] = None, rotation: int = 0, title: str = "Total required samples", yscale: str = 'lin', size: tuple = None, ax: mpl.axes.Axes = None): """ Bar plot of total required sample number over all iterations, i.e. a single-colored bar per history, in contrast to `plot_sample_numbers`, which visually distinguishes iterations. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. rotation: int, optional (default = 0) Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: str, optional (default = "Total required samples") Title for the plot. yscale: str, optional (default = 'lin') The scale on which to plot the counts. Can be one of 'lin', 'log' (basis e) or 'log10' size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() n_run = len(histories) # extract sample numbers samples = [] for history in histories: # note: the first entry corresponds to the calibration and should # be included here to be fair against methods not requiring # calibration samples.append(np.sum(history.get_all_populations()['samples'])) samples = np.array(samples) # apply scale ylabel = "Total samples" if yscale == 'log': samples = np.log(samples) ylabel = "log(" + ylabel + ")" elif yscale == 'log10': samples = np.log10(samples) ylabel = "log10(" + ylabel + ")" # plot bars ax.bar(x=np.arange(n_run), height=samples) # add labels ax.set_xticks(np.arange(n_run)) ax.set_xticklabels(labels, rotation=rotation) ax.set_title(title) ax.set_ylabel(ylabel) ax.set_xlabel("Run") # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax
def plot_effective_sample_sizes( histories: Union[List, History], labels: Union[List, str] = None, rotation: int = 0, title: str = "Effective sample size", relative: bool = False, colors: List = None, size: tuple = None, ax: mpl.axes.Axes = None, ): """ Plot effective sample sizes over all iterations. Parameters ---------- histories: Union[List, History] The histories to plot from. History ids must be set correctly. labels: Union[List ,str], optional Labels corresponding to the histories. If None are provided, indices are used as labels. rotation: int, optional (default = 0) Rotation to apply to the plot's x tick labels. For longer labels, a tilting of 45 or even 90 can be preferable. title: str, optional (default = "Total required samples") Title for the plot. relative: bool, optional (default = False) Whether to show relative sizes (to 1) or w.r.t. the real number of particles. colors: List, optional Colors to use for the lines. If None, then the matplotlib default values are used. size: tuple of float, optional The size of the plot in inches. ax: matplotlib.axes.Axes, optional The axis object to use. A new one is created if None. Returns ------- ax: Axis of the generated plot. """ # preprocess input histories = to_lists(histories) labels = get_labels(labels, len(histories)) if colors is None: colors = [None for _ in range(len(histories))] # create figure if ax is None: fig, ax = plt.subplots() else: fig = ax.get_figure() # extract effective sample sizes essss = [] # :) for history in histories: esss = [] for t in range(0, history.max_t + 1): # we need the weights not normalized to 1 for each model w = history.get_weighted_distances(t=t)['w'] ess = effective_sample_size(w) if relative: ess /= len(w) esss.append(ess) essss.append(esss) # plot for esss, label, color in zip(essss, labels, colors): ax.plot(range(0, len(esss)), esss, 'x-', label=label, color=color) # format ax.set_xlabel("Population index") ax.set_ylabel("ESS") if any(lab is not None for lab in labels): ax.legend() ax.set_title(title) # enforce integer ticks ax.xaxis.set_major_locator(MaxNLocator(integer=True)) # set size if size is not None: fig.set_size_inches(size) fig.tight_layout() return ax