def quantile_plot(
        ax: matplotlib.axes.Axes, x: np.ndarray, median: np.ndarray,
        lower: np.ndarray, upper: np.ndarray, shaded_kwargs: Dict[str, Any],
        line_kwargs: Dict[str, Any]) -> List[matplotlib.lines.Line2D]:
    """Create mean +- sd plot."""
    ax.fill_between(x, lower, upper, **shaded_kwargs)
    return ax.plot(x, median, **line_kwargs)
예제 #2
0
def spm_plot(ax: matplotlib.axes.Axes, x: np.ndarray, spm_test: _SPM0Dinference, shaded_kwargs: Dict[str, Any],
             line_kwargs: Dict[str, Any]) -> List[matplotlib.lines.Line2D]:
    """Create SPM plot."""
    ax.axhline(spm_test.zstar, ls='--', color='grey')
    ax.axhline(-spm_test.zstar, ls='--', color='grey')
    ax.fill_between(x, spm_test.zstar, spm_test.z, where=(spm_test.z > spm_test.zstar), **shaded_kwargs)
    ax.fill_between(x, -spm_test.zstar, spm_test.z, where=(spm_test.z < -spm_test.zstar), **shaded_kwargs)
    return ax.plot(x, spm_test.z, **line_kwargs)
예제 #3
0
파일: utils.py 프로젝트: biorack/metatlas
def fill_under(
    ax: matplotlib.axes.Axes,
    x: List[float],
    y: List[float],
    between: Optional[Tuple[float, float]] = None,
    **kwargs,
) -> None:
    """Fill under a curve with fill limited by x-range in between"""
    where = None if between is None else is_in_range(x, between[0], between[1])
    ax.fill_between(x, y, [0] * len(x), where=where, **kwargs)
예제 #4
0
파일: plot.py 프로젝트: kapilsh/pyqstrat
def _plot_data(ax: mpl.axes.Axes, data: PlotData) -> Optional[List[mpl.lines.Line2D]]:
    
    x, y = None, None
    
    lines = None  # Return line objects so we can add legends
    
    disp = data.display_attributes
    
    if isinstance(data, XYData) or isinstance(data, TimeSeries):
        x, y = (data.x, data.y) if isinstance(data, XYData) else (np.arange(len(data.timestamps)), data.values)
        if isinstance(disp, LinePlotAttributes):
            lines, = ax.plot(x, y, linestyle=disp.line_type, linewidth=disp.line_width, color=disp.color)
            if disp.marker is not None:  # type: ignore
                ax.scatter(x, y, marker=disp.marker, c=disp.marker_color, s=disp.marker_size, zorder=100)
        elif isinstance(disp, ScatterPlotAttributes):
            lines = ax.scatter(x, y, marker=disp.marker, c=disp.marker_color, s=disp.marker_size, zorder=100)
        elif isinstance(disp, BarPlotAttributes):
            lines = ax.bar(x, y, color=disp.color)  # type: ignore
        elif isinstance(disp, FilledLinePlotAttributes):
            x, y = np.nan_to_num(x), np.nan_to_num(y)
            pos_values = np.where(y > 0, y, 0)
            neg_values = np.where(y < 0, y, 0)
            ax.fill_between(x, pos_values, color=disp.positive_color, step='post', linewidth=0.0)
            ax.fill_between(x, neg_values, color=disp.negative_color, step='post', linewidth=0.0)
        else:
            raise Exception(f'unknown plot combination: {type(data)} {type(disp)}')
            
        # For scatter and filled line, xlim and ylim does not seem to get set automatically
        if isinstance(disp, ScatterPlotAttributes) or isinstance(disp, FilledLinePlotAttributes):
            xmin, xmax = _adjust_axis_limit(ax.get_xlim(), x)
            if not np.isnan(xmin) and not np.isnan(xmax): ax.set_xlim((xmin, xmax))

            ymin, ymax = _adjust_axis_limit(ax.get_ylim(), y)
            if not np.isnan(ymin) and not np.isnan(ymax): ax.set_ylim((ymin, ymax))
                
    elif isinstance(data, TradeSet) and isinstance(disp, ScatterPlotAttributes):
        lines = ax.scatter(np.arange(len(data.timestamps)), data.values, marker=disp.marker, c=disp.marker_color, s=disp.marker_size, zorder=100)
    elif isinstance(data, TradeBarSeries) and isinstance(disp, CandleStickPlotAttributes):
        draw_candlestick(ax, np.arange(len(data.timestamps)), data.o, data.h, data.l, data.c, data.v, data.vwap, colorup=disp.colorup, colordown=disp.colordown)
    elif isinstance(data, BucketedValues) and isinstance(disp, BoxPlotAttributes):
        draw_boxplot(
            ax, data.bucket_names, data.bucket_values, disp.proportional_widths, disp.notched,  # type: ignore
            disp.show_outliers, disp.show_means, disp.show_all)  # type: ignore
    elif isinstance(data, XYZData) and (isinstance(disp, SurfacePlotAttributes) or isinstance(disp, ContourPlotAttributes)):
        display_type: str = 'contour' if isinstance(disp, ContourPlotAttributes) else 'surface'
        draw_3d_plot(ax, data.x, data.y, data.z, display_type, disp.marker, disp.marker_size, 
                     disp.marker_color, disp.interpolation, disp.cmap)
    else:
        raise Exception(f'unknown plot combination: {type(data)} {type(disp)}')

    return lines
예제 #5
0
def fill_to_zenith(
        ax: matplotlib.axes.Axes,
        az: np.ndarray,
        alt: np.ndarray,
        alpha: float = 0.2,
        color: Optional[str] = None
    ) -> None:
    """Fill the region between a curve and zenith."""
    ax.fill_between(
        np.radians(az),
        90.0 - alt,
        np.zeros_like(az),
        alpha=alpha,
        color=color,
        linewidth=0,
    )
예제 #6
0
def fill_to_horizon(
        ax: matplotlib.axes.Axes,
        az: np.ndarray,
        alt: np.ndarray,
        alpha: float = 0.2,
        color=None
    ) -> None:
    """Fill the region between a curve and the horizon."""
    az = az[alt >= 0]
    alt = alt[alt >= 0]
    ax.fill_between(
        np.radians(az),
        90.0 - alt,
        100*np.ones_like(az),
        alpha=alpha,
        color=color,
        linewidth=0,
    )
예제 #7
0
def err_plot(x, y, yerr=None, xerr=None, axis: mpl.axes.Axes = None, **mpl_args):
    """Plot graph with error bars.

    Decided weather to plot points with error bars or lines with shaded areas
    depending on the number of plotted points.

    Parameters
    ----------
    x, y : array_like
        x and y coordinates of the data to plot.
    yerr, xerr : array_like, optional
        The corresponding error of the data. Has same shape `x` and `y`.
    axis : mpl.axes.Axes, optional
        `mpl.axes.Axes` object used for plotting.
    mpl_args :
        Arguments for plotting passed to `axis.errorbar` or `axis.plot`.

    """
    axis = plt.gca() if axis is None else axis
    x = np.asarray(x)
    if x.size > 50:  # continuous plot
        try:
            ecolor = mpl_args.pop('ecolor')
        except KeyError:  # no color defined -> try color else default
            ecolor = mpl_args.get('color', None)
        try:
            fmt = mpl_args.pop('fmt')
        except KeyError:
            baseline, = axis.plot(x, y, **mpl_args)
        else:
            baseline, = axis.plot(x, y, fmt, **mpl_args)
        if ecolor is None:
            ecolor = baseline.get_color()
        if yerr is not None:
            axis.fill_between(x, y-yerr, y+yerr, color=ecolor, alpha=.3, zorder=1)
        if xerr is not None:
            axis.fill_betweenx(y, x-xerr, x+xerr, color=ecolor, alpha=.3, zorder=1)
    else:
        default_args = {'capsize': 2.5, 'elinewidth': .3}
        default_args.update(mpl_args)
        axis.errorbar(x, y, yerr=yerr, **default_args)
예제 #8
0
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
예제 #9
0
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
예제 #10
0
    def plot(
        self,
        x_label: str = "Method 1",
        y_label: str = "Method 2",
        title: str = None,
        line_reference: bool = True,
        line_CI: bool = True,
        legend: bool = True,
        square: bool = False,
        ax: matplotlib.axes.Axes = None,
        point_kws: Optional[Dict] = None,
        color_regr: Optional[str] = None,
        alpha_regr: Optional[float] = None,
    ) -> matplotlib.axes.Axes:
        """Plot regression result

        Parameters
        ----------
        x_label : str, optional
            The label which is added to the X-axis. (default: "Method 1")
        y_label : str, optional
            The label which is added to the Y-axis. (default: "Method 2")
        title : str, optional
            Title of the regression plot. If None is provided, no title will be plotted.
        line_reference : bool, optional
            If True, a grey reference line at y=x will be plotted in the plot
            (default: True)
        line_CI : bool, optional
            If True, dashed lines will be plotted at the boundaries of the confidence
            intervals.
            (default: False)
        legend : bool, optional
            If True, will provide a legend containing the computed regression equation.
            (default: True)
        square : bool, optional
            If True, set the Axes aspect to "equal" so each cell will be
            square-shaped. (default: True)
        ax : matplotlib.axes.Axes, optional
            matplotlib axis object, if not passed, uses gca()
        point_kws : Optional[Dict], optional
            Additional keywords to plt
        color_regr : Optional[str], optional
            color for regression line and CI area
        alpha_regr : Optional[float], optional
            alpha for regression CI area

        Returns
        ------------------
        matplotlib.axes.Axes
            axes object with the plot
        """
        ax = ax or plt.gca()

        # Set scatter plot keywords to defaults and apply override
        pkws = self.DEFAULT_POINT_KWS.copy()
        pkws.update(point_kws or {})

        # Get regression parameters
        slope = self.result["slope"]
        intercept = self.result["intercept"]

        # plot individual points
        ax.scatter(self.method1, self.method2, **pkws)

        # plot reference line
        if line_reference:
            ax.plot(
                [0, 1],
                [0, 1],
                label="Reference",
                color="grey",
                linestyle="--",
                transform=ax.transAxes,
            )

        # Compute x and y values
        xvals = np.array(ax.get_xlim())
        yvals = xvals[:, None] * slope + intercept

        # Plot regression line 0
        ax.plot(
            xvals,
            yvals[:, 0],
            label=
            f"{y_label} = {intercept[0]:.2f} + {slope[0]:.2f} * {x_label}",
            color=color_regr,
            linestyle="-",
        )

        # Plot confidence region
        if yvals.shape[1] > 2:
            ax.fill_between(
                xvals,
                yvals[:, 1],
                yvals[:, 2],
                color=color_regr or self.DEFAULT_REGRESSION_KWS["color"],
                alpha=alpha_regr or self.DEFAULT_REGRESSION_KWS["alpha"],
            )
            if line_CI:
                ax.plot(xvals, yvals[:, 1], linestyle="--")
                ax.plot(xvals, yvals[:, 2], linestyle="--")

        # Set axes labels
        ax.set(
            xlabel=x_label or "",
            ylabel=y_label or "",
            title=title or "",
        )

        if legend:
            ax.legend(loc="upper left", frameon=False)

        if square:
            ax.set_aspect("equal")
        return ax
def mean_sd_plot(ax: matplotlib.axes.Axes, x: np.ndarray, mean: np.ndarray,
                 sd: np.ndarray, shaded_kwargs: Dict[str, Any],
                 line_kwargs: Dict[str, Any]) -> List[matplotlib.lines.Line2D]:
    """Create mean +- sd plot."""
    ax.fill_between(x, mean - sd, mean + sd, **shaded_kwargs)
    return ax.plot(x, mean, **line_kwargs)
예제 #12
0
    def plot(
        self,
        figsize: Tuple[float, float] = (15, 10),
        same_plot: bool = False,
        hide_cells: bool = False,
        perc: Tuple[float, float] = None,
        abs_prob_cmap: mcolors.ListedColormap = cm.viridis,
        cell_color: str = "black",
        lineage_color: str = "black",
        alpha: float = 0.8,
        lineage_alpha: float = 0.2,
        title: Optional[str] = None,
        size: int = 15,
        lw: float = 2,
        show_cbar: bool = True,
        margins: float = 0.015,
        xlabel: str = "pseudotime",
        ylabel: str = "expression",
        show_conf_int: bool = True,
        dpi: int = None,
        fig: mpl.figure.Figure = None,
        ax: mpl.axes.Axes = None,
        return_fig: bool = False,
        save: Optional[str] = None,
    ) -> Optional[mpl.figure.Figure]:
        """
        Plot the smoothed gene expression.

        Parameters
        ----------
        figsize
            Size of the figure.
        same_plot
            Whether to plot all trends in the same plot.
        hide_cells
            Whether to hide the cells.
        perc
            Percentile by which to clip the absorption probabilities./
        abs_prob_cmap
            Colormap to use when coloring in the absorption probabilities.
        cell_color
            Color for the cells when not coloring absorption probabilities.
        lineage_color
            Color for the lineage.
        alpha
            Alpha channel for cells.
        lineage_alpha
            Alpha channel for lineage confidence intervals.
        title
            Title of the plot.
        size
            Size of the points.
        lw
            Line width for the smoothed values.
        show_cbar
            Whether to show colorbar.
        margins
            Margins around the plot.
        xlabel
            Label on the x-axis.
        ylabel
            Label on the y-axis.
        show_conf_int
            Whether to show the confidence interval.
        dpi
            Dots per inch.
        fig
            Figure to use, if `None`, create a new one.
        ax: :class:`matplotlib.axes.Axes`
            Ax to use, if `None`, create a new one.
        return_fig
            If `True`, return the figure object.
        save
            Filename where to save the plot. If `None`, just shows the plots.

        Returns
        -------
        %(just_plots)s
        """

        if fig is None or ax is None:
            fig, ax = plt.subplots(figsize=figsize, constrained_layout=True)

        if dpi is not None:
            fig.set_dpi(dpi)

        vmin, vmax = _minmax(self.w, perc)
        if not hide_cells:
            _ = ax.scatter(
                self.x_all.squeeze(),
                self.y_all.squeeze(),
                c=cell_color if same_plot or np.allclose(self.w_all, 1.0) else
                self.w_all.squeeze(),
                s=size,
                cmap=abs_prob_cmap,
                vmin=vmin,
                vmax=vmax,
                alpha=alpha,
            )

        if title is None:
            title = f"{self._gene} @ {self._lineage}"

        _ = ax.plot(self.x_test,
                    self.y_test,
                    color=lineage_color,
                    lw=lw,
                    label=title)

        ax.set_title(title)
        ax.set_ylabel(ylabel)
        ax.set_xlabel(xlabel)

        ax.margins(margins)

        if show_conf_int and self.conf_int is not None:
            ax.fill_between(
                self.x_test.squeeze(),
                self.conf_int[:, 0],
                self.conf_int[:, 1],
                alpha=lineage_alpha,
                color=lineage_color,
                linestyle="--",
            )

        if (show_cbar and not hide_cells and not same_plot
                and not np.allclose(self.w_all, 1)):
            norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
            divider = make_axes_locatable(ax)
            cax = divider.append_axes("right", size="2.5%", pad=0.1)
            _ = mpl.colorbar.ColorbarBase(cax,
                                          norm=norm,
                                          cmap=abs_prob_cmap,
                                          label="absorption probability")

        if save is not None:
            save_fig(fig, save)

        if return_fig:
            return fig
예제 #13
0
    def plot(
        self,
        figsize: Tuple[float, float] = (8, 5),
        same_plot: bool = False,
        hide_cells: bool = False,
        perc: Tuple[float, float] = None,
        abs_prob_cmap: mcolors.ListedColormap = cm.viridis,
        cell_color: str = "black",
        lineage_color: str = "black",
        alpha: float = 0.8,
        lineage_alpha: float = 0.2,
        title: Optional[str] = None,
        size: int = 15,
        lw: float = 2,
        cbar: bool = True,
        margins: float = 0.015,
        xlabel: str = "pseudotime",
        ylabel: str = "expression",
        conf_int: bool = True,
        lineage_probability: bool = False,
        lineage_probability_conf_int: Union[bool, float] = False,
        lineage_probability_color: Optional[str] = None,
        dpi: int = None,
        fig: mpl.figure.Figure = None,
        ax: mpl.axes.Axes = None,
        return_fig: bool = False,
        save: Optional[str] = None,
        **kwargs,
    ) -> Optional[mpl.figure.Figure]:
        """
        Plot the smoothed gene expression.

        Parameters
        ----------
        figsize
            Size of the figure.
        same_plot
            Whether to plot all trends in the same plot.
        hide_cells
            Whether to hide the cells.
        perc
            Percentile by which to clip the absorption probabilities.
        abs_prob_cmap
            Colormap to use when coloring in the absorption probabilities.
        cell_color
            Color for the cells when not coloring absorption probabilities.
        lineage_color
            Color for the lineage.
        alpha
            Alpha channel for cells.
        lineage_alpha
            Alpha channel for lineage confidence intervals.
        title
            Title of the plot.
        size
            Size of the points.
        lw
            Line width for the smoothed values.
        cbar
            Whether to show colorbar.
        margins
            Margins around the plot.
        xlabel
            Label on the x-axis.
        ylabel
            Label on the y-axis.
        conf_int
            Whether to show the confidence interval.
        lineage_probability
            Whether to show smoothed lineage probability as a dashed line.
            Note that this will require 1 additional model fit.
        lineage_probability_conf_int
            Whether to compute and show smoothed lineage probability confidence interval.
            If :paramref:`self` is :class:`cellrank.ul.models.GAMR`, it can also specify the confidence level,
            the default is `0.95`. Only used when ``show_lineage_probability=True``.
        lineage_probability_color
            Color to use when plotting the smoothed ``lineage_probability``.
            If `None`, it's the same as ``lineage_color``. Only used when ``show_lineage_probability=True``.
        dpi
            Dots per inch.
        fig
            Figure to use, if `None`, create a new one.
        ax: :class:`matplotlib.axes.Axes`
            Ax to use, if `None`, create a new one.
        return_fig
            If `True`, return the figure object.
        save
            Filename where to save the plot. If `None`, just shows the plots.
        **kwargs
            Keyword arguments for :meth:`matplotlib.axes.Axes.legend`, e.g. to disable the legend, specify ``loc=None``.
            Only available when ``show_lineage_probability=True``.

        Returns
        -------
        %(just_plots)s
        """

        if self.y_test is None:
            raise RuntimeError("Run `.predict()` first.")

        if fig is None or ax is None:
            fig, ax = plt.subplots(figsize=figsize, constrained_layout=True)

        if dpi is not None:
            fig.set_dpi(dpi)

        conf_int = conf_int and self.conf_int is not None
        hide_cells = (hide_cells or self.x_all is None or self.w_all is None
                      or self.y_all is None)

        lineage_probability_color = (lineage_color
                                     if lineage_probability_color is None else
                                     lineage_probability_color)

        scaler = kwargs.pop(
            "scaler",
            self._create_scaler(
                lineage_probability,
                show_conf_int=conf_int,
            ),
        )

        if lineage_probability:
            if ylabel in ("expression", self._gene):
                ylabel = f"scaled {ylabel}"

        vmin, vmax = None, None
        if not hide_cells:
            vmin, vmax = _minmax(self.w_all, perc)
            _ = ax.scatter(
                self.x_all.squeeze(),
                scaler(self.y_all.squeeze()),
                c=cell_color if same_plot or np.allclose(self.w_all, 1.0) else
                self.w_all.squeeze(),
                s=size,
                cmap=abs_prob_cmap,
                vmin=vmin,
                vmax=vmax,
                alpha=alpha,
            )

        if title is None:
            title = (f"{self._gene} @ {self._lineage}"
                     if self._lineage is not None else f"{self._gene}")

        ax.plot(self.x_test,
                scaler(self.y_test),
                color=lineage_color,
                lw=lw,
                label=title)

        if title is not None:
            ax.set_title(title)
        if ylabel is not None:
            ax.set_ylabel(ylabel)
        if xlabel is not None:
            ax.set_xlabel(xlabel)

        ax.margins(margins)

        if conf_int:
            ax.fill_between(
                self.x_test.squeeze(),
                scaler(self.conf_int[:, 0]),
                scaler(self.conf_int[:, 1]),
                alpha=lineage_alpha,
                color=lineage_color,
                linestyle="--",
            )

        if (lineage_probability and not isinstance(self, FittedModel)
                and not np.allclose(self.w, 1.0)):
            from cellrank.pl._utils import _is_any_gam_mgcv

            model = deepcopy(self)
            model._y = self._reshape_and_retype(self.w).copy()
            model = model.fit()

            if not lineage_probability_conf_int:
                y = model.predict()
            elif _is_any_gam_mgcv(model):
                y = model.predict(
                    level=lineage_probability_conf_int if isinstance(
                        lineage_probability_conf_int, float) else 0.95)
            else:
                y = model.predict()
                model.confidence_interval()

                ax.fill_between(
                    model.x_test.squeeze(),
                    model.conf_int[:, 0],
                    model.conf_int[:, 1],
                    alpha=lineage_alpha,
                    color=lineage_probability_color,
                    linestyle="--",
                )

            handle = ax.plot(
                model.x_test,
                y,
                color=lineage_probability_color,
                lw=lw,
                linestyle="--",
                zorder=-1,
                label="probability",
            )

            if kwargs.get("loc", "best") is not None:
                ax.legend(handles=handle, **kwargs)

        if (cbar and not hide_cells and not same_plot
                and not np.allclose(self.w_all, 1.0)):
            norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
            divider = make_axes_locatable(ax)
            cax = divider.append_axes("right", size="2%", pad=0.1)
            _ = mpl.colorbar.ColorbarBase(
                cax,
                norm=norm,
                cmap=abs_prob_cmap,
                ticks=np.linspace(norm.vmin, norm.vmax, 5),
            )

        if save is not None:
            save_fig(fig, save)

        if return_fig:
            return fig