jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def _plot_pdp(self, test_data, feature_name, path):
        feature_role = self._model.reader._roles[feature_name].name
        # I. Count interpretation
        print("Calculating interpretation for {}:".format(feature_name))
        grid, ys, counts = self._model.get_individual_pdp(
            test_data=test_data,
            feature_name=feature_name,
            n_bins=self.interpretation_params["n_bins"],
            top_n_categories=self.interpretation_params["top_n_categories"],
            datetime_level=self.interpretation_params["datetime_level"],
        )
        # II. Plot pdp
        sns.set(style="whitegrid", font_scale=1.5)
        fig, axs = plt.subplots(2, 1, figsize=(16, 12), gridspec_kw={"height_ratios": [3, 1]})
        axs[0].set_title("PDP plot: " + feature_name)
        n_classes = ys[0].shape[1]
        if n_classes == 1:
            data = pd.concat(
                [pd.DataFrame({"x": grid[i], "y": ys[i].ravel()}) for i, _ in enumerate(grid)]
            ).reset_index(drop=True)
            if feature_role in ["Numeric", "Datetime"]:
                g0 = sns.lineplot(data=data, x="x", y="y", ax=axs[0], color="m")
            else:
                g0 = sns.boxplot(data=data, x="x", y="y", ax=axs[0], showfliers=False, color="m")
        else:
            if self.mapping:
                classes = sorted(self.mapping, key=self.mapping.get)[: self.interpretation_params["top_n_classes"]]
            else:
                classes = np.arange(min(n_classes, self.interpretation_params["top_n_classes"]))
            data = pd.concat(
                [
                    pd.DataFrame({"x": grid[i], "y": ys[i][:, k], "class": name})
                    for i, _ in enumerate(grid)
                    for k, name in enumerate(classes)
                ]
            ).reset_index(drop=True)
            if self._model.reader._roles[feature_name].name in ["Numeric", "Datetime"]:
                g0 = sns.lineplot(data=data, x="x", y="y", hue="class", ax=axs[0])
            else:
                g0 = sns.boxplot(data=data, x="x", y="y", hue="class", ax=axs[0], showfliers=False)
        g0.set(ylabel="y_pred")
        # III. Plot distribution
        counts = np.array(counts) / sum(counts)
        if feature_role == "Numeric":
            g0.set(xlabel="feature value")
            g1 = sns.histplot(test_data[feature_name], kde=True, color="gray", ax=axs[1])
        elif feature_role == "Category":
            g0.set(xlabel=None)
            axs[0].set_xticklabels(grid, rotation=90)
            g1 = sns.barplot(x=grid, y=counts, ax=axs[1], color="gray")
        else:
            g0.set(xlabel=self.interpretation_params["datetime_level"])
            g1 = sns.barplot(x=grid, y=counts, ax=axs[1], color="gray")
        g1.set(xlabel=None)
        g1.set(ylabel="Frequency")
        g1.set(xticklabels=[])
        # IV. Save picture
        plt.tight_layout()
        fig.savefig(path, bbox_inches="tight")
        plt.close()
//  [python]
def make_plot(eval_metric):
    plt.clf()
    training_plot = plt.plot(eval_metric)
    fig = training_plot[0].figure
    buffer = BytesIO()
    fig.savefig(buffer)
    image_data = base64.b64encode(buffer.getvalue())
    return MetadataValue.md(f"![img](data:image/png;base64,{image_data.decode()})")
//  [python]
def make_plot(eval_metric):
    plt.clf()
    training_plot = seaborn.lineplot(eval_metric)
    fig = training_plot.get_figure()
    buffer = BytesIO()
    fig.savefig(buffer)
    image_data = base64.b64encode(buffer.getvalue())
    return MetadataValue.md(f"![img](data:image/png;base64,{image_data.decode()})")
//  [python]
def vis_fusion_weight(self):
        vis_fusion_weight = getattr(self.cfg, 'vis_fusion_weight', True)
        if 'feature_fusion' in self.model.module.local and vis_fusion_weight:
            weight = self.model.module.local['feature_fusion'][0].weight.data
            y = weight.squeeze().detach().abs().mean(0).cpu().numpy()
            local_channels = self.model.module.local_channels
            global_channels = y.shape[0] - 3*local_channels
            x = np.arange(y.shape[0])

            fig = plt.figure()
            x1 = global_channels
            x2 = global_channels + local_channels
            x3 = global_channels + 2*local_channels
            plt.plot(x[:x1], y[:x1], label='global')
            plt.plot(x[x1:x2], y[x1:x2], label='head')
            plt.plot(x[x2:x3], y[x2:x3], label='torso')
            plt.plot(x[x3:], y[x3:], label='legs')
            plt.legend()
            self.writer.add_figure('fusion_weight/{}'.format(self.iter),
                                   fig, self.iter)
            directory = os.path.join(self.cfg.work_dir, 'figs', 'fusion_weight')
            os.makedirs(directory, exist_ok=True)
            save_path = os.path.join(directory, '{}.pdf'.format(self.iter))
            plt.savefig(save_path)
            plt.close()
        return
//  [python]
def vis_fusion_weight(self):
        vis_fusion_weight = getattr(self.cfg, 'vis_fusion_weight', True)
        if 'feature_fusion' in self.model.module.local and vis_fusion_weight:
            weight = self.model.module.local['feature_fusion'][0].weight.data
            y = weight.squeeze().detach().abs().mean(0).cpu().numpy()
            local_channels = self.model.module.local_channels
            global_channels = y.shape[0] - 3*local_channels
            x = np.arange(y.shape[0])

            fig = plt.figure()
            x1 = global_channels
            x2 = global_channels + local_channels
            x3 = global_channels + 2*local_channels
            sns.lineplot(x=x[:x1], y=y[:x1], label='global')
            sns.lineplot(x=x[x1:x2], y=y[x1:x2], label='head')
            sns.lineplot(x=x[x2:x3], y=y[x2:x3], label='torso')
            sns.lineplot(x=x[x3:], y=y[x3:], label='legs')
            self.writer.add_figure('fusion_weight/{}'.format(self.iter),
                                   fig, self.iter)
            directory = os.path.join(self.cfg.work_dir, 'figs', 'fusion_weight')
            os.makedirs(directory, exist_ok=True)
            save_path = os.path.join(directory, '{}.pdf'.format(self.iter))
            plt.savefig(save_path)
            plt.close()
        return
//  [python]
def plot_drift(params: Dict[str, LogSvParams] = DRIFT_PARAMS,
               axs: List[plt.Subplot] = None
               ) -> None:
    x = np.linspace(0.0, 2.0, 200)

    drifts = []
    drifts_delta = []
    for key, param in params.items():
        drift1 = param.kappa1*(param.theta - x)
        drift2 = param.kappa1 * param.theta - (param.kappa1 - param.kappa2 * param.theta) * x - param.kappa2 * x * x
        drifts.append(pd.Series(drift2, index=x, name=key))
        drifts_delta.append(pd.Series(drift2-drift1, index=x, name=key))
    drifts = pd.concat(drifts, axis=1) / 260.0
    drifts_delta = pd.concat(drifts_delta, axis=1) / 260.0

    dfs = {'(A) Volatility drift per day as function of $\sigma_{t}$': drifts,
           '(B) Volatility drift relative to the linear drift': drifts_delta}

    for idx, (key, df) in enumerate(dfs.items()):
        ax = axs[idx]
        sns.tsplot(data=df, dashes=False,  ax=ax)
        yvar_format = '{:.2f}'
        ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda z, _: yvar_format.format(z)))
        ax.set_title(key, fontsize=12, color='darkblue')
        ax.set_xlabel('$\sigma_{t}$', fontsize=12)
        ax.set_xlim((0.0, None))
    plot.align_y_limits_axs(axs=axs)
//  [python]
def plot_drift(params: Dict[str, LogSvParams] = DRIFT_PARAMS,
               axs: List[plt.Subplot] = None
               ) -> None:
    x = np.linspace(0.0, 2.0, 200)

    drifts = []
    drifts_delta = []
    for key, param in params.items():
        drift1 = param.kappa1*(param.theta - x)
        drift2 = param.kappa1 * param.theta - (param.kappa1 - param.kappa2 * param.theta) * x - param.kappa2 * x * x
        drifts.append(pd.Series(drift2, index=x, name=key))
        drifts_delta.append(pd.Series(drift2-drift1, index=x, name=key))
    drifts = pd.concat(drifts, axis=1) / 260.0
    drifts_delta = pd.concat(drifts_delta, axis=1) / 260.0

    dfs = {'(A) Volatility drift per day as function of $\sigma_{t}$': drifts,
           '(B) Volatility drift relative to the linear drift': drifts_delta}

    for idx, (key, df) in enumerate(dfs.items()):
        ax = axs[idx]
        sns.lineplot(data=df, dashes=False,  ax=ax)
        yvar_format = '{:.2f}'
        ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda z, _: yvar_format.format(z)))
        ax.set_title(key, fontsize=12, color='darkblue')
        ax.set_xlabel('$\sigma_{t}$', fontsize=12)
        ax.set_xlim((0.0, None))
    plot.align_y_limits_axs(axs=axs)
//  [python]
def _seaborn_viz_decomposition(df, result):
    """Create timeseries decomposition visualization.

    Args:
        df: The dataframe
        result: The statsmodels.tsa.seasonal.DecomposeResult object.

    Returns:
        The visualization
    """
    fig, ax = plt.subplots(
        nrows=4,
        ncols=1,
        sharex=True,
        figsize=(
            get_option("display.matplotlib.fig_width"),
            get_option("display.matplotlib.fig_height"),
        ),
    )
    s.lineplot(y=result.observed, x=df.index, ax=ax[0])
    s.lineplot(y=result.trend, x=df.index, ax=ax[1])
    s.lineplot(y=result.seasonal, x=df.index, ax=ax[2])
    s.lineplot(y=result.resid, x=df.index, ax=ax[3])
    fig.suptitle("Time Series Decomposition", fontsize=18)

    plt.close()
    return fig
//  [python]
def _seaborn_viz_decomposition(df, result):
    """Create timeseries decomposition visualization.

    Args:
        df: The dataframe
        result: The statsmodels.tsa.seasonal.DecomposeResult object.

    Returns:
        The visualization
    """
    fig, ax = plt.subplots(
        nrows=4,
        ncols=1,
        sharex=True,
        figsize=(
            get_option("display.matplotlib.fig_width"),
            get_option("display.matplotlib.fig_height"),
        ),
    )
    sns.lineplot(y=result.observed, x=df.index, ax=ax[0])
    sns.lineplot(y=result.trend, x=df.index, ax=ax[1])
    sns.lineplot(y=result.seasonal, x=df.index, ax=ax[2])
    sns.lineplot(y=result.resid, x=df.index, ax=ax[3])
    fig.suptitle("Time Series Decomposition", fontsize=18)

    plt.close()
    return fig
//  [python]
def _seaborn_viz_plot_time_series(df, col, result=None, decompose=False):
    """Create timeseries visualization.

    Args:
        df: The dataframe
        col (str or [str]): Column of interest. Column datatype must be numerical.
        result: The statsmodels.tsa.seasonal.DecomposeResult object. Defaults to None.
        decompose: Set as True to decompose the timeseries with moving average. result must not be None. Defaults to False.

    Returns:
        The visualization
    """
    fig, ax = plt.subplots(
        figsize=(
            get_option("display.matplotlib.fig_width"),
            get_option("display.matplotlib.fig_height"),
        )
    )

    if isinstance(col, list):
        for i in col:
            fig = s.lineplot(x=df.index, y=df[i], legend="full", ax=ax)
        ax.legend(labels=col)
    elif isinstance(col, str) and not decompose:
        fig = s.lineplot(x=df.index, y=df[col], legend="full", ax=ax)
    elif decompose:
        fig = _seaborn_viz_decomposition(df, result)
        plt.close()
    return fig
//  [python]
def _seaborn_viz_plot_time_series(df, col, result=None, decompose=False):
    """Create timeseries visualization.

    Args:
        df: The dataframe
        col (str or [str]): Column of interest. Column datatype must be numerical.
        result: The statsmodels.tsa.seasonal.DecomposeResult object. Defaults to None.
        decompose: Set as True to decompose the timeseries with moving average. result must not be None. Defaults to False.

    Returns:
        The visualization
    """
    fig, ax = plt.subplots(
        figsize=(
            get_option("display.matplotlib.fig_width"),
            get_option("display.matplotlib.fig_height"),
        )
    )

    if isinstance(col, list):
        for i in col:
            fig = sns.lineplot(x=df.index, y=df[i], legend="full", ax=ax)
        ax.legend(labels=col)
    elif isinstance(col, str) and not decompose:
        fig = sns.lineplot(x=df.index, y=df[col], legend="full", ax=ax)
    elif decompose:
        fig = _seaborn_viz_decomposition(df, result)
        plt.close()
    return fig
//  [python]
def categorical_distribution(real, fake, xlabel, ylabel, col=None, ax=None):
    ax = ax if ax else plt.subplots()[1]
    if col is not None:
        real = real[col]
        fake = fake[col]
    y_r = real.value_counts().sort_index() / len(real)
    y_f = fake.value_counts().sort_index() / len(fake)

    # width = 0.35  # the width of the bars
    ind = np.arange(len(y_r.index))

    ax.grid()
    yr_cumsum = y_r.cumsum()
    yf_cumsum = y_f.cumsum()
    values = yr_cumsum.values.tolist() + yf_cumsum.values.tolist()
    real = [1 for _ in range(len(yr_cumsum))] + [0 for _ in range(len(yf_cumsum))]
    classes = yr_cumsum.index.tolist() + yf_cumsum.index.tolist()
    data = pd.DataFrame({'values': values,
                         'real': real,
                         'class': classes})
    paper_rc = {'lines.linewidth': 8}
    sns.set_context("paper", rc=paper_rc)
    #     ax.plot(x=yr_cumsum.index.tolist(), y=yr_cumsum.values.tolist(), ms=8)
    sns.pointplot(y='values', x='class', data=data, ax=ax, hue='real')
    #     ax.bar(ind - width / 2, y_r.values, width, label='Real')
    #     ax.bar(ind + width / 2, y_f.values, width, label='Fake')

    ax.set_ylabel('Distributions per variable')

    axis_font = {'size': '18'}
    ax.set_xlabel(xlabel, **axis_font)
    ax.set_ylabel(ylabel, **axis_font)

    ax.set_xticks(ind)
    ax.set_xticklabels(y_r.index, rotation='vertical')
    ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1), ncol=3)
//  [python]
def categorical_distribution(real, fake, xlabel, ylabel, col=None, ax=None):
    ax = ax if ax else plt.subplots()[1]
    if col is not None:
        real = real[col]
        fake = fake[col]
    y_r = real.value_counts().sort_index() / len(real)
    y_f = fake.value_counts().sort_index() / len(fake)

    # width = 0.35  # the width of the bars
    ind = np.arange(len(y_r.index))

    ax.grid()
    yr_cumsum = y_r.cumsum()
    yf_cumsum = y_f.cumsum()
    values = yr_cumsum.values.tolist() + yf_cumsum.values.tolist()
    real = [1 for _ in range(len(yr_cumsum))] + [0 for _ in range(len(yf_cumsum))]
    classes = yr_cumsum.index.tolist() + yf_cumsum.index.tolist()
    data = pd.DataFrame({'values': values,
                         'real': real,
                         'class': classes})
    paper_rc = {'lines.linewidth': 8}
    sns.set_context("paper", rc=paper_rc)
    #     ax.plot(x=yr_cumsum.index.tolist(), y=yr_cumsum.values.tolist(), ms=8)
    sns.lineplot(y='values', x='class', data=data, ax=ax, hue='real')
    #     ax.bar(ind - width / 2, y_r.values, width, label='Real')
    #     ax.bar(ind + width / 2, y_f.values, width, label='Fake')

    ax.set_ylabel('Distributions per variable')

    axis_font = {'size': '18'}
    ax.set_xlabel(xlabel, **axis_font)
    ax.set_ylabel(ylabel, **axis_font)

    ax.set_xticks(ind)
    ax.set_xticklabels(y_r.index, rotation='vertical')
    ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1), ncol=3)
//  [python]
train = []

for epoch in range(num_epochs):
    bar = tqdm(train_loader)
    for i, data in enumerate(bar):
        _, mini_batch_loss = training_model(data.z, data.pos, data.batch, data.y)
        loss = float(mini_batch_loss.mean())
        train.append({"epoch": epoch, "step": i, "loss": loss})
        bar.set_description(f"Epoch {epoch} loss: {loss:0.6f}")

training_model.detachFromDevice()

df = pd.DataFrame(train)
g = sns.tsplot(data=df[df.epoch > 0], time="epoch", value="loss", unit="step")
//  [python]
train = []

for epoch in range(num_epochs):
    bar = tqdm(train_loader)
    for i, data in enumerate(bar):
        _, mini_batch_loss = training_model(data.z, data.pos, data.batch, data.y)
        loss = float(mini_batch_loss.mean())
        train.append({"epoch": epoch, "step": i, "loss": loss})
        bar.set_description(f"Epoch {epoch} loss: {loss:0.6f}")

training_model.detachFromDevice()

df = pd.DataFrame(train)
g = sns.lineplot(data=df[df.epoch > 0], x="epoch", y="loss", errorbar="sd")
//  [python]
def plot_mean_std(real, fake, ax=None):
    if ax is None:
        fig, ax = plt.subplots(1, 2, figsize=(10, 5))
        fig.suptitle('Absolute Log Mean and STDs of numeric data\n', fontsize=16)

    real = real._get_numeric_data()
    fake = fake._get_numeric_data()
    real_mean = np.log(np.add(abs(real.mean()).values, 1e-5))
    fake_mean = np.log(np.add(abs(fake.mean()).values, 1e-5))
    min_mean = min(real_mean) - 1
    max_mean = max(real_mean) + 1
    line = np.arange(min_mean, max_mean)
    sns.lineplot(x=line, y=line, ax=ax[0])
    sns.scatterplot(x=real_mean,
                    y=fake_mean,
                    ax=ax[0])
    ax[0].set_title('Means of real and fake data')
    ax[0].set_xlabel('real data mean (log)')
    ax[0].set_ylabel('fake data mean (log)')

    real_std = np.log(np.add(real.std().values, 1e-5))
    fake_std = np.log(np.add(fake.std().values, 1e-5))
    min_std = min(real_std) - 1
    max_std = max(real_std) + 1
    line = np.arange(min_std, max_std)
    sns.lineplot(x=line, y=line, ax=ax[1])
    sns.scatterplot(x=real_std,
                    y=fake_std,
                    ax=ax[1])
    ax[1].set_title('Stds of real and fake data')
    ax[1].set_xlabel('real data std (log)')
    ax[1].set_ylabel('fake data std (log)')
    ax[0].grid(True)
    ax[1].grid(True)
    if ax is None:
        plt.show()
//  [python]
def figure_cbas(dir_path=('plot/wee1_noseed', 'plot/big_newlr'),
                ylim=(-12, -6), plot_best=False,
                return_best=False,
                use_norm_score=False, obj='logp',
                successive=False):
    fig, ax = plt.subplots(1, 2)

    wee1, drd3 = dir_path

    iterations, mus, stds, batch_size, newslist, title, best_scores, best_smiles = plot_one(drd3,
                                                                                            use_norm_score,
                                                                                            obj,
                                                                                            successive=successive)
    print(newslist)
    mus = np.array(mus)
    stds = np.array(stds)
    ax[0].fill_between(iterations, mus + stds, mus - stds, alpha=.25)
    plt.plot(iterations, mus, ax=ax[0], label='DRD3')
    ax[1].plot(iterations, newslist, label='DRD3')

    iterations, mus, stds, batch_size, newslist, title, best_scores, best_smiles = plot_one(wee1,
                                                                                            use_norm_score,
                                                                                            obj,
                                                                                            successive=successive)
    print(newslist)
    mus = np.array(mus)
    stds = np.array(stds)
    ax[0].fill_between(iterations, mus + stds, mus - stds, alpha=.25)
    plt.plot(iterations, mus, ax=ax[0], label='WEE1')
    # sns.lineplot(iterations, mus, ax=ax[0], label='WEE1',size=2)
    ax[1].plot(iterations, newslist, label='WEE1')

    ax[0].set_ylim(ylim[0], ylim[1])
    ax[0].set_xlim(1, iterations[-1] + 0.2)
    ax[1].set_ylim(0, batch_size + 100)

    sns.despine()
    ax[0].set_xlabel('Iterations')
    ax[0].set_ylabel('Docking Score (kcal/mol)')
    ax[1].set_xlabel('Iterations')
    ax[1].set_ylabel('Novel samples')
    ax[1].legend()
    fig.tight_layout(pad=2.0)
    fig.align_labels()
    plt.savefig("cbas_fig2.pdf", format="pdf")
    plt.show()
//  [python]
def figure_cbas(dir_path=('plot/wee1_noseed', 'plot/big_newlr'),
                ylim=(-12, -6), plot_best=False,
                return_best=False,
                use_norm_score=False, obj='logp',
                successive=False):
    fig, ax = plt.subplots(1, 2)

    wee1, drd3 = dir_path

    iterations, mus, stds, batch_size, newslist, title, best_scores, best_smiles = plot_one(drd3,
                                                                                            use_norm_score,
                                                                                            obj,
                                                                                            successive=successive)
    print(newslist)
    mus = np.array(mus)
    stds = np.array(stds)
    ax[0].fill_between(iterations, mus + stds, mus - stds, alpha=.25)
    sns.lineplot(iterations, mus, ax=ax[0], label='DRD3')
    ax[1].plot(iterations, newslist, label='DRD3')

    iterations, mus, stds, batch_size, newslist, title, best_scores, best_smiles = plot_one(wee1,
                                                                                            use_norm_score,
                                                                                            obj,
                                                                                            successive=successive)
    print(newslist)
    mus = np.array(mus)
    stds = np.array(stds)
    ax[0].fill_between(iterations, mus + stds, mus - stds, alpha=.25)
    sns.lineplot(iterations, mus, ax=ax[0], label='WEE1')
    # sns.lineplot(iterations, mus, ax=ax[0], label='WEE1',size=2)
    ax[1].plot(iterations, newslist, label='WEE1')

    ax[0].set_ylim(ylim[0], ylim[1])
    ax[0].set_xlim(1, iterations[-1] + 0.2)
    ax[1].set_ylim(0, batch_size + 100)

    sns.despine()
    ax[0].set_xlabel('Iterations')
    ax[0].set_ylabel('Docking Score (kcal/mol)')
    ax[1].set_xlabel('Iterations')
    ax[1].set_ylabel('Novel samples')
    ax[1].legend()
    fig.tight_layout(pad=2.0)
    fig.align_labels()
    plt.savefig("cbas_fig2.pdf", format="pdf")
    plt.show()
//  [python]
def get_results(train_loss_results, train_cost_results, val_cost, save_results=True, filename=None, plots=True):

    epochs_num = len(train_loss_results)

    df_train = pd.DataFrame(data={'epochs': list(range(epochs_num)),
                                  'loss': train_loss_results,
                                  'cost': train_cost_results,
                                  })
    df_test = pd.DataFrame(data={'epochs': list(range(epochs_num)),
                                 'val_сost': val_cost})
    if save_results:
        df_train.to_excel('train_results_{}.xlsx'.format(filename), index=False)
        df_test.to_excel('test_results_{}.xlsx'.format(filename), index=False)

    if plots:
        plt.figure(figsize=(15, 9))
        ax = sns.pointplot(x='epochs', y='loss', data=df_train, color='salmon', label='train loss')
        ax2 = ax.twinx()
        sns.pointplot(x='epochs', y='cost', data=df_train, color='cornflowerblue', label='train cost', ax=ax2)
        sns.pointplot(x='epochs', y='val_сost', data=df_test, palette='darkblue', label='val cost').set(ylabel='cost')
        ax.legend(loc=(0.75, 0.90), ncol=1)
        ax2.legend(loc=(0.75, 0.95), ncol=2)
        ax.grid(axis='x')
        ax2.grid(True)
        plt.savefig('learning_curve_plot_{}.jpg'.format(filename))
        plt.show()
//  [python]
def get_results(train_loss_results, train_cost_results, val_cost, save_results=True, filename=None, plots=True):

    epochs_num = len(train_loss_results)

    df_train = pd.DataFrame(data={'epochs': list(range(epochs_num)),
                                  'loss': train_loss_results,
                                  'cost': train_cost_results,
                                  })
    df_test = pd.DataFrame(data={'epochs': list(range(epochs_num)),
                                 'val_сost': val_cost})
    if save_results:
        df_train.to_excel('train_results_{}.xlsx'.format(filename), index=False)
        df_test.to_excel('test_results_{}.xlsx'.format(filename), index=False)

    if plots:
        plt.figure(figsize=(15, 9))
        ax = sns.lineplot(x='epochs', y='loss', data=df_train, color='salmon', label='train loss')
        ax2 = ax.twinx()
        sns.lineplot(x='epochs', y='cost', data=df_train, color='cornflowerblue', label='train cost', ax=ax2)
        sns.lineplot(x='epochs', y='val_сost', data=df_test, palette='darkblue', label='val cost').set(ylabel='cost')
        ax.legend(loc=(0.75, 0.90), ncol=1)
        ax2.legend(loc=(0.75, 0.95), ncol=2)
        ax.grid(axis='x')
        ax2.grid(True)
        plt.savefig('learning_curve_plot_{}.jpg'.format(filename))
        plt.show()
//  [python]
def interpolate_and_predict(
        self,
        seed: int,
        steps: int = 100,
        outcome_idx: int = 0,
    ) -> Tuple[List, ...]:
        """Interpolates between starting and ending classes for a seed,
        recording raw images, processed images, and predictions.

        Args:
            seed (int): Seed for random noise vector.
            steps (int, optional): Number of steps during interpolation.
                Defaults to 100.

        Returns:
            Tuple[List, ...]: Raw images, processed images, and predictions.
        """
        if not isinstance(seed, int):
            raise ValueError("Seed must be an integer.")
        import matplotlib.pyplot as plt
        import seaborn as sns

        imgs = []
        proc_imgs = []
        preds = []

        for img in tqdm(self.class_interpolate(seed, steps),
                         total=steps,
                         desc=f"Working on seed {seed}..."):
            img = torch.from_numpy(np.expand_dims(img, axis=0)).permute(0, 3, 1, 2)
            img = (img / 127.5) - 1
            img = self._crop_and_convert_to_uint8(img)
            img = self._preprocess_from_uint8(img, standardize=False, normalize=True)
            processed_img = self._standardize(img)
            img = sf.io.convert_dtype(img, np.float32)[0]

            if self.features is not None:
                pred = self.features(processed_img)[-1]
                if self._classifier_backend == 'torch':
                    pred = pred.cpu()
                pred = pred.numpy()
                preds += [pred[0][outcome_idx]]
            imgs += [img]
            proc_imgs += [processed_img[0]]

        plt.plot(range(len(preds)), preds, label=f"Seed {seed}")
        plt.axhline(y=0, color='black', linestyle='--')
        plt.title("Prediction during interpolation")
        plt.xlabel("Interpolation Step (Start -> End)")
        plt.ylabel("Prediction")

        return imgs, proc_imgs, preds
//  [python]
def interpolate_and_predict(
        self,
        seed: int,
        steps: int = 100,
        outcome_idx: int = 0,
    ) -> Tuple[List, ...]:
        """Interpolates between starting and ending classes for a seed,
        recording raw images, processed images, and predictions.

        Args:
            seed (int): Seed for random noise vector.
            steps (int, optional): Number of steps during interpolation.
                Defaults to 100.

        Returns:
            Tuple[List, ...]: Raw images, processed images, and predictions.
        """
        if not isinstance(seed, int):
            raise ValueError("Seed must be an integer.")
        import matplotlib.pyplot as plt
        import seaborn as sns

        imgs = []
        proc_imgs = []
        preds = []

        for img in tqdm(self.class_interpolate(seed, steps),
                         total=steps,
                         desc=f"Working on seed {seed}..."):
            img = torch.from_numpy(np.expand_dims(img, axis=0)).permute(0, 3, 1, 2)
            img = (img / 127.5) - 1
            img = self._crop_and_convert_to_uint8(img)
            img = self._preprocess_from_uint8(img, standardize=False, normalize=True)
            processed_img = self._standardize(img)
            img = sf.io.convert_dtype(img, np.float32)[0]

            if self.features is not None:
                pred = self.features(processed_img)[-1]
                if self._classifier_backend == 'torch':
                    pred = pred.cpu()
                pred = pred.numpy()
                preds += [pred[0][outcome_idx]]
            imgs += [img]
            proc_imgs += [processed_img[0]]

        sns.lineplot(x=range(len(preds)), y=preds, label=f"Seed {seed}")
        plt.axhline(y=0, color='black', linestyle='--')
        plt.title("Prediction during interpolation")
        plt.xlabel("Interpolation Step (Start -> End)")
        plt.ylabel("Prediction")

        return imgs, proc_imgs, preds
//  [python]
def plot_cmc(distmat, query_ids, gallery_ids, topk):
    m, n = distmat.shape
    # Sort and find correct matches
    indices = np.argsort(distmat, axis=1)
    matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
    # Compute CMC for each query
    ret = np.zeros(topk)
    for i in range(m):
        k = np.nonzero(matches[i])[0][0]
        if k < topk:
            ret[k] += 1
    val = [0] + [rate for rate in ret.cumsum() / m]
    cmc = sns.lineplot(data=val, legend=False)
    cmc.set_title("CMC")
    cmc.set_xlabel("Rank")
    cmc.set_ylabel("Matching Rates (%)")
    cmc.yaxis.set_major_formatter(mtick.PercentFormatter(1.00))
    cmc.set(xlim=(0, topk), ylim=(0, 1))
    fig = cmc.get_figure()
    output_dir = Path("./examples/pedestrian_tracking/multiedge_inference_bench/cmc")
    output_dir.mkdir(parents=True, exist_ok=True)
    output_file = Path(output_dir, datetime.datetime.now().strftime("%Y%m%d%H%M%S") + ".png")
    fig.savefig(output_file)
    return output_file
//  [python]
def plot_lr(self):
        all_lr = []
        for i in range(self.epochs):
            all_lr.append(self.new_lr)
            self.step()
        fig = matplotlib.pyplot.plot(range(self.epochs), all_lr)
        fig = matplotlib.pyplot.gcf()
        fig.savefig('./lr_schedule.jpg', dpi=200)
        self.step_i = 0
        self.new_lr = self.min_lr
//  [python]
def plot_lr(self):
        all_lr = []
        for i in range(self.epochs):
            all_lr.append(self.new_lr)
            self.step()
        fig = seaborn.lineplot(x=range(self.epochs), y=all_lr)
        fig = fig.get_figure()
        fig.savefig('./lr_schedule.jpg', dpi=200)
        self.step_i = 0
        self.new_lr = self.min_lr
//  [python]
def plot_results(x, y, hue, kind, ylabel):
    custom_palette = ["#000000", "#7aff00", "#00BFFF", ["#AC5B27"]] # a list of custom colors in hex format
    sns.set(style="whitegrid")
    if kind == "line":
        sns.pointplot(x=x, y=y, hue=hue, data=df, palette=custom_palette)
    elif kind == "bar":
        g = sns.catplot(x=x, y=y, hue=hue, data=df, kind=kind, palette = custom_palette)
        g.despine(left=True)
        g.set_ylabels(ylabel)
        g.set_xlabels("Number of Bodies")
    plt.xlabel("Number of Bodies")
    plt.ylabel(ylabel)
    plt.show()
//  [python]
def plot_results(x, y, hue, kind, ylabel):
    custom_palette = ["#000000", "#7aff00", "#00BFFF", ["#AC5B27"]] # a list of custom colors in hex format
    sns.set(style="whitegrid")
    if kind == "line":
        sns.lineplot(x=x, y=y, hue=hue, data=df, palette=custom_palette)
    elif kind == "bar":
        g = sns.catplot(x=x, y=y, hue=hue, data=df, kind=kind, palette = custom_palette)
        g.despine(left=True)
        g.set_ylabels(ylabel)
        g.set_xlabels("Number of Bodies")
    plt.xlabel("Number of Bodies")
    plt.ylabel(ylabel)
    plt.show()
//  [python]
def get_fig_multi(ls_sym, df_c):
    '''Returns pyplot figure for list of symbols and input dataframe
    Args:
        ls_sym (list of str)
        df_c (pandas.Dataframe): datetime, close, sma180, rsi14, vwap, peak_valley, divergence, proba
    '''
    if not ls_sym: return
    n = (len(ls_sym)+(3-1))//3
    fig, axs = plt.subplots(nrows=n, ncols=3, figsize=(3*4,n*4))
    bar = st.progress(0.0)
    for i in range(n):
        for j in range(3):
            pos = (i, j)
            if n==1:
                pos = j
            try:
                sym = ls_sym[i*3+j]
                df = df_c[df_c['sym']==sym].copy()
                df['close_div'] = np.where(df['proba']>0, df['close'], np.nan)
                df['close_div_profit'] = np.where((df['proba']>0)&(df['profit']>0), df['close'], np.nan)
                df['close_div_loss'] = np.where((df['proba']>0)&(df['profit']<0), df['close'], np.nan)
                axs[pos].plot(df['datetime'], df['close'])
                axs[pos].plot(df['datetime'], df['vwap'], color='r')
                axs[pos].scatter(df['datetime'], df['close_div'], color='y')
                axs[pos].scatter(df['datetime'], df['close_div_loss'], color='r')
                axs[pos].scatter(df['datetime'], df['close_div_profit'], color='lime')
                axs[pos].set_title(sym, fontsize=20)
                bar.progress((i*3+j+1)/(len(ls_sym)))
            except:
                fig.delaxes(axs[pos])
            axs[pos].set(xticks=[])
            axs[pos].set(xlabel=None)
            axs[pos].set(yticks=[])
            axs[pos].set(ylabel=None)
    bar.empty()
    return fig
//  [python]
def get_fig_multi(ls_sym, df_c):
    '''Returns pyplot figure for list of symbols and input dataframe
    Args:
        ls_sym (list of str)
        df_c (pandas.Dataframe): datetime, close, sma180, rsi14, vwap, peak_valley, divergence, proba
    '''
    if not ls_sym: return
    n = (len(ls_sym)+(3-1))//3
    fig, axs = plt.subplots(nrows=n, ncols=3, figsize=(3*4,n*4))
    bar = st.progress(0.0)
    for i in range(n):
        for j in range(3):
            pos = (i, j)
            if n==1:
                pos = j
            try:
                sym = ls_sym[i*3+j]
                df = df_c[df_c['sym']==sym].copy()
                df['close_div'] = np.where(df['proba']>0, df['close'], np.nan)
                df['close_div_profit'] = np.where((df['proba']>0)&(df['profit']>0), df['close'], np.nan)
                df['close_div_loss'] = np.where((df['proba']>0)&(df['profit']<0), df['close'], np.nan)
                sns.lineplot(data=df, x='datetime', y='close', ax=axs[pos])
                sns.lineplot(data=df, x='datetime', y='vwap', color='r', ax=axs[pos])
                sns.scatterplot(data=df, x='datetime', y='close_div', color='y', ax=axs[pos])
                sns.scatterplot(data=df, x='datetime', y='close_div_loss', color='r', ax=axs[pos])
                sns.scatterplot(data=df, x='datetime', y='close_div_profit', color='lime', ax=axs[pos])
                axs[pos].set_title(sym, fontsize=20)
                bar.progress((i*3+j+1)/(len(ls_sym)))
            except:
                fig.delaxes(axs[pos])
            axs[pos].set(xticks=[])
            axs[pos].set(xlabel=None)
            axs[pos].set(yticks=[])
            axs[pos].set(ylabel=None)
    bar.empty()
    return fig
//  [python]
def plot_heatmap_and_line(self, data, xticklabels='auto', yticklabels='auto'):
        if data.shape[0] > 1:
            data = np.expand_dims(data[1], axis=0)
        print('gat = np.array([')
        for row in data[0]:
            rowstr = ', '.join([f'{x:.2f}' for x in row])
            print(f'    [{rowstr}],')
        print(']')

        size = 3
        tik_label_size = 15
        fig, axes = plt.subplots(2, 1, figsize=(size+2, (size+1) * 2))
        plt.yticks(rotation=0)
        for i in range(data.shape[0]):
            cur_ax = axes[0]
            if data.shape[0] > 1:
                axes[i].set_title(f'Head #{i}')
                cur_ax = axes[i]
            ax = sb.heatmap(data[i], annot=True, fmt='.2f', cbar=False,
                            xticklabels=xticklabels, yticklabels=yticklabels, ax=cur_ax)
            ax.xaxis.tick_top()
            ax.tick_params(labelsize=tik_label_size)
            ax.set_yticklabels(ax.get_yticklabels(), rotation=0)

        cur_ax = axes[1]
        d = {q: row for q, row in zip(yticklabels, data[i])}
        df = pd.DataFrame(d, index=xticklabels)
        ax = sb.pointplot(data=df, ax=cur_ax)
        ax.tick_params(labelsize=tik_label_size)


        plt.setp(ax.get_legend().get_texts(), fontsize=tik_label_size)
        plt.subplots_adjust(hspace=0.08, top=0.96, bottom=0.04)

        plt.legend(bbox_to_anchor=(0.99, 0.45), loc='right', prop={'size': tik_label_size, }, labelspacing=0.1,
                   borderaxespad=0.,)

        # plt.savefig('gatv2.pdf')
        plt.show()
//  [python]
def plot_heatmap_and_line(self, data, xticklabels='auto', yticklabels='auto'):
        if data.shape[0] > 1:
            data = np.expand_dims(data[1], axis=0)
        print('gat = np.array([')
        for row in data[0]:
            rowstr = ', '.join([f'{x:.2f}' for x in row])
            print(f'    [{rowstr}],')
        print(']')

        size = 3
        tik_label_size = 15
        fig, axes = plt.subplots(2, 1, figsize=(size+2, (size+1) * 2))
        plt.yticks(rotation=0)
        for i in range(data.shape[0]):
            cur_ax = axes[0]
            if data.shape[0] > 1:
                axes[i].set_title(f'Head #{i}')
                cur_ax = axes[i]
            ax = sb.heatmap(data[i], annot=True, fmt='.2f', cbar=False,
                            xticklabels=xticklabels, yticklabels=yticklabels, ax=cur_ax)
            ax.xaxis.tick_top()
            ax.tick_params(labelsize=tik_label_size)
            ax.set_yticklabels(ax.get_yticklabels(), rotation=0)

        cur_ax = axes[1]
        d = {q: row for q, row in zip(yticklabels, data[i])}
        df = pd.DataFrame(d, index=xticklabels)
        ax = sb.lineplot(data=df, ax=cur_ax)
        ax.tick_params(labelsize=tik_label_size)


        plt.setp(ax.get_legend().get_texts(), fontsize=tik_label_size)
        plt.subplots_adjust(hspace=0.08, top=0.96, bottom=0.04)

        plt.legend(bbox_to_anchor=(0.99, 0.45), loc='right', prop={'size': tik_label_size, }, labelspacing=0.1,
                   borderaxespad=0.,)

        # plt.savefig('gatv2.pdf')
        plt.show()
//  [python]
def get_fig(df_c):
    '''Returns price chart and rsi chart for input dataframe
    Args:
        df_c (pandas.Dataframe): datetime, close, sma180, rsi14, vwap, peak_valley, divergence, proba
    Returns:
        fig (pyplot.figure)
    '''
    df = df_c.copy()
    bar = st.progress(0.0)
    # new columns
    if 'proba' not in df.columns: df['proba'] = -1
    df['proba'] = df['proba'].fillna(0)
    df['period'] = df['Date'] if 'Date' in df.columns else df['datetime'].dt.time.astype('str').str[:5]
    df['close_div'] = np.where(df['proba']>0, df['close'], np.nan)
    df['close_div_profit'] = np.where((df['proba']>0)&(df['profit']>0), df['close'], np.nan)
    df['close_div_loss'] = np.where((df['proba']>0)&(df['profit']<0), df['close'], np.nan)
    df['pv'] = np.where(df['peak_valley']!=0, df['close'], np.nan)
    # setup plot
    fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(9,6))
    # top plot - price line plot
    plt.plot(df['period'], df['close'], ax=axs[0])
    plt.plot(df['period'], df['vwap'], color='r', ax=axs[0])
    bar.progress(0.33)
    plt.plot(df['period'], df['close_div'], 'o', color='gold', ax=axs[0])
    plt.plot(df['period'], df['close_div_loss'], 'o', color='r', ax=axs[0])
    plt.plot(df['period'], df['close_div_profit'], 'o', color='lime', ax=axs[0])
    # top plot - profit proba labels
    bar.progress(0.66)
    for i, point in df.iterrows():
        x = point['period']
        y = df['close'].min() #point['close'] * 0.99
        proba = point['proba']
        profit = point['profit']
        close = point['close']
        if proba:
            axs[0].text(x, y, f'{round(proba, 2)}\n({round(profit, 2)})', fontsize=9)
        if i == df.shape[0]-1:
            axs[0].text(x, close, str(round(close, 2)), fontsize=9) # prices at end of line
    # bottom plot - rsi14 line plot
    bar.progress(1.0)
    plt.plot(df['period'], df['rsi14'], color='k', ax=axs[1])
    axs[1].axhline(y=30, color='g')
    axs[1].axhline(y=70, color='darkorange')
    date_str = df['datetime'].astype('str').to_list()[0][:10]
    axs[1].text(df['period'].max(), df['rsi14'].min(), date_str, horizontalalignment='right')
    # set ticks
    for ax in axs:
        ax.label_outer()
        ax.set_xticks(ax.get_xticks()[::len(ax.get_xticks())//10+1])
        for tick in ax.get_xticklabels():
            tick.set_rotation(45)
    bar.empty()
    return fig
//  [python]
def get_fig(df_c):
    '''Returns price chart and rsi chart for input dataframe
    Args:
        df_c (pandas.Dataframe): datetime, close, sma180, rsi14, vwap, peak_valley, divergence, proba
    Returns:
        fig (pyplot.figure)
    '''
    df = df_c.copy()
    bar = st.progress(0.0)
    # new columns
    if 'proba' not in df.columns: df['proba'] = -1
    df['proba'] = df['proba'].fillna(0)
    df['period'] = df['Date'] if 'Date' in df.columns else df['datetime'].dt.time.astype('str').str[:5]
    df['close_div'] = np.where(df['proba']>0, df['close'], np.nan)
    df['close_div_profit'] = np.where((df['proba']>0)&(df['profit']>0), df['close'], np.nan)
    df['close_div_loss'] = np.where((df['proba']>0)&(df['profit']<0), df['close'], np.nan)
    df['pv'] = np.where(df['peak_valley']!=0, df['close'], np.nan)
    # setup plot
    fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(9,6))
    # top plot - price line plot
    sns.lineplot(data=df, x='period', y='close', ax=axs[0])
    sns.lineplot(data=df, x='period', y='vwap', color='r', ax=axs[0])
    bar.progress(0.33)
    sns.scatterplot(data=df, x='period', y='close_div', color='gold', ax=axs[0])
    sns.scatterplot(data=df, x='period', y='close_div_loss', color='r', ax=axs[0])
    sns.scatterplot(data=df, x='period', y='close_div_profit', color='lime', ax=axs[0])
    # top plot - profit proba labels
    bar.progress(0.66)
    for i, point in df.iterrows():
        x = point['period']
        y = df['close'].min() #point['close'] * 0.99
        proba = point['proba']
        profit = point['profit']
        close = point['close']
        if proba:
            axs[0].text(x, y, f'{round(proba, 2)}\n({round(profit, 2)})', fontsize=9)
        if i == df.shape[0]-1:
            axs[0].text(x, close, str(round(close, 2)), fontsize=9) # prices at end of line
    # bottom plot - rsi14 line plot
    bar.progress(1.0)
    sns.lineplot(data=df, x='period', y='rsi14', color='k', ax=axs[1])
    axs[1].axhline(y=30, color='g')
    axs[1].axhline(y=70, color='darkorange')
    date_str = df['datetime'].astype('str').to_list()[0][:10]
    axs[1].text(df['period'].max(), df['rsi14'].min(), date_str, horizontalalignment='right')
    # set ticks
    for ax in axs:
        ax.label_outer()
        ax.set_xticks(ax.get_xticks()[::len(ax.get_xticks())//10+1])
        for tick in ax.get_xticklabels():
            tick.set_rotation(45)
    bar.empty()
    return fig
//  [python]
sns.set_theme(style="dark")
flights = sns.load_dataset("flights")

# Plot each year's time series in its own facet
g = sns.relplot(
    data=flights,
    x="month", y="passengers", col="year", hue="year",
    kind="line", palette="crest", linewidth=4, zorder=5,
    col_wrap=3, height=2, aspect=1.5, legend=False,
)

# Iterate over each subplot to customize further
for year, ax in g.axes_dict.items():

    # Add the title as an annotation within the plot
    ax.text(.8, .85, year, transform=ax.transAxes, fontweight="bold")

    # Plot every year's time series in the background
    sns.tsplot(
        data=flights, time="month", value="passengers", unit="year",
        estimator=None, color=".7", linewidth=1, ax=ax,
    )
//  [python]
sns.set_theme(style="dark")
flights = sns.load_dataset("flights")

# Plot each year's time series in its own facet
g = sns.relplot(
    data=flights,
    x="month", y="passengers", col="year", hue="year",
    kind="line", palette="crest", linewidth=4, zorder=5,
    col_wrap=3, height=2, aspect=1.5, legend=False,
)

# Iterate over each subplot to customize further
for year, ax in g.axes_dict.items():

    # Add the title as an annotation within the plot
    ax.text(.8, .85, year, transform=ax.transAxes, fontweight="bold")

    # Plot every year's time series in the background
    sns.lineplot(
        data=flights, x="month", y="passengers", units="year",
        estimator=None, color=".7", linewidth=1, ax=ax,
    )
//  [python]
def _calculate(self, df: pd.DataFrame, ax: list[plt.Axes]) -> pd.Series:
        df = df.merge(
            cs_fetch(
                path='getPuellMultiple',
                data_selector='puellMultiplList',
                col_name='Puell',
            ),
            on='Date',
            how='left',
        )
        df['Puell'] = df['Puell'].ffill()
        df['PuellLog'] = np.log(df['Puell'])

        high_rows = df.loc[df['PriceHigh'] == 1]
        high_x = high_rows.index.values.reshape(-1, 1)
        high_y = high_rows['PuellLog'].values.reshape(-1, 1)

        # low_rows = df.loc[df['PriceLow'] == 1][1:]
        # low_x = low_rows.index.values.reshape(-1, 1)
        # low_y = low_rows['PuellLog'].values.reshape(-1, 1)

        x = df.index.values.reshape(-1, 1)

        lin_model = LinearRegression()
        lin_model.fit(high_x, high_y)
        df['PuellLogHighModel'] = lin_model.predict(x)

        # lin_model.fit(low_x, low_y)
        # df['PuellLogLowModel'] = lin_model.predict(x)
        df['PuellLogLowModel'] = -1

        df['PuellIndex'] = (df['PuellLog'] - df['PuellLogLowModel']) / (
            df['PuellLogHighModel'] - df['PuellLogLowModel']
        )

        df['PuellIndexNoNa'] = df['PuellIndex'].fillna(0)
        ax[0].set_title(self.description)
        plt.plot(df['Date'], df['PuellIndexNoNa'], ax=ax[0])
        add_common_markers(df, ax[0])

        plt.plot(df['Date'], df['PuellLog'], ax=ax[1])
        plt.plot(df['Date'], df['PuellLogHighModel'], ax=ax[1])
        plt.plot(df['Date'], df['PuellLogLowModel'], ax=ax[1])
        add_common_markers(df, ax[1], price_line=False)

        return df['PuellIndex']
//  [python]
def _calculate(self, df: pd.DataFrame, ax: list[plt.Axes]) -> pd.Series:
        df = df.merge(
            cs_fetch(
                path='getPuellMultiple',
                data_selector='puellMultiplList',
                col_name='Puell',
            ),
            on='Date',
            how='left',
        )
        df['Puell'] = df['Puell'].ffill()
        df['PuellLog'] = np.log(df['Puell'])

        high_rows = df.loc[df['PriceHigh'] == 1]
        high_x = high_rows.index.values.reshape(-1, 1)
        high_y = high_rows['PuellLog'].values.reshape(-1, 1)

        # low_rows = df.loc[df['PriceLow'] == 1][1:]
        # low_x = low_rows.index.values.reshape(-1, 1)
        # low_y = low_rows['PuellLog'].values.reshape(-1, 1)

        x = df.index.values.reshape(-1, 1)

        lin_model = LinearRegression()
        lin_model.fit(high_x, high_y)
        df['PuellLogHighModel'] = lin_model.predict(x)

        # lin_model.fit(low_x, low_y)
        # df['PuellLogLowModel'] = lin_model.predict(x)
        df['PuellLogLowModel'] = -1

        df['PuellIndex'] = (df['PuellLog'] - df['PuellLogLowModel']) / (
            df['PuellLogHighModel'] - df['PuellLogLowModel']
        )

        df['PuellIndexNoNa'] = df['PuellIndex'].fillna(0)
        ax[0].set_title(self.description)
        sns.lineplot(data=df, x='Date', y='PuellIndexNoNa', ax=ax[0])
        add_common_markers(df, ax[0])

        sns.lineplot(data=df, x='Date', y='PuellLog', ax=ax[1])
        sns.lineplot(data=df, x='Date', y='PuellLogHighModel', ax=ax[1])
        sns.lineplot(data=df, x='Date', y='PuellLogLowModel', ax=ax[1])
        add_common_markers(df, ax[1], price_line=False)

        return df['PuellIndex']