Simple Athlete Monitoring Dashboard Tutorial

A tutorial for building a simple Athlete Monitoring Dashboard by a complete beginner

Simple Athlete Monitoring Dashboard Tutorial

A tutorial for building a simple Athlete Monitoring Dashboard by a complete beginner

Introduction

Athlete monitoring has become a common practice in high-performance sports, with the primary goal of using collected data to improve training, enhance performance, and minimise injury and illness risks [@bourdon2017; @coutts2017; @neupert2024; @thornton2019]. Monitoring systems involve the collection and analysis of data related to both internal and external training loads, as well as athlete responses [@bourdon2017; @coutts2017; @thornton2019].

External training loads are objective measures of the work performed by the athlete, including power output, speed, acceleration, time-motion analysis, and GPS parameters @bourdon2017. These metrics, often derived from GPS, can include distance covered at various speeds or the number of acceleration efforts @bourdon2017. However, it's crucial to define these metrics precisely to coaches and practitioners as interpretations can vary @bourdon2017. For example, 'sprint distance' might refer to distance covered above a certain velocity threshold rather than a traditional sprint from a static start @bourdon2017. Internal training loads, on the other hand, refer to the biological stressors placed on the athlete and are measured through indicators like heart rate, blood lactate, oxygen consumption, and ratings of perceived exertion (RPE) [@bourdon2017; @neupert2024; @coutts2017; @thornton2019].

The fitness-fatigue model provides a theoretical basis for athlete monitoring by quantifying the relationship between training load, fitness, fatigue and performance [@coutts2017; @neupert2024]. This model suggests that training results in both a positive fitness response and a negative fatigue response [@coutts2017; @thornton2019]. An appropriate balance between training and recovery is necessary for positive adaptations to occur [@bourdon2017; @coutts2017]. During intensified training, fatigue can accumulate, negatively impacting performance [@coutts2017; @neupert2024; @bourdon2017]. However, the complex psychobiological adaptations that occur make it difficult to predict the fitness and fatigue effects of training for individual athletes, requiring regular monitoring of these dose-response relationships [@coutts2017; @thornton2019].

Athlete monitoring systems use various tools and methods to assess training load and athlete responses. These include questionnaires to assess mood, stress, and recovery [@bourdon2017; @coutts2017], and wearable technologies for monitoring external loads @bourdon2017. Within session, common indices include the Profile of Mood States and its derivatives, Borg RPE, session RPE (sRPE), and the Recovery-Stress Questionnaire for Athletes @bourdon2017. The Acute Recovery and Stress Scale and its short version, the Short Recovery and Stress Scale, have also been developed for quantifying recovery and stress @bourdon2017. For internal load measures, heart rate is a frequently used non-invasive method @coutts2017. Subjective fatigue is considered essential to monitor, and can be assessed through various psychometric tools, though short, customised questionnaires are often preferred for regular use [@coutts2017; @neupert2024; @thornton2019]. Biochemical markers may also be used, though more often to investigate overtraining [@coutts2017; @thornton2019].

Analysis of training load data is critical for making informed decisions about training efficacy, athlete readiness, and injury risk [@bourdon2017; @thornton2019]. Common analytical methods include calculating acute and chronic workloads, and determining the acute:chronic workload ratio (ACWR) [@bourdon2017; @thornton2019]. An ACWR in the range of 0.8 to 1.3 is associated with lower injury risk, while an ACWR ≥1.5 increases injury risk @bourdon2017. High chronic training loads can protect athletes from injury by improving load tolerance and developing physical qualities [@bourdon2017; @coutts2017]. However, both overtraining and undertraining can increase injury likelihood [@bourdon2017; @coutts2017].

Despite the progress in athlete monitoring, some challenges remain [@bourdon2017; @thornton2019]. A major challenge is the ability to make meaningful inferences from training data [@neupert2024; @thornton2019]. The efficacy of monitoring systems has been questioned with some research suggesting that the evidence supporting it is not high @neupert2024. Key barriers to AMS efficacy include metric-related factors such as measure reliability, validity, and scientific underpinnings, and socio-environmental factors such as stakeholder buy-in, culture and practitioner expertise @neupert2024. Practitioners may lack confidence in their monitoring systems, and it has been noted that practitioners may focus more on metric-related factors over socio-environmental factors [@bourdon2017; @neupert2024; @thornton2019].

The importance of data visualisation and communication has also been highlighted @thornton2019. The ability of practitioners to communicate important information to coaches is essential to a successful monitoring system and can be facilitated by attractive, informative reports highlighting the key findings [@bourdon2017; @neupert2024; @thornton2019].

It is therefore important that the value of athlete monitoring systems is regularly reviewed, considering both the metric and socio-environmental factors to provide maximum benefit [@neupert2024; @thornton2019].

Loading Libraries

Firstly, let's start with the prerequisites. Loading the necessary libraries is crucial for building our Athlete Monitoring Dashboard. Each library serves a specific purpose:

  • shiny and shinydashboard: These libraries are used to create interactive web applications and dashboards in R.
  • readr: This library provides functions to read rectangular data, such as CSV files.
  • tidyverse: A collection of R packages designed for data science, including dplyr, ggplot2, readr, and others.
  • ggplot2: A part of the tidyverse, this library is used for data visualization.
  • plotly: This library allows for interactive, web-based visualizations built on top of ggplot2.
  • dplyr: Another part of the tidyverse, used for data manipulation.
  • DT: This library provides an interface to the DataTables library, enabling the creation of interactive tables.
  • RColorBrewer: This library offers colours schemes for maps and other graphics.
  • janitor: This library is used for data cleaning, including functions to clean column names and remove empty rows.

Now, let's load these libraries:

library(shiny)
library(shinydashboard)
library(readr)
library(tidyverse)
library(ggplot2)
library(plotly)
library(dplyr)
library(DT)
library(RColorBrewer)
library(janitor)

Loading the dataset

ams_data_original <- read_csv("monitoring_data_r.csv") %>%
  clean_names()

ams_data <- ams_data_original %>% 
  pivot_longer(cols = -c(1:6), names_to = "variable", values_to = "value") %>% 
  write_csv("ams_data.csv")

This code snippet reads a CSV file named "monitoring_data_r.csv" into a dataframe called ams_data_original. It then cleans the column names of the dataframe using the clean_names() function from the janitor package. After that, it transforms the dataframe from a wide format to a long format using the pivot_longer() function. The columns from the 7th to the last are pivoted into two columns: "variable" and "value". Finally, the transformed dataframe is written to a new CSV file named "ams_data.csv".

Custom colours palette for line graphs

custom_colours <- c("#FF6B6B", "#4ECDC4", "#45B7D1", 
                   "#96CEB4", "#FFEEAD", "#D4A5A5", 
                   "#9B59B6", "#3498DB", "#E74C3C", 
                   "#2ECC71")

Custom colours palettes can be defined to ensure consistency and aesthetic appeal in visualizations. In this case, a vector of hexadecimal colours codes is created to be used in line graphs. These colours can be customized to suit the specific requirements of the dashboard, in this case, here is a breakdown of what each hexadecimal colours code corresponds to:

  • 2ECC71: A shade of green
  • E74C3C: A shade of red
  • 3498DB: A shade of blue
  • 9B59B6: A shade of purple
  • D4A5A5: A light pinkish colours
  • FFEEAD: A shade of yellow
  • 96CEB4: A shade of green
  • 45B7D1: A shade of blue
  • 4ECDC4: A shade of turquoise
  • FF6B6B: A shade of red

These colours can be used to differentiate various lines or elements in your visualizations, making them more visually appealing and easier to interpret.

Define the plot functions

Daily Metrics Plot Function

plot_ams_data_daily <- function(data, session_date, metric, title, subtitle, caption) {
  p1 <- ggplot(data %>% filter(date %in% session_date, variable %in% metric),
               aes(x = pid, y = value, fill = position)) +
    geom_col() +
    scale_x_continuous(breaks = seq(from = 1, to = 28, by = 1)) +
    scale_fill_brewer(palette = "Set3") +
    coord_flip() +
    theme_dark() +
    theme(
      plot.background = element_rect(fill = "#2D2D2D"),
      panel.background = element_rect(fill = "#2D2D2D"),
      panel.grid.major = element_line(color = "#404040"),
      panel.grid.minor = element_line(color = "#404040"),
      text = element_text(color = "white"),
      axis.text = element_text(color = "white"),
      legend.background = element_rect(fill = "#2D2D2D"),
      legend.text = element_text(color = "white")
    ) +
    facet_wrap(vars(variable), scales = "free_x", ncol = 2) +
    labs(
      x = "Player ID",
      y = "Value",
      fill = "Position",
      title = title,
      subtitle = subtitle,
      caption = caption
    )
  
  ggplotly(p1) %>% 
    layout(paper_bgcolor = "#2D2D2D", plot_bgcolor = "#2D2D2D",
           font = list(color = "white"))
}

This function, plot_ams_data_daily, takes the following arguments:

  • data: The dataframe containing the athlete monitoring data.
  • session_date: The date(s) for which the data should be plotted.
  • metric: The metric(s) to be plotted.
  • title: The title of the plot.
  • subtitle: The subtitle of the plot.
  • caption: The caption of the plot.

The function creates a bar plot using ggplot2 to visualize the daily monitoring metrics for the selected date(s) and metric(s). The plot is then converted to an interactive plot using plotly for better interactivity. The function also customizes the appearance of the plot, including the colours palette, theme, and labels. Additionally, it includes a facet wrap to display multiple metrics in separate panels for better comparison.

Longitudinal Metrics Plot Function

plot_ams_data_longitudinal <- function(data, player, metric, title, subtitle, caption) {
  p2 <- ggplot(data %>% filter(pid %in% player, variable %in% metric),
               aes(x = date, y = value)) +
    geom_line(aes(label = pid, group = pid, colour = factor(pid)), size = 1.1) +
    geom_point(shape = 19, size = 3.5, aes(colour = factor(pid))) +
    scale_colour_manual(values = custom_colours) +
    geom_text(
      data = data %>% filter(pid %in% player, variable %in% metric, date == "05/10/2024"),
      aes(label = pid), 
      hjust = 0, 
      vjust = 0, 
      nudge_x = 0.1, 
      nudge_y = 0.1, 
      color = "white"
    ) +
    theme_dark() +
    theme(
      plot.background = element_rect(fill = "#2D2D2D"),
      panel.background = element_rect(fill = "#2D2D2D"),
      panel.grid.major = element_line(color = "#404040"),
      panel.grid.minor = element_line(color = "#404040"),
      text = element_text(color = "white"),
      axis.text = element_text(color = "white"),
      legend.position = "none"
    ) +
    facet_wrap(vars(variable), scales = "free", ncol = 2) +
    labs(
      x = "Date",
      y = "Value",
      title = title,
      subtitle = subtitle,
      caption = caption
    )
  
  ggplotly(p2) %>% 
    layout(paper_bgcolor = "#2D2D2D", plot_bgcolor = "#2D2D2D",
           font = list(color = "white"))
}

This function, plot_ams_data_longitudinal, takes the following arguments:

  • data: The dataframe containing the athlete monitoring data.
  • player: The player(s) for which the data should be plotted.
  • metric: The metric(s) to be plotted.
  • title: The title of the plot.
  • subtitle: The subtitle of the plot.
  • caption: The caption of the plot.

The function creates a line plot using ggplot2 to visualize the longitudinal monitoring metrics for the selected player(s) and metric(s). The plot is then converted to an interactive plot using plotly for better interactivity. The function also customizes the appearance of the plot, including the colours palette, theme, and labels. Additionally, it includes text annotations for each player at a specific date to provide additional context.

Guage Plot Function

The create_gauge_plot function is used to create a gauge plot for a specific metric and player on a selected date. The function takes the following arguments:

  • data: The dataframe containing the athlete monitoring data.
  • session_date: The date for which the gauge plot should be created.
  • metric: The metric for which the gauge plot should be created.
  • player: The player for which the gauge plot should be created.

The function filters the data based on the selected date, metric, and player. It then extracts the value of the metric for the selected player on the selected date. The maximum value of the metric across all players is also calculated to set the range of the gauge plot. The function uses the plot_ly function from the plotly package to create an indicator gauge plot. The gauge plot displays the value of the metric for the selected player on the selected date, with a gauge range from 0 to the maximum value of the metric. The colours of the gauge bar is set to a specific colours, and the background colours of the plot is customized. The function returns the gauge plot as an interactive plotly object.

create_gauge_plot <- function(data, session_date, metric, player) {
  filtered_data <- data %>% 
    filter(date %in% session_date, 
           variable %in% metric,
           pid %in% player)
  
  value <- filtered_data$value[1]
  max_value <- max(data$value[data$variable %in% metric])
  
  plot_ly(
    type = "indicator",
    mode = "gauge+number",
    value = value,
    gauge = list(
      axis = list(range = list(0, max_value), 
                  tickcolor = "white",
                  ticktext = list("Low", "Medium", "High"),
                  tickvals = list(0, max_value/2, max_value)),
      bar = list(color = "#FF6B6B"),
      bgcolor = "#2D2D2D",
      bordercolor = "white"
    ),
    title = list(text = paste("Gauge for", metric),
                 font = list(color = "white")),
    domain = list(x = c(0, 1), y = c(0, 1))
  ) %>%
    layout(paper_bgcolor = "#2D2D2D",
           plot_bgcolor = "#2D2D2D",
           font = list(color = "white"))
}