{"id":1796,"date":"2020-07-21T16:47:16","date_gmt":"2020-07-21T16:47:16","guid":{"rendered":"http:\/\/optimumsportsperformance.com\/blog\/?p=1796"},"modified":"2020-07-21T17:04:17","modified_gmt":"2020-07-21T17:04:17","slug":"r-tips-tricks-building-a-shiny-training-load-dashboard","status":"publish","type":"post","link":"https:\/\/optimumsportsperformance.com\/blog\/r-tips-tricks-building-a-shiny-training-load-dashboard\/","title":{"rendered":"R Tips &#038; Tricks: Building a Shiny Training Load Dashboard"},"content":{"rendered":"<p>In <span style=\"color: #0000ff;\"><strong><a style=\"color: #0000ff;\" href=\"https:\/\/optimumsportsperformance.com\/blog\/tidyx-episode-19-formattable-for-dashboard-design\/\">TidyX Episode 19<\/a><\/strong><\/span> we discussed a way of building a dashboard with the {<span style=\"color: #0000ff;\"><strong>formattable<\/strong><\/span>} package. The dashboard included both a data table and a small visual using {<span style=\"color: #0000ff;\"><strong>sparkline<\/strong><\/span>}. Such a table is great when you need to make a report for a presentation but there are times when you might want to have something that is more interactive and flowing. In the sports science setting, this often comes in the form of evaluating athlete training loads. As such, I decided to spin up a quick {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} web app to show how easy it is to create whatever you want without sinking a ton of money into an athlete management system.<\/p>\n<p>The code is available on my <strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"https:\/\/github.com\/pw2\/R-Tips-Tricks\">GITHUB<\/a><\/span><\/strong> page.<\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Packages &amp; Custom Functions<\/strong><\/span><\/p>\n<p>Before doing anything, always start by loading the packages you will need for your work. In this tutorial, we will using the {<span style=\"color: #0000ff;\"><strong>tidyverse<\/strong><\/span>} and {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} packages, so be sure to install them if you haven&#8217;t already. I also like to set my plot theme to classic so that I get rid of grid lines for the {<span style=\"color: #0000ff;\"><strong>ggplot2<\/strong><\/span>} figures that I create.<\/p>\n<p>Finally, I also wrote a custom function for calculating a z-score. This will come in handy when we go to visualize our data.<\/p>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\n# custom function for calculating z-score\r\nz_score &lt;- function(x){\r\n  z = (x - mean(x, na.rm = T)) \/ sd(x, na.rm = T)\r\n  return(z)\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Data<\/strong><\/span><\/p>\n<p>Next we simulate a bunch of fake data for a basketball team so that we have something to build our {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} app against. We will simulate total weekly training loads for 10 athletes across three different positions (fwd, guard, center), for a 3 week pre-season and a 15 week in-season.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"brush: r; title: ; notranslate\" title=\"\">\r\nset.seed(55)\r\nathlete &lt;- rep(LETTERS&#x5B;1:10], each = 15)\r\nposition &lt;- rep(c(&quot;fwd&quot;, &quot;fwd&quot;, &quot;fwd&quot;, &quot;fwd&quot;, &quot;guard&quot;, &quot;guard&quot;, &quot;guard&quot;, &quot;center&quot;, &quot;center&quot;, &quot;center&quot;), each = 15)\r\nweek &lt;- rep(c(&quot;pre_1&quot;, &quot;pre_2&quot;, &quot;pre_3&quot;, 1:12), times = 10)\r\ntraining_load &lt;- round(rnorm(n = length(athlete), mean = 1500, sd = 350), 1)\r\n\r\ndf &lt;- data.frame(athlete, position, week, training_load)\r\ndf$flag &lt;- factor(df$flag, levels = c(&quot;high&quot;, &quot;normal&quot;, &quot;low&quot;))\r\ndf$week &lt;- factor(df$week, levels = c(&quot;pre_1&quot;, &quot;pre_2&quot;, &quot;pre_3&quot;, &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;10&quot;, &quot;11&quot;, &quot;12&quot;)) \r\n\r\ndf %&gt;% head()\r\n<\/pre>\n<p>The first few rows of the data look like this:<\/p>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.26.14-AM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1797\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.26.14-AM.png\" alt=\"\" width=\"441\" height=\"191\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.26.14-AM.png 564w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.26.14-AM-300x130.png 300w\" sizes=\"auto, (max-width: 441px) 100vw, 441px\" \/><\/a><\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Shiny App<\/strong><\/span><\/p>\n<p>There are two components to building a {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} app:<\/p>\n<p>1) The user interface, which defines the input that the user specifies on the web app.<br \/>\n2) The server, which reacts to what the user does and then produces the output that the user sees.<\/p>\n<p>The user interface I will define as <span style=\"color: #0000ff;\"><strong>tl_ui<\/strong><\/span> (training load user interface). I start by creating a fluid page (since the user is going to be defining what they want). I set the parameters of what the user can control. Here, I provide them with the ability to select the position group of interest and then the week(s) of training they would like to see in their table and plot.<\/p>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1798\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM-1024x511.png\" alt=\"\" width=\"625\" height=\"312\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM-1024x511.png 1024w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM-300x150.png 300w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM-768x383.png 768w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM-624x312.png 624w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.31.35-AM.png 1274w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/p>\n<p>The server, which I call <span style=\"color: #0000ff;\"><strong>tl_server<\/strong><\/span> (training load server), is a little more involved as we need to have it take an input from the user interface , get the correct data from our stored data frame, and then produce an output. This part can be a bit involved. Below you can see the code and I&#8217;ll try and articulate a few of the main things that are taking place.<\/p>\n<p>1) First, I get the data for the visualization the user wants. I&#8217;m using our simulated data and I calculate the z-score for each individual athlete, using the custom function I wrote. After that I create my flag (\u00b11 SD from the individual athlete&#8217;s mean) and then I finally filter the data specific to the inputs provided in the user interface. This last part is critical. If I filter before applying my z-score and flags, then the mean and SD used to calculate the z-score will only be relative to the data that has been filtered down. This probably isn&#8217;t what we want. Rather, we will want to calculate our z-score using all data, season-to-date. So we retain everything and perform our calculation and then we filter the data.<\/p>\n<p>2) After getting the data, I build my plot using {<span style=\"color: #0000ff;\"><strong>ggplot2<\/strong><\/span>}.<\/p>\n<p>3) Then I create another data set specific to the table weeks of training selected by the user. This time, we will retain the raw values (non-standardized) for each athlete as the user may want to see raw values in a table. Notice that I also pivot that data using the <strong><span style=\"color: #0000ff;\">pivot_wider()<\/span> <\/strong>function as the data is currently stored in a long format with each row representing a new week for the athlete. Instead, I&#8217;d like the user to see this data across their screen, with each week representing a new column. As the user selects new weeks of data to visualize our server will tell {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} to populate the next column for that athlete.<\/p>\n<p>4) Once I have the data we need, I simply render the table.<\/p>\n<p>Those 2 steps complete the building of our {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} app!<\/p>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.34.42-AM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1799\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.34.42-AM.png\" alt=\"\" width=\"407\" height=\"907\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.34.42-AM.png 407w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.34.42-AM-135x300.png 135w\" sizes=\"auto, (max-width: 407px) 100vw, 407px\" \/><\/a><\/p>\n<p>Finally, the last step is to run the {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} app using the <span style=\"color: #0000ff;\"><strong>shinyApp()<\/strong><\/span> function which takes two arguments, the name of your user interface (<span style=\"color: #0000ff;\"><strong>tl_ui<\/strong><\/span>) and the name of your server (<span style=\"color: #0000ff;\"><strong>tl_server<\/strong><\/span>). This will open up a shiny app in a new window within R Studio that you can then open in your browser.<\/p>\n<p>Visually, you can see what the app looks like below. If you want to see it in action, aside from getting my code and running it yourself, I put together this short video &gt;&gt; <a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/R-Tips-Tricks-Training-Load-Dashboard.mov\"><strong><span style=\"color: #0000ff;\">R Tips &amp; Tricks &#8211; Training Load Dashboard<\/span><\/strong><\/a><\/p>\n<p><a href=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1800\" src=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM-1024x917.png\" alt=\"\" width=\"625\" height=\"560\" srcset=\"https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM-1024x917.png 1024w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM-300x269.png 300w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM-768x687.png 768w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM-624x559.png 624w, https:\/\/optimumsportsperformance.com\/blog\/wp-content\/uploads\/2020\/07\/Screen-Shot-2020-07-21-at-9.44.20-AM.png 1602w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/p>\n<p><span style=\"text-decoration: underline;\"><strong>Conclusion<\/strong><\/span><\/p>\n<p>As you can tell, it is pretty easy to build and customize your own {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} app. I&#8217;ll admit, the syntax for {<span style=\"color: #0000ff;\"><strong>shiny<\/strong><\/span>} can get a bit frustrating but once you get the hang of it, like most things, it isn&#8217;t that bad. Also, unless you are doing really complex tasks, most of the syntax for something as simple as a training load dashboard wont be too challenging. Finally, such a quick and easy method not only allows you to customize things exactly as you want them, but it also saves you money from having to purchase an expensive athlete management system.<\/p>\n<p>As always, you can obtain the code for this tutorial on my <span style=\"color: #0000ff;\"><strong><a style=\"color: #0000ff;\" href=\"https:\/\/github.com\/pw2\/R-Tips-Tricks\">GITHUB<\/a><\/strong><\/span> page.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In TidyX Episode 19 we discussed a way of building a dashboard with the {formattable} package. The dashboard included both a data table and a small visual using {sparkline}. Such a table is great when you need to make a report for a presentation but there are times when you might want to have something [&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,42],"tags":[],"class_list":["post-1796","post","type-post","status-publish","format-standard","hentry","category-r-tips-tricks","category-sports-science"],"_links":{"self":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/1796","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=1796"}],"version-history":[{"count":4,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/1796\/revisions"}],"predecessor-version":[{"id":1805,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/posts\/1796\/revisions\/1805"}],"wp:attachment":[{"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/media?parent=1796"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/categories?post=1796"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/optimumsportsperformance.com\/blog\/wp-json\/wp\/v2\/tags?post=1796"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}