Skip to content

Commit 42a83e9

Browse files
committed
Separate y axis for delta-delta plot
1 parent 36f1594 commit 42a83e9

2 files changed

Lines changed: 143 additions & 80 deletions

File tree

dabest/_classes.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,8 +1753,8 @@ def plot(self, color_col=None,
17531753

17541754
raw_marker_size=6, es_marker_size=9,
17551755

1756-
swarm_label=None, barchart_label=None, contrast_label=None,
1757-
swarm_ylim=None, barchart_ylim=None, contrast_ylim=None,
1756+
swarm_label=None, barchart_label=None, contrast_label=None, delta_label=None,
1757+
swarm_ylim=None, barchart_ylim=None, contrast_ylim=None, delta_ylim=None,
17581758

17591759
custom_palette=None, swarm_desat=0.5, barchart_desat=0.5, halfviolin_desat=1,
17601760
halfviolin_alpha=0.8,
@@ -1790,15 +1790,16 @@ def plot(self, color_col=None,
17901790
es_marker_size : float, default 9
17911791
The size (in points) of the effect size points on the difference
17921792
axes.
1793-
swarm_label, contrast_label : strings, default None
1793+
swarm_label, contrast_label, delta_label : strings, default None
17941794
Set labels for the y-axis of the swarmplot and the contrast plot,
17951795
respectively. If `swarm_label` is not specified, it defaults to
17961796
"value", unless a column name was passed to `y`. If
17971797
`contrast_label` is not specified, it defaults to the effect size
1798-
being plotted.
1799-
swarm_ylim, contrast_ylim : tuples, default None
1800-
The desired y-limits of the raw data (swarmplot) axes and the
1801-
difference axes respectively, as a tuple. These will be autoscaled
1798+
being plotted. If `delta_label` is not specifed, it defaults to
1799+
"delta - delta"
1800+
swarm_ylim, contrast_ylim, delta_ylim : tuples, default None
1801+
The desired y-limits of the raw data (swarmplot) axes, the
1802+
difference axes and the delta-delta axes respectively, as a tuple. These will be autoscaled
18021803
to sensible values if they are not specified.
18031804
custom_palette : dict, list, or matplotlib color palette, default None
18041805
This keyword accepts a dictionary with {'group':'color'} pairings,

dabest/plotter.py

Lines changed: 135 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
1414
**plot_kwargs:
1515
color_col=None
1616
raw_marker_size=6, es_marker_size=9,
17-
swarm_label=None, contrast_label=None,
18-
swarm_ylim=None, contrast_ylim=None,
17+
swarm_label=None, contrast_label=None, delta_label=None,
18+
swarm_ylim=None, contrast_ylim=None, delta_ylim=None,
1919
custom_palette=None, swarm_desat=0.5, halfviolin_desat=1,
2020
halfviolin_alpha=0.8,
2121
float_contrast=True,
@@ -298,7 +298,14 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
298298
gridspec_kw={"width_ratios": width_ratios_ga,
299299
"wspace": 0},
300300
**init_fig_kwargs)
301-
301+
rawdata_axes = axx[0]
302+
contrast_axes = axx[1]
303+
elif delta2:
304+
fig = plt.subplots(gridspec_kw={"hspace": 0.3},
305+
**init_fig_kwargs)
306+
rawdata_axes = plt.subplot2grid((2,3), (0,0), colspan=3)
307+
contrast_axes = plt.subplot2grid((2,3), (1,0), colspan=2)
308+
delta_axes = plt.subplot2grid((2,3), (1,2))
302309
else:
303310
fig, axx = plt.subplots(nrows=2,
304311
gridspec_kw={"hspace": 0.3},
@@ -309,11 +316,12 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
309316
contrast_ax_ylim_low = list()
310317
contrast_ax_ylim_high = list()
311318
contrast_ax_ylim_tickintervals = list()
312-
rawdata_axes = axx[0]
313-
contrast_axes = axx[1]
319+
rawdata_axes = axx[0]
320+
contrast_axes = axx[1]
314321
rawdata_axes.set_frame_on(False)
315322
contrast_axes.set_frame_on(False)
316-
323+
if delta2:
324+
delta_axes.set_frame_on(False)
317325
redraw_axes_kwargs = {'colors' : ytick_color,
318326
'facecolors' : ytick_color,
319327
'lw' : 1,
@@ -464,8 +472,8 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
464472

465473
# plot one more halfviolin graph if the plot is for delta-delta
466474
if delta2:
467-
ticks_to_plot.append(ticks_to_plot[-1]+2)
468-
ticks_to_skip.append(ticks_to_skip[-1]+2)
475+
ticks_to_plot_delta = [0]
476+
ticks_to_skip_delta = [1]
469477

470478
# Plot the bootstraps, then the effect sizes and CIs.
471479
es_marker_size = plot_kwargs["es_marker_size"]
@@ -474,7 +482,7 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
474482

475483
results = EffectSizeDataFrame.results
476484
contrast_xtick_labels = []
477-
485+
478486
for j, tick in enumerate(ticks_to_plot):
479487
current_group = results.test[j]
480488
current_control = results.control[j]
@@ -513,25 +521,65 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
513521
contrast_xtick_labels.append("{}\nminus\n{}".format(current_group,
514522
current_control))
515523

524+
# plot delta-delta violin
525+
if delta2:
526+
current_group = results.test[2]
527+
current_control = results.control[2]
528+
current_bootstrap = results.bootstraps[2]
529+
current_effsize = results.difference[2]
530+
current_ci_low = results.bca_low[2]
531+
current_ci_high = results.bca_high[2]
532+
533+
# Create the violinplot.
534+
# New in v0.2.6: drop negative infinities before plotting.
535+
v = delta_axes.violinplot(current_bootstrap[~np.isinf(current_bootstrap)],
536+
positions=[0.5],
537+
**violinplot_kwargs)
538+
# Turn the violinplot into half, and color it the same as the swarmplot.
539+
# Do this only if the color column is not specified.
540+
# Ideally, the alpha (transparency) fo the violin plot should be
541+
# less than one so the effect size and CIs are visible.
542+
if bootstraps_color_by_group is True:
543+
fc = plot_palette_contrast[current_group]
544+
else:
545+
fc = "grey"
546+
547+
halfviolin(v, fill_color=fc, alpha=halfviolin_alpha)
548+
549+
# Plot the effect size.
550+
delta_axes.plot([0.5], current_effsize, marker='o',
551+
color=ytick_color,
552+
markersize=es_marker_size)
553+
# Plot the confidence interval.
554+
delta_axes.plot([0.5, 0.5],
555+
[current_ci_low, current_ci_high],
556+
linestyle="-",
557+
color=ytick_color,
558+
linewidth=group_summary_kwargs['lw'])
559+
560+
delta_xtick_labels = "{}\nminus\n{}".format(current_group,
561+
current_control)
562+
516563

517564
# Make sure the contrast_axes x-lims match the rawdata_axes xlims,
518565
# and add an extra violinplot tick for delta-delta plot.
519-
raw_x_stick = rawdata_axes.get_xticks()
566+
contrast_axes.set_xticks(rawdata_axes.get_xticks())
567+
520568
if delta2:
521-
raw_x_stick = np.append(raw_x_stick, max(raw_x_stick)+1)
522-
raw_x_stick = np.append(raw_x_stick, max(raw_x_stick)+1)
523-
contrast_axes.set_xticks(raw_x_stick)
569+
delta_axes.set_xticks([0,0.5])
524570

525571
if show_pairs is True:
526572
max_x = contrast_axes.get_xlim()[1]
527573
rawdata_axes.set_xlim(-0.375, max_x)
528574

529575
if float_contrast is True:
530576
contrast_axes.set_xlim(0.5, 1.5)
531-
elif not is_paired and delta2:
577+
elif delta2:
578+
contrast_axes.set_xlim(rawdata_axes.get_xlim())
532579
temp = rawdata_axes.get_xlim()
533-
contrast_axes.set_xlim(temp[0], temp[1]+2)
534580
rawdata_axes.set_xlim(temp[0], temp[1]+2)
581+
delta_axes.set_xlim(-0.5, 1)
582+
delta_axes.set_xticklabels(["", delta_xtick_labels])
535583
else:
536584
contrast_axes.set_xlim(rawdata_axes.get_xlim())
537585

@@ -711,7 +759,7 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
711759
contrast_axes.set_ylim(low, high)
712760
else:
713761
contrast_axes.set_ylim(custom_contrast_ylim)
714-
762+
715763
# If 0 lies within the ylim of the contrast axes,
716764
# draw a zero reference line.
717765
contrast_axes_ylim = contrast_axes.get_ylim()
@@ -722,11 +770,35 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
722770
if contrast_ylim_low < 0 < contrast_ylim_high:
723771
contrast_axes.axhline(y=0, **reflines_kwargs)
724772

725-
if is_paired == "baseline" and show_pairs == True:
726-
if delta2:
727-
temp_ticks_to_skip = ticks_to_skip[:-1]
773+
774+
if delta2 and plot_kwargs['delta_ylim'] is not None:
775+
custom_delta_ylim = plot_kwargs['delta_ylim']
776+
777+
if len(custom_delta_ylim) != 2:
778+
err1 = "Please check `delta_ylim` consists of "
779+
err2 = "exactly two numbers."
780+
raise ValueError(err1 + err2)
781+
782+
if effect_size_type == "cliffs_delta":
783+
# Ensure the ylims for a cliffs_delta plot never exceed [-1, 1].
784+
l = plot_kwargs['delta_ylim'][0]
785+
h = plot_kwargs['delta_ylim'][1]
786+
low = -1 if l < -1 else l
787+
high = 1 if h > 1 else h
788+
delta_axes.set_ylim(low, high)
728789
else:
729-
temp_ticks_to_skip = ticks_to_skip
790+
delta_axes.set_ylim(custom_delta_ylim)
791+
792+
if delta2:
793+
delta_axes_ylim = delta_axes.get_ylim()
794+
if delta_axes_ylim[0] < delta_axes_ylim[1]:
795+
delta_ylim_low, delta_ylim_high = delta_axes_ylim
796+
else:
797+
delta_ylim_high, delta_ylim_low = delta_axes_ylim
798+
if delta_ylim_low < 0 < delta_ylim_high:
799+
delta_axes.axhline(y=0, xmin = 0.25, xmax = 1.5, **reflines_kwargs)
800+
801+
if is_paired == "baseline" and show_pairs == True:
730802
rightend_ticks_raw = np.array([len(i)-1 for i in temp_idx]) + np.array(temp_ticks_to_skip)
731803
for ax in [rawdata_axes]:
732804
sns.despine(ax=ax, bottom=True)
@@ -744,8 +816,6 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
744816
del redraw_axes_kwargs['y']
745817

746818
temp_length = [(len(i)-1)*2-1 for i in idx]
747-
if delta2:
748-
temp_length.append(1)
749819
rightend_ticks_contrast = np.array(temp_length) + np.array(ticks_to_skip_contrast)
750820
for ax in [contrast_axes]:
751821
sns.despine(ax=ax, bottom=True)
@@ -763,61 +833,38 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
763833
del redraw_axes_kwargs['y']
764834
else:
765835
# Compute the end of each x-axes line.
766-
# Compute the end of each x-axes line.
767-
if delta2:
768-
temp = np.array([len(i)-1 for i in idx])
769-
temp = np.append(temp, 1)
770-
rightend_ticks = np.array(temp) + np.array(ticks_to_skip)
771-
else:
772-
rightend_ticks = np.array([len(i)-1 for i in idx]) + np.array(ticks_to_skip)
773-
if not delta2:
774-
for ax in [rawdata_axes, contrast_axes]:
775-
sns.despine(ax=ax, bottom=True)
776-
777-
ylim = ax.get_ylim()
778-
xlim = ax.get_xlim()
779-
redraw_axes_kwargs['y'] = ylim[0]
780-
781-
for k, start_tick in enumerate(ticks_to_skip):
782-
end_tick = rightend_ticks[k]
783-
ax.hlines(xmin=start_tick, xmax=end_tick,
784-
**redraw_axes_kwargs)
785-
786-
ax.set_ylim(ylim)
787-
del redraw_axes_kwargs['y']
788-
else:
789-
for ax in [rawdata_axes]:
790-
sns.despine(ax=ax, bottom=True)
791-
792-
ylim = ax.get_ylim()
793-
xlim = ax.get_xlim()
794-
redraw_axes_kwargs['y'] = ylim[0]
836+
rightend_ticks = np.array([len(i)-1 for i in idx]) + np.array(ticks_to_skip)
837+
for ax in [rawdata_axes, contrast_axes]:
838+
sns.despine(ax=ax, bottom=True)
839+
840+
ylim = ax.get_ylim()
841+
xlim = ax.get_xlim()
842+
redraw_axes_kwargs['y'] = ylim[0]
795843

796-
for k, start_tick in enumerate(ticks_to_skip[:-1]):
797-
end_tick = rightend_ticks[k]
798-
ax.hlines(xmin=start_tick, xmax=end_tick,
844+
for k, start_tick in enumerate(ticks_to_skip):
845+
end_tick = rightend_ticks[k]
846+
ax.hlines(xmin=start_tick, xmax=end_tick,
799847
**redraw_axes_kwargs)
800848

801-
ax.set_ylim(ylim)
802-
del redraw_axes_kwargs['y']
803-
804-
for ax in [contrast_axes]:
805-
sns.despine(ax=ax, bottom=True)
806-
807-
ylim = ax.get_ylim()
808-
xlim = ax.get_xlim()
809-
redraw_axes_kwargs['y'] = ylim[0]
810-
811-
for k, start_tick in enumerate(ticks_to_skip):
812-
end_tick = rightend_ticks[k]
813-
ax.hlines(xmin=start_tick, xmax=end_tick,
814-
**redraw_axes_kwargs)
849+
ax.set_ylim(ylim)
850+
del redraw_axes_kwargs['y']
851+
852+
if delta2:
853+
rightend_ticks = np.array([len(i)-1 for i in idx]) + np.array(ticks_to_skip)
815854

816-
ax.set_ylim(ylim)
817-
del redraw_axes_kwargs['y']
818-
855+
sns.despine(ax=delta_axes, bottom=True)
856+
ylim_contrast = contrast_axes.get_ylim()
857+
ylim_delta = delta_axes.get_ylim()
858+
ylim=(min(ylim_contrast[0], ylim_delta[0]), max(ylim_contrast[1], ylim_delta[1]))
819859

860+
xlim = delta_axes.get_xlim()
861+
redraw_axes_kwargs['y'] = ylim[0]
820862

863+
delta_axes.hlines(xmin=0, xmax=0.5, **redraw_axes_kwargs)
864+
865+
delta_axes.set_ylim(ylim)
866+
contrast_axes.set_ylim(ylim)
867+
del redraw_axes_kwargs['y']
821868

822869
# Set raw axes y-label.
823870
swarm_label = plot_kwargs['swarm_label']
@@ -833,7 +880,7 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
833880
'hedges_g' : "Hedges' g",
834881
'cliffs_delta' : "Cliff's delta"}
835882
default_contrast_label = contrast_label_dict[EffectSizeDataFrame.effect_size]
836-
883+
837884
if plot_kwargs['contrast_label'] is None:
838885
if is_paired:
839886
contrast_label = "paired\n{}".format(default_contrast_label)
@@ -847,6 +894,14 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
847894
if float_contrast is True:
848895
contrast_axes.yaxis.set_label_position("right")
849896

897+
if delta2:
898+
if plot_kwargs['delta_label'] is None:
899+
delta_label = "delta - delta"
900+
else:
901+
delta_label = plot_kwargs['delta_label']
902+
delta_axes.yaxis.tick_right()
903+
delta_axes.set_ylabel(delta_label)
904+
delta_axes.yaxis.set_label_position("right")
850905

851906
# Set the rawdata axes labels appropriately
852907
rawdata_axes.set_ylabel(swarm_label)
@@ -873,12 +928,19 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
873928
og_ylim_contrast[0], og_ylim_contrast[1],
874929
**redraw_axes_kwargs)
875930

876-
931+
if delta2:
932+
og_xlim_delta = delta_axes.get_xlim()
933+
og_ylim_delta = delta_axes.get_ylim()
934+
delta_axes.vlines(og_xlim_delta[1],
935+
og_ylim_delta[0], og_ylim_delta[1],
936+
**redraw_axes_kwargs)
877937

878938
# Make sure no stray ticks appear!
879939
rawdata_axes.xaxis.set_ticks_position('bottom')
880940
rawdata_axes.yaxis.set_ticks_position('left')
881941
contrast_axes.xaxis.set_ticks_position('bottom')
942+
if delta2:
943+
delta_axes.xaxis.set_ticks_position('bottom')
882944
if float_contrast is False:
883945
contrast_axes.yaxis.set_ticks_position('left')
884946

0 commit comments

Comments
 (0)