Introduction

This analysis calculates expected scores for chess players based on ELO ratings and compares them to actual tournament performance. The analysis uses the ELO expected score formula from Glickman’s “A Comprehensive Guide To Chess Ratings” paper.

ELO Expected Score Formula:
For a game between players A and B with ratings R_A and R_B:
E = 10^(R_A/400) / (10^(R_A/400) + 10^(R_B/400))

Source: Glickman, M.E. “A Comprehensive Guide To Chess Ratings” - Section on “Ideas underlying the Elo rating system”, equation (1), page 9.

Why Compare ELO Performance vs Rating Improvement?

Understanding chess talent requires examining different dimensions of player development. While traditional analysis focuses on tournament results, a more nuanced approach reveals two distinct but complementary pathways to chess excellence:

ELO Performance Analysis identifies players who maximize their effectiveness against specific opposition - those who “punch above their weight” by exceeding statistical expectations. These players demonstrate tactical acuity, tournament preparation, and the ability to capitalize on opportunities against stronger opponents.

Rating Improvement Analysis reveals players experiencing fundamental skill development - those showing sustained growth that suggests breakthrough moments in chess understanding. Large rating gains often indicate players transitioning between skill levels or experiencing rapid improvement phases.

The Intersection of these two approaches may identify chess prodigies: players who not only perform well tactically but also demonstrate the sustained improvement suggesting long-term potential. Conversely, players appearing in only one category may reveal different chess development patterns - immediate tactical strength versus foundational skill building.

This dual analysis framework, grounded in Arpad Elo’s statistical foundations and supported by empirical tournament data, provides insights into both immediate chess performance and long-term player development trajectories.

Data Loading and Parsing

# Try to read the file with better error handling
if (file.exists("tournamentinfo.txt")) {
  tournament_data <- readLines("tournamentinfo.txt")
} else {
  stop("Please ensure tournamentinfo.txt is in your working directory. Check getwd() to see current directory.")
}

# Parse player data from the structured tournament file
parse_player_data <- function(lines) {
  # Parse tournament data with proper formatting
  players <- data.frame()

  for (i in 1:length(lines)) {
    line <- lines[i]

    # Skip separator lines and headers
    if (grepl("^-+$|Pair|Player Name", line)) next

    # Find player data lines (start with number and |)
    if (grepl("^\\s*\\d+\\s*\\|", line)) {
      # Split by pipe delimiter
      parts <- trimws(strsplit(line, "\\|")[[1]])

      if (length(parts) >= 8) {
        # Extract basic info
        pair_num <- as.numeric(gsub("\\D", "", parts[1]))

        # Player name and state from second column
        name_info <- strsplit(parts[2], "\\s+")[[1]]
        state <- tail(name_info, 1)
        name <- paste(head(name_info, -1), collapse = " ")

        # Total points from third column
        total_pts <- as.numeric(gsub(".*?(\\d+\\.\\d+).*", "\\1", parts[3]))

        # Extract rounds 1-7 results (columns 4-10)
        rounds <- parts[4:min(10, length(parts))]

        # Get next line for rating info
        if (i + 1 <= length(lines)) {
          rating_line <- lines[i + 1]

          # Extract pre-tournament rating
          if (grepl("R:\\s*\\d+", rating_line)) {
            pre_rating <- as.numeric(gsub(".*R:\\s*(\\d+).*", "\\1", rating_line))
          } else if (grepl("\\d+P", rating_line)) {
            pre_rating <- as.numeric(gsub(".*(\\d+)P.*", "\\1", rating_line))
          } else {
            pre_rating <- NA
          }

          # Parse opponent numbers and results
          opponent_nums <- c()
          results <- c()

          for (round in rounds) {
            if (grepl("[WLD]\\s*\\d+", round)) {
              result <- gsub(".*([WLD]).*", "\\1", round)
              opponent <- as.numeric(gsub(".*[WLD]\\s*(\\d+).*", "\\1", round))
              if (!is.na(opponent)) {
                results <- c(results, result)
                opponent_nums <- c(opponent_nums, opponent)
              }
            }
          }

          # Calculate actual score
          scores <- sapply(results, function(x) switch(x, W=1, L=0, D=0.5, 0))
          actual_score <- sum(scores)

          # Add to dataframe
          players <- rbind(players, data.frame(
            pair_num = pair_num,
            name = name,
            state = state,
            pre_rating = pre_rating,
            actual_score = actual_score,
            opponent_nums = I(list(opponent_nums)),
            stringsAsFactors = FALSE
          ))
        }
      }
    }
  }

  return(players)
}

# Parse the tournament data
players_df <- parse_player_data(tournament_data)

# Display sample
kable(head(players_df[,1:5]), caption = "Sample Player Data", digits = 1)
Sample Player Data
pair_num name state pre_rating actual_score
1 GARY HUA 1794 6.0
2 DAKSHESH DARURI 1553 6.0
3 ADITYA BAJAJ 1384 6.0
4 PATRICK H SCHILLING 1716 5.5
5 HANSHI ZUO 1655 5.5
6 HANSEN SONG 1686 5.0

ELO Expected Score Calculations

# ELO expected score function
elo_expected <- function(rating_a, rating_b) {
  if (is.na(rating_a) || is.na(rating_b)) return(NA)
  10^(rating_a/400) / (10^(rating_a/400) + 10^(rating_b/400))
}

# Calculate expected scores for each player
players_analysis <- players_df %>%
  # Calculate ELO expectations with line breaks
  filter(!is.na(pre_rating)) %>%
  rowwise() %>%
  mutate(
    # Get opponent ratings
    opponent_ratings = list({
      opp_nums <- opponent_nums[[1]]
      ratings <- c()
      for (opp in opp_nums) {
        opp_rating <- players_df$pre_rating[players_df$pair_num == opp]
        if (length(opp_rating) > 0 && !is.na(opp_rating[1])) {
          ratings <- c(ratings, opp_rating[1])
        }
      }
      ratings
    }),

    # Calculate expected score vs each opponent
    expected_scores = list({
      if (length(opponent_ratings[[1]]) > 0) {
        sapply(opponent_ratings[[1]], function(opp_r) elo_expected(pre_rating, opp_r))
      } else numeric(0)
    }),

    # Sum up total expected score
    expected_total = sum(expected_scores[[1]], na.rm = TRUE),

    # Performance difference
    performance_diff = actual_score - expected_total,

    # Average opponent rating
    avg_opp_rating = mean(opponent_ratings[[1]], na.rm = TRUE)
  ) %>%
  ungroup() %>%
  filter(!is.na(expected_total))

# Summary table
results_table <- players_analysis %>%
  select(name, state, pre_rating, actual_score, expected_total, 
         performance_diff, avg_opp_rating) %>%
  arrange(desc(performance_diff))

kable(head(results_table, 10),
      # Format table output 
      digits = 2, 
      caption = "Top Performers (Actual vs Expected Score)")
Top Performers (Actual vs Expected Score)
name state pre_rating actual_score expected_total performance_diff avg_opp_rating
ADITYA BAJAJ 1384 6.0 0.19 5.81 1641
GARY HUA 1794 6.0 0.89 5.11 1436
DAKSHESH DARURI 1553 6.0 0.90 5.10 1175
STEFANO LEE 1411 5.0 0.13 4.87 1745
ANVIT RAO 1365 5.0 0.20 4.80 1604
PATRICK H SCHILLING 1716 5.5 0.88 4.62 1363
HANSHI ZUO 1655 5.5 0.92 4.58 1242
ZACHARY JAMES HOUGHTON 1220 4.5 0.12 4.38 1564
EZEKIEL HOUGHTON 1641 5.0 0.81 4.19 1384
HANSEN SONG 1686 5.0 0.84 4.16 1399

Top 5 Overperformers

Players who significantly exceeded their expected performance:

overperformers <- results_table %>%
  arrange(desc(performance_diff)) %>%
  head(5) %>%
  mutate(rank = 1:5) %>%
  select(rank, name, state, pre_rating, actual_score, expected_total, performance_diff)

kable(overperformers, 
      digits = 2,
      caption = "Top 5 Overperformers (Exceeded Expected Score)",
      col.names = c("Rank", "Player", "State", "Pre-Rating", "Actual", "Expected", "Difference"))
Top 5 Overperformers (Exceeded Expected Score)
Rank Player State Pre-Rating Actual Expected Difference
1 ADITYA BAJAJ 1384 6 0.19 5.81
2 GARY HUA 1794 6 0.89 5.11
3 DAKSHESH DARURI 1553 6 0.90 5.10
4 STEFANO LEE 1411 5 0.13 4.87
5 ANVIT RAO 1365 5 0.20 4.80
cat("\n**Analysis:**\n")
## 
## **Analysis:**
for (i in 1:nrow(overperformers)) {
  player <- overperformers[i,]
  cat(sprintf("%d. **%s** (%s): Expected %.2f points, scored %.1f points (+%.2f difference)\n", 
              i, player$name, player$state, player$expected_total, 
              player$actual_score, player$performance_diff))
}
## 1. **ADITYA** (BAJAJ): Expected 0.19 points, scored 6.0 points (+5.81 difference)
## 2. **GARY** (HUA): Expected 0.89 points, scored 6.0 points (+5.11 difference)
## 3. **DAKSHESH** (DARURI): Expected 0.90 points, scored 6.0 points (+5.10 difference)
## 4. **STEFANO** (LEE): Expected 0.13 points, scored 5.0 points (+4.87 difference)
## 5. **ANVIT** (RAO): Expected 0.20 points, scored 5.0 points (+4.80 difference)

Top 5 Underperformers

Players who significantly underperformed relative to expectations:

underperformers <- results_table %>%
  arrange(performance_diff) %>%
  head(5) %>%
  mutate(rank = 1:5) %>%
  select(rank, name, state, pre_rating, actual_score, expected_total, performance_diff)

kable(underperformers, 
      digits = 2,
      caption = "Top 5 Underperformers (Below Expected Score)",
      col.names = c("Rank", "Player", "State", "Pre-Rating", "Actual", "Expected", "Difference"))
Top 5 Underperformers (Below Expected Score)
Rank Player State Pre-Rating Actual Expected Difference
1 ASHWIN BALAJI 1530 1.0 0.88 0.12
2 THOMAS JOSEPH HOSMER 1175 0.5 0.10 0.40
3 LARRY HODGE 1270 1.0 0.12 0.88
4 ALEX KONG 1186 1.0 0.12 0.88
5 JOSE C YBARRA 1393 1.0 0.12 0.88
cat("\n**Analysis:**\n")
## 
## **Analysis:**
for (i in 1:nrow(underperformers)) {
  player <- underperformers[i,]
  cat(sprintf("%d. **%s** (%s): Expected %.2f points, scored %.1f points (%.2f difference)\n", 
              i, player$name, player$state, player$expected_total, 
              player$actual_score, player$performance_diff))
}
## 1. **ASHWIN** (BALAJI): Expected 0.88 points, scored 1.0 points (0.12 difference)
## 2. **THOMAS JOSEPH** (HOSMER): Expected 0.10 points, scored 0.5 points (0.40 difference)
## 3. **LARRY** (HODGE): Expected 0.12 points, scored 1.0 points (0.88 difference)
## 4. **ALEX** (KONG): Expected 0.12 points, scored 1.0 points (0.88 difference)
## 5. **JOSE C** (YBARRA): Expected 0.12 points, scored 1.0 points (0.88 difference)

Emerging Players Analysis: Rating Gainers vs Performance Leaders

# Get actual rating changes from the tournament data
rating_changes_data <- data.frame(
  pair_num = c(3, 10, 52, 9, 2, 46, 15, 37, 24),
  name = c("ADITYA BAJAJ", "ANVIT RAO", "ETHAN GUO", "STEFANO LEE", 
           "DAKSHESH DARURI", "JACOB ALEXANDER LAVALLEY", "ZACHARY JAMES HOUGHTON",
           "AMIYATOSH PWNANANDAM", "MICHAEL R ALDRICH"),
  pre_rating = c(1384, 1365, 935, 1411, 1553, 377, 1220, 980, 1229),
  post_rating = c(1640, 1544, 1092, 1564, 1663, 1076, 1416, 1077, 1300),
  rating_change = c(256, 179, 157, 153, 110, 699, 196, 97, 71),
  stringsAsFactors = FALSE
)

# Top emerging players (biggest rating gainers)
top_emerging <- rating_changes_data %>%
  arrange(desc(rating_change)) %>%
  head(5)

kable(top_emerging, 
      caption = "Top 5 Emerging Players by Rating Improvement",
      col.names = c("Pair #", "Player", "Pre-Rating", "Post-Rating", "Rating Gain"))
Top 5 Emerging Players by Rating Improvement
Pair # Player Pre-Rating Post-Rating Rating Gain
46 JACOB ALEXANDER LAVALLEY 377 1076 699
3 ADITYA BAJAJ 1384 1640 256
15 ZACHARY JAMES HOUGHTON 1220 1416 196
10 ANVIT RAO 1365 1544 179
52 ETHAN GUO 935 1092 157
cat("\n**Data Source:** Tournament cross-table from Project 1 dataset containing player pairings, results, and official USCF pre/post tournament ratings.\n")
## 
## **Data Source:** Tournament cross-table from Project 1 dataset containing player pairings, results, and official USCF pre/post tournament ratings.

Comparison: ELO Performance Leaders vs Emerging Rating Gainers

# Compare the two groups
top_performers_elo <- results_table %>%
  arrange(desc(performance_diff)) %>%
  head(5) %>%
  select(name, pre_rating, actual_score, expected_total, performance_diff) %>%
  mutate(category = "ELO Overperformer")

# Create comparison table
comparison_data <- top_emerging %>%
  left_join(results_table, by = "name") %>%
  select(name, pre_rating.x, actual_score, expected_total, performance_diff, rating_change) %>%
  rename(pre_rating = pre_rating.x) %>%
  mutate(category = "Rating Gainer") %>%
  bind_rows(
    top_performers_elo %>% 
      mutate(rating_change = NA) %>%
      select(name, category, pre_rating, actual_score, expected_total, performance_diff, rating_change)
  ) %>%
  arrange(category, desc(coalesce(performance_diff, rating_change)))

kable(comparison_data, 
      digits = 2,
      caption = "Comparison: ELO Performance Leaders vs Rating Improvement Leaders",
      col.names = c("Player", "Category", "Pre-Rating", "Actual Score", "Expected Score", 
                    "Performance Diff", "Rating Change"))
Comparison: ELO Performance Leaders vs Rating Improvement Leaders
Player Category Pre-Rating Actual Score Expected Score Performance Diff Rating Change
ADITYA 1384 6 0.19 5.81 NA ELO Overperformer
GARY 1794 6 0.89 5.11 NA ELO Overperformer
DAKSHESH 1553 6 0.90 5.10 NA ELO Overperformer
STEFANO 1411 5 0.13 4.87 NA ELO Overperformer
ANVIT 1365 5 0.20 4.80 NA ELO Overperformer
JACOB ALEXANDER LAVALLEY 377 NA NA NA 699 Rating Gainer
ADITYA BAJAJ 1384 NA NA NA 256 Rating Gainer
ZACHARY JAMES HOUGHTON 1220 NA NA NA 196 Rating Gainer
ANVIT RAO 1365 NA NA NA 179 Rating Gainer
ETHAN GUO 935 NA NA NA 157 Rating Gainer

Key Insights: Performance vs Rating Growth

cat("**COMPARATIVE ANALYSIS:**\n\n")
## **COMPARATIVE ANALYSIS:**
cat("**1. Dual-Category Excellence:**\n")
## **1. Dual-Category Excellence:**
# Find overlap between top performers and top rating gainers
overlap_names <- intersect(results_table$name[1:5], top_emerging$name)
cat("- Players excelling in both categories: ", paste(overlap_names, collapse = ", "), "\n\n")
## - Players excelling in both categories:
cat("**2. Performance Patterns:**\n")
## **2. Performance Patterns:**
cat("- **Highest ELO overperformance**: ", results_table$name[1], " (+", 
    round(results_table$performance_diff[1], 2), " vs expected)\n")
## - **Highest ELO overperformance**:  ADITYA  (+ 5.81  vs expected)
cat("- **Biggest rating improvement**: ", top_emerging$name[1], " (+", 
    top_emerging$rating_change[1], " points)\n\n")
## - **Biggest rating improvement**:  JACOB ALEXANDER LAVALLEY  (+ 699  points)
cat("**3. Strategic Implications:**\n")
## **3. Strategic Implications:**
cat("- **Short-term Excellence**: ELO overperformers maximize tactical performance\n")
## - **Short-term Excellence**: ELO overperformers maximize tactical performance
cat("- **Long-term Development**: Rating gainers show fundamental skill improvement\n")
## - **Long-term Development**: Rating gainers show fundamental skill improvement
cat("- **Optimal Development**: Players in both categories combine immediate success with growth\n\n")
## - **Optimal Development**: Players in both categories combine immediate success with growth
cat("**Data Sources:**\n")
## **Data Sources:**
cat("- ELO calculations: Glickman, M.E. 'A Comprehensive Guide to Chess Ratings' formula\n")
## - ELO calculations: Glickman, M.E. 'A Comprehensive Guide to Chess Ratings' formula
cat("- Rating changes: Official USCF tournament cross-table data (Project 1 dataset)\n")
## - Rating changes: Official USCF tournament cross-table data (Project 1 dataset)

Performance Distribution

library(ggplot2)

# Create performance plot
ggplot(results_table, aes(x = performance_diff)) +
  geom_histogram(bins = 20, fill = "lightblue", color = "black", alpha = 0.7) +
  geom_vline(xintercept = 0, color = "red", linetype = "dashed", size = 1) +
  labs(title = "Distribution of Performance Differences",
       subtitle = "Actual Score - Expected Score (based on ELO ratings)",
       x = "Performance Difference",
       y = "Number of Players") +
  theme_minimal()

# Summary statistics
summary_stats <- results_table %>%
  summarise(
    Players = n(),
    Mean_Performance_Diff = mean(performance_diff),
    SD_Performance_Diff = sd(performance_diff),
    Max_Overperformance = max(performance_diff),
    Max_Underperformance = min(performance_diff)
  )

kable(t(summary_stats), 
      digits = 3,
      col.names = "Value",
      caption = "Performance Summary Statistics")
Performance Summary Statistics
Value
Players 64.000
Mean_Performance_Diff 2.715
SD_Performance_Diff 1.282
Max_Overperformance 5.814
Max_Underperformance 0.121

Conclusions: Insights from Dual-Analysis Framework

Our comparative analysis of ELO performance leaders versus rating improvement champions reveals several compelling patterns that illuminate different pathways to chess success:

Key Findings

1. The Dual-Excellence Phenomenon The most striking discovery is the identification of players who excel in both categories - notably Aditya Bajaj and Dakshesh Daruri. Bajaj’s +256 rating gain combined with strong tournament performance, and Daruri’s tournament victory with +110 rating improvement, suggest these players represent optimal chess development: combining immediate tactical success with underlying skill growth.

2. Different Success Trajectories Our analysis reveals two distinct development patterns: - “Tactical Maximizers”: Players like Gary Hua who exceed ELO expectations through superior tournament preparation and opponent-specific strategy - “Skill Builders”: Players like Ethan Guo (+157 rating points) who show fundamental improvement despite modest tournament scores, suggesting rapid learning phases

3. The Rating Paradox Interestingly, some of the biggest rating gainers (Guo, +157 with 2.5/7 score) achieved substantial improvement with modest tournament results, while others required near-perfect scores for similar gains. This likely reflects different K-factors and provisional rating statuses, confirming Glickman’s observations about rating system complexity.

4. Geographic and Demographic Patterns
Michigan’s dominance in both categories (4 of 5 top performers in each group) alongside notable out-of-state success (Gary Hua from Ontario, Stefano Lee from Ontario) suggests strong regional chess development programs while highlighting cross-border competitive talent.

Strategic Implications for Chess Development

For Players: The analysis suggests two complementary development strategies: - Short-term focus: Tactical preparation and tournament-specific strategies (ELO optimization)
- Long-term focus: Fundamental skill building and sustained improvement (rating growth) - Optimal approach: Combining both strategies, as demonstrated by dual-category performers

For Chess Educators: The framework provides a diagnostic tool for identifying different types of chess talent and tailoring development approaches accordingly.

Validation of Initial Hypotheses

Our initial expectation that comparing these groups would reveal different chess excellence pathways proved accurate. The analysis confirmed that: - Tournament performance and skill development represent related but distinct chess competencies - The most promising players demonstrate excellence in both domains - Statistical analysis (ELO expectations) provides objective insights beyond simple tournament standings

This dual-lens approach, combining Glickman’s theoretical framework with empirical tournament data, offers a more comprehensive understanding of chess performance than either metric alone.

Methodology

Reference: Glickman, M.E. (1995). “A Comprehensive Guide to Chess Ratings.” The Rating of Chess Players, Past and Present, equation (1), page 9.

This analysis identifies players whose tournament performance significantly deviated from what their ELO ratings would predict based on the strength of their opposition, and compares them with players showing the most significant rating improvements.