def _set_figure(cls, ax: axes.Axes, index: dict, range: Sequence): """set figure and axes for plotting :params ax: matplotlib.axes.Axes object :params index: dict of label of points of x-axis and its index in data file. Range of x-axis based on index.value() :params range: range of y-axis """ keys = [] values = [] for t in index: if isinstance(t, tuple): keys.append(t[0]) values.append(t[1]) elif isinstance(t, (int, float)): keys.append('') values.append(t) # x-axis ax.set_xticks(values) ax.set_xticklabels(keys) ax.set_xlim(values[0], values[-1]) ax.set_xlabel("Wave Vector") # y-axis if range: ax.set_ylim(range[0], range[1]) ax.set_ylabel(r"$E-E_{fermi}(eV)$") # others ax.grid(axis='x', lw=1.2) ax.axhline(0, linestyle="--", c='b', lw=1.0) handles, labels = ax.get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) ax.legend(by_label.values(), by_label.keys())
def _plot( self, ax: Axes, ylabel: Optional[str] = None, color: Optional[str] = None, channel: Optional[Channel] = None, label: str = "", start_t: int = 0, ) -> None: ax.set_xlabel("t (ns)") samples = (self.samples if channel is None else self.modulated_samples(channel)) ts = np.arange(len(samples)) + start_t if not channel and start_t: # Adds zero on both ends to show rise and fall samples = np.pad(samples, 1) # Repeats the times on the edges once ts = np.pad(ts, 1, mode="edge") if color: color_dict = {"color": color} hline_color = color ax.tick_params(axis="y", labelcolor=color) else: color_dict = {} hline_color = "black" if ylabel: ax.set_ylabel(ylabel, fontsize=14, **color_dict) ax.plot(ts, samples, label=label, **color_dict) ax.axhline(0, color=hline_color, linestyle=":", linewidth=0.5) if label: plt.legend()
def add_stat_line( ax: Axes, series_input: SeriesPlotIn, stat: Callable[[Iterable[float]], float] = np.mean, vert: bool = True, **kwargs ) -> Axes: if "linestyle" not in kwargs.keys(): kwargs.update({"linestyle": "--"}) stat_val: float = stat(series_input.data) if vert: ax.axvline(x=stat_val, color=series_input.color, **kwargs) else: ax.axhline(y=stat_val, color=series_input.color, **kwargs) return ax
def plot_residuals(y_true: Data, y_pred: Data, title: str = None, ax: Axes = None) -> Axes: """ Plots residuals from a regression. :param y_true: True values :param y_pred: Models predicted value :param title: Plot title :param ax: Pass your own ax :return: matplotlib.Axes """ title = f'Residual Plot' if title is None else title residuals = y_pred - y_true r2 = r2_score(y_true, y_pred) if ax is None: fig, ax = plt.subplots() ax.scatter(y_pred, residuals, label=f'$R^2 = {r2:0.3f}$') ax.axhline(y=0, color='grey', linestyle='--') ax.set_ylabel('Residuals') ax.set_xlabel('Predicted Value') ax.set_title(title) ax.legend(loc='best') return ax
def plot_residuals( y_true: DataType, y_pred: DataType, title: str = None, ax: Axes = None ) -> Axes: """ Plots residuals from a regression. :param y_true: True values :param y_pred: Models predicted value :param title: Plot title :param ax: Pass your own ax :return: matplotlib.Axes """ title = "Residual Plot" if title is None else title residuals = y_pred - y_true r2 = r2_score(y_true, y_pred) if ax is None: fig, ax = plt.subplots() ax.scatter(y_pred, residuals, label=f"$R^2 = {r2:0.3f}$") ax.axhline(y=0, color="grey", linestyle="--") ax.set_ylabel("Residuals") ax.set_xlabel("Predicted Value") ax.set_title(title) ax.legend(loc="best") return ax
def plot_envelope_dist(self, ax: Axes): yrange = self.e_t.span sample = choice(self.e_t, 5000) kde = KernelDensity(bandwidth=0.02 * yrange) kde.fit(as_data_matrix(sample)) e_dom = linspace(*self.e_t.range, 500) density = exp(kde.score_samples(as_data_matrix(e_dom))) ax.fill_betweenx(e_dom, density, color=envelope_color) ax.axhline(0, color="gray", lw=thin_lw) rm = self.reference_maker threshold_extent = 1.21 kwargs = dict( xmin=0, xmax=threshold_extent, color=threshold_color, lw=thin_lw, clip_on=False, ) ax.axhline(rm.ripple_threshold_high, **kwargs) ax.axhline(rm.ripple_threshold_low, **kwargs) ax.axhline(rm.envelope_median, linestyle=":", **kwargs) ax.set_xticks([]) ax.set_yticks([]) add_title(ax, "Empirical\ndistribution of $n_t$", envelope_color, x=0.1, y=0.8) text_kwargs = dict( x=threshold_extent + 0.05, color=threshold_color, transform=ax.get_yaxis_transform(), fontsize=0.69 * annotation_text_size, va="center", ) ax.text(y=rm.ripple_threshold_high, s="$T_{high}$", **text_kwargs) ax.text(y=rm.ripple_threshold_low, s="$T_{low}$", **text_kwargs) ax.text(y=rm.envelope_median, s="Median", **text_kwargs)
def growth_curve(ax: Axes, plate: Plate, scatter_color: str, line_color: str = None, growth_params: bool = True): """ Add a growth curve scatter plot, with median, to an axis :param ax: a Matplotlib Axes object to add a plot to :param plate: a Plate instance :param scatter_color: a Colormap color :param line_color: a Colormap color for the median """ from statistics import median if line_color is None: line_color = scatter_color for colony in plate.items: ax.scatter( # Matplotlib does not yet support timedeltas so we have to convert manually to float [ td.total_seconds() / 3600 for td in sorted(colony.growth_curve.data.keys()) ], list(colony.growth_curve.data.values()), color=scatter_color, marker="o", s=1, alpha=0.25) # Plot the median ax.plot([ td.total_seconds() / 3600 for td in sorted(plate.growth_curve.data.keys()) ], [median(val) for _, val in sorted(plate.growth_curve.data.items())], color=line_color, label="Median" if growth_params else f"Plate {plate.id}", linewidth=2) if growth_params: # Plot lag, vmax and carrying capacity lines if plate.growth_curve.lag_time.total_seconds() > 0: line = ax.axvline(plate.growth_curve.lag_time.total_seconds() / 3600, color="grey", linestyle="dashed", alpha=0.5) line.set_label("Lag time") if plate.growth_curve.carrying_capacity > 0: line = ax.axhline(plate.growth_curve.carrying_capacity, color="blue", linestyle="dashed", alpha=0.5) line.set_label("Carrying\ncapacity") if plate.growth_curve.growth_rate > 0: y0, y1 = 0, plate.growth_curve.carrying_capacity x0 = plate.growth_curve.lag_time.total_seconds() / 3600 x1 = ((y1 - y0) / (plate.growth_curve.growth_rate * 3600)) + x0 ax.plot([x0, x1], [y0, y1], color="red", linestyle="dashed", alpha=0.5, label="Maximum\ngrowth rate")
def convergence_boxplot(targets: pd.DataFrame, results: pd.DataFrame, filter_func: Callable[[pd.Series], pd.Series], adjust_target: bool = True, percentage: bool = True, band: Tuple[float, float] = None, simple_labels: bool = True, ax: Axes = None, fp: Path = None, title: str = None) -> Axes: """Measures convergence of constrained location-choice models (such as work-location choice). Can be used to produce multiple box plots for different sub-sets of zones, usually based on size. Args: targets (pandas.DataFrame): results (pandas.DataFrame): filter_func (Callable[[pandas.Series], pandas.Series]): adjust_target (bool, optional): percentage (bool, optional): band (Tuple[float, float], optional): simple_labels (bool, optional): ax (Axes, optional): fp (Path, optional): title (str, optional): Returns: matplotlib.Axes """ assert results.columns.equals(targets.columns) columns, filters, n = [], [], 0 for colname in targets: filter_ = filter_func(targets[colname]) filters.append(filter_) n = max(n, filter_.sum()) columns.append(colname) unlabelled_zones = np.full([n, len(columns)], np.nan, dtype=np.float64) model_sums, target_sums = [], [] for i, (colname, filter_) in enumerate(zip(columns, filters)): m = filter_.sum() model_vector = results.loc[filter_, colname] target_vector = targets.loc[filter_, colname] if adjust_target and target_vector.sum() > 0: factor = model_vector.sum() / target_vector.sum() target_vector = target_vector * factor model_sums.append(model_vector.sum()) target_sums.append(target_vector.sum()) err = model_vector - target_vector if percentage: err /= target_vector unlabelled_zones[:m, i] = err.values if not simple_labels: columns = [ "{}\n{} workers\n{} jobs\n{} zones".format(c, model_sums[i], int(target_sums[i]), filters[i].sum()) for i, c in enumerate(columns) ] unlabelled_zones = pd.DataFrame(unlabelled_zones, columns=columns) with np.errstate(invalid='ignore'): ax = unlabelled_zones.plot.box(ax=ax, figsize=[12, 6]) ax.axhline(0) if percentage: ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: "{}%".format(np.round(x, 2) * 100))) ax.set_ylabel("Relative error ((Model - Target) / Target)") else: ax.set_ylabel("Error (Model - Target)") xlabel = "Occupation & Employment Status" if adjust_target: xlabel += "\n(Targets adjusted to match model totals)" ax.set_xlabel(xlabel) if band is not None: lower, upper = band ax.axhline(lower, color='black', linewidth=1, alpha=0.5) ax.axhline(upper, color='black', linewidth=1, alpha=0.5) if title: ax.set_title(title) if fp is not None: plt.savefig(str(fp)) return ax
def plot_lift_curve( y_true: DataType, y_proba: DataType, title: str = None, ax: Axes = None, labels: List[str] = None, threshold: float = 0.5, ) -> Axes: """ Plot a lift chart from results. Also calculates lift score based on a .5 threshold Parameters ---------- y_true: DataType True labels y_proba: DataType Model's predicted probability title: str Plot title ax: Axes Pass your own ax labels: List of str Labels to use per class threshold: float Threshold to use when determining lift score Returns ------- matplotlib.Axes """ if ax is None: fig, ax = plt.subplots() title = "Lift Curve" if title is None else title classes = np.unique(y_true) binarized_labels = label_binarize(y_true, classes=classes) if labels and len(labels) != len(classes): raise VizError("Number of labels must match number of classes: " f"got {len(labels)} labels and {len(classes)} classes") if binarized_labels.shape[1] == 1: # Binary classification case percents, gains = _cum_gain_curve(binarized_labels, y_proba[:, 1]) score = lift_score(binarized_labels.ravel(), y_proba[:, 1] > threshold) ax.plot(percents, gains / percents, label=f"$Lift = {score:.2f}$") else: # Multi-class case for class_ in classes: percents, gains = _cum_gain_curve(binarized_labels[:, class_], y_proba[:, class_]) score = lift_score(binarized_labels[:, class_], y_proba[:, class_] > threshold) ax.plot( percents, gains / percents, label=f"Class {labels[class_] if labels else class_} " f"$Lift = {score:.2f}$ ", ) ax.axhline(y=1, color="grey", linestyle="--", label="Baseline") ax.set_title(title) ax.set_ylabel("Lift") ax.set_xlabel("% of Data") formatter = PercentFormatter(xmax=1) ax.xaxis.set_major_formatter(formatter) ax.legend() return ax
def plot_areas( fig: Figure, ax: Axes, samples: SampleList, compound_names: List[str], include_none: bool = False, show_scores: bool = False, legend_cols: int = 6, mz_range=None, show_score_in_legend: bool = False, ) -> Tuple[Figure, Axes]: """ Plot the peak area and score for the compounds with the given names :param fig: :param ax: :param samples: A list of samples to plot on the chart :param compound_names: A list of compounds to plot :param include_none: Whether samples where the compound was not found should be plotted. :param show_scores: Whether the scores should be shown on the chart. :param legend_cols: :param mz_range: :param show_score_in_legend: """ areas_dict, scores_dict = samples.get_areas_and_scores_for_compounds( compound_names, include_none) if show_scores: ax2 = ax.twiny() y_positions = numpy.arange(len(areas_dict)) y_positions = [x * 1.5 for x in y_positions] # n_samples = areas_dict.n_samples n_compounds = len(compound_names) bar_width = 1.5 / (n_compounds + 1) # TODO: bar_spacing = bar_width / (n_samples + 1) bar_offsets = list( numpy.arange((0 - ((bar_width / 2) * (n_compounds - 1))), (0 + ((bar_width / 2) * n_compounds)), bar_width))[::-1] # Reverse order sample_names = areas_dict.sample_names divider_positions = set() for cpd_idx, compound_name in enumerate(compound_names): compound_y_positions = [] compound_areas = areas_dict.get_compound_areas(compound_name) compound_scores = scores_dict.get_compound_scores(compound_name) for sample_idx, sample_name in enumerate(sample_names): compound_y_positions.append(y_positions[sample_idx] + bar_offsets[cpd_idx]) divider_positions.add((y_positions[sample_idx] + bar_offsets)[0] + bar_width) divider_positions.add((y_positions[sample_idx] + bar_offsets)[-1] - bar_width) ax.barh(compound_y_positions, compound_areas, label=compound_name, height=bar_width, edgecolor="black", linewidth=0.25) if show_scores: score_scatter = ax2.scatter( [x if x else None for x in compound_scores], compound_y_positions, color=[ "black" if s >= 75 else "orange" for s in compound_scores ], s=bar_width * 50) # for area, ypos in zip(compound_areas, compound_y_positions): # score_text = ax.text(area, ypos, "Score", va='center') for pos in divider_positions: ax.axhline(pos, color="black", linewidth=0.5, xmin=-0.01, clip_on=False) loc = plticker.MultipleLocator(base=bar_width) ax.yaxis.set_minor_locator(loc) ax.grid(which="minor", axis='y', linestyle='-') ax.set_ylim(bottom=min(divider_positions), top=max(divider_positions)) ax.set_yticks(y_positions) ax.set_xscale("log") ax.set_xlabel("Log$_{10}$(Peak Area)", labelpad=0) ax.set_yticklabels(sample_names) if show_scores: min_score = 40 ax2.set_xlim( left=min_score, right=100 ) # Compounds with scores below 50 were excluded by MassHunter ax2.grid(False) ax2.set_xlabel("Score", ha="center") ax2.set_xticks(numpy.arange(min_score, 110, 10)) # ax2.set_xticks([0, 10, 20, 30, 40, 60, 70, 80, 90, 100]) ax.scatter([], [], label="Score", color="black") legend(fig, loc="lower center", ncol=legend_cols, mz_range=mz_range, show_score=show_score_in_legend) return fig, ax
def plot_head2tail( ax: Axes, top_mass_spec: MassSpectrum, bottom_mass_spec: MassSpectrum, top_spec_kwargs: Optional[Dict] = None, bottom_spec_kwargs: Optional[Dict] = None, ) -> Tuple[BarContainer, BarContainer]: """ Plots two mass spectra head to tail. :param ax: The axes to plot the MassSpectra on :param top_mass_spec: The Mass Spectrum to plot on top :param bottom_mass_spec: The Mass Spectrum to plot on the bottom :param top_spec_kwargs: A dictionary of keyword arguments for the top mass spectrum. Defaults to red with a line width of 0.5 :no-default top_spec_kwargs: :param bottom_spec_kwargs: A dictionary of keyword arguments for the bottom mass spectrum. Defaults to blue with a line width of 0.5 :no-default bottom_spec_kwargs: `top_spec_kwargs` and `bottom_spec_kwargs` are used to specify properties like a line label (for auto legends), linewidth, antialiasing, marker face color. See https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.lines.Line2D.html for the list of possible kwargs :return: A tuple of container with all the bars and optionally errorbars for the top and bottom spectra. :rtype: tuple of :class:`matplotlib.container.BarContainer` """ if not isinstance(top_mass_spec, MassSpectrum): raise TypeError("'top_mass_spec' must be a MassSpectrum") if not isinstance(bottom_mass_spec, MassSpectrum): raise TypeError("'bottom_mass_spec' must be a MassSpectrum") if top_spec_kwargs is None: top_spec_kwargs = dict(color="red", width=0.5) elif not isinstance(top_spec_kwargs, dict): raise TypeError("'top_spec_kwargs' must be a dictionary of keyword arguments for the top mass spectrum.") if bottom_spec_kwargs is None: bottom_spec_kwargs = dict(color="blue", width=0.5) elif not isinstance(bottom_spec_kwargs, dict): raise TypeError( "'bottom_spec_kwargs' must be a dictionary of keyword arguments for the bottom mass spectrum." ) # Plot a line at y=0 with same width and colour as Spines ax.axhline(y=0, color=ax.spines["bottom"].get_edgecolor(), linewidth=ax.spines["bottom"].get_linewidth()) # Normalize the mass spectra top_mass_spec = normalize_mass_spec(top_mass_spec) bottom_mass_spec = normalize_mass_spec(bottom_mass_spec) # Invert bottom mass spec invert_mass_spec(bottom_mass_spec, inplace=True) top_plot = plot_mass_spec(ax, top_mass_spec, **top_spec_kwargs) bottom_plot = plot_mass_spec(ax, bottom_mass_spec, **bottom_spec_kwargs) # Set ylim to 1.1 times max/min values ax.set_ylim( bottom=min(bottom_mass_spec.intensity_list) * 1.1, top=max(top_mass_spec.intensity_list) * 1.1, ) # ax.spines['bottom'].set_position('zero') return top_plot, bottom_plot
def show_perf_vs_size( x_list: List[np.ndarray], y_list: List[np.ndarray], label_list: List[str], *, xlabel: str = None, ylabel: str = None, title: str = None, ax: Axes = None, xticks=(0, 25, 50, 75, 100), yticks=(0, 0.5, 1), xlim=(0, 100), ylim=(0, 1), xticklabels=('0', '25', '50', '75', '100'), yticklabels=('0', '0.5', '1'), style_list=None, linewidth=1, show_legend=True, legend_param=None, vline=None, hline=None, xlabel_param=None, # letter=None, ): """x being model size, number of parameter, dataset size, etc. y being performance. """ if style_list is None: # should give a default set raise NotImplementedError if xlabel_param is None: xlabel_param = dict() # if letter is not None: # ax.text(0, 1, letter, horizontalalignment='left', verticalalignment='top', # transform=ax.get_figure().transFigure, fontweight='bold') assert len(x_list) == len(y_list) == len(label_list) for idx, (x_this, y_this, label_this) in enumerate(zip(x_list, y_list, label_list)): linestyle, color, marker = style_list[idx] ax.plot(x_this, y_this, linestyle=linestyle, color=color, marker=marker, label=label_this, linewidth=linewidth) if vline is not None: # color maybe adjusted later ax.axvline(vline, color='black', linewidth=linewidth, linestyle='--') if hline is not None: # color maybe adjusted later ax.axhline(hline, color='black', linewidth=linewidth, linestyle='--') # ax.set_xlim(0, 1) ax.set_xlim(*xlim) ax.set_ylim(*ylim) ax.set_xticks(xticks) ax.set_yticks(yticks) ax.set_xticklabels(xticklabels, **xlabel_param) ax.set_yticklabels(yticklabels) if xlabel is not None: ax.set_xlabel(xlabel) if ylabel is not None: ax.set_ylabel(ylabel) if title is not None: ax.set_title(title) if show_legend: if legend_param is None: ax.legend() else: ax.legend(**legend_param)
def plot_z_trends(self, axis: Axes = None) -> None: if axis is None: axis = self.figure.add_subplot(111) cluster = Cluster(simulation_name=self.simulation.simulation_name, clusterID=0, redshift='z000p000') aperture_float = self.get_apertures(cluster)[ self.aperture_id] / cluster.r200 if not os.path.isfile( os.path.join( self.path, f'redshift_rot0rot4_bootstrap_aperture_{self.aperture_id}.npy' )): warnings.warn( f"File redshift_rot0rot4_bootstrap_aperture_{self.aperture_id}.npy not found." ) print("self.make_simbootstrap() activated.") self.make_simbootstrap() print( f"Retrieving npy files: redshift_rot0rot4_bootstrap_aperture_{self.aperture_id}.npy" ) sim_bootstrap = np.load(os.path.join( self.path, f'redshift_rot0rot4_bootstrap_aperture_' f'{self.aperture_id}.npy'), allow_pickle=True) sim_bootstrap = np.asarray(sim_bootstrap) items_labels = f""" REDSHIFT TRENDS Number of clusters: {self.simulation.totalClusters:d} $z$ = 0.0 - 1.8 Aperture radius = {aperture_float:.2f} $R_{{200\ true}}$""" print(items_labels) sim_colors = { 'ceagle': 'pink', 'celr_e': 'lime', 'celr_b': 'orange', 'macsis': 'aqua', } axis.axhline(90, linestyle='--', color='k', alpha=0.5, linewidth=2) axis.plot(sim_bootstrap[0, 0], sim_bootstrap[3, 0], color=sim_colors[self.simulation.simulation_name], alpha=1, linestyle='none', marker='^', markersize=10) axis.plot(sim_bootstrap[0, 0], sim_bootstrap[2, 0], color=sim_colors[self.simulation.simulation_name], alpha=1, linestyle='none', marker='o', markersize=10) axis.plot(sim_bootstrap[0, 0], sim_bootstrap[1, 0], color=sim_colors[self.simulation.simulation_name], alpha=1, linestyle='none', marker='v', markersize=10) for marker_index in range(len(sim_bootstrap[0, 0])): if marker_index is 0: align_toggle = 'edge' x_edge_left = sim_bootstrap[0, 0][marker_index] x_edge_right = sim_bootstrap[ 0, 0][marker_index] + sim_bootstrap[0, 1][marker_index] else: align_toggle = 'center' x_edge_left = sim_bootstrap[ 0, 0][marker_index] - sim_bootstrap[0, 1][marker_index] / 2 x_edge_right = sim_bootstrap[ 0, 0][marker_index] + sim_bootstrap[0, 1][marker_index] / 2 axis.plot([x_edge_left, x_edge_right], [ sim_bootstrap[3, 0][marker_index], sim_bootstrap[3, 0][marker_index] ], color=sim_colors[self.simulation.simulation_name], alpha=0.8, linestyle='--', lw=1.5) axis.plot([x_edge_left, x_edge_right], [ sim_bootstrap[2, 0][marker_index], sim_bootstrap[2, 0][marker_index] ], color=sim_colors[self.simulation.simulation_name], alpha=0.8, linestyle='-', lw=1.5) axis.plot([x_edge_left, x_edge_right], [ sim_bootstrap[1, 0][marker_index], sim_bootstrap[1, 0][marker_index] ], color=sim_colors[self.simulation.simulation_name], alpha=0.8, linestyle='-.', lw=1.5) axis.bar(sim_bootstrap[0, 0][marker_index], 2 * sim_bootstrap[3, 1][marker_index], bottom=sim_bootstrap[3, 0][marker_index] - sim_bootstrap[3, 1][marker_index], width=sim_bootstrap[0, 1][marker_index], align=align_toggle, color=sim_colors[self.simulation.simulation_name], alpha=0.2, edgecolor='none', linewidth=0) axis.bar(sim_bootstrap[0, 0][marker_index], 2 * sim_bootstrap[2, 1][marker_index], bottom=sim_bootstrap[2, 0][marker_index] - sim_bootstrap[2, 1][marker_index], width=sim_bootstrap[0, 1][marker_index], align=align_toggle, color=sim_colors[self.simulation.simulation_name], alpha=0.2, edgecolor='none', linewidth=0) axis.bar(sim_bootstrap[0, 0][marker_index], 2 * sim_bootstrap[1, 1][marker_index], bottom=sim_bootstrap[1, 0][marker_index] - sim_bootstrap[1, 1][marker_index], width=sim_bootstrap[0, 1][marker_index], align=align_toggle, color=sim_colors[self.simulation.simulation_name], alpha=0.2, edgecolor='none', linewidth=0) perc84 = Line2D([], [], color='k', marker='^', linestyle='--', markersize=10, label=r'$84^{th}$ percentile') perc50 = Line2D([], [], color='k', marker='o', linestyle='-', markersize=10, label=r'median') perc16 = Line2D([], [], color='k', marker='v', linestyle='-.', markersize=10, label=r'$16^{th}$ percentile') patch_ceagle = Patch(facecolor=sim_colors['ceagle'], label='C-EAGLE', edgecolor='k', linewidth=1) patch_celre = Patch(facecolor=sim_colors['celr_e'], label='CELR-E', edgecolor='k', linewidth=1) patch_celrb = Patch(facecolor=sim_colors['celr_b'], label='CELR-B', edgecolor='k', linewidth=1) patch_macsis = Patch(facecolor=sim_colors['macsis'], label='MACSIS', edgecolor='k', linewidth=1) leg1 = axis.legend(handles=[perc84, perc50, perc16], loc='lower right', handlelength=3, fontsize=20) leg2 = axis.legend( handles=[patch_ceagle, patch_celre, patch_celrb, patch_macsis], loc='lower left', handlelength=1, fontsize=20) axis.add_artist(leg1) axis.add_artist(leg2) axis.text(0.03, 0.97, items_labels, horizontalalignment='left', verticalalignment='top', transform=axis.transAxes, size=15) axis.set_xlabel(r"$z$", size=25) axis.set_ylabel( r"$\Delta \theta \equiv (\mathbf{L},\mathrm{\widehat{CoP}},\mathbf{v_{pec}})$\quad[degrees]", size=25) axis.set_ylim(0, 180)