def make_plot(df, column_name, output_dir, filename_suffix):

        # get datetime x-values
        xs = get_datetime_xs(df)

        # create a figure
        fig, ax = plt.subplots(figsize=(15, 4.5))
        plt.plot(xs, df[column_name])

        # label axes
        plt.xlabel('Time', fontsize=12)
        plt.ylabel(column_name, fontsize=12)

        # calculate Kendall taus and annotate plot
        tau, p = get_kendell_tau(df[column_name].dropna())
        textstr = f'Kendall $\\tau,~p = {tau:.3f}$, ${p:.4f}$'
        ax.text(0.1,
                0.95,
                textstr,
                transform=ax.transAxes,
                fontsize=14,
                verticalalignment='top')

        # save the plot
        output_filename = f'{column_name}' + filename_suffix + '.png'
        collection_prefix = column_name.split('_')[0]
        print(
            f'Plotting {collection_prefix} correlation moving window analysis...'
        )
        plt.savefig(os.path.join(output_dir, output_filename), dpi=DPI)
        plt.close(fig)
    def make_plot(df, column, output_dir):
        """
        Plot STL decomposition results.

        Parameters
        ----------
        df : DataFrame
            The input time-series.
        column : str
            Column name to run STL on.
        output_dir : str
            Directory to save the plot in.
        """

        # run fit
        res = stl_decomposition(df[column], period)

        # concert x values to datetime objects
        xs = get_datetime_xs(df)

        # formatting
        default_figsize = plt.rcParams['figure.figsize']
        default_fontsize = plt.rcParams['font.size']
        plt.rc('figure', figsize=(20, 8))
        plt.rc('font', size=15)

        fig = res.plot()
        ax_list = fig.axes
        for ax in ax_list[:-1]:
            ax.tick_params(labelbottom=False)

        # set xlabel with datetime object
        #ax_list[-1].set_xticklabels(xs, rotation=0, va="center")

        # save plot
        filename = os.path.join(output_dir, column + '_STL_decomposition.png')
        plt.savefig(filename, dpi=DPI)
        plt.close(fig)

        # undo rc changes
        plt.rc('figure', figsize=default_figsize)
        plt.rc('font', size=default_fontsize)
def plot_ews_resiliance(series_name, EWSmetrics_df, Kendalltau_df, dates,
                        output_dir):
    """
    Make early warning signals resiliance plots using the output
    from the ewstools package.

    Parameters
    ----------
    series_name : str
        String containing data collection and time series variable.
    EWSmetrics_df : DataFrame
        DataFrame from ewstools containing ews time series.
    Kendalltau_df : DataFrame
        DataFrame from ewstools containing Kendall tau values for EWSmetrics_df time series
    output_dir: str
        Output dir to save plot in.
    """
    def zoom_out(ys):
        ymin = ys.mean() - 2 * ((ys.mean() - ys).abs().max())
        ymax = ys.mean() + 2 * ((ys.mean() - ys).abs().max())
        return [ymin, ymax]

    def annotate(text, xy=(6, 70), size=10):
        if 'Kendall' in text:
            xy = (xy[0], 60)
        plt.gca().annotate(text,
                           xy=xy,
                           xycoords='axes points',
                           size=size,
                           ha='left',
                           va='top')

    dates = get_datetime_xs(pd.DataFrame(dates).dropna())
    fig, _ = plt.subplots(figsize=(4, 8), sharex='col')

    ax1 = plt.subplot(611)
    ys = EWSmetrics_df['State variable']

    plt.plot(dates[-len(ys):], ys, color='black')
    plt.plot(dates[-len(ys):],
             EWSmetrics_df['Smoothing'],
             color='red',
             linestyle='dashed')
    plt.ylim(zoom_out(ys))
    annotate(series_name)

    ax2 = plt.subplot(612, sharex=ax1)
    ys = EWSmetrics_df['Residuals']
    plt.plot(dates[-len(ys):], ys, color='black', label='Time Series')
    plt.ylim(zoom_out(ys))
    annotate('Residuals')

    ax3 = plt.subplot(613, sharex=ax1)
    ys = EWSmetrics_df['Lag-1 AC']
    plt.plot(dates[-len(ys):], ys, color='black')
    plt.ylim(zoom_out(ys))
    annotate('Lag-1 AC')
    tau = Kendalltau_df['Lag-1 AC'].iloc[0]
    annotate(f'Kendall $\\tau = {tau:.2f}$', size=8)
    """ys = EWSmetrics_df['Lag-2 AC']
    plt.plot(ys, color='navy')
    plt.ylim(zoom_out(ys))
    annotate('Lag-1 AC', xy=(8, 65))
    tau = Kendalltau_df['Lag-1 AC'].iloc[0]
    annotate(f'Kendall $\\tau = {tau:.2f}$', xy=(8, 55), size=8)"""

    ax4 = plt.subplot(614, sharex=ax1)
    ys = EWSmetrics_df['Standard deviation']
    plt.plot(dates[-len(ys):], ys, color='black')
    plt.ylim(zoom_out(ys))
    annotate('Standard deviation')
    tau = Kendalltau_df['Standard deviation'].iloc[0]
    annotate(f'Kendall $\\tau = {tau:.2f}$', size=8)

    ax5 = plt.subplot(615, sharex=ax1)
    ys = EWSmetrics_df['Skewness']
    plt.plot(dates[-len(ys):], ys, color='black')
    plt.ylim(zoom_out(ys))
    annotate('Skewness')
    tau = Kendalltau_df['Skewness'].iloc[0]
    annotate(f'Kendall $\\tau = {tau:.2f}$', size=8)

    ax6 = plt.subplot(616, sharex=ax1)
    ys = EWSmetrics_df['Kurtosis']
    plt.plot(dates[-len(ys):], ys, color='black')
    plt.ylim(zoom_out(ys))
    annotate('Kurtosis')
    tau = Kendalltau_df['Kurtosis'].iloc[0]
    annotate(f'Kendall $\\tau = {tau:.2f}$', size=8)

    plt.xlabel('Time')

    # remove vertical space between plots
    plt.subplots_adjust(hspace=0.0)

    # tick formatting
    for ax in [ax1, ax2, ax3, ax4, ax5]:
        ax.tick_params(axis='both',
                       which='both',
                       bottom=True,
                       top=False,
                       labelbottom=False,
                       left=True,
                       labelleft=True,
                       direction='out',
                       labelsize=8)

    # show x labels for bottom plot
    ax6.tick_params(axis='both',
                    which='both',
                    bottom=True,
                    top=False,
                    labelbottom=True,
                    left=True,
                    labelleft=True,
                    direction='out',
                    labelsize=8)

    # save the plot
    output_filename = series_name.replace(' ', '-') + '-ews.png'
    print(f'Plotting {series_name} ews plots...')
    plt.savefig(os.path.join(output_dir, output_filename),
                dpi=DPI,
                bbox_inches='tight')
    plt.close(fig)
    def make_plot(df, column, output_dir, smoothing_option):
        """
        Parameters
        ----------
        df : DataFrame
            The time-series results for variance and AR1.
        column : str
            Column name an offset50 variance column in df.
        output_dir : str
            Directory to save the plot in.
        smoothing_option: str
            Label for smoothing variable to be used
        """

        # get short string prefix on column name
        collection_prefix = column.split(
            '_')[0] if 'offset50' in column else 'precipitation'

        # hand mismatched NaNs
        ar1_df = df.dropna(subset=[column.replace('var', 'ar1')])
        var_df = df.dropna(subset=[column])

        # extract x values and convert to datetime objects
        ar1_xs = get_datetime_xs(ar1_df)
        var_xs = get_datetime_xs(var_df)

        # extract individual time series
        variance = var_df[column]
        ar1 = ar1_df[column.replace('var', 'ar1')]
        ar1_se = ar1_df[column.replace('var', 'ar1_se')]

        if any([smoothing_option in c for c in df.columns]):
            variance_smooth = var_df[column.replace(
                'offset50_mean', 'offset50_' + smoothing_option + '_mean')]
            ar1_smooth = ar1_df[column.replace('var', 'ar1').replace(
                'offset50_mean', 'offset50_' + smoothing_option + '_mean')]
            ar1_se_smooth = ar1_df[column.replace('var', 'ar1_se').replace(
                'offset50_mean', 'offset50_' + smoothing_option + '_mean')]

        # create a figure
        fig, ax = plt.subplots(figsize=(15, 5))
        plt.xlabel('Time', fontsize=12)

        # set up veg y axis
        color = 'tab:blue'
        ax.set_ylabel(f'{collection_prefix} AR1', color=color, fontsize=12)
        ax.tick_params(axis='y', labelcolor=color)

        # plot unsmoothed vegetation ar1 and std
        ax.plot(ar1_xs, ar1, label='AR1', linewidth=2, color='tab:blue')
        ax.fill_between(ar1_xs,
                        ar1 - ar1_se,
                        ar1 + ar1_se,
                        facecolor='blue',
                        alpha=0.1,
                        label='AR1 SE')

        if any([smoothing_option in c for c in df.columns]):
            # plot smoothed vegetation ar1 and std
            ax.plot(ar1_xs,
                    ar1_smooth,
                    label='AR1 Smoothed',
                    linewidth=2,
                    color='tab:blue',
                    linestyle='dotted')
            ax.fill_between(ar1_xs,
                            ar1_smooth - ar1_se_smooth,
                            ar1_smooth + ar1_se_smooth,
                            facecolor='none',
                            alpha=0.15,
                            label='AR1 SE Smoothed',
                            hatch='X',
                            edgecolor='tab:blue')

        # set y lim
        try:  # in case there are no ar1 values, the array will be empty
            ax.set_ylim([
                min(ar1 - ar1_se) - 0.8 * max(ar1 + ar1_se),
                1.8 * max(ar1 + ar1_se)
            ])
        except:
            return

        # plot legend
        plt.legend(loc='upper left')

        # duplicate x-axis for variance
        ax2 = ax.twinx()
        color = 'tab:red'
        ax2.set_ylabel(f'{collection_prefix} Variance',
                       color=color,
                       fontsize=12)
        ax2.tick_params(axis='y', labelcolor=color)

        # plot variance
        ax2.plot(var_xs,
                 variance,
                 linewidth=2,
                 color=color,
                 alpha=0.75,
                 label='Variance')
        if any([smoothing_option in c for c in df.columns]):
            ax2.plot(var_xs,
                     variance_smooth,
                     linewidth=2,
                     color=color,
                     alpha=0.75,
                     linestyle='dotted',
                     label='Variance Smoothed')

        # set y lim
        ax2.set_ylim([0, 2 * max(variance)])

        # add legend
        plt.legend(loc='lower left')

        # add Kendall tau

        tau, p = get_kendell_tau(ar1)
        tau_var, p_var = get_kendell_tau(variance)

        if any([smoothing_option in c for c in df.columns]):
            tau_smooth, p_smooth = get_kendell_tau(ar1_smooth)
            tau_var_smooth, p_var_smooth = get_kendell_tau(variance_smooth)

        # add to plot
        textstr = ''
        if any([smoothing_option in c for c in df.columns]):
            textstr += f'AR1 Kendall $\\tau,~p$-$\\mathrm{{value}}={tau_smooth:.2f}$, ${p_smooth:.2f}$'
        textstr += f' (${tau:.2f}$, ${p:.2f}$ unsmoothed)'
        ax2.text(0.43,
                 0.95,
                 textstr,
                 transform=ax2.transAxes,
                 fontsize=14,
                 verticalalignment='top')

        textstr = ''
        if any([smoothing_option in c for c in df.columns]):
            textstr += f'Variance Kendall $\\tau,~p$-$\\mathrm{{value}}={tau_var_smooth:.2f}$, ${p_var_smooth:.2f}$'
        textstr += f' (${tau_var:.2f}$, ${p_var:.2f}$ unsmoothed)'
        ax2.text(0.43,
                 0.85,
                 textstr,
                 transform=ax2.transAxes,
                 fontsize=14,
                 verticalalignment='top')

        # layout
        fig.tight_layout()

        # save the plot
        output_filename = collection_prefix + '-AR1-var-' + smoothing_option + '.png'
        print(f'Plotting {collection_prefix} moving window time series...')
        plt.savefig(os.path.join(output_dir, output_filename), dpi=DPI)
        plt.close(fig)
    def make_plot(df,
                  veg_prefix,
                  output_dir,
                  veg_prefix_b=None,
                  smoothing_option='smooth'):

        # handle the case where vegetation and precipitation have mismatched NaNs
        veg_df = df.dropna(subset=[veg_prefix + '_offset50_mean'])

        # get vegetation x values to datetime objects
        veg_xs = get_datetime_xs(veg_df)

        # get vegetation y values
        veg_means = veg_df[veg_prefix + '_offset50_mean']
        veg_std = veg_df[veg_prefix + '_offset50_std']

        # create a figure
        fig, ax = plt.subplots(figsize=(15, 4.5))
        plt.xlabel('Time', fontsize=14)

        # set up veg y axis
        color = 'tab:green'
        ax.set_ylabel(f'{veg_prefix} Offset50', color=color, fontsize=14)
        ax.tick_params(axis='y', labelcolor=color)
        ax.set_ylim([
            veg_means.min() - 1 * veg_std.max(),
            veg_means.max() + 3 * veg_std.max()
        ])

        # plot unsmoothed vegetation means
        ax.plot(veg_xs,
                veg_means,
                label='Unsmoothed',
                linewidth=1,
                color='dimgray',
                linestyle='dotted')

        # add smoothed time series if availible
        if any([
                smoothing_option in c and veg_prefix in c
                for c in veg_df.columns
        ]):

            # get smoothed mean, std
            veg_means_smooth = veg_df[veg_prefix + '_offset50_' +
                                      smoothing_option + '_mean']
            veg_stds_smooth = veg_df[veg_prefix + '_offset50_' +
                                     smoothing_option + '_std']

            # plot smoothed vegetation means and std
            ax.plot(veg_xs,
                    veg_means_smooth,
                    marker='o',
                    markersize=7,
                    markeredgecolor=(0.9172, 0.9627, 0.9172),
                    markeredgewidth=2,
                    label='Smoothed',
                    linewidth=2,
                    color='green')

            ax.fill_between(veg_xs,
                            veg_means_smooth - veg_stds_smooth,
                            veg_means_smooth + veg_stds_smooth,
                            facecolor='green',
                            alpha=0.1,
                            label='Std Dev')

        # plot vegetation legend
        plt.legend(loc='upper left')

        # plot precipitation if availible
        if 'total_precipitation' in df.columns:
            # handle the case where vegetation and precipitation have mismatched NaNs
            precip_df = df.dropna(subset=['total_precipitation'])
            precip_ys = precip_df.total_precipitation

            # get precipitation x values to datetime objects
            precip_xs = get_datetime_xs(precip_df)

            # duplicate axis for preciptation
            ax2 = ax.twinx()
            color = 'tab:blue'
            ax2.set_ylabel(f'Precipitation [mm]', color=color, fontsize=14)
            ax2.tick_params(axis='y', labelcolor=color)
            ax2.set_ylim([
                min(precip_ys) - 1 * np.array(precip_ys).std(),
                max(precip_ys) + 2 * np.array(precip_ys).std()
            ])

            # plot precipitation
            ax2.plot(precip_xs,
                     precip_ys,
                     linewidth=2,
                     color=color,
                     alpha=0.75)

            # add veg-precip correlation
            max_corr_smooth, max_corr = get_max_lagged_cor(
                os.path.dirname(output_dir), veg_prefix)
            textstr = f'$r_{{t-{max_corr_smooth[1]}}}={max_corr_smooth[0]:.2f}$ '
            textstr += f'($r_{{t-{max_corr[1]}}}={max_corr[0]:.2f}$ unsmoothed)'

            # old correlation just calculates the 0-lag correlation
            #raw_corr = veg_means.corr(precip_ys)
            #smoothed_corr = veg_means_smooth.corr(precip_ys)
            #textstr = f'$r={smoothed_corr:.2f}$ (${raw_corr:.2f}$ unsmoothed)'
            ax2.text(0.13,
                     0.95,
                     textstr,
                     transform=ax2.transAxes,
                     fontsize=14,
                     verticalalignment='top')

        # plot second vegetation time series if availible
        if veg_prefix_b:

            # handle the case where vegetation and precipitation have mismatched NaNs
            veg_df_b = df.dropna(subset=[veg_prefix_b + '_offset50_mean'])

            # get vegetation x values to datetime objects
            veg_xs_b = get_datetime_xs(veg_df_b)

            # get vegetation y values
            veg_means_b = veg_df_b[veg_prefix_b + '_offset50_mean']
            #veg_std_b = veg_df[veg_prefix_b+'_offset50_std']
            veg_means_smooth_b = veg_df_b[veg_prefix_b +
                                          '_offset50_smooth_mean']
            veg_stds_smooth_b = veg_df_b[veg_prefix_b + '_offset50_smooth_std']

            # plot secondary time series
            ax3 = ax.twinx()
            ax3.spines["left"].set_position(("axes", -0.08))
            ax3.spines["left"].set_visible(True)
            color = 'tab:purple'
            ax3.set_ylabel(veg_prefix_b + ' Offset50',
                           color=color,
                           fontsize=14)
            ax3.tick_params(axis='y', labelcolor=color)
            ax3.yaxis.tick_left()
            ax3.yaxis.set_label_position('left')
            ax3.set_ylim([
                veg_means.min() - 1 * veg_std.max(),
                veg_means.max() + 3 * veg_std.max()
            ])

            # plot unsmoothed vegetation means
            ax.plot(veg_xs_b,
                    veg_means_b,
                    label='Unsmoothed',
                    linewidth=1,
                    color='indigo',
                    linestyle='dashed',
                    alpha=0.2)

            # plot smoothed vegetation means and std
            ax3.plot(veg_xs_b,
                     veg_means_smooth_b,
                     marker='o',
                     markersize=7,
                     markeredgecolor=(0.8172, 0.7627, 0.9172),
                     markeredgewidth=2,
                     label='Smoothed',
                     linewidth=2,
                     color=color)

            ax3.fill_between(veg_xs_b,
                             veg_means_smooth_b - veg_stds_smooth_b,
                             veg_means_smooth_b + veg_stds_smooth_b,
                             facecolor='tab:purple',
                             alpha=0.1,
                             label='Std Dev')

            # add veg-veg correlation
            vegveg_corr = veg_means.corr(veg_means_b)
            vegveg_corr_smooth = veg_means_smooth.corr(veg_means_smooth_b)
            textstr = f'$r_{{vv}}={vegveg_corr_smooth:.2f}$ (${vegveg_corr:.2f}$ unsmoothed)'
            ax2.text(0.55,
                     0.85,
                     textstr,
                     transform=ax2.transAxes,
                     fontsize=14,
                     verticalalignment='top')

            # update prefix for filename use
            veg_prefix = veg_prefix + '+' + veg_prefix_b

        # add autoregression info
        veg_means.index = veg_df.date
        unsmoothed_ar1, unsmoothed_ar1_se = get_AR1_parameter_estimate(
            veg_means)
        if any(['smooth' in c and veg_prefix in c for c in veg_df.columns]):
            veg_means_smooth.index = veg_df.date
            smoothed_ar1, smoothed_ar1_se = get_AR1_parameter_estimate(
                veg_means_smooth)
        else:
            smoothed_ar1, smoothed_ar1_se = np.NaN, np.NaN
        ar1_dict = {}
        ar1_dict['AR1'] = {
            'unsmoothed': {
                'param': unsmoothed_ar1,
                'se': unsmoothed_ar1_se
            },
            'smoothed': {
                'param': smoothed_ar1,
                'se': smoothed_ar1_se
            }
        }
        write_to_json(os.path.join(output_dir, veg_prefix + '_stats.json'),
                      ar1_dict)
        textstr = f'AR$(1)={smoothed_ar1:.2f} \pm {smoothed_ar1_se:.2f}$ (${unsmoothed_ar1:.2f} \pm {unsmoothed_ar1_se:.2f}$ unsmoothed)'
        ax.text(0.55,
                0.95,
                textstr,
                transform=ax.transAxes,
                fontsize=14,
                verticalalignment='top')

        # add Kendall tau
        tau, p = get_kendell_tau(veg_means)
        if any(['smooth' in c and veg_prefix in c for c in veg_df.columns]):
            tau_smooth, p_smooth = get_kendell_tau(veg_means_smooth)
        else:
            tau_smooth, p_smooth = np.NaN, np.NaN

        kendall_tau_dict = {}
        kendall_tau_dict['Kendall_tau'] = {
            'unsmoothed': {
                'tau': tau,
                'p': p
            },
            'smoothed': {
                'tau': tau_smooth,
                'p': p_smooth
            }
        }
        write_to_json(os.path.join(output_dir, veg_prefix + '_stats.json'),
                      kendall_tau_dict)
        textstr = f'$\\tau,~p$-$\\mathrm{{value}}={tau_smooth:.2f}$, ${p:.2f}$ (${tau:.2f}$, ${p_smooth:.2f}$ unsmoothed)'
        ax.text(0.13,
                0.85,
                textstr,
                transform=ax.transAxes,
                fontsize=14,
                verticalalignment='top')

        # layout
        sns.set_style('white')
        fig.tight_layout()

        filename_suffix = '_' + smoothing_option

        # save the plot
        output_filename = veg_prefix + '-time-series' + filename_suffix + '.png'
        plt.savefig(os.path.join(output_dir, output_filename), dpi=DPI)
        plt.close(fig)
    def make_plot(df, veg_prefix, col_name, utput_dir):

        veg_df = df.dropna(subset=[col_name])

        # get vegetation x values to datetime objects
        veg_xs = get_datetime_xs(veg_df)

        # get vegetation y values
        veg_means = veg_df[col_name]
        #veg_means = veg_df[veg_prefix + '_ndvi_veg_mean']
        if any([col_name.replace('mean', 'std') == c for c in df.columns]):
            veg_std = veg_df[col_name.replace('mean', 'std')]

        # create a figure
        fig, ax = plt.subplots(figsize=(15, 4.5))
        plt.xlabel('Time', fontsize=14)

        # set up veg y axis
        color = 'tab:green'
        ax.set_ylabel(f'{veg_prefix} NDVI', color=color, fontsize=14)
        ax.tick_params(axis='y', labelcolor=color)

        if any([col_name.replace('mean', 'std') == c for c in df.columns]):
            ax.set_ylim([
                veg_means.min() - 1 * veg_std.max(),
                veg_means.max() + 3 * veg_std.max()
            ])

        # plot ndvi
        #ax.plot(veg_xs, ndvi_means, label='Unsmoothed', linewidth=1, color='dimgray', linestyle='dotted')

        ax.plot(veg_xs,
                veg_means,
                marker='o',
                markersize=7,
                markeredgecolor=(0.9172, 0.9627, 0.9172),
                markeredgewidth=2,
                label='Smoothed',
                linewidth=2,
                color='green')

        if any([col_name.replace('mean', 'std') == c for c in df.columns]):
            ax.fill_between(veg_xs,
                            veg_means - veg_std,
                            veg_means + veg_std,
                            facecolor='green',
                            alpha=0.1,
                            label='Std Dev')

        # plot precipitation if availible
        if 'total_precipitation' in df.columns:
            # handle the case where vegetation and precipitation have mismatched NaNs
            precip_df = df.dropna(subset=['total_precipitation'])
            precip_ys = precip_df.total_precipitation

            # get precipitation x values to datetime objects
            precip_xs = get_datetime_xs(precip_df)

            # duplicate axis for preciptation
            ax2 = ax.twinx()
            color = 'tab:blue'
            ax2.set_ylabel(f'Precipitation [mm]', color=color, fontsize=14)
            ax2.tick_params(axis='y', labelcolor=color)
            ax2.set_ylim([
                min(precip_ys) - 1 * np.array(precip_ys).std(),
                max(precip_ys) + 2 * np.array(precip_ys).std()
            ])

            # plot precipitation
            ax2.plot(precip_xs,
                     precip_ys,
                     linewidth=2,
                     color=color,
                     alpha=0.75)

            # add correlation information
            correlations = get_corrs_by_lag(df[col_name],
                                            df['total_precipitation'])
            max_corr = np.max(np.array(correlations))
            max_corr_lag = np.array(np.argmax(correlations))
            textstr = f'$r_{{t-{max_corr_lag}}}={max_corr:.2f}$ '
            ax2.text(0.13,
                     0.95,
                     textstr,
                     transform=ax2.transAxes,
                     fontsize=14,
                     verticalalignment='top')

        # layout
        sns.set_style('white')
        fig.tight_layout()

        # save the plot
        output_filename = veg_prefix + '-ndvi-time-series.png'
        plt.savefig(os.path.join(output_dir, output_filename), dpi=DPI)
        plt.close(fig)
Esempio n. 7
0
    def make_plot(df, veg_prefix, output_dir, veg_prefix_b=None, smoothing_option='smooth'):

        # handle the case where vegetation and precipitation have mismatched NaNs
        veg_df = df.dropna(subset=[veg_prefix+'_offset50_mean'])

        # get vegetation x values to datetime objects
        veg_xs = get_datetime_xs(veg_df)

        # get vegetation y values
        veg_means = veg_df[veg_prefix + '_offset50_mean']
        veg_std = veg_df[veg_prefix + '_offset50_std']

        # create a figure
        fig, ax = plt.subplots(figsize=(15, 4.5))
        plt.xlabel('Time', fontsize=14)

        # set up veg y axis
        color = 'tab:green'
        ax.set_ylabel(f'{veg_prefix} Offset50', color=color, fontsize=14)
        ax.tick_params(axis='y', labelcolor=color)
        ax.set_ylim([veg_means.min() - 1*veg_std.max(), veg_means.max() + 3*veg_std.max()])


        # plot smoothed vegetation means and std
        ax.plot(veg_xs, veg_means, marker='o', markersize=7,
                markeredgecolor=(0.9172, 0.9627, 0.9172), markeredgewidth=2,
                label='Offset50', linewidth=2, color='green')

        ax.fill_between(veg_xs, veg_means - veg_std, veg_means + veg_std,
                        facecolor='green', alpha=0.1, label='Std Dev')

        #    # get smoothed mean, std
        veg_means_smooth = veg_df[veg_prefix+'_offset50_'+smoothing_option+'_mean']

        # plot vegetation legend
        plt.legend(loc='upper left')

        # plot precipitation if availible
        if 'total_precipitation' in df.columns:
            # handle the case where vegetation and precipitation have mismatched NaNs
            precip_df = df.dropna(subset=['total_precipitation'])
            precip_ys = precip_df.total_precipitation

            # get precipitation x values to datetime objects
            precip_xs = get_datetime_xs(precip_df)

            # duplicate axis for preciptation
            ax2 = ax.twinx()
            color = 'tab:blue'
            ax2.set_ylabel(f'Precipitation [mm]', color=color, fontsize=14)
            ax2.tick_params(axis='y', labelcolor=color)
            ax2.set_ylim([min(precip_ys)-1*np.array(precip_ys).std(), max(precip_ys)+2*np.array(precip_ys).std()])

            # plot precipitation
            ax2.plot(precip_xs, precip_ys, linewidth=2, color=color, alpha=0.75)

            # add veg-precip correlation
            max_corr_smooth, max_corr = get_max_lagged_cor(os.path.dirname(output_dir), veg_prefix)
            textstr = f'$r_{{t-{max_corr[1]}}}={max_corr[0]:.2f}$'

            # old correlation just calculates the 0-lag correlation
            #raw_corr = veg_means.corr(precip_ys)
            #smoothed_corr = veg_means_smooth.corr(precip_ys)
            #textstr = f'$r={smoothed_corr:.2f}$ (${raw_corr:.2f}$ unsmoothed)'
            ax2.text(0.13, 0.95, textstr, transform=ax2.transAxes, fontsize=14, verticalalignment='top')

        # plot second vegetation time series if availible
        if veg_prefix_b:

            # handle the case where vegetation and precipitation have mismatched NaNs
            veg_df_b = df.dropna(subset=[veg_prefix_b+'_offset50_mean'])

            # get vegetation x values to datetime objects
            veg_xs_b = get_datetime_xs(veg_df_b)

            # get vegetation y values
            veg_means_b = veg_df_b[veg_prefix_b+'_offset50_mean']
            veg_means_smooth_b = veg_df_b[veg_prefix_b+'_offset50_smooth_mean']
            veg_stds_smooth_b = veg_df_b[veg_prefix_b+'_offset50_smooth_std']

            # plot secondary time series
            ax3 = ax.twinx()
            ax3.spines["left"].set_position(("axes", -0.08))
            ax3.spines["left"].set_visible(True)
            color = 'tab:purple'
            ax3.set_ylabel(veg_prefix_b + ' Offset50', color=color, fontsize=14)
            ax3.tick_params(axis='y', labelcolor=color)
            ax3.yaxis.tick_left()
            ax3.yaxis.set_label_position('left')
            ax3.set_ylim([veg_means.min() - 1*veg_std.max(), veg_means.max() + 3*veg_std.max()])

            # plot unsmoothed vegetation means
            ax.plot(veg_xs_b, veg_means_b, label='Unsmoothed', linewidth=1, color='indigo', linestyle='dashed', alpha=0.2)

            # plot smoothed vegetation means and std
            ax3.plot(veg_xs_b, veg_means_smooth_b, marker='o', markersize=7, 
                    markeredgecolor=(0.8172, 0.7627, 0.9172), markeredgewidth=2, 
                    label='Smoothed', linewidth=2, color=color)

            ax3.fill_between(veg_xs_b, veg_means_smooth_b - veg_stds_smooth_b, veg_means_smooth_b + veg_stds_smooth_b, 
                            facecolor='tab:purple', alpha=0.1, label='Std Dev')

            # add veg-veg correlation
            vegveg_corr = veg_means.corr(veg_means_b)
            vegveg_corr_smooth = veg_means_smooth.corr(veg_means_smooth_b)
            textstr = f'$r_{{vv}} = {vegveg_corr:.2f}$'
            ax2.text(0.55, 0.85, textstr, transform=ax2.transAxes, fontsize=14, verticalalignment='top')

            # update prefix for filename use
            veg_prefix = veg_prefix + '+' + veg_prefix_b

        # add autoregression info
        veg_means.index = veg_df.date

        # layout
        sns.set_style('white')
        fig.tight_layout()

        filename_suffix = '_' + smoothing_option

        # save the plot
        output_filename = veg_prefix + '-time-series' + filename_suffix + '.png'
        plt.savefig(os.path.join(output_dir, output_filename), dpi=DPI)