{"id":2743,"date":"2022-12-20T05:27:53","date_gmt":"2022-12-20T05:27:53","guid":{"rendered":"http:\/\/optimumsportsperformance.com\/blog\/?p=2743"},"modified":"2022-12-20T15:04:51","modified_gmt":"2022-12-20T15:04:51","slug":"box-dotplots-for-performance-visuals","status":"publish","type":"post","link":"https:\/\/optimumsportsperformance.com\/blog\/box-dotplots-for-performance-visuals\/","title":{"rendered":"Box &#038; Dotplots for Performance Visuals"},"content":{"rendered":"<p>A colleague recently asked me about visualizing athlete performance of athletes relative to their teammates. More specifically, they wanted something that showed some sort of team average and normal range and then a way to highlight where the individual athlete of interest resided within the population.<\/p>\n<p>Immediately, my mind went to some type of boxplot visualization combined with a dotplot where the athlete can clearly identified. Here are a few examples I quickly came up with.<\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Simulate Performance Data<\/strong><\/span><\/p>\n<p>First we will simulate some performance data for a group of athletes.<\/p>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\n### Load libraries -----------------------------------------------\r\nlibrary(tidyverse)\r\nlibrary(randomNames)\r\n\r\n### Data -------------------------------------------------------\r\nset.seed(2022)\r\ndat &lt;- tibble(\r\n  participant = randomNames(n = 20),\r\n  performance = rnorm(n = 20, mean = 100, sd = 10))\r\n<\/pre>\n<p><span style=\"text-decoration: underline;\"><strong>Plot 1 &#8211; Boxplot with Points<br \/>\n<\/strong><\/span><\/p>\n<p>The first plot is a simple boxplot plot with dots below it.<\/p>\n<p>A couple of notes:<\/p>\n<ul>\n<li>I&#8217;ve selected a few athletes to be our <strong>&#8220;of_interest&#8221;<\/strong> players for the plot.<\/li>\n<li>This plot doesn&#8217;t have a y-axis, since all I am doing is plotting the boxplot for the distribution of performance. Therefore, I set the y-axis variable to a factor, so that is simply identifies a space within the grid to organize my plot.<\/li>\n<li>I&#8217;m using a <strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"http:\/\/www.cookbook-r.com\/Graphs\/Colors_(ggplot2)\/\">colorblind friendly palette<\/a><\/span><\/strong> to ensure that the colors are viewable to a broad audience.<\/li>\n<li>Everything else after that is basic <strong>{ggplot2}<\/strong> code with some simple theme styling for the plot space and the legend position.<\/li>\n<\/ul>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\ndat %&gt;%\r\n  mutate(of_interest = case_when(participant %in% c(&quot;Gallegos, Dennis&quot;, &quot;Vonfeldt, Mckenna&quot;) ~ participant,\r\n                                 TRUE ~ &quot;everyone else&quot;)) %&gt;%\r\n  ggplot(aes(x = performance, y = factor(0))) +\r\n  geom_boxplot(width = 0.2) +\r\n  geom_point(aes(x = performance, y = factor(0),\r\n                  fill = of_interest),\r\n              position = position_nudge(y = -0.2),\r\n              shape = 21,\r\n              size = 8,\r\n              color = &quot;black&quot;,\r\n              alpha = 0.6) +\r\n  scale_fill_manual(values = c(&quot;Gallegos, Dennis&quot; = &quot;#E69F00&quot;, &quot;Vonfeldt, Mckenna&quot; = &quot;#56B4E9&quot;, &quot;everyone else&quot; = &quot;#999999&quot;)) +\r\n  labs(x = &quot;Performance&quot;,\r\n       title = &quot;Team Performance&quot;,\r\n       fill = &quot;Participants&quot;) +\r\n  theme_classic() +\r\n  theme(axis.text.y = element_blank(),\r\n        axis.title.y = element_blank(),\r\n        axis.text.x = element_text(size = 10, face = &quot;bold&quot;),\r\n        axis.title.x = element_text(size = 12, face = &quot;bold&quot;),\r\n        plot.title = element_text(size = 18),\r\n        legend.position = &quot;top&quot;)\r\n<\/pre>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2746\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM-1024x871.png\" alt=\"\" width=\"625\" height=\"532\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM-1024x871.png 1024w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM-300x255.png 300w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM-768x653.png 768w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM-624x531.png 624w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.33.08-PM.png 1594w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/p>\n<p>Not too bad! You might have more data than we&#8217;ve simulated and thus the inline dots might start to get busy. We can use <strong>geom_dotplot()<\/strong> to create separation between dots in areas where there is more density and expose that density of performance scores a bit better.<\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Plot 2 &#8211; Boxplot with Dotplots<\/strong><\/span><\/p>\n<p>Swapping out <strong>geom_point() <\/strong>with <strong>geom_dotplot()<\/strong> allows us to produce the same plot above just with a dotplot.<\/p>\n<ul>\n<li>Here, I set <strong>&#8220;y = positional&#8221;<\/strong> since, as before, we don&#8217;t have a true y-axis. Doing so allows me to specify where on the y-axis I want to place by boxplot and dotplot in their respective <strong>aes()<\/strong>.<\/li>\n<\/ul>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\n# Horizontal\r\ndat %&gt;%\r\n  mutate(of_interest = case_when(participant %in% c(&quot;Gallegos, Dennis&quot;, &quot;Vonfeldt, Mckenna&quot;) ~ participant,\r\n                                 TRUE ~ &quot;everyone else&quot;)) %&gt;%\r\n  ggplot(aes(x = performance, y = positional)) +\r\n  geom_boxplot(aes(y = 0.2),\r\n    width = 0.2) +\r\n  geom_dotplot(aes(y = 0, \r\n                   fill = of_interest),\r\n              color = &quot;black&quot;,\r\n              stackdir=&quot;center&quot;,\r\n              binaxis = &quot;x&quot;,\r\n              alpha = 0.6) +\r\n  scale_fill_manual(values = c(&quot;Gallegos, Dennis&quot; = &quot;#E69F00&quot;, &quot;Vonfeldt, Mckenna&quot; = &quot;#56B4E9&quot;, &quot;everyone else&quot; = &quot;#999999&quot;)) +\r\n  labs(x = &quot;Performance&quot;,\r\n       title = &quot;Team Performance&quot;,\r\n       fill = &quot;Participants&quot;) +\r\n  theme_classic() +\r\n  theme(axis.text.y = element_blank(),\r\n        axis.title.y = element_blank(),\r\n        axis.text.x = element_text(size = 10, face = &quot;bold&quot;),\r\n        axis.title.x = element_text(size = 12, face = &quot;bold&quot;),\r\n        plot.title = element_text(size = 18),\r\n        legend.position = &quot;top&quot;,\r\n        legend.title = element_text(size = 13),\r\n        legend.text = element_text(size = 11),\r\n        legend.key.size = unit(2, &quot;line&quot;)) \r\n<\/pre>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2747\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM-1024x872.png\" alt=\"\" width=\"625\" height=\"532\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM-1024x872.png 1024w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM-300x255.png 300w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM-768x654.png 768w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM-624x531.png 624w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.42.38-PM.png 1590w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/p>\n<p>If you don&#8217;t like the idea of a horizontal plot you can also do it in vertical.<\/p>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\n# vertical\r\ndat %&gt;%\r\n  mutate(of_interest = case_when(participant %in% c(&quot;Gallegos, Dennis&quot;, &quot;Vonfeldt, Mckenna&quot;) ~ participant,\r\n                                 TRUE ~ &quot;everyone else&quot;)) %&gt;%\r\n  ggplot(aes(x=positional, y= performance)) +\r\n  geom_dotplot(aes(x = 1.75, \r\n                   fill = of_interest), \r\n               binaxis=&quot;y&quot;, \r\n               stackdir=&quot;center&quot;) +\r\n  geom_boxplot(aes(x = 2), \r\n               width=0.2) +\r\n  scale_fill_manual(values = c(&quot;Gallegos, Dennis&quot; = &quot;#E69F00&quot;, &quot;Vonfeldt, Mckenna&quot; = &quot;#56B4E9&quot;, &quot;everyone else&quot; = &quot;#999999&quot;)) +\r\n  labs(y = &quot;Performance&quot;,\r\n       title = &quot;Team Performance&quot;,\r\n       fill = &quot;Participants&quot;) +\r\n  theme_classic() +\r\n  theme(axis.text.x = element_blank(),\r\n        axis.title.x = element_blank(),\r\n        axis.text.y = element_text(size = 10, face = &quot;bold&quot;),\r\n        axis.title.y = element_text(size = 12, face = &quot;bold&quot;),\r\n        plot.title = element_text(size = 18),\r\n        legend.position = &quot;top&quot;,\r\n        legend.title = element_text(size = 13),\r\n        legend.text = element_text(size = 11),\r\n        legend.key.size = unit(2, &quot;line&quot;)) +\r\n  xlim(1.5, 2.25)\r\n<\/pre>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2748\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM-1024x744.png\" alt=\"\" width=\"625\" height=\"454\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM-1024x744.png 1024w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM-300x218.png 300w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM-768x558.png 768w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM-624x453.png 624w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2022\/12\/Screen-Shot-2022-12-19-at-9.44.21-PM.png 1680w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Wrapping Up<\/strong><\/span><\/p>\n<p>Visualizing an athlete&#8217;s performance relative to their team or population can be a useful for communicating data. Boxplots with dotplots can be a compelling way to show where an athlete falls when compared to their peers. Other options could have been to show a density plot with points below it (like a Raincloud plot). However, I often feel like people have a harder time grasping a density plot whereas the the boxplot clearly gives them an average (line inside of the box) and some &#8220;normal&#8221; range (interquartile range, referenced by the box) to anchor themselves to. Finally, this plot can easily be built into an interactive plot using <strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"https:\/\/optimumsportsperformance.com\/blog\/total-score-of-athleticism\/\">Shiny<\/a><\/span><\/strong>.<\/p>\n<p>All of the code for this article is available on my <strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"https:\/\/github.com\/pw2\/R-Tips-Tricks\/blob\/master\/boxplot_with_points_for_performance_visual.R\">GITHUB page<\/a><\/span><\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A colleague recently asked me about visualizing athlete performance of athletes relative to their teammates. More specifically, they wanted something that showed some sort of team average and normal range and then a way to highlight where the individual athlete of interest resided within the population. Immediately, my mind went to some type of boxplot [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[45,43,42,27],"tags":[],"class_list":["post-2743","post","type-post","status-publish","format-standard","hentry","category-r-tips-tricks","category-sports-analytics","category-sports-science","category-strength-and-conditioning"],"_links":{"self":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/2743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/comments?post=2743"}],"version-history":[{"count":3,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/2743\/revisions"}],"predecessor-version":[{"id":2745,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/2743\/revisions\/2745"}],"wp:attachment":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/media?parent=2743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/categories?post=2743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/tags?post=2743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}