Introduction

During my final semester at Miami University, I had the opportunity to work with the Senior Synchronized Skating Team, analyzing past competition data to identify ways to optimize their performance. My goal was to help the team focus on specific skating elements (or moves) that could maximize their overall score in competitions.

In synchronized skating, teams are judged using the International Judging System (IJS), where the combined score from both the Short Program and Long Program can reach a maximum of 190 points. To assist the team in strategic planning, I developed an R script that simulates a competition scenario based on IJS scoring protocols.

This script allows for:

By running thousands of simulations, this analysis helps pinpoint optimal scoring strategies and provides actionable insights on which elements the team should refine to achieve a score close to 190.

Required Libraries

if (!require(tidyverse)) install.packages("tidyverse")
## Loading required package: tidyverse
## Warning: package 'ggplot2' was built under R version 4.3.2
## Warning: package 'tidyr' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
if (!require(readxl)) install.packages("readxl")
## Loading required package: readxl
library(tidyverse)
library(readxl)

Importing Data

file_path <- "Copy of 24-25 IJS Protocol Testing.xlsx"
sov <- read_excel(file_path, sheet = "Scale of Value")

# Remove the first row (column headers) and convert BASE to numeric
sov <- sov[-1, ]
sov$BASE <- as.numeric(sov$BASE)

# Rename columns for consistency
colnames(sov) <- c("Element", "BASE", "-5", "-4", "-3", "-2", "-1", "0", "1", "2", "3", "4", "5")

Program Definitions

short_program <- tibble(
  Element = c("SySp4", "I4+pi4", "TrE4", "NHEb+s4", "ME4"),
  Base_Value = c(6.50, 7.50, 5.00, 5.00, 6.50)
)

long_program <- tibble(
  Element = c("Pa4", "PB4", "GL4", "I4+pi4", "TwE4", "ME4", "Cr1", "I4+piB", "NHE4+sB", "AC2"),
  Base_Value = c(6.50, 6.50, 7.50, 7.00, 6.50, 6.50, 4.00, 4.25, 4.00, 4.00)
)

Functions for GOE and Component Scores

# Function to generate GOE scores between 1 and 3
generate_goe <- function(num_elements) {
  sample(1:3, num_elements, replace = TRUE)
}

# Function to generate component scores
generate_component_scores <- function() {
  runif(3, 7.00, 9.75)  # Generate 3 scores between 7.00 and 9.75
}

calculate_goe_value <- function(element, base_value, goe) {
  goe_value <- sov[[as.character(goe)]][sov$Element == element]
  if (is.na(goe_value) || length(goe_value) == 0) {
    goe_value <- base_value * (goe / 10)
  }
  as.numeric(goe_value)
}

Program Score Calculation

calculate_program_score <- function(program, goe_scores, component_scores, is_short_program) {
  technical_score <- program %>%
    mutate(
      GOE = goe_scores,
      GOE_Value = mapply(calculate_goe_value, Element, Base_Value, GOE),
      Total = Base_Value + GOE_Value
    )

  component_factor <- if(is_short_program) 1.33 else 2.67
  component_score <- sum(component_scores) * component_factor

  list(
    technical_details = technical_score,
    component_scores = component_scores,
    technical_score = sum(technical_score$Total),
    component_score = component_score,
    total_score = sum(technical_score$Total) + component_score
  )
}

Simulating the Competition

simulate_competition <- function() {
  sp_goe <- generate_goe(nrow(short_program))
  lp_goe <- generate_goe(nrow(long_program))

  sp_components <- generate_component_scores()
  lp_components <- generate_component_scores()

  sp_details <- calculate_program_score(short_program, sp_goe, sp_components, TRUE)
  lp_details <- calculate_program_score(long_program, lp_goe, lp_components, FALSE)

  total_score <- sp_details$total_score + lp_details$total_score

  list(
    SP = sp_details,
    LP = lp_details,
    Total = total_score
  )
}

# Run simulations
num_simulations <- 10000
results <- replicate(num_simulations, simulate_competition(), simplify = FALSE)

# Find best combination
best_simulation <- which.min(abs(sapply(results, function(x) x$Total) - 190))
best_result <- results[[best_simulation]]

cat("\nBest combination for score close to 190:\n")
## 
## Best combination for score close to 190:
cat("\nShort Program:\n")
## 
## Short Program:
print(best_result$SP$technical_details)
## # A tibble: 5 × 5
##   Element Base_Value   GOE GOE_Value Total
##   <chr>        <dbl> <int>     <dbl> <dbl>
## 1 SySp4          6.5     1      0.65  7.15
## 2 I4+pi4         7.5     2      1.5   9   
## 3 TrE4           5       2      1     6   
## 4 NHEb+s4        5       1      0.5   5.5 
## 5 ME4            6.5     1      0.65  7.15
cat("Component Scores:", best_result$SP$component_scores, "\n")
## Component Scores: 7.422705 7.528882 8.332669
cat("Total Short Program Score:", best_result$SP$total_score, "\n")
## Total Short Program Score: 65.76806
cat("\nLong Program:\n")
## 
## Long Program:
print(best_result$LP$technical_details)
## # A tibble: 10 × 5
##    Element Base_Value   GOE GOE_Value Total
##    <chr>        <dbl> <int>     <dbl> <dbl>
##  1 Pa4           6.5      2      1.3   7.8 
##  2 PB4           6.5      1      0.65  7.15
##  3 GL4           7.5      2      1.5   9   
##  4 I4+pi4        7        1      0.75  7.75
##  5 TwE4          6.5      1      0.65  7.15
##  6 ME4           6.5      1      0.65  7.15
##  7 Cr1           4        2      0.8   4.8 
##  8 I4+piB        4.25     3      1.27  5.52
##  9 NHE4+sB       4        1      0.4   4.4 
## 10 AC2           4        1      0.4   4.4
cat("Component Scores:", best_result$LP$component_scores, "\n")
## Component Scores: 7.19239 7.039825 8.075722
cat("Total Long Program Score:", best_result$LP$total_score, "\n")
## Total Long Program Score: 124.6872
cat("\nTotal Combined Score:", best_result$Total, "\n")
## 
## Total Combined Score: 190.4553