Skip to content

Commit 996643f

Browse files
committed
Add delta text to forest plot
1 parent 09d38d3 commit 996643f

29 files changed

+281
-37
lines changed

dabest/forest_plot.py

+78-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# %matplotlib inline
1111
import seaborn as sns
1212
from typing import List, Optional, Union
13-
13+
import numpy as np
1414

1515
# %% ../nbs/API/forest_plot.ipynb 6
1616
def load_plot_data(
@@ -265,6 +265,7 @@ def get_kwargs(
265265
horizontal,
266266
marker_kwargs,
267267
errorbar_kwargs,
268+
delta_text_kwargs,
268269
marker_size
269270
):
270271
from .misc_tools import merge_two_dicts
@@ -317,7 +318,25 @@ def get_kwargs(
317318
else:
318319
errorbar_kwargs = merge_two_dicts(default_errorbar_kwargs, errorbar_kwargs)
319320

320-
return violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs
321+
322+
# Delta text kwargs
323+
default_delta_text_kwargs = {
324+
"color": None,
325+
"alpha": 1,
326+
"fontsize": 10,
327+
"ha": 'center',
328+
"va": 'center',
329+
"rotation": 0,
330+
"x_coordinates": None,
331+
"y_coordinates": None,
332+
"offset": 0
333+
}
334+
if delta_text_kwargs is None:
335+
delta_text_kwargs = default_delta_text_kwargs
336+
else:
337+
delta_text_kwargs = merge_two_dicts(default_delta_text_kwargs, delta_text_kwargs)
338+
339+
return violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs, delta_text_kwargs
321340

322341

323342
def color_palette(
@@ -371,6 +390,9 @@ def forest_plot(
371390
yticklabels: Optional[list[str]] = None,
372391
remove_spines: bool = True,
373392

393+
delta_text: bool = True,
394+
delta_text_kwargs: dict = None,
395+
374396
violin_kwargs: Optional[dict] = None,
375397
zeroline_kwargs: Optional[dict] = None,
376398
marker_kwargs: Optional[dict] = None,
@@ -426,6 +448,9 @@ def forest_plot(
426448
Custom y-tick labels for the plot.
427449
remove_spines : bool, default=True
428450
If True, removes plot spines (except the relevant dependent variable spine).
451+
452+
453+
429454
violin_kwargs : Optional[dict], default=None
430455
Additional arguments for violin plot customization.
431456
zeroline_kwargs : Optional[dict], default=None
@@ -487,13 +512,15 @@ def forest_plot(
487512
fig, ax = plt.subplots(figsize=fig_size)
488513

489514
# Get Kwargs
490-
violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs = get_kwargs(
491-
violin_kwargs = violin_kwargs,
492-
zeroline_kwargs = zeroline_kwargs,
493-
horizontal = horizontal,
494-
marker_kwargs = marker_kwargs,
495-
errorbar_kwargs = errorbar_kwargs,
496-
marker_size = marker_size
515+
(violin_kwargs, zeroline_kwargs,
516+
marker_kwargs, errorbar_kwargs, delta_text_kwargs) = get_kwargs(
517+
violin_kwargs = violin_kwargs,
518+
zeroline_kwargs = zeroline_kwargs,
519+
horizontal = horizontal,
520+
marker_kwargs = marker_kwargs,
521+
errorbar_kwargs = errorbar_kwargs,
522+
delta_text_kwargs = delta_text_kwargs,
523+
marker_size = marker_size
497524
)
498525

499526
# Plot the violins and make adjustments
@@ -592,6 +619,48 @@ def forest_plot(
592619
spines = ["top", "right", "left"] if horizontal else ["top", "right", "bottom"]
593620
ax.spines[spines].set_visible(False)
594621

622+
# Delta Text
623+
if delta_text:
624+
if delta_text_kwargs.get('color') is not None:
625+
delta_text_colors = [delta_text_kwargs.pop('color')] * number_of_curves_to_plot
626+
else:
627+
delta_text_colors = violin_colors
628+
delta_text_kwargs.pop('color')
629+
630+
# Collect the X-coordinates for the delta text
631+
delta_text_x_coordinates = delta_text_kwargs.pop('x_coordinates')
632+
delta_text_x_adjustment = delta_text_kwargs.pop('offset')
633+
634+
if delta_text_x_coordinates is not None:
635+
if not isinstance(delta_text_x_coordinates, (list, tuple)) or not all(isinstance(x, (int, float)) for x in delta_text_x_coordinates):
636+
raise TypeError("delta_text_kwargs['x_coordinates'] must be a list of x-coordinates.")
637+
if len(delta_text_x_coordinates) != number_of_curves_to_plot:
638+
raise ValueError("delta_text_kwargs['x_coordinates'] must have the same length as the number of ticks to plot.")
639+
else:
640+
delta_text_x_coordinates = (np.arange(1, number_of_curves_to_plot + 1)
641+
+ (0.5 if not horizontal else -0.4)
642+
+ delta_text_x_adjustment
643+
)
644+
645+
# Collect the Y-coordinates for the delta text
646+
delta_text_y_coordinates = delta_text_kwargs.pop('y_coordinates')
647+
648+
if delta_text_y_coordinates is not None:
649+
if not isinstance(delta_text_y_coordinates, (list, tuple)) or not all(isinstance(y, (int, float)) for y in delta_text_y_coordinates):
650+
raise TypeError("delta_text_kwargs['y_coordinates'] must be a list of y-coordinates.")
651+
if len(delta_text_y_coordinates) != number_of_curves_to_plot:
652+
raise ValueError("delta_text_kwargs['y_coordinates'] must have the same length as the number of ticks to plot.")
653+
else:
654+
delta_text_y_coordinates = differences
655+
656+
if horizontal:
657+
delta_text_x_coordinates, delta_text_y_coordinates = delta_text_y_coordinates, delta_text_x_coordinates
658+
659+
for idx, x, y, delta in zip(np.arange(0, number_of_curves_to_plot, 1), delta_text_x_coordinates,
660+
delta_text_y_coordinates, differences):
661+
delta_text = np.format_float_positional(delta, precision=2, sign=True, trim="k", min_digits=2)
662+
ax.text(x, y, delta_text, color=delta_text_colors[idx], zorder=5, **delta_text_kwargs)
663+
595664
## Invert Y-axis if horizontal
596665
if horizontal:
597666
ax.invert_yaxis()

dabest/plot_tools.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1164,13 +1164,13 @@ def delta_text_plotter(
11641164

11651165
# Plot the delta text
11661166
for x,y,t,tick in zip(delta_text_x_coordinates, delta_text_y_coordinates,Delta_Values,ticks_to_plot):
1167-
Delta_Text = np.format_float_positional(t, precision=2, sign=True, trim="k", min_digits=2)
1167+
delta_text = np.format_float_positional(t, precision=2, sign=True, trim="k", min_digits=2)
11681168
idx_selector = (
11691169
int(tick)
11701170
if type(delta_text_colors) == list
11711171
else unpacked_idx[int(tick)]
11721172
)
1173-
ax_to_plot.text(x, y, Delta_Text, color=delta_text_colors[idx_selector], zorder=5, **delta_text_kwargs)
1173+
ax_to_plot.text(x, y, delta_text, color=delta_text_colors[idx_selector], zorder=5, **delta_text_kwargs)
11741174

11751175

11761176
def delta_dots_plotter(

nbs/API/forest_plot.ipynb

+79-9
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
"import matplotlib.pyplot as plt\n",
6363
"# %matplotlib inline\n",
6464
"import seaborn as sns\n",
65-
"from typing import List, Optional, Union\n"
65+
"from typing import List, Optional, Union\n",
66+
"import numpy as np"
6667
]
6768
},
6869
{
@@ -324,6 +325,7 @@
324325
" horizontal,\n",
325326
" marker_kwargs,\n",
326327
" errorbar_kwargs,\n",
328+
" delta_text_kwargs,\n",
327329
" marker_size\n",
328330
" ):\n",
329331
" from .misc_tools import merge_two_dicts\n",
@@ -376,7 +378,25 @@
376378
" else:\n",
377379
" errorbar_kwargs = merge_two_dicts(default_errorbar_kwargs, errorbar_kwargs)\n",
378380
"\n",
379-
" return violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs\n",
381+
"\n",
382+
" # Delta text kwargs\n",
383+
" default_delta_text_kwargs = {\n",
384+
" \"color\": None, \n",
385+
" \"alpha\": 1,\n",
386+
" \"fontsize\": 10, \n",
387+
" \"ha\": 'center', \n",
388+
" \"va\": 'center', \n",
389+
" \"rotation\": 0, \n",
390+
" \"x_coordinates\": None, \n",
391+
" \"y_coordinates\": None,\n",
392+
" \"offset\": 0\n",
393+
" }\n",
394+
" if delta_text_kwargs is None:\n",
395+
" delta_text_kwargs = default_delta_text_kwargs\n",
396+
" else:\n",
397+
" delta_text_kwargs = merge_two_dicts(default_delta_text_kwargs, delta_text_kwargs)\n",
398+
"\n",
399+
" return violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs, delta_text_kwargs\n",
380400
"\n",
381401
"\n",
382402
"def color_palette(\n",
@@ -430,6 +450,9 @@
430450
" yticklabels: Optional[list[str]] = None,\n",
431451
" remove_spines: bool = True,\n",
432452
"\n",
453+
" delta_text: bool = True,\n",
454+
" delta_text_kwargs: dict = None,\n",
455+
"\n",
433456
" violin_kwargs: Optional[dict] = None,\n",
434457
" zeroline_kwargs: Optional[dict] = None,\n",
435458
" marker_kwargs: Optional[dict] = None,\n",
@@ -485,6 +508,9 @@
485508
" Custom y-tick labels for the plot.\n",
486509
" remove_spines : bool, default=True\n",
487510
" If True, removes plot spines (except the relevant dependent variable spine).\n",
511+
"\n",
512+
"\n",
513+
"\n",
488514
" violin_kwargs : Optional[dict], default=None\n",
489515
" Additional arguments for violin plot customization.\n",
490516
" zeroline_kwargs : Optional[dict], default=None\n",
@@ -546,13 +572,15 @@
546572
" fig, ax = plt.subplots(figsize=fig_size)\n",
547573
"\n",
548574
" # Get Kwargs\n",
549-
" violin_kwargs, zeroline_kwargs, marker_kwargs, errorbar_kwargs = get_kwargs(\n",
550-
" violin_kwargs = violin_kwargs,\n",
551-
" zeroline_kwargs = zeroline_kwargs,\n",
552-
" horizontal = horizontal,\n",
553-
" marker_kwargs = marker_kwargs,\n",
554-
" errorbar_kwargs = errorbar_kwargs,\n",
555-
" marker_size = marker_size\n",
575+
" (violin_kwargs, zeroline_kwargs, \n",
576+
" marker_kwargs, errorbar_kwargs, delta_text_kwargs) = get_kwargs(\n",
577+
" violin_kwargs = violin_kwargs,\n",
578+
" zeroline_kwargs = zeroline_kwargs,\n",
579+
" horizontal = horizontal,\n",
580+
" marker_kwargs = marker_kwargs,\n",
581+
" errorbar_kwargs = errorbar_kwargs,\n",
582+
" delta_text_kwargs = delta_text_kwargs,\n",
583+
" marker_size = marker_size\n",
556584
" )\n",
557585
" \n",
558586
" # Plot the violins and make adjustments\n",
@@ -651,6 +679,48 @@
651679
" spines = [\"top\", \"right\", \"left\"] if horizontal else [\"top\", \"right\", \"bottom\"]\n",
652680
" ax.spines[spines].set_visible(False)\n",
653681
"\n",
682+
" # Delta Text\n",
683+
" if delta_text:\n",
684+
" if delta_text_kwargs.get('color') is not None:\n",
685+
" delta_text_colors = [delta_text_kwargs.pop('color')] * number_of_curves_to_plot\n",
686+
" else:\n",
687+
" delta_text_colors = violin_colors\n",
688+
" delta_text_kwargs.pop('color')\n",
689+
"\n",
690+
" # Collect the X-coordinates for the delta text\n",
691+
" delta_text_x_coordinates = delta_text_kwargs.pop('x_coordinates')\n",
692+
" delta_text_x_adjustment = delta_text_kwargs.pop('offset')\n",
693+
"\n",
694+
" if delta_text_x_coordinates is not None:\n",
695+
" if not isinstance(delta_text_x_coordinates, (list, tuple)) or not all(isinstance(x, (int, float)) for x in delta_text_x_coordinates):\n",
696+
" raise TypeError(\"delta_text_kwargs['x_coordinates'] must be a list of x-coordinates.\")\n",
697+
" if len(delta_text_x_coordinates) != number_of_curves_to_plot:\n",
698+
" raise ValueError(\"delta_text_kwargs['x_coordinates'] must have the same length as the number of ticks to plot.\")\n",
699+
" else:\n",
700+
" delta_text_x_coordinates = (np.arange(1, number_of_curves_to_plot + 1) \n",
701+
" + (0.5 if not horizontal else -0.4)\n",
702+
" + delta_text_x_adjustment\n",
703+
" )\n",
704+
"\n",
705+
" # Collect the Y-coordinates for the delta text\n",
706+
" delta_text_y_coordinates = delta_text_kwargs.pop('y_coordinates')\n",
707+
"\n",
708+
" if delta_text_y_coordinates is not None:\n",
709+
" if not isinstance(delta_text_y_coordinates, (list, tuple)) or not all(isinstance(y, (int, float)) for y in delta_text_y_coordinates):\n",
710+
" raise TypeError(\"delta_text_kwargs['y_coordinates'] must be a list of y-coordinates.\")\n",
711+
" if len(delta_text_y_coordinates) != number_of_curves_to_plot:\n",
712+
" raise ValueError(\"delta_text_kwargs['y_coordinates'] must have the same length as the number of ticks to plot.\")\n",
713+
" else:\n",
714+
" delta_text_y_coordinates = differences\n",
715+
"\n",
716+
" if horizontal:\n",
717+
" delta_text_x_coordinates, delta_text_y_coordinates = delta_text_y_coordinates, delta_text_x_coordinates\n",
718+
"\n",
719+
" for idx, x, y, delta in zip(np.arange(0, number_of_curves_to_plot, 1), delta_text_x_coordinates, \n",
720+
" delta_text_y_coordinates, differences):\n",
721+
" delta_text = np.format_float_positional(delta, precision=2, sign=True, trim=\"k\", min_digits=2)\n",
722+
" ax.text(x, y, delta_text, color=delta_text_colors[idx], zorder=5, **delta_text_kwargs)\n",
723+
"\n",
654724
" ## Invert Y-axis if horizontal \n",
655725
" if horizontal:\n",
656726
" ax.invert_yaxis()\n",

nbs/API/plot_tools.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1214,13 +1214,13 @@
12141214
"\n",
12151215
" # Plot the delta text\n",
12161216
" for x,y,t,tick in zip(delta_text_x_coordinates, delta_text_y_coordinates,Delta_Values,ticks_to_plot):\n",
1217-
" Delta_Text = np.format_float_positional(t, precision=2, sign=True, trim=\"k\", min_digits=2)\n",
1217+
" delta_text = np.format_float_positional(t, precision=2, sign=True, trim=\"k\", min_digits=2)\n",
12181218
" idx_selector = (\n",
12191219
" int(tick)\n",
12201220
" if type(delta_text_colors) == list \n",
12211221
" else unpacked_idx[int(tick)]\n",
12221222
" )\n",
1223-
" ax_to_plot.text(x, y, Delta_Text, color=delta_text_colors[idx_selector], zorder=5, **delta_text_kwargs)\n",
1223+
" ax_to_plot.text(x, y, delta_text, color=delta_text_colors[idx_selector], zorder=5, **delta_text_kwargs)\n",
12241224
"\n",
12251225
"\n",
12261226
"def delta_dots_plotter(\n",
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

nbs/tests/mpl_image_tests/test_05_forest_plot.py

+12
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,15 @@ def test_519_minimeta_with_deltas_forest():
390390
idx=[(0, 3),(0, 3),(0, 3)],
391391
labels=['Contrast A1', 'Mini_Meta A', 'Contrast B1', 'Mini_Meta B', 'Contrast C1', 'Mini_Meta C']
392392
)
393+
394+
@pytest.mark.mpl_image_compare(tolerance=8)
395+
def test_520_minimeta_with_deltas_and_delta_text_kwargs_forest():
396+
plt.rcdefaults()
397+
return forest_plot(
398+
contrasts_mini_meta,
399+
idx=[(0, 3),(0, 3),(0, 3)],
400+
labels=['Contrast A1', 'Mini_Meta A', 'Contrast B1', 'Mini_Meta B', 'Contrast C1', 'Mini_Meta C'],
401+
delta_text_kwargs={'color': 'black','fontsize': 8, 'rotation': 45, 'va': 'bottom',
402+
'x_coordinates': [1.4, 2.4, 3.4, 4.4, 5.4, 6.4],
403+
'y_coordinates': [0.6, 0.1, -2, -1.5, -1.5, -1.5]}
404+
)

nbs/tutorials/07-forest_plot.ipynb

+107-14
Large diffs are not rendered by default.

nbs/tutorials/09-plot_aesthetics.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@
14301430
"source": [
14311431
"Delta text kwargs can be utilised via `delta_text_kwargs` in the `plot()` function.\n",
14321432
"\n",
1433-
"The relevant inputs to `summary_bars_kwargs` are:\n",
1433+
"The relevant inputs to `delta_text_kwargs` are:\n",
14341434
"\n",
14351435
"- `'color'` - Color. If color is not specified, the color of the effect size curve will be used. \n",
14361436
"- `'alpha'`- Alpha (transparency)\n",

0 commit comments

Comments
 (0)