The Elo rating system is commonly used to estimate the expected performance of chess players based on their ratings. In this assignment, I use the Elo formula to calculate each player’s expected tournament score and compare it with their actual score. The difference between the expected and actual scores is used to identify players who performed above or below expectations.
The objective of this assignment is to use the Elo rating system to estimate each player’s expected tournament score and compare it with the player’s actual score from the Project 1 chess tournament.
The analysis will identify:
This analysis uses the structured dataset created for Project 1.
The dataset contains one row for each player and includes the following variables:
This dataset was exported as a CSV file from Project 1 . The file will be loaded into R using the GitHub raw link.
chess <- read.csv(
"https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-607/main/Project-1/Project1_Chess_Data_Summary.csv"
)
chess <- as_tibble(chess)
glimpse(chess)Rows: 64
Columns: 5
$ Player_Name <chr> "Gary Hua", "Dakshesh Daruri", "Aditya Baj…
$ State <chr> "ON", "MI", "MI", "MI", "MI", "OH", "MI", …
$ Total_Points <dbl> 6.0, 6.0, 6.0, 5.5, 5.5, 5.0, 5.0, 5.0, 5.…
$ Pre_Rating <int> 1794, 1553, 1384, 1716, 1655, 1686, 1649, …
$ Average_Opponent_Pre_Rating <int> 1605, 1469, 1564, 1574, 1501, 1519, 1372, …
[1] "Player_Name" "State"
[3] "Total_Points" "Pre_Rating"
[5] "Average_Opponent_Pre_Rating"
The ELO rating system uses the difference between two players’ ratings to estimate the expected score of a game.
The expected score for a player is calculated using the following formula:
\[ E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}} \]
Where:
The expected score can be interpreted as the expected number of points from a game, where a win is worth 1 point, a draw is worth 0.5 points, and a loss is worth 0 points.
For example, a player with a higher rating than their opponent will have an expected score greater than 0.5, while a player with a lower rating will have an expected score below 0.5.
Sources:
- Glickman, M. E. A Comprehensive Guide to Chess Ratings: https://www.glicko.net/research/acjpaper.pdf
- ELO rating system (Wikipedia): https://en.wikipedia.org/wiki/Elo_rating_system
- The ELO Rating System for Chess and Beyond (YouTube, 2019):
https://www.youtube.com/watch?v=AsYfbmp0To0
In a standard ELO calculation, the expected score is calculated separately for every game using the actual rating of each opponent. The expected scores from all games are then added together to obtain the player’s expected tournament score.
The Project 1 dataset, however, contains the average pre-tournament rating of each player’s opponents rather than the individual opponent ratings for every round.
Because the individual opponent ratings are not available in this dataset, I will use the average opponent rating as an approximation.
The expected score for each player will therefore be calculated as:
\[ E = \frac{1}{1 + 10^{(\overline{R_{opp}} - R_{player})/400}} \]
The expected tournament score will then be estimated by multiplying the expected score per game by the seven rounds in the tournament:
\[ \text{Expected Tournament Score} = n \times E \]
Where:
This is an approximation of the standard Elo calculation because it uses the average opponent rating rather than the rating of each individual opponent.
Because the Project 1 output retains only the average opponent pre-rating (not the full opponent list), I approximated the expected tournament score by computing expected per-game score versus the average opponent rating, then multiplying by the number of rounds played.
In this tournament there are 7 rounds, so we use
n_rounds = 7.
n_rounds <- 7
elo_expected <- function(r_player, r_opp_avg) {
1 / (1 + 10^((r_opp_avg - r_player) / 400))
}
results <- chess %>%
mutate(
expected_per_game = elo_expected(Pre_Rating, Average_Opponent_Pre_Rating),
expected_score = n_rounds * expected_per_game,
actual_score = Total_Points,
performance_diff = actual_score - expected_score
)
results %>%
select(Player_Name, State, Pre_Rating, Average_Opponent_Pre_Rating, actual_score, expected_score, performance_diff) %>%
arrange(desc(performance_diff)) %>%
head(10) %>%
kable(digits = 3, caption = "Top 10 players by performance difference (Actual - Expected)")| Player_Name | State | Pre_Rating | Average_Opponent_Pre_Rating | actual_score | expected_score | performance_diff |
|---|---|---|---|---|---|---|
| Aditya Bajaj | MI | 1384 | 1564 | 6.0 | 1.833 | 4.167 |
| Zachary James Houghton | MI | 1220 | 1484 | 4.5 | 1.257 | 3.243 |
| Anvit Rao | MI | 1365 | 1554 | 5.0 | 1.764 | 3.236 |
| Jacob Alexander Lavalley | MI | 377 | 1358 | 3.0 | 0.025 | 2.975 |
| Amiyatosh Pwnanandam | MI | 980 | 1385 | 3.5 | 0.620 | 2.880 |
| Stefano Lee | ON | 1411 | 1523 | 5.0 | 2.409 | 2.591 |
| Ethan Guo | MI | 935 | 1495 | 2.5 | 0.268 | 2.232 |
| Michael R Aldrich | MI | 1229 | 1357 | 4.0 | 2.266 | 1.734 |
| Dakshesh Daruri | MI | 1553 | 1469 | 6.0 | 4.330 | 1.670 |
| Tejas Ayyagari | MI | 1011 | 1356 | 2.5 | 0.845 | 1.655 |
The analysis will proceed as follows:
Project1_Chess_Data_Summary.csv directly from the
GitHub raw link using read.csv() to ensure
reproducibility.\[ \text{Performance Difference} = \text{Actual Score} - \text{Expected Score} \]
The final results will include the following information:
Two groups of results will be presented:
Top 5 Overperformers
These are the five players with the largest positive difference between their actual score and their estimated expected score.
top_5_over <- results %>%
arrange(desc(performance_diff)) %>%
select(Player_Name, State, Pre_Rating, actual_score, expected_score, performance_diff) %>%
slice(1:5)
kable(top_5_over, digits = 3, caption = "Top 5 Overperformers (Actual - Expected)")| Player_Name | State | Pre_Rating | actual_score | expected_score | performance_diff |
|---|---|---|---|---|---|
| Aditya Bajaj | MI | 1384 | 6.0 | 1.833 | 4.167 |
| Zachary James Houghton | MI | 1220 | 4.5 | 1.257 | 3.243 |
| Anvit Rao | MI | 1365 | 5.0 | 1.764 | 3.236 |
| Jacob Alexander Lavalley | MI | 377 | 3.0 | 0.025 | 2.975 |
| Amiyatosh Pwnanandam | MI | 980 | 3.5 | 0.620 | 2.880 |
Top 5 Underperformers
These are the five players with the largest negative difference between their actual score and their estimated expected score.
top_5_under <- results %>%
arrange(performance_diff) %>%
select(Player_Name, State, Pre_Rating, actual_score, expected_score, performance_diff) %>%
slice(1:5)
kable(top_5_under, digits = 3, caption = "Top 5 Underperformers (Actual - Expected)")| Player_Name | State | Pre_Rating | actual_score | expected_score | performance_diff |
|---|---|---|---|---|---|
| Ashwin Balaji | MI | 1530 | 1.0 | 6.151 | -5.151 |
| Loren Schwiebert | MI | 1745 | 3.5 | 6.301 | -2.801 |
| George Avery Jones | ON | 1522 | 3.5 | 6.286 | -2.786 |
| Gaurav Gidwani | MI | 1552 | 3.5 | 6.089 | -2.589 |
| Chiedozie Okorie | MI | 1602 | 3.5 | 5.880 | -2.380 |
The histogram shows the distribution of performance differences between actual and expected scores. Positive values indicate players who scored more points than expected, while negative values indicate players who scored fewer points than expected. The numbers above the bars show the number of players in each performance-difference range.
ggplot(results, aes(x = performance_diff)) +
geom_histogram(
bins = 15,
fill = "lightblue",
color = "black"
) +
geom_text(
stat = "bin",
bins = 15,
aes(label = after_stat(count)),
vjust = -0.5
) +
labs(
title = "Distribution of Performance Difference (Actual - Expected)",
x = "Performance Difference",
y = "Number of Players"
) +
scale_x_continuous(
expand = expansion(mult = c(0.02, 0.08))
) +
theme_minimal()ggplot(results, aes(x = Pre_Rating, y = performance_diff)) +
geom_point(size = 3) +
geom_hline(yintercept = 0, linetype = "dashed") +
labs(
title = "Pre-Tournament Rating vs. Performance Difference",
x = "Pre-Tournament Rating",
y = "Performance Difference (Actual - Expected)"
) +
theme_minimal()This scatter plot shows the relationship between pre-tournament rating and performance difference. Players above zero scored more points than expected, while players below zero scored fewer points than expected. It helps show how actual performance compared with Elo-based expectations.
This analysis extends the Project 1 chess dataset by applying the Elo rating model to compare expected and actual tournament performance. Because the individual opponent ratings are not included in the Project 1 summary dataset, the average opponent rating will be used to estimate each player’s expected tournament score.
The resulting performance difference will provide a simple way to compare how each player performed relative to what would be expected from their pre-tournament rating and the average strength of their opponents.
Players with the largest positive performance differences scored considerably more points than expected based on their pre-tournament ratings and average opponent ratings. Several lower-rated players performed well above their rating-based expectations.
In contrast, players with the largest negative performance differences scored fewer points than expected based on the Elo model. These differences show where actual tournament results differed most from the expected results.
Because the expected scores were calculated using each player’s average opponent rating rather than the rating of each opponent in individual games, the results are an approximation of standard Elo expectations. Even with this limitation, the analysis provides a useful way to identify players whose actual performance differed most from their expected performance.