Chess ELO calculations
Based on difference in ratings between the chess players and each of their opponents in our Project 1 tournament, calculate each player’s expected score (e.g. 4.3) and the difference from their actual score (e.g 4.0). List the five players who most overperformed relative to their expected score, and the five players that most underperformed relative to their expected score.
Step 1: Calculate the expected score of each player using the ELO formula mentioned here https://www.youtube.com/watch?v=AsYfbmp0To0 Step 2: calculate difference of players expected score from their actual score, in my data “Total_Points”. Step 3: Sort result data in descending order to find five most overperformed players relative to their actual score. step 4: Sort result data in ascending order to find five most underperformed players relative to their actual score.
-Find expected score of all the palyers, sort them to analyse it.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── 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
# 1. Read and clean text
text <- read_lines("https://raw.githubusercontent.com/lhamo07/Data-607-Assignment/refs/heads/Project1/tournamentinfo.txt")
tournamentInfo_df <- tibble(text = text) %>%
filter(!str_detect(text, "^\\s*-+\\s*$")) %>%
slice(-(1:2)) %>%
mutate(person = ceiling(row_number() / 2)) %>%
group_by(person) %>%
summarise(text = paste(text, collapse = " "), .groups = "drop") %>%
mutate(
Pair_Num = as.numeric(str_extract(text, "^\\s*(\\d+)")),
Player_name = str_trim(str_extract(text, "(?<=\\|\\s)[A-Z]+(?:[-\\s]+[A-Z]+)+(?=\\s*\\|)")),
Total_Points = as.numeric(str_extract(text, "(?<=\\|)\\s*\\d+\\.\\d+")),
Player_pre_Rating = as.numeric(str_extract(text, "(?<=R:)\\s*\\d+"))
)
# 2. Extract raw round text blocks and parse opponent ID numbers
rounds_df <- tournamentInfo_df %>%
mutate(rounds_text = str_extract(text, "(\\|\\s*[WLDXHP\\s]\\s*\\d*\\s*){7}(?=\\|)")) %>%
separate(rounds_text, into = c("drop", "R1", "R2", "R3", "R4", "R5", "R6", "R7"), sep = "\\|") %>%
mutate(across(R1:R7, ~ as.numeric(str_extract(.x, "\\d+")))) %>%
select(Pair_Num, Player_name, Total_Points, Player_pre_Rating, R1:R7)
# 3. Create a clean lookup table for player ratings
rating_lookup <- rounds_df %>%
select(Pair_Num, Player_pre_Rating)
# 4. Map opponent IDs to their respective pre-tournament ratings
opponent_info_df <- rounds_df %>%
pivot_longer(cols = R1:R7, names_to = "Round", values_to = "Opponent_Num") %>%
left_join(rating_lookup, by = c("Opponent_Num" = "Pair_Num")) %>%
rename(Opponent_Pre_Rating = Player_pre_Rating.y, Player_pre_Rating = Player_pre_Rating.x) %>%
# Pivot back into a wide format showing Opponent ID and Opponent Rating side-by-side per round
pivot_wider(
names_from = Round,
values_from = c(Opponent_Num, Opponent_Pre_Rating),
names_glue = "{Round}_{.value}"
) %>%
# Reorder columns chronologically (R1 info, R2 info, etc.)
select(
Pair_Num, Player_name, Total_Points, Player_pre_Rating,
R1_Opponent_Pre_Rating,
R2_Opponent_Pre_Rating,
R3_Opponent_Pre_Rating,
R4_Opponent_Pre_Rating,
R5_Opponent_Pre_Rating,
R6_Opponent_Pre_Rating,
R7_Opponent_Pre_Rating
)
opponent_info_df
## # A tibble: 64 × 11
## Pair_Num Player_name Total_Points Player_pre_Rating R1_Opponent_Pre_Rating
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 1 GARY HUA 6 1794 1436
## 2 2 DAKSHESH DARU… 6 1553 1175
## 3 3 ADITYA BAJAJ 6 1384 1641
## 4 4 PATRICK H SCH… 5.5 1716 1363
## 5 5 HANSHI ZUO 5.5 1655 1242
## 6 6 HANSEN SONG 5 1686 1399
## 7 7 GARY DEE SWAT… 5 1649 1092
## 8 8 EZEKIEL HOUGH… 5 1641 1384
## 9 9 STEFANO LEE 5 1411 1745
## 10 10 ANVIT RAO 5 1365 1604
## # ℹ 54 more rows
## # ℹ 6 more variables: R2_Opponent_Pre_Rating <dbl>,
## # R3_Opponent_Pre_Rating <dbl>, R4_Opponent_Pre_Rating <dbl>,
## # R5_Opponent_Pre_Rating <dbl>, R6_Opponent_Pre_Rating <dbl>,
## # R7_Opponent_Pre_Rating <dbl>
The standard Elo expected score formula is \[E_A = \frac{1}{1 + 10^{(R_B - R_A) / 400}}\]
Where: * \(R_A\) is Player A’s current rating. * \(R_B\) is Player B’s current rating.
elo_df<-opponent_info_df%>%
# Drops any row where Rating_A or Rating_B is missing
drop_na(R1_Opponent_Pre_Rating, R2_Opponent_Pre_Rating, R3_Opponent_Pre_Rating,
R4_Opponent_Pre_Rating, R5_Opponent_Pre_Rating, R6_Opponent_Pre_Rating,
R7_Opponent_Pre_Rating)%>% mutate(ELO_R1=1/(1+10^((R1_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R2=1/(1+10^((R2_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R3=1/(1+10^((R3_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R4=1/(1+10^((R4_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R5=1/(1+10^((R5_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R6=1/(1+10^((R6_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_R7=1/(1+10^((R7_Opponent_Pre_Rating-Player_pre_Rating)/400)),
ELO_total=ELO_R1+ELO_R2+ELO_R3+ELO_R4+ELO_R5+ELO_R6+ELO_R7
)
elo_df
## # A tibble: 41 × 19
## Pair_Num Player_name Total_Points Player_pre_Rating R1_Opponent_Pre_Rating
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 1 GARY HUA 6 1794 1436
## 2 2 DAKSHESH DARU… 6 1553 1175
## 3 3 ADITYA BAJAJ 6 1384 1641
## 4 4 PATRICK H SCH… 5.5 1716 1363
## 5 5 HANSHI ZUO 5.5 1655 1242
## 6 6 HANSEN SONG 5 1686 1399
## 7 7 GARY DEE SWAT… 5 1649 1092
## 8 8 EZEKIEL HOUGH… 5 1641 1384
## 9 9 STEFANO LEE 5 1411 1745
## 10 10 ANVIT RAO 5 1365 1604
## # ℹ 31 more rows
## # ℹ 14 more variables: R2_Opponent_Pre_Rating <dbl>,
## # R3_Opponent_Pre_Rating <dbl>, R4_Opponent_Pre_Rating <dbl>,
## # R5_Opponent_Pre_Rating <dbl>, R6_Opponent_Pre_Rating <dbl>,
## # R7_Opponent_Pre_Rating <dbl>, ELO_R1 <dbl>, ELO_R2 <dbl>, ELO_R3 <dbl>,
## # ELO_R4 <dbl>, ELO_R5 <dbl>, ELO_R6 <dbl>, ELO_R7 <dbl>, ELO_total <dbl>
final_df<-elo_df%>%
mutate(Score_minus_Expected=Total_Points-ELO_total)%>%
select(Pair_Num,Player_name,Total_Points,Player_pre_Rating,ELO_total,Score_minus_Expected)
final_df
## # A tibble: 41 × 6
## Pair_Num Player_name Total_Points Player_pre_Rating ELO_total
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 1 GARY HUA 6 1794 5.16
## 2 2 DAKSHESH DARURI 6 1553 3.78
## 3 3 ADITYA BAJAJ 6 1384 1.95
## 4 4 PATRICK H SCHILLING 5.5 1716 4.74
## 5 5 HANSHI ZUO 5.5 1655 4.38
## 6 6 HANSEN SONG 5 1686 4.94
## 7 7 GARY DEE SWATHELL 5 1649 4.58
## 8 8 EZEKIEL HOUGHTON 5 1641 5.03
## 9 9 STEFANO LEE 5 1411 2.29
## 10 10 ANVIT RAO 5 1365 1.94
## # ℹ 31 more rows
## # ℹ 1 more variable: Score_minus_Expected <dbl>
overperform_df<-final_df%>%
arrange(desc(Score_minus_Expected))
head(overperform_df,n=5)
## # A tibble: 5 × 6
## Pair_Num Player_name Total_Points Player_pre_Rating ELO_total
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 3 ADITYA BAJAJ 6 1384 1.95
## 2 15 ZACHARY JAMES HOUGHTON 4.5 1220 1.37
## 3 10 ANVIT RAO 5 1365 1.94
## 4 46 JACOB ALEXANDER LAVALLEY 3 377 0.0432
## 5 9 STEFANO LEE 5 1411 2.29
## # ℹ 1 more variable: Score_minus_Expected <dbl>
underperform_df<-final_df%>%
arrange(Score_minus_Expected)
head(underperform_df,n=5)
## # A tibble: 5 × 6
## Pair_Num Player_name Total_Points Player_pre_Rating ELO_total
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 25 LOREN SCHWIEBERT 3.5 1745 6.28
## 2 30 GEORGE AVERY JONES 3.5 1522 6.02
## 3 42 JARED GE 3 1332 5.01
## 4 31 RISHI SHETTY 3.5 1494 5.09
## 5 35 JOSHUA DAVID LEE 3.5 1438 4.96
## # ℹ 1 more variable: Score_minus_Expected <dbl>
Based on the overperformed relative to their expected score data, Aditya Bajaj scored 6.0 total points even though his predicted score was only 1.95 points. This means Aditya performed much better than expected. Similarly, Zachary James Houghton and Anvit Rao also performed significantly better than the model predicted. Based on the underperformed relative to their expected score data, Loren Schwiebert scored only 3.5 total points even though his predicted score was 6.28 points. This means he performed much worse than expected. Similarly, George Avery Jones, Jared Ge, Rishi Shetty, and Joshua David Lee also scored fewer points than the model predicted.
[Large language model]. google.com. Accessed October 4, 2026. https://share.google/aimode/j7NEiRnQ1M8CZXzpC