Source code for alchemlyb.visualisation.convergence

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.font_manager import FontProperties as FP
from matplotlib.axes import Axes
import pandas as pd

from ..postprocessors.units import get_unit_converter


[docs] def plot_convergence( dataframe: pd.DataFrame, units: None | str = None, final_error: None | float = None, ax: None | Axes = None, ) -> Axes: """Plot the forward and backward convergence. The input could be the result from :func:`~alchemlyb.convergence.forward_backward_convergence` or :func:`~alchemlyb.convergence.fwdrev_cumavg_Rc`. The input should be a :class:`pandas.DataFrame` which has column `Forward`, `Backward` and :attr:`pandas.DataFrame.attrs` should compile with :ref:`note-on-units`. The errorbar will be plotted if column `Forward_Error` and `Backward_Error` is present. `Forward`: A column of free energy estimate from the first X% of data, where optional `Forward_Error` column is the corresponding error. `Backward`: A column of free energy estimate from the last X% of data., where optional `Backward_Error` column is the corresponding error. `final_error` is the error of the final value and is shown as the error band around the final value. It can be provided in case an estimate is available that is more appropriate than the default, which is the error of the last value in `Backward`. Parameters ---------- dataframe : Dataframe Output Dataframe has column `Forward`, `Backward` or optionally `Forward_Error`, `Backward_Error` see :ref:`plot_convergence <plot_convergence>`. units : str The unit of the estimate. The default is `None`, which is to use the unit in the input. Setting this will change the output unit. final_error : float The error of the final value in ``units``. If not given, takes the last error in `backward_error`. ax : matplotlib.axes.Axes Matplotlib axes object where the plot will be drawn on. If ``ax=None``, a new axes will be generated. Returns ------- matplotlib.axes.Axes An axes with the forward and backward convergence drawn. Note ---- The code is taken and modified from `Alchemical Analysis <https://github.com/MobleyLab/alchemical-analysis>`_. .. versionchanged:: 1.0.0 Keyword arg final_error for plotting a horizontal error bar. The array input has been deprecated. The units default to `None` which uses the units in the input. .. versionchanged:: 0.6.0 data now takes in dataframe .. versionadded:: 0.4.0 """ if units is not None: dataframe = get_unit_converter(units)(dataframe) forward = dataframe["Forward"].to_numpy(dtype=float) if "Forward_Error" in dataframe: forward_error = dataframe["Forward_Error"].to_numpy(dtype=float) else: forward_error = np.zeros(len(forward)) backward = dataframe["Backward"].to_numpy(dtype=float) if "Backward_Error" in dataframe: backward_error = dataframe["Backward_Error"].to_numpy(dtype=float) else: backward_error = np.zeros(len(backward)) if ax is None: # pragma: no cover fig, ax = plt.subplots(figsize=(8, 6)) plt.setp(ax.spines["bottom"], color="#D2B9D3", lw=3, zorder=-2) plt.setp(ax.spines["left"], color="#D2B9D3", lw=3, zorder=-2) for dire in ["top", "right"]: ax.spines[dire].set_color("none") ax.xaxis.set_ticks_position("bottom") ax.yaxis.set_ticks_position("left") f_ts = np.linspace(0, 1, len(forward) + 1)[1:] r_ts = np.linspace(0, 1, len(backward) + 1)[1:] if final_error is None: final_error = backward_error[-1] if np.isfinite(backward[-1]) and np.isfinite(final_error): ax.fill_between( [0, 1], backward[-1] - final_error, backward[-1] + final_error, color="#D2B9D3", zorder=1, ) line1 = ax.errorbar( f_ts, forward, yerr=forward_error, color="#736AFF", lw=3, zorder=2, marker="o", mfc="w", mew=2.5, mec="#736AFF", ms=12, ) line2 = ax.errorbar( r_ts, backward, yerr=backward_error, color="#C11B17", lw=3, zorder=3, marker="o", mfc="w", mew=2.5, mec="#C11B17", ms=12, ) xticks_spacing = len(r_ts) // 10 or 1 xticks = r_ts[::xticks_spacing] plt.xticks(xticks, [f"{i:.2f}" for i in xticks], fontsize=10) plt.yticks(fontsize=10) ax.legend( (line1[0], line2[0]), ("Forward", "Reverse"), loc="best", prop=FP(size=18), frameon=False, ) ax.set_xlabel(r"Fraction of the simulation time", fontsize=16, color="#151B54") ax.set_ylabel(r"$\Delta G$ ({})".format(units), fontsize=16, color="#151B54") plt.tick_params(axis="x", color="#D2B9D3") plt.tick_params(axis="y", color="#D2B9D3") plt.tight_layout() return ax
[docs] def plot_block_average( dataframe: pd.DataFrame, units: None | str = None, final_error: None | float = None, ax: None | Axes = None, ) -> Axes: """Plot the forward and backward convergence. The input could be the result from :func:`~alchemlyb.convergence.forward_backward_convergence` or :func:`~alchemlyb.convergence.fwdrev_cumavg_Rc`. The input should be a :class:`pandas.DataFrame` which has column `FE` and :attr:`pandas.DataFrame.attrs` should compile with :ref:`note-on-units`. The errorbar will be plotted if column `FE_Error` and `Backward_Error` is present. `FE`: A column of free energy estimate from some X% block of the data, where optional `FE_Error` column is the corresponding error. `final_error` is the error of the final value and is shown as the error band around the final value. It can be provided in case an estimate is available that is more appropriate than the default, which is the error of the last value in `Backward`. Parameters ---------- dataframe : Dataframe Output Dataframe has column `Forward`, `Backward` or optionally `Forward_Error`, `Backward_Error` see :ref:`plot_convergence <plot_convergence>`. units : str The unit of the estimate. The default is `None`, which is to use the unit in the input. Setting this will change the output unit. final_error : float The error (standard deviation) of the final value in ``units``. If not given, takes the overall error of the time blocks, unless these were not provided, it which case it equals 1 kT. ax : matplotlib.axes.Axes Matplotlib axes object where the plot will be drawn on. If ``ax=None``, a new axes will be generated. Returns ------- matplotlib.axes.Axes An axes with the forward and backward convergence drawn. Note ---- The code is taken and modified from `Alchemical Analysis <https://github.com/MobleyLab/alchemical-analysis>`_. .. versionadded:: 2.4.0 """ if units is not None: dataframe = get_unit_converter(units)(dataframe) df_avg = dataframe["FE"].to_numpy(dtype=float) if "FE_Error" in dataframe: df_avg_error = dataframe["FE_Error"].to_numpy(dtype=float) else: df_avg_error = np.zeros(len(df_avg)) if ax is None: # pragma: no cover fig, ax = plt.subplots(figsize=(8, 6)) plt.setp(ax.spines["bottom"], color="#D2B9D3", lw=3, zorder=-2) plt.setp(ax.spines["left"], color="#D2B9D3", lw=3, zorder=-2) for dire in ["top", "right"]: ax.spines[dire].set_color("none") ax.xaxis.set_ticks_position("bottom") ax.yaxis.set_ticks_position("left") f_ts = np.linspace(0, 1, len(df_avg) + 1)[1:] if final_error is None: if np.sum(df_avg_error) != 0: final_error = np.std(df_avg) else: final_error = 1.0 if final_error is not None and np.isfinite(final_error): ax.fill_between( [0, 1], np.mean(df_avg) - final_error, np.mean(df_avg) + final_error, color="#D2B9D3", zorder=1, ) ax.errorbar( f_ts, df_avg, yerr=df_avg_error, color="#736AFF", lw=3, zorder=2, marker="o", mfc="w", mew=2.5, mec="#736AFF", ms=12, label="Avg FE", ) xticks_spacing = len(f_ts) // 10 or 1 xticks = f_ts[::xticks_spacing] plt.xticks(xticks, [f"{i:.2f}" for i in xticks], fontsize=10) plt.yticks(fontsize=10) ax.legend( loc="best", prop=FP(size=18), frameon=False, ) ax.set_xlabel(r"Fraction of the Simulation Time", fontsize=16, color="#151B54") ax.set_ylabel(r"$\Delta G$ ({})".format(units), fontsize=16, color="#151B54") plt.tick_params(axis="x", color="#D2B9D3") plt.tick_params(axis="y", color="#D2B9D3") plt.tight_layout() return ax