From 36e3533e87270aa1b55b27ad6f5d67a5dc18687d Mon Sep 17 00:00:00 2001 From: JAnns98 Date: Thu, 12 Sep 2024 13:44:30 +0800 Subject: [PATCH] Trimmed plotter and added new functions in misc_tools to edit plot aesthetics for cummings and gardner altman style plots --- dabest/_modidx.py | 7 +- dabest/misc_tools.py | 349 ++++++++++++++++++++++++++++++++++- dabest/plotter.py | 380 ++++++--------------------------------- nbs/API/misc_tools.ipynb | 348 ++++++++++++++++++++++++++++++++++- nbs/API/plotter.ipynb | 380 ++++++--------------------------------- 5 files changed, 805 insertions(+), 659 deletions(-) diff --git a/dabest/_modidx.py b/dabest/_modidx.py index 37e8374a..951aa07d 100644 --- a/dabest/_modidx.py +++ b/dabest/_modidx.py @@ -65,7 +65,11 @@ 'dabest/forest_plot.py'), 'dabest.forest_plot.forest_plot': ('API/forest_plot.html#forest_plot', 'dabest/forest_plot.py'), 'dabest.forest_plot.load_plot_data': ('API/forest_plot.html#load_plot_data', 'dabest/forest_plot.py')}, - 'dabest.misc_tools': { 'dabest.misc_tools.add_counts_to_ticks': ( 'API/misc_tools.html#add_counts_to_ticks', + 'dabest.misc_tools': { 'dabest.misc_tools.Cumming_Plot_Aesthetic_Adjustments': ( 'API/misc_tools.html#cumming_plot_aesthetic_adjustments', + 'dabest/misc_tools.py'), + 'dabest.misc_tools.Gardner_Altman_Plot_Aesthetic_Adjustments': ( 'API/misc_tools.html#gardner_altman_plot_aesthetic_adjustments', + 'dabest/misc_tools.py'), + 'dabest.misc_tools.add_counts_to_ticks': ( 'API/misc_tools.html#add_counts_to_ticks', 'dabest/misc_tools.py'), 'dabest.misc_tools.extract_contrast_plotting_ticks': ( 'API/misc_tools.html#extract_contrast_plotting_ticks', 'dabest/misc_tools.py'), @@ -79,6 +83,7 @@ 'dabest.misc_tools.print_greeting': ('API/misc_tools.html#print_greeting', 'dabest/misc_tools.py'), 'dabest.misc_tools.set_xaxis_ticks_and_lims': ( 'API/misc_tools.html#set_xaxis_ticks_and_lims', 'dabest/misc_tools.py'), + 'dabest.misc_tools.show_legend': ('API/misc_tools.html#show_legend', 'dabest/misc_tools.py'), 'dabest.misc_tools.unpack_and_add': ('API/misc_tools.html#unpack_and_add', 'dabest/misc_tools.py')}, 'dabest.plot_tools': { 'dabest.plot_tools.DeltaDotsPlotter': ('API/plot_tools.html#deltadotsplotter', 'dabest/plot_tools.py'), 'dabest.plot_tools.SwarmPlot': ('API/plot_tools.html#swarmplot', 'dabest/plot_tools.py'), diff --git a/dabest/misc_tools.py b/dabest/misc_tools.py index 760c7bcf..5b89a921 100644 --- a/dabest/misc_tools.py +++ b/dabest/misc_tools.py @@ -3,7 +3,8 @@ # %% auto 0 __all__ = ['merge_two_dicts', 'unpack_and_add', 'print_greeting', 'get_varname', 'get_params', 'get_kwargs', 'get_color_palette', 'initialize_fig', 'get_plot_groups', 'add_counts_to_ticks', 'extract_contrast_plotting_ticks', - 'set_xaxis_ticks_and_lims'] + 'set_xaxis_ticks_and_lims', 'show_legend', 'Gardner_Altman_Plot_Aesthetic_Adjustments', + 'Cumming_Plot_Aesthetic_Adjustments'] # %% ../nbs/API/misc_tools.ipynb 4 import datetime as dt @@ -565,7 +566,8 @@ def extract_contrast_plotting_ticks(is_paired, show_pairs, two_col_sankey, plot_ return ticks_to_skip, ticks_to_plot, ticks_to_skip_contrast, ticks_to_start_twocol_sankey -def set_xaxis_ticks_and_lims(show_delta2, show_mini_meta, rawdata_axes, contrast_axes, show_pairs, float_contrast): +def set_xaxis_ticks_and_lims(show_delta2, show_mini_meta, rawdata_axes, contrast_axes, show_pairs, float_contrast, + ticks_to_skip, contrast_xtick_labels, plot_kwargs): if show_delta2 is False and show_mini_meta is False: contrast_axes.set_xticks(rawdata_axes.get_xticks()) @@ -590,3 +592,346 @@ def set_xaxis_ticks_and_lims(show_delta2, show_mini_meta, rawdata_axes, contrast contrast_axes.set_xlim(rawdata_axes.get_xlim()) else: contrast_axes.set_xlim(rawdata_axes.get_xlim()) + + # Properly label the contrast ticks. + for t in ticks_to_skip: + contrast_xtick_labels.insert(t, "") + + if plot_kwargs["fontsize_contrastxlabel"] is not None: + fontsize_contrastxlabel = plot_kwargs["fontsize_contrastxlabel"] + + contrast_axes.set_xticklabels( + contrast_xtick_labels, fontsize=fontsize_contrastxlabel + ) + + +def show_legend(legend_labels, legend_handles, rawdata_axes, contrast_axes, float_contrast, show_pairs, legend_kwargs): + + legend_labels_unique = np.unique(legend_labels) + unique_idx = np.unique(legend_labels, return_index=True)[1] + legend_handles_unique = ( + pd.Series(legend_handles, dtype="object").loc[unique_idx] + ).tolist() + + if len(legend_handles_unique) > 0: + if float_contrast: + axes_with_legend = contrast_axes + if show_pairs: + bta = (1.75, 1.02) + else: + bta = (1.5, 1.02) + else: + axes_with_legend = rawdata_axes + if show_pairs: + bta = (1.02, 1.0) + else: + bta = (1.0, 1.0) + leg = axes_with_legend.legend( + legend_handles_unique, + legend_labels_unique, + bbox_to_anchor=bta, + **legend_kwargs + ) + if show_pairs: + for line in leg.get_lines(): + line.set_linewidth(3.0) + +def Gardner_Altman_Plot_Aesthetic_Adjustments(effect_size_type, plot_data, xvar, yvar, current_control, current_group, + rawdata_axes, contrast_axes, results, current_effsize, is_paired, one_sankey, + reflines_kwargs, redraw_axes_kwargs, swarm_ylim, og_xlim_raw, og_ylim_raw): + from ._stats_tools.effsize import ( + _compute_standardizers, + _compute_hedges_correction_factor, + ) + # Normalize ylims and despine the floating contrast axes. + # Check that the effect size is within the swarm ylims. + if effect_size_type in ["mean_diff", "cohens_d", "hedges_g", "cohens_h"]: + control_group_summary = ( + plot_data.groupby(xvar) + .mean(numeric_only=True) + .loc[current_control, yvar] + ) + test_group_summary = ( + plot_data.groupby(xvar).mean(numeric_only=True).loc[current_group, yvar] + ) + elif effect_size_type == "median_diff": + control_group_summary = ( + plot_data.groupby(xvar).median().loc[current_control, yvar] + ) + test_group_summary = ( + plot_data.groupby(xvar).median().loc[current_group, yvar] + ) + + if swarm_ylim is None: + swarm_ylim = rawdata_axes.get_ylim() + + _, contrast_xlim_max = contrast_axes.get_xlim() + + difference = float(results.difference[0]) + + if effect_size_type in ["mean_diff", "median_diff"]: + # Align 0 of contrast_axes to reference group mean of rawdata_axes. + # If the effect size is positive, shift the contrast axis up. + rawdata_ylims = np.array(rawdata_axes.get_ylim()) + if current_effsize > 0: + rightmin, rightmax = rawdata_ylims - current_effsize + # If the effect size is negative, shift the contrast axis down. + elif current_effsize < 0: + rightmin, rightmax = rawdata_ylims + current_effsize + else: + rightmin, rightmax = rawdata_ylims + + contrast_axes.set_ylim(rightmin, rightmax) + + og_ylim_contrast = rawdata_axes.get_ylim() - np.array(control_group_summary) + + contrast_axes.set_ylim(og_ylim_contrast) + contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max) + + elif effect_size_type in ["cohens_d", "hedges_g", "cohens_h"]: + if is_paired: + which_std = 1 + else: + which_std = 0 + temp_control = plot_data[plot_data[xvar] == current_control][yvar] + temp_test = plot_data[plot_data[xvar] == current_group][yvar] + + stds = _compute_standardizers(temp_control, temp_test) + if is_paired: + pooled_sd = stds[1] + else: + pooled_sd = stds[0] + + if effect_size_type == "hedges_g": + gby_count = plot_data.groupby(xvar).count() + len_control = gby_count.loc[current_control, yvar] + len_test = gby_count.loc[current_group, yvar] + + hg_correction_factor = _compute_hedges_correction_factor( + len_control, len_test + ) + + ylim_scale_factor = pooled_sd / hg_correction_factor + + elif effect_size_type == "cohens_h": + ylim_scale_factor = ( + np.mean(temp_test) - np.mean(temp_control) + ) / difference + + else: + ylim_scale_factor = pooled_sd + + scaled_ylim = ( + (rawdata_axes.get_ylim() - control_group_summary) / ylim_scale_factor + ).tolist() + + contrast_axes.set_ylim(scaled_ylim) + og_ylim_contrast = scaled_ylim + + contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max) + + if one_sankey is None: + # Draw summary lines for control and test groups.. + for jj, axx in enumerate([rawdata_axes, contrast_axes]): + # Draw effect size line. + if jj == 0: + ref = control_group_summary + diff = test_group_summary + effsize_line_start = 1 + + elif jj == 1: + ref = 0 + diff = ref + difference + effsize_line_start = contrast_xlim_max - 1.1 + + xlimlow, xlimhigh = axx.get_xlim() + + # Draw reference line. + axx.hlines( + ref, # y-coordinates + 0, + xlimhigh, # x-coordinates, start and end. + **reflines_kwargs + ) + + # Draw effect size line. + axx.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs) + else: + ref = 0 + diff = ref + difference + effsize_line_start = contrast_xlim_max - 0.9 + xlimlow, xlimhigh = contrast_axes.get_xlim() + # Draw reference line. + contrast_axes.hlines( + ref, # y-coordinates + effsize_line_start, + xlimhigh, # x-coordinates, start and end. + **reflines_kwargs + ) + + # Draw effect size line. + contrast_axes.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs) + rawdata_axes.set_xlim(og_xlim_raw) # to align the axis + # Despine appropriately. + sns.despine(ax=rawdata_axes, bottom=True) + sns.despine(ax=contrast_axes, left=True, right=False) + + # Insert break between the rawdata axes and the contrast axes + # by re-drawing the x-spine. + rawdata_axes.hlines( + og_ylim_raw[0], # yindex + rawdata_axes.get_xlim()[0], + 1.3, # xmin, xmax + **redraw_axes_kwargs + ) + rawdata_axes.set_ylim(og_ylim_raw) + + contrast_axes.hlines( + contrast_axes.get_ylim()[0], + contrast_xlim_max - 0.8, + contrast_xlim_max, + **redraw_axes_kwargs + ) + + +def Cumming_Plot_Aesthetic_Adjustments(plot_kwargs, show_delta2, effect_size_type, contrast_axes, reflines_kwargs, + is_paired, show_pairs, two_col_sankey, idx, ticks_to_start_twocol_sankey, + proportional, ticks_to_skip, temp_idx, rawdata_axes, redraw_axes_kwargs, + ticks_to_skip_contrast): + # Set custom contrast_ylim, if it was specified. + if plot_kwargs["contrast_ylim"] is not None or ( + plot_kwargs["delta2_ylim"] is not None and show_delta2 + ): + if plot_kwargs["contrast_ylim"] is not None: + custom_contrast_ylim = plot_kwargs["contrast_ylim"] + if plot_kwargs["delta2_ylim"] is not None and show_delta2: + custom_delta2_ylim = plot_kwargs["delta2_ylim"] + if custom_contrast_ylim != custom_delta2_ylim: + err1 = "Please check if `contrast_ylim` and `delta2_ylim` are assigned" + err2 = "with same values." + raise ValueError(err1 + err2) + else: + custom_delta2_ylim = plot_kwargs["delta2_ylim"] + custom_contrast_ylim = custom_delta2_ylim + + if len(custom_contrast_ylim) != 2: + err1 = "Please check `contrast_ylim` consists of " + err2 = "exactly two numbers." + raise ValueError(err1 + err2) + + if effect_size_type == "cliffs_delta": + # Ensure the ylims for a cliffs_delta plot never exceed [-1, 1]. + l = plot_kwargs["contrast_ylim"][0] + h = plot_kwargs["contrast_ylim"][1] + low = -1 if l < -1 else l + high = 1 if h > 1 else h + contrast_axes.set_ylim(low, high) + else: + contrast_axes.set_ylim(custom_contrast_ylim) + + + # If 0 lies within the ylim of the contrast axes, + # draw a zero reference line. + contrast_axes_ylim = contrast_axes.get_ylim() + if contrast_axes_ylim[0] < contrast_axes_ylim[1]: + contrast_ylim_low, contrast_ylim_high = contrast_axes_ylim + else: + contrast_ylim_high, contrast_ylim_low = contrast_axes_ylim + if contrast_ylim_low < 0 < contrast_ylim_high: + contrast_axes.axhline(y=0, **reflines_kwargs) + + if is_paired == "baseline" and show_pairs: + if two_col_sankey: + rightend_ticks_raw = np.array([len(i) - 2 for i in idx]) + np.array( + ticks_to_start_twocol_sankey + ) + elif proportional and is_paired is not None: + rightend_ticks_raw = np.array([len(i) - 1 for i in idx]) + np.array( + ticks_to_skip + ) + else: + rightend_ticks_raw = np.array( + [len(i) - 1 for i in temp_idx] + ) + np.array(ticks_to_skip) + for ax in [rawdata_axes]: + sns.despine(ax=ax, bottom=True) + + ylim = ax.get_ylim() + xlim = ax.get_xlim() + redraw_axes_kwargs["y"] = ylim[0] + + if two_col_sankey: + for k, start_tick in enumerate(ticks_to_start_twocol_sankey): + end_tick = rightend_ticks_raw[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + else: + for k, start_tick in enumerate(ticks_to_skip): + end_tick = rightend_ticks_raw[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + ax.set_ylim(ylim) + del redraw_axes_kwargs["y"] + + if not proportional: + temp_length = [(len(i) - 1) for i in idx] + else: + temp_length = [(len(i) - 1) * 2 - 1 for i in idx] + if two_col_sankey: + rightend_ticks_contrast = np.array( + [len(i) - 2 for i in idx] + ) + np.array(ticks_to_start_twocol_sankey) + elif proportional and is_paired is not None: + rightend_ticks_contrast = np.array( + [len(i) - 1 for i in idx] + ) + np.array(ticks_to_skip) + else: + rightend_ticks_contrast = np.array(temp_length) + np.array( + ticks_to_skip_contrast + ) + for ax in [contrast_axes]: + sns.despine(ax=ax, bottom=True) + + ylim = ax.get_ylim() + xlim = ax.get_xlim() + redraw_axes_kwargs["y"] = ylim[0] + + if two_col_sankey: + for k, start_tick in enumerate(ticks_to_start_twocol_sankey): + end_tick = rightend_ticks_contrast[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + else: + for k, start_tick in enumerate(ticks_to_skip_contrast): + end_tick = rightend_ticks_contrast[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + + ax.set_ylim(ylim) + del redraw_axes_kwargs["y"] + else: + # Compute the end of each x-axes line. + if two_col_sankey: + rightend_ticks = np.array([len(i) - 2 for i in idx]) + np.array( + ticks_to_start_twocol_sankey + ) + else: + rightend_ticks = np.array([len(i) - 1 for i in idx]) + np.array( + ticks_to_skip + ) + + for ax in [rawdata_axes, contrast_axes]: + sns.despine(ax=ax, bottom=True) + + ylim = ax.get_ylim() + xlim = ax.get_xlim() + redraw_axes_kwargs["y"] = ylim[0] + + if two_col_sankey: + for k, start_tick in enumerate(ticks_to_start_twocol_sankey): + end_tick = rightend_ticks[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + else: + for k, start_tick in enumerate(ticks_to_skip): + end_tick = rightend_ticks[k] + ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) + + ax.set_ylim(ylim) + del redraw_axes_kwargs["y"] + ... diff --git a/dabest/plotter.py b/dabest/plotter.py index f94ffbac..188c2b78 100644 --- a/dabest/plotter.py +++ b/dabest/plotter.py @@ -67,6 +67,9 @@ def effectsize_df_plotter(effectsize_df, **plot_kwargs): add_counts_to_ticks, extract_contrast_plotting_ticks, set_xaxis_ticks_and_lims, + show_legend, + Gardner_Altman_Plot_Aesthetic_Adjustments, + Cumming_Plot_Aesthetic_Adjustments, ) from .plot_tools import ( get_swarm_spans, @@ -364,343 +367,66 @@ def effectsize_df_plotter(effectsize_df, **plot_kwargs): rawdata_axes=rawdata_axes, contrast_axes=contrast_axes, show_pairs=show_pairs, - float_contrast=float_contrast) - - # Properly label the contrast ticks. - for t in ticks_to_skip: - contrast_xtick_labels.insert(t, "") - - if plot_kwargs["fontsize_contrastxlabel"] is not None: - fontsize_contrastxlabel = plot_kwargs["fontsize_contrastxlabel"] - - contrast_axes.set_xticklabels( - contrast_xtick_labels, fontsize=fontsize_contrastxlabel - ) - + float_contrast=float_contrast, + ticks_to_skip=ticks_to_skip, + contrast_xtick_labels=contrast_xtick_labels, + plot_kwargs=plot_kwargs, + ) + # Legend if bootstraps_color_by_group is False: - legend_labels_unique = np.unique(legend_labels) - unique_idx = np.unique(legend_labels, return_index=True)[1] - legend_handles_unique = ( - pd.Series(legend_handles, dtype="object").loc[unique_idx] - ).tolist() - - if len(legend_handles_unique) > 0: - if float_contrast: - axes_with_legend = contrast_axes - if show_pairs: - bta = (1.75, 1.02) - else: - bta = (1.5, 1.02) - else: - axes_with_legend = rawdata_axes - if show_pairs: - bta = (1.02, 1.0) - else: - bta = (1.0, 1.0) - leg = axes_with_legend.legend( - legend_handles_unique, - legend_labels_unique, - bbox_to_anchor=bta, - **legend_kwargs - ) - if show_pairs: - for line in leg.get_lines(): - line.set_linewidth(3.0) + show_legend(legend_labels=legend_labels, + legend_handles=legend_handles, + rawdata_axes=rawdata_axes, + contrast_axes=contrast_axes, + float_contrast=float_contrast, + show_pairs=show_pairs, + legend_kwargs=legend_kwargs + ) og_ylim_raw = rawdata_axes.get_ylim() og_xlim_raw = rawdata_axes.get_xlim() if float_contrast: # For Gardner-Altman plots only. - - # Normalize ylims and despine the floating contrast axes. - # Check that the effect size is within the swarm ylims. - if effect_size_type in ["mean_diff", "cohens_d", "hedges_g", "cohens_h"]: - control_group_summary = ( - plot_data.groupby(xvar) - .mean(numeric_only=True) - .loc[current_control, yvar] - ) - test_group_summary = ( - plot_data.groupby(xvar).mean(numeric_only=True).loc[current_group, yvar] - ) - elif effect_size_type == "median_diff": - control_group_summary = ( - plot_data.groupby(xvar).median().loc[current_control, yvar] - ) - test_group_summary = ( - plot_data.groupby(xvar).median().loc[current_group, yvar] - ) - - if swarm_ylim is None: - swarm_ylim = rawdata_axes.get_ylim() - - _, contrast_xlim_max = contrast_axes.get_xlim() - - difference = float(results.difference[0]) - - if effect_size_type in ["mean_diff", "median_diff"]: - # Align 0 of contrast_axes to reference group mean of rawdata_axes. - # If the effect size is positive, shift the contrast axis up. - rawdata_ylims = np.array(rawdata_axes.get_ylim()) - if current_effsize > 0: - rightmin, rightmax = rawdata_ylims - current_effsize - # If the effect size is negative, shift the contrast axis down. - elif current_effsize < 0: - rightmin, rightmax = rawdata_ylims + current_effsize - else: - rightmin, rightmax = rawdata_ylims - - contrast_axes.set_ylim(rightmin, rightmax) - - og_ylim_contrast = rawdata_axes.get_ylim() - np.array(control_group_summary) - - contrast_axes.set_ylim(og_ylim_contrast) - contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max) - - elif effect_size_type in ["cohens_d", "hedges_g", "cohens_h"]: - if is_paired: - which_std = 1 - else: - which_std = 0 - temp_control = plot_data[plot_data[xvar] == current_control][yvar] - temp_test = plot_data[plot_data[xvar] == current_group][yvar] - - stds = _compute_standardizers(temp_control, temp_test) - if is_paired: - pooled_sd = stds[1] - else: - pooled_sd = stds[0] - - if effect_size_type == "hedges_g": - gby_count = plot_data.groupby(xvar).count() - len_control = gby_count.loc[current_control, yvar] - len_test = gby_count.loc[current_group, yvar] - - hg_correction_factor = _compute_hedges_correction_factor( - len_control, len_test - ) - - ylim_scale_factor = pooled_sd / hg_correction_factor - - elif effect_size_type == "cohens_h": - ylim_scale_factor = ( - np.mean(temp_test) - np.mean(temp_control) - ) / difference - - else: - ylim_scale_factor = pooled_sd - - scaled_ylim = ( - (rawdata_axes.get_ylim() - control_group_summary) / ylim_scale_factor - ).tolist() - - contrast_axes.set_ylim(scaled_ylim) - og_ylim_contrast = scaled_ylim - - contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max) - - if one_sankey is None: - # Draw summary lines for control and test groups.. - for jj, axx in enumerate([rawdata_axes, contrast_axes]): - # Draw effect size line. - if jj == 0: - ref = control_group_summary - diff = test_group_summary - effsize_line_start = 1 - - elif jj == 1: - ref = 0 - diff = ref + difference - effsize_line_start = contrast_xlim_max - 1.1 - - xlimlow, xlimhigh = axx.get_xlim() - - # Draw reference line. - axx.hlines( - ref, # y-coordinates - 0, - xlimhigh, # x-coordinates, start and end. - **reflines_kwargs - ) - - # Draw effect size line. - axx.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs) - else: - ref = 0 - diff = ref + difference - effsize_line_start = contrast_xlim_max - 0.9 - xlimlow, xlimhigh = contrast_axes.get_xlim() - # Draw reference line. - contrast_axes.hlines( - ref, # y-coordinates - effsize_line_start, - xlimhigh, # x-coordinates, start and end. - **reflines_kwargs - ) - - # Draw effect size line. - contrast_axes.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs) - rawdata_axes.set_xlim(og_xlim_raw) # to align the axis - # Despine appropriately. - sns.despine(ax=rawdata_axes, bottom=True) - sns.despine(ax=contrast_axes, left=True, right=False) - - # Insert break between the rawdata axes and the contrast axes - # by re-drawing the x-spine. - rawdata_axes.hlines( - og_ylim_raw[0], # yindex - rawdata_axes.get_xlim()[0], - 1.3, # xmin, xmax - **redraw_axes_kwargs - ) - rawdata_axes.set_ylim(og_ylim_raw) - - contrast_axes.hlines( - contrast_axes.get_ylim()[0], - contrast_xlim_max - 0.8, - contrast_xlim_max, - **redraw_axes_kwargs - ) + Gardner_Altman_Plot_Aesthetic_Adjustments(effect_size_type=effect_size_type, + plot_data=plot_data, + xvar=xvar, + yvar=yvar, + current_control=current_control, + current_group=current_group, + rawdata_axes=rawdata_axes, + contrast_axes=contrast_axes, + results=results, + current_effsize=current_effsize, + is_paired=is_paired, + one_sankey=one_sankey, + reflines_kwargs=reflines_kwargs, + redraw_axes_kwargs=redraw_axes_kwargs, + swarm_ylim=swarm_ylim, + og_xlim_raw=og_xlim_raw, + og_ylim_raw=og_ylim_raw + ) else: # For Cumming Plots only. - - # Set custom contrast_ylim, if it was specified. - if plot_kwargs["contrast_ylim"] is not None or ( - plot_kwargs["delta2_ylim"] is not None and show_delta2 - ): - if plot_kwargs["contrast_ylim"] is not None: - custom_contrast_ylim = plot_kwargs["contrast_ylim"] - if plot_kwargs["delta2_ylim"] is not None and show_delta2: - custom_delta2_ylim = plot_kwargs["delta2_ylim"] - if custom_contrast_ylim != custom_delta2_ylim: - err1 = "Please check if `contrast_ylim` and `delta2_ylim` are assigned" - err2 = "with same values." - raise ValueError(err1 + err2) - else: - custom_delta2_ylim = plot_kwargs["delta2_ylim"] - custom_contrast_ylim = custom_delta2_ylim - - if len(custom_contrast_ylim) != 2: - err1 = "Please check `contrast_ylim` consists of " - err2 = "exactly two numbers." - raise ValueError(err1 + err2) - - if effect_size_type == "cliffs_delta": - # Ensure the ylims for a cliffs_delta plot never exceed [-1, 1]. - l = plot_kwargs["contrast_ylim"][0] - h = plot_kwargs["contrast_ylim"][1] - low = -1 if l < -1 else l - high = 1 if h > 1 else h - contrast_axes.set_ylim(low, high) - else: - contrast_axes.set_ylim(custom_contrast_ylim) - - # If 0 lies within the ylim of the contrast axes, - # draw a zero reference line. - contrast_axes_ylim = contrast_axes.get_ylim() - if contrast_axes_ylim[0] < contrast_axes_ylim[1]: - contrast_ylim_low, contrast_ylim_high = contrast_axes_ylim - else: - contrast_ylim_high, contrast_ylim_low = contrast_axes_ylim - if contrast_ylim_low < 0 < contrast_ylim_high: - contrast_axes.axhline(y=0, **reflines_kwargs) - - if is_paired == "baseline" and show_pairs: - if two_col_sankey: - rightend_ticks_raw = np.array([len(i) - 2 for i in idx]) + np.array( - ticks_to_start_twocol_sankey - ) - elif proportional and is_paired is not None: - rightend_ticks_raw = np.array([len(i) - 1 for i in idx]) + np.array( - ticks_to_skip - ) - else: - rightend_ticks_raw = np.array( - [len(i) - 1 for i in temp_idx] - ) + np.array(ticks_to_skip) - for ax in [rawdata_axes]: - sns.despine(ax=ax, bottom=True) - - ylim = ax.get_ylim() - xlim = ax.get_xlim() - redraw_axes_kwargs["y"] = ylim[0] - - if two_col_sankey: - for k, start_tick in enumerate(ticks_to_start_twocol_sankey): - end_tick = rightend_ticks_raw[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - else: - for k, start_tick in enumerate(ticks_to_skip): - end_tick = rightend_ticks_raw[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - ax.set_ylim(ylim) - del redraw_axes_kwargs["y"] - - if not proportional: - temp_length = [(len(i) - 1) for i in idx] - else: - temp_length = [(len(i) - 1) * 2 - 1 for i in idx] - if two_col_sankey: - rightend_ticks_contrast = np.array( - [len(i) - 2 for i in idx] - ) + np.array(ticks_to_start_twocol_sankey) - elif proportional and is_paired is not None: - rightend_ticks_contrast = np.array( - [len(i) - 1 for i in idx] - ) + np.array(ticks_to_skip) - else: - rightend_ticks_contrast = np.array(temp_length) + np.array( - ticks_to_skip_contrast - ) - for ax in [contrast_axes]: - sns.despine(ax=ax, bottom=True) - - ylim = ax.get_ylim() - xlim = ax.get_xlim() - redraw_axes_kwargs["y"] = ylim[0] - - if two_col_sankey: - for k, start_tick in enumerate(ticks_to_start_twocol_sankey): - end_tick = rightend_ticks_contrast[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - else: - for k, start_tick in enumerate(ticks_to_skip_contrast): - end_tick = rightend_ticks_contrast[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - - ax.set_ylim(ylim) - del redraw_axes_kwargs["y"] - else: - # Compute the end of each x-axes line. - if two_col_sankey: - rightend_ticks = np.array([len(i) - 2 for i in idx]) + np.array( - ticks_to_start_twocol_sankey - ) - else: - rightend_ticks = np.array([len(i) - 1 for i in idx]) + np.array( - ticks_to_skip - ) - - for ax in [rawdata_axes, contrast_axes]: - sns.despine(ax=ax, bottom=True) - - ylim = ax.get_ylim() - xlim = ax.get_xlim() - redraw_axes_kwargs["y"] = ylim[0] - - if two_col_sankey: - for k, start_tick in enumerate(ticks_to_start_twocol_sankey): - end_tick = rightend_ticks[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - else: - for k, start_tick in enumerate(ticks_to_skip): - end_tick = rightend_ticks[k] - ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs) - - ax.set_ylim(ylim) - del redraw_axes_kwargs["y"] + Cumming_Plot_Aesthetic_Adjustments(plot_kwargs=plot_kwargs, + show_delta2=show_delta2, + effect_size_type=effect_size_type, + contrast_axes=contrast_axes, + reflines_kwargs=reflines_kwargs, + is_paired=is_paired, + show_pairs=show_pairs, + two_col_sankey=two_col_sankey, + idx=idx, + ticks_to_start_twocol_sankey=ticks_to_start_twocol_sankey, + proportional=proportional, + ticks_to_skip=ticks_to_skip, + temp_idx=temp_idx if is_paired == "baseline" and show_pairs else None, + rawdata_axes=rawdata_axes, + redraw_axes_kwargs=redraw_axes_kwargs, + ticks_to_skip_contrast=ticks_to_skip_contrast, + ) + if show_delta2 or show_mini_meta: ylim = contrast_axes.get_ylim() diff --git a/nbs/API/misc_tools.ipynb b/nbs/API/misc_tools.ipynb index 4235d630..30040ee9 100644 --- a/nbs/API/misc_tools.ipynb +++ b/nbs/API/misc_tools.ipynb @@ -621,7 +621,8 @@ " \n", " return ticks_to_skip, ticks_to_plot, ticks_to_skip_contrast, ticks_to_start_twocol_sankey\n", "\n", - "def set_xaxis_ticks_and_lims(show_delta2, show_mini_meta, rawdata_axes, contrast_axes, show_pairs, float_contrast):\n", + "def set_xaxis_ticks_and_lims(show_delta2, show_mini_meta, rawdata_axes, contrast_axes, show_pairs, float_contrast,\n", + " ticks_to_skip, contrast_xtick_labels, plot_kwargs):\n", "\n", " if show_delta2 is False and show_mini_meta is False:\n", " contrast_axes.set_xticks(rawdata_axes.get_xticks())\n", @@ -645,7 +646,350 @@ " rawdata_axes.set_xlim(temp[0], temp[1] + 2)\n", " contrast_axes.set_xlim(rawdata_axes.get_xlim())\n", " else:\n", - " contrast_axes.set_xlim(rawdata_axes.get_xlim())" + " contrast_axes.set_xlim(rawdata_axes.get_xlim())\n", + "\n", + " # Properly label the contrast ticks.\n", + " for t in ticks_to_skip:\n", + " contrast_xtick_labels.insert(t, \"\")\n", + "\n", + " if plot_kwargs[\"fontsize_contrastxlabel\"] is not None:\n", + " fontsize_contrastxlabel = plot_kwargs[\"fontsize_contrastxlabel\"]\n", + "\n", + " contrast_axes.set_xticklabels(\n", + " contrast_xtick_labels, fontsize=fontsize_contrastxlabel\n", + " )\n", + "\n", + "\n", + "def show_legend(legend_labels, legend_handles, rawdata_axes, contrast_axes, float_contrast, show_pairs, legend_kwargs):\n", + "\n", + " legend_labels_unique = np.unique(legend_labels)\n", + " unique_idx = np.unique(legend_labels, return_index=True)[1]\n", + " legend_handles_unique = (\n", + " pd.Series(legend_handles, dtype=\"object\").loc[unique_idx]\n", + " ).tolist()\n", + "\n", + " if len(legend_handles_unique) > 0:\n", + " if float_contrast:\n", + " axes_with_legend = contrast_axes\n", + " if show_pairs:\n", + " bta = (1.75, 1.02)\n", + " else:\n", + " bta = (1.5, 1.02)\n", + " else:\n", + " axes_with_legend = rawdata_axes\n", + " if show_pairs:\n", + " bta = (1.02, 1.0)\n", + " else:\n", + " bta = (1.0, 1.0)\n", + " leg = axes_with_legend.legend(\n", + " legend_handles_unique,\n", + " legend_labels_unique,\n", + " bbox_to_anchor=bta,\n", + " **legend_kwargs\n", + " )\n", + " if show_pairs:\n", + " for line in leg.get_lines():\n", + " line.set_linewidth(3.0)\n", + " \n", + "def Gardner_Altman_Plot_Aesthetic_Adjustments(effect_size_type, plot_data, xvar, yvar, current_control, current_group,\n", + " rawdata_axes, contrast_axes, results, current_effsize, is_paired, one_sankey,\n", + " reflines_kwargs, redraw_axes_kwargs, swarm_ylim, og_xlim_raw, og_ylim_raw):\n", + " from ._stats_tools.effsize import (\n", + " _compute_standardizers,\n", + " _compute_hedges_correction_factor,\n", + " )\n", + " # Normalize ylims and despine the floating contrast axes.\n", + " # Check that the effect size is within the swarm ylims.\n", + " if effect_size_type in [\"mean_diff\", \"cohens_d\", \"hedges_g\", \"cohens_h\"]:\n", + " control_group_summary = (\n", + " plot_data.groupby(xvar)\n", + " .mean(numeric_only=True)\n", + " .loc[current_control, yvar]\n", + " )\n", + " test_group_summary = (\n", + " plot_data.groupby(xvar).mean(numeric_only=True).loc[current_group, yvar]\n", + " )\n", + " elif effect_size_type == \"median_diff\":\n", + " control_group_summary = (\n", + " plot_data.groupby(xvar).median().loc[current_control, yvar]\n", + " )\n", + " test_group_summary = (\n", + " plot_data.groupby(xvar).median().loc[current_group, yvar]\n", + " )\n", + "\n", + " if swarm_ylim is None:\n", + " swarm_ylim = rawdata_axes.get_ylim()\n", + "\n", + " _, contrast_xlim_max = contrast_axes.get_xlim()\n", + "\n", + " difference = float(results.difference[0])\n", + "\n", + " if effect_size_type in [\"mean_diff\", \"median_diff\"]:\n", + " # Align 0 of contrast_axes to reference group mean of rawdata_axes.\n", + " # If the effect size is positive, shift the contrast axis up.\n", + " rawdata_ylims = np.array(rawdata_axes.get_ylim())\n", + " if current_effsize > 0:\n", + " rightmin, rightmax = rawdata_ylims - current_effsize\n", + " # If the effect size is negative, shift the contrast axis down.\n", + " elif current_effsize < 0:\n", + " rightmin, rightmax = rawdata_ylims + current_effsize\n", + " else:\n", + " rightmin, rightmax = rawdata_ylims\n", + "\n", + " contrast_axes.set_ylim(rightmin, rightmax)\n", + "\n", + " og_ylim_contrast = rawdata_axes.get_ylim() - np.array(control_group_summary)\n", + "\n", + " contrast_axes.set_ylim(og_ylim_contrast)\n", + " contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)\n", + "\n", + " elif effect_size_type in [\"cohens_d\", \"hedges_g\", \"cohens_h\"]:\n", + " if is_paired:\n", + " which_std = 1\n", + " else:\n", + " which_std = 0\n", + " temp_control = plot_data[plot_data[xvar] == current_control][yvar]\n", + " temp_test = plot_data[plot_data[xvar] == current_group][yvar]\n", + "\n", + " stds = _compute_standardizers(temp_control, temp_test)\n", + " if is_paired:\n", + " pooled_sd = stds[1]\n", + " else:\n", + " pooled_sd = stds[0]\n", + "\n", + " if effect_size_type == \"hedges_g\":\n", + " gby_count = plot_data.groupby(xvar).count()\n", + " len_control = gby_count.loc[current_control, yvar]\n", + " len_test = gby_count.loc[current_group, yvar]\n", + "\n", + " hg_correction_factor = _compute_hedges_correction_factor(\n", + " len_control, len_test\n", + " )\n", + "\n", + " ylim_scale_factor = pooled_sd / hg_correction_factor\n", + "\n", + " elif effect_size_type == \"cohens_h\":\n", + " ylim_scale_factor = (\n", + " np.mean(temp_test) - np.mean(temp_control)\n", + " ) / difference\n", + "\n", + " else:\n", + " ylim_scale_factor = pooled_sd\n", + "\n", + " scaled_ylim = (\n", + " (rawdata_axes.get_ylim() - control_group_summary) / ylim_scale_factor\n", + " ).tolist()\n", + "\n", + " contrast_axes.set_ylim(scaled_ylim)\n", + " og_ylim_contrast = scaled_ylim\n", + "\n", + " contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)\n", + "\n", + " if one_sankey is None:\n", + " # Draw summary lines for control and test groups..\n", + " for jj, axx in enumerate([rawdata_axes, contrast_axes]):\n", + " # Draw effect size line.\n", + " if jj == 0:\n", + " ref = control_group_summary\n", + " diff = test_group_summary\n", + " effsize_line_start = 1\n", + "\n", + " elif jj == 1:\n", + " ref = 0\n", + " diff = ref + difference\n", + " effsize_line_start = contrast_xlim_max - 1.1\n", + "\n", + " xlimlow, xlimhigh = axx.get_xlim()\n", + "\n", + " # Draw reference line.\n", + " axx.hlines(\n", + " ref, # y-coordinates\n", + " 0,\n", + " xlimhigh, # x-coordinates, start and end.\n", + " **reflines_kwargs\n", + " )\n", + "\n", + " # Draw effect size line.\n", + " axx.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)\n", + " else:\n", + " ref = 0\n", + " diff = ref + difference\n", + " effsize_line_start = contrast_xlim_max - 0.9\n", + " xlimlow, xlimhigh = contrast_axes.get_xlim()\n", + " # Draw reference line.\n", + " contrast_axes.hlines(\n", + " ref, # y-coordinates\n", + " effsize_line_start,\n", + " xlimhigh, # x-coordinates, start and end.\n", + " **reflines_kwargs\n", + " )\n", + "\n", + " # Draw effect size line.\n", + " contrast_axes.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)\n", + " rawdata_axes.set_xlim(og_xlim_raw) # to align the axis\n", + " # Despine appropriately.\n", + " sns.despine(ax=rawdata_axes, bottom=True)\n", + " sns.despine(ax=contrast_axes, left=True, right=False)\n", + "\n", + " # Insert break between the rawdata axes and the contrast axes\n", + " # by re-drawing the x-spine.\n", + " rawdata_axes.hlines(\n", + " og_ylim_raw[0], # yindex\n", + " rawdata_axes.get_xlim()[0],\n", + " 1.3, # xmin, xmax\n", + " **redraw_axes_kwargs\n", + " )\n", + " rawdata_axes.set_ylim(og_ylim_raw)\n", + "\n", + " contrast_axes.hlines(\n", + " contrast_axes.get_ylim()[0],\n", + " contrast_xlim_max - 0.8,\n", + " contrast_xlim_max,\n", + " **redraw_axes_kwargs\n", + " )\n", + "\n", + "\n", + "def Cumming_Plot_Aesthetic_Adjustments(plot_kwargs, show_delta2, effect_size_type, contrast_axes, reflines_kwargs, \n", + " is_paired, show_pairs, two_col_sankey, idx, ticks_to_start_twocol_sankey,\n", + " proportional, ticks_to_skip, temp_idx, rawdata_axes, redraw_axes_kwargs,\n", + " ticks_to_skip_contrast):\n", + " # Set custom contrast_ylim, if it was specified.\n", + " if plot_kwargs[\"contrast_ylim\"] is not None or (\n", + " plot_kwargs[\"delta2_ylim\"] is not None and show_delta2\n", + " ):\n", + " if plot_kwargs[\"contrast_ylim\"] is not None:\n", + " custom_contrast_ylim = plot_kwargs[\"contrast_ylim\"]\n", + " if plot_kwargs[\"delta2_ylim\"] is not None and show_delta2:\n", + " custom_delta2_ylim = plot_kwargs[\"delta2_ylim\"]\n", + " if custom_contrast_ylim != custom_delta2_ylim:\n", + " err1 = \"Please check if `contrast_ylim` and `delta2_ylim` are assigned\"\n", + " err2 = \"with same values.\"\n", + " raise ValueError(err1 + err2)\n", + " else:\n", + " custom_delta2_ylim = plot_kwargs[\"delta2_ylim\"]\n", + " custom_contrast_ylim = custom_delta2_ylim\n", + "\n", + " if len(custom_contrast_ylim) != 2:\n", + " err1 = \"Please check `contrast_ylim` consists of \"\n", + " err2 = \"exactly two numbers.\"\n", + " raise ValueError(err1 + err2)\n", + "\n", + " if effect_size_type == \"cliffs_delta\":\n", + " # Ensure the ylims for a cliffs_delta plot never exceed [-1, 1].\n", + " l = plot_kwargs[\"contrast_ylim\"][0]\n", + " h = plot_kwargs[\"contrast_ylim\"][1]\n", + " low = -1 if l < -1 else l\n", + " high = 1 if h > 1 else h\n", + " contrast_axes.set_ylim(low, high)\n", + " else:\n", + " contrast_axes.set_ylim(custom_contrast_ylim)\n", + "\n", + "\n", + " # If 0 lies within the ylim of the contrast axes,\n", + " # draw a zero reference line.\n", + " contrast_axes_ylim = contrast_axes.get_ylim()\n", + " if contrast_axes_ylim[0] < contrast_axes_ylim[1]:\n", + " contrast_ylim_low, contrast_ylim_high = contrast_axes_ylim\n", + " else:\n", + " contrast_ylim_high, contrast_ylim_low = contrast_axes_ylim\n", + " if contrast_ylim_low < 0 < contrast_ylim_high:\n", + " contrast_axes.axhline(y=0, **reflines_kwargs)\n", + "\n", + " if is_paired == \"baseline\" and show_pairs:\n", + " if two_col_sankey:\n", + " rightend_ticks_raw = np.array([len(i) - 2 for i in idx]) + np.array(\n", + " ticks_to_start_twocol_sankey\n", + " )\n", + " elif proportional and is_paired is not None:\n", + " rightend_ticks_raw = np.array([len(i) - 1 for i in idx]) + np.array(\n", + " ticks_to_skip\n", + " )\n", + " else:\n", + " rightend_ticks_raw = np.array(\n", + " [len(i) - 1 for i in temp_idx]\n", + " ) + np.array(ticks_to_skip)\n", + " for ax in [rawdata_axes]:\n", + " sns.despine(ax=ax, bottom=True)\n", + "\n", + " ylim = ax.get_ylim()\n", + " xlim = ax.get_xlim()\n", + " redraw_axes_kwargs[\"y\"] = ylim[0]\n", + "\n", + " if two_col_sankey:\n", + " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", + " end_tick = rightend_ticks_raw[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + " else:\n", + " for k, start_tick in enumerate(ticks_to_skip):\n", + " end_tick = rightend_ticks_raw[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + " ax.set_ylim(ylim)\n", + " del redraw_axes_kwargs[\"y\"]\n", + "\n", + " if not proportional:\n", + " temp_length = [(len(i) - 1) for i in idx]\n", + " else:\n", + " temp_length = [(len(i) - 1) * 2 - 1 for i in idx]\n", + " if two_col_sankey:\n", + " rightend_ticks_contrast = np.array(\n", + " [len(i) - 2 for i in idx]\n", + " ) + np.array(ticks_to_start_twocol_sankey)\n", + " elif proportional and is_paired is not None:\n", + " rightend_ticks_contrast = np.array(\n", + " [len(i) - 1 for i in idx]\n", + " ) + np.array(ticks_to_skip)\n", + " else:\n", + " rightend_ticks_contrast = np.array(temp_length) + np.array(\n", + " ticks_to_skip_contrast\n", + " )\n", + " for ax in [contrast_axes]:\n", + " sns.despine(ax=ax, bottom=True)\n", + "\n", + " ylim = ax.get_ylim()\n", + " xlim = ax.get_xlim()\n", + " redraw_axes_kwargs[\"y\"] = ylim[0]\n", + "\n", + " if two_col_sankey:\n", + " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", + " end_tick = rightend_ticks_contrast[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + " else:\n", + " for k, start_tick in enumerate(ticks_to_skip_contrast):\n", + " end_tick = rightend_ticks_contrast[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + "\n", + " ax.set_ylim(ylim)\n", + " del redraw_axes_kwargs[\"y\"]\n", + " else:\n", + " # Compute the end of each x-axes line.\n", + " if two_col_sankey:\n", + " rightend_ticks = np.array([len(i) - 2 for i in idx]) + np.array(\n", + " ticks_to_start_twocol_sankey\n", + " )\n", + " else:\n", + " rightend_ticks = np.array([len(i) - 1 for i in idx]) + np.array(\n", + " ticks_to_skip\n", + " )\n", + "\n", + " for ax in [rawdata_axes, contrast_axes]:\n", + " sns.despine(ax=ax, bottom=True)\n", + "\n", + " ylim = ax.get_ylim()\n", + " xlim = ax.get_xlim()\n", + " redraw_axes_kwargs[\"y\"] = ylim[0]\n", + "\n", + " if two_col_sankey:\n", + " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", + " end_tick = rightend_ticks[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + " else:\n", + " for k, start_tick in enumerate(ticks_to_skip):\n", + " end_tick = rightend_ticks[k]\n", + " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", + "\n", + " ax.set_ylim(ylim)\n", + " del redraw_axes_kwargs[\"y\"]\n", + " ..." ] } ], diff --git a/nbs/API/plotter.ipynb b/nbs/API/plotter.ipynb index 01246677..15ecac76 100644 --- a/nbs/API/plotter.ipynb +++ b/nbs/API/plotter.ipynb @@ -126,6 +126,9 @@ " add_counts_to_ticks,\n", " extract_contrast_plotting_ticks,\n", " set_xaxis_ticks_and_lims,\n", + " show_legend,\n", + " Gardner_Altman_Plot_Aesthetic_Adjustments,\n", + " Cumming_Plot_Aesthetic_Adjustments,\n", " )\n", " from .plot_tools import (\n", " get_swarm_spans,\n", @@ -423,343 +426,66 @@ " rawdata_axes=rawdata_axes, \n", " contrast_axes=contrast_axes, \n", " show_pairs=show_pairs, \n", - " float_contrast=float_contrast)\n", - "\n", - " # Properly label the contrast ticks.\n", - " for t in ticks_to_skip:\n", - " contrast_xtick_labels.insert(t, \"\")\n", - "\n", - " if plot_kwargs[\"fontsize_contrastxlabel\"] is not None:\n", - " fontsize_contrastxlabel = plot_kwargs[\"fontsize_contrastxlabel\"]\n", - "\n", - " contrast_axes.set_xticklabels(\n", - " contrast_xtick_labels, fontsize=fontsize_contrastxlabel\n", - " )\n", - "\n", + " float_contrast=float_contrast,\n", + " ticks_to_skip=ticks_to_skip, \n", + " contrast_xtick_labels=contrast_xtick_labels, \n", + " plot_kwargs=plot_kwargs,\n", + " )\n", + " # Legend\n", " if bootstraps_color_by_group is False:\n", - " legend_labels_unique = np.unique(legend_labels)\n", - " unique_idx = np.unique(legend_labels, return_index=True)[1]\n", - " legend_handles_unique = (\n", - " pd.Series(legend_handles, dtype=\"object\").loc[unique_idx]\n", - " ).tolist()\n", - "\n", - " if len(legend_handles_unique) > 0:\n", - " if float_contrast:\n", - " axes_with_legend = contrast_axes\n", - " if show_pairs:\n", - " bta = (1.75, 1.02)\n", - " else:\n", - " bta = (1.5, 1.02)\n", - " else:\n", - " axes_with_legend = rawdata_axes\n", - " if show_pairs:\n", - " bta = (1.02, 1.0)\n", - " else:\n", - " bta = (1.0, 1.0)\n", - " leg = axes_with_legend.legend(\n", - " legend_handles_unique,\n", - " legend_labels_unique,\n", - " bbox_to_anchor=bta,\n", - " **legend_kwargs\n", - " )\n", - " if show_pairs:\n", - " for line in leg.get_lines():\n", - " line.set_linewidth(3.0)\n", + " show_legend(legend_labels=legend_labels, \n", + " legend_handles=legend_handles, \n", + " rawdata_axes=rawdata_axes, \n", + " contrast_axes=contrast_axes, \n", + " float_contrast=float_contrast, \n", + " show_pairs=show_pairs, \n", + " legend_kwargs=legend_kwargs\n", + " )\n", "\n", " og_ylim_raw = rawdata_axes.get_ylim()\n", " og_xlim_raw = rawdata_axes.get_xlim()\n", "\n", " if float_contrast:\n", " # For Gardner-Altman plots only.\n", - "\n", - " # Normalize ylims and despine the floating contrast axes.\n", - " # Check that the effect size is within the swarm ylims.\n", - " if effect_size_type in [\"mean_diff\", \"cohens_d\", \"hedges_g\", \"cohens_h\"]:\n", - " control_group_summary = (\n", - " plot_data.groupby(xvar)\n", - " .mean(numeric_only=True)\n", - " .loc[current_control, yvar]\n", - " )\n", - " test_group_summary = (\n", - " plot_data.groupby(xvar).mean(numeric_only=True).loc[current_group, yvar]\n", - " )\n", - " elif effect_size_type == \"median_diff\":\n", - " control_group_summary = (\n", - " plot_data.groupby(xvar).median().loc[current_control, yvar]\n", - " )\n", - " test_group_summary = (\n", - " plot_data.groupby(xvar).median().loc[current_group, yvar]\n", - " )\n", - "\n", - " if swarm_ylim is None:\n", - " swarm_ylim = rawdata_axes.get_ylim()\n", - "\n", - " _, contrast_xlim_max = contrast_axes.get_xlim()\n", - "\n", - " difference = float(results.difference[0])\n", - "\n", - " if effect_size_type in [\"mean_diff\", \"median_diff\"]:\n", - " # Align 0 of contrast_axes to reference group mean of rawdata_axes.\n", - " # If the effect size is positive, shift the contrast axis up.\n", - " rawdata_ylims = np.array(rawdata_axes.get_ylim())\n", - " if current_effsize > 0:\n", - " rightmin, rightmax = rawdata_ylims - current_effsize\n", - " # If the effect size is negative, shift the contrast axis down.\n", - " elif current_effsize < 0:\n", - " rightmin, rightmax = rawdata_ylims + current_effsize\n", - " else:\n", - " rightmin, rightmax = rawdata_ylims\n", - "\n", - " contrast_axes.set_ylim(rightmin, rightmax)\n", - "\n", - " og_ylim_contrast = rawdata_axes.get_ylim() - np.array(control_group_summary)\n", - "\n", - " contrast_axes.set_ylim(og_ylim_contrast)\n", - " contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)\n", - "\n", - " elif effect_size_type in [\"cohens_d\", \"hedges_g\", \"cohens_h\"]:\n", - " if is_paired:\n", - " which_std = 1\n", - " else:\n", - " which_std = 0\n", - " temp_control = plot_data[plot_data[xvar] == current_control][yvar]\n", - " temp_test = plot_data[plot_data[xvar] == current_group][yvar]\n", - "\n", - " stds = _compute_standardizers(temp_control, temp_test)\n", - " if is_paired:\n", - " pooled_sd = stds[1]\n", - " else:\n", - " pooled_sd = stds[0]\n", - "\n", - " if effect_size_type == \"hedges_g\":\n", - " gby_count = plot_data.groupby(xvar).count()\n", - " len_control = gby_count.loc[current_control, yvar]\n", - " len_test = gby_count.loc[current_group, yvar]\n", - "\n", - " hg_correction_factor = _compute_hedges_correction_factor(\n", - " len_control, len_test\n", - " )\n", - "\n", - " ylim_scale_factor = pooled_sd / hg_correction_factor\n", - "\n", - " elif effect_size_type == \"cohens_h\":\n", - " ylim_scale_factor = (\n", - " np.mean(temp_test) - np.mean(temp_control)\n", - " ) / difference\n", - "\n", - " else:\n", - " ylim_scale_factor = pooled_sd\n", - "\n", - " scaled_ylim = (\n", - " (rawdata_axes.get_ylim() - control_group_summary) / ylim_scale_factor\n", - " ).tolist()\n", - "\n", - " contrast_axes.set_ylim(scaled_ylim)\n", - " og_ylim_contrast = scaled_ylim\n", - "\n", - " contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)\n", - "\n", - " if one_sankey is None:\n", - " # Draw summary lines for control and test groups..\n", - " for jj, axx in enumerate([rawdata_axes, contrast_axes]):\n", - " # Draw effect size line.\n", - " if jj == 0:\n", - " ref = control_group_summary\n", - " diff = test_group_summary\n", - " effsize_line_start = 1\n", - "\n", - " elif jj == 1:\n", - " ref = 0\n", - " diff = ref + difference\n", - " effsize_line_start = contrast_xlim_max - 1.1\n", - "\n", - " xlimlow, xlimhigh = axx.get_xlim()\n", - "\n", - " # Draw reference line.\n", - " axx.hlines(\n", - " ref, # y-coordinates\n", - " 0,\n", - " xlimhigh, # x-coordinates, start and end.\n", - " **reflines_kwargs\n", - " )\n", - "\n", - " # Draw effect size line.\n", - " axx.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)\n", - " else:\n", - " ref = 0\n", - " diff = ref + difference\n", - " effsize_line_start = contrast_xlim_max - 0.9\n", - " xlimlow, xlimhigh = contrast_axes.get_xlim()\n", - " # Draw reference line.\n", - " contrast_axes.hlines(\n", - " ref, # y-coordinates\n", - " effsize_line_start,\n", - " xlimhigh, # x-coordinates, start and end.\n", - " **reflines_kwargs\n", - " )\n", - "\n", - " # Draw effect size line.\n", - " contrast_axes.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)\n", - " rawdata_axes.set_xlim(og_xlim_raw) # to align the axis\n", - " # Despine appropriately.\n", - " sns.despine(ax=rawdata_axes, bottom=True)\n", - " sns.despine(ax=contrast_axes, left=True, right=False)\n", - "\n", - " # Insert break between the rawdata axes and the contrast axes\n", - " # by re-drawing the x-spine.\n", - " rawdata_axes.hlines(\n", - " og_ylim_raw[0], # yindex\n", - " rawdata_axes.get_xlim()[0],\n", - " 1.3, # xmin, xmax\n", - " **redraw_axes_kwargs\n", - " )\n", - " rawdata_axes.set_ylim(og_ylim_raw)\n", - "\n", - " contrast_axes.hlines(\n", - " contrast_axes.get_ylim()[0],\n", - " contrast_xlim_max - 0.8,\n", - " contrast_xlim_max,\n", - " **redraw_axes_kwargs\n", - " )\n", + " Gardner_Altman_Plot_Aesthetic_Adjustments(effect_size_type=effect_size_type, \n", + " plot_data=plot_data, \n", + " xvar=xvar, \n", + " yvar=yvar, \n", + " current_control=current_control, \n", + " current_group=current_group,\n", + " rawdata_axes=rawdata_axes, \n", + " contrast_axes=contrast_axes, \n", + " results=results, \n", + " current_effsize=current_effsize, \n", + " is_paired=is_paired, \n", + " one_sankey=one_sankey,\n", + " reflines_kwargs=reflines_kwargs, \n", + " redraw_axes_kwargs=redraw_axes_kwargs, \n", + " swarm_ylim=swarm_ylim, \n", + " og_xlim_raw=og_xlim_raw,\n", + " og_ylim_raw=og_ylim_raw\n", + " )\n", "\n", " else:\n", " # For Cumming Plots only.\n", - "\n", - " # Set custom contrast_ylim, if it was specified.\n", - " if plot_kwargs[\"contrast_ylim\"] is not None or (\n", - " plot_kwargs[\"delta2_ylim\"] is not None and show_delta2\n", - " ):\n", - " if plot_kwargs[\"contrast_ylim\"] is not None:\n", - " custom_contrast_ylim = plot_kwargs[\"contrast_ylim\"]\n", - " if plot_kwargs[\"delta2_ylim\"] is not None and show_delta2:\n", - " custom_delta2_ylim = plot_kwargs[\"delta2_ylim\"]\n", - " if custom_contrast_ylim != custom_delta2_ylim:\n", - " err1 = \"Please check if `contrast_ylim` and `delta2_ylim` are assigned\"\n", - " err2 = \"with same values.\"\n", - " raise ValueError(err1 + err2)\n", - " else:\n", - " custom_delta2_ylim = plot_kwargs[\"delta2_ylim\"]\n", - " custom_contrast_ylim = custom_delta2_ylim\n", - "\n", - " if len(custom_contrast_ylim) != 2:\n", - " err1 = \"Please check `contrast_ylim` consists of \"\n", - " err2 = \"exactly two numbers.\"\n", - " raise ValueError(err1 + err2)\n", - "\n", - " if effect_size_type == \"cliffs_delta\":\n", - " # Ensure the ylims for a cliffs_delta plot never exceed [-1, 1].\n", - " l = plot_kwargs[\"contrast_ylim\"][0]\n", - " h = plot_kwargs[\"contrast_ylim\"][1]\n", - " low = -1 if l < -1 else l\n", - " high = 1 if h > 1 else h\n", - " contrast_axes.set_ylim(low, high)\n", - " else:\n", - " contrast_axes.set_ylim(custom_contrast_ylim)\n", - "\n", - " # If 0 lies within the ylim of the contrast axes,\n", - " # draw a zero reference line.\n", - " contrast_axes_ylim = contrast_axes.get_ylim()\n", - " if contrast_axes_ylim[0] < contrast_axes_ylim[1]:\n", - " contrast_ylim_low, contrast_ylim_high = contrast_axes_ylim\n", - " else:\n", - " contrast_ylim_high, contrast_ylim_low = contrast_axes_ylim\n", - " if contrast_ylim_low < 0 < contrast_ylim_high:\n", - " contrast_axes.axhline(y=0, **reflines_kwargs)\n", - "\n", - " if is_paired == \"baseline\" and show_pairs:\n", - " if two_col_sankey:\n", - " rightend_ticks_raw = np.array([len(i) - 2 for i in idx]) + np.array(\n", - " ticks_to_start_twocol_sankey\n", - " )\n", - " elif proportional and is_paired is not None:\n", - " rightend_ticks_raw = np.array([len(i) - 1 for i in idx]) + np.array(\n", - " ticks_to_skip\n", - " )\n", - " else:\n", - " rightend_ticks_raw = np.array(\n", - " [len(i) - 1 for i in temp_idx]\n", - " ) + np.array(ticks_to_skip)\n", - " for ax in [rawdata_axes]:\n", - " sns.despine(ax=ax, bottom=True)\n", - "\n", - " ylim = ax.get_ylim()\n", - " xlim = ax.get_xlim()\n", - " redraw_axes_kwargs[\"y\"] = ylim[0]\n", - "\n", - " if two_col_sankey:\n", - " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", - " end_tick = rightend_ticks_raw[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - " else:\n", - " for k, start_tick in enumerate(ticks_to_skip):\n", - " end_tick = rightend_ticks_raw[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - " ax.set_ylim(ylim)\n", - " del redraw_axes_kwargs[\"y\"]\n", - "\n", - " if not proportional:\n", - " temp_length = [(len(i) - 1) for i in idx]\n", - " else:\n", - " temp_length = [(len(i) - 1) * 2 - 1 for i in idx]\n", - " if two_col_sankey:\n", - " rightend_ticks_contrast = np.array(\n", - " [len(i) - 2 for i in idx]\n", - " ) + np.array(ticks_to_start_twocol_sankey)\n", - " elif proportional and is_paired is not None:\n", - " rightend_ticks_contrast = np.array(\n", - " [len(i) - 1 for i in idx]\n", - " ) + np.array(ticks_to_skip)\n", - " else:\n", - " rightend_ticks_contrast = np.array(temp_length) + np.array(\n", - " ticks_to_skip_contrast\n", - " )\n", - " for ax in [contrast_axes]:\n", - " sns.despine(ax=ax, bottom=True)\n", - "\n", - " ylim = ax.get_ylim()\n", - " xlim = ax.get_xlim()\n", - " redraw_axes_kwargs[\"y\"] = ylim[0]\n", - "\n", - " if two_col_sankey:\n", - " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", - " end_tick = rightend_ticks_contrast[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - " else:\n", - " for k, start_tick in enumerate(ticks_to_skip_contrast):\n", - " end_tick = rightend_ticks_contrast[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - "\n", - " ax.set_ylim(ylim)\n", - " del redraw_axes_kwargs[\"y\"]\n", - " else:\n", - " # Compute the end of each x-axes line.\n", - " if two_col_sankey:\n", - " rightend_ticks = np.array([len(i) - 2 for i in idx]) + np.array(\n", - " ticks_to_start_twocol_sankey\n", - " )\n", - " else:\n", - " rightend_ticks = np.array([len(i) - 1 for i in idx]) + np.array(\n", - " ticks_to_skip\n", - " )\n", - "\n", - " for ax in [rawdata_axes, contrast_axes]:\n", - " sns.despine(ax=ax, bottom=True)\n", - "\n", - " ylim = ax.get_ylim()\n", - " xlim = ax.get_xlim()\n", - " redraw_axes_kwargs[\"y\"] = ylim[0]\n", - "\n", - " if two_col_sankey:\n", - " for k, start_tick in enumerate(ticks_to_start_twocol_sankey):\n", - " end_tick = rightend_ticks[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - " else:\n", - " for k, start_tick in enumerate(ticks_to_skip):\n", - " end_tick = rightend_ticks[k]\n", - " ax.hlines(xmin=start_tick, xmax=end_tick, **redraw_axes_kwargs)\n", - "\n", - " ax.set_ylim(ylim)\n", - " del redraw_axes_kwargs[\"y\"]\n", + " Cumming_Plot_Aesthetic_Adjustments(plot_kwargs=plot_kwargs, \n", + " show_delta2=show_delta2, \n", + " effect_size_type=effect_size_type, \n", + " contrast_axes=contrast_axes, \n", + " reflines_kwargs=reflines_kwargs, \n", + " is_paired=is_paired, \n", + " show_pairs=show_pairs, \n", + " two_col_sankey=two_col_sankey, \n", + " idx=idx, \n", + " ticks_to_start_twocol_sankey=ticks_to_start_twocol_sankey,\n", + " proportional=proportional, \n", + " ticks_to_skip=ticks_to_skip, \n", + " temp_idx=temp_idx if is_paired == \"baseline\" and show_pairs else None, \n", + " rawdata_axes=rawdata_axes, \n", + " redraw_axes_kwargs=redraw_axes_kwargs,\n", + " ticks_to_skip_contrast=ticks_to_skip_contrast,\n", + " )\n", + " \n", "\n", " if show_delta2 or show_mini_meta:\n", " ylim = contrast_axes.get_ylim()\n",