Introduction

This project analyzes chess tournament data provided in a structured text file. The goal is to extract each player’s name, state, total points, and pre-tournament rating, and then calculate the average pre-tournament rating of each player’s opponents. The final results will be exported as a CSV file.

Planned Approach

First, I will read the tournament text file directly from GitHub to ensure that the data are accessible when the code is run on another computer. Then, I will identify the rows containing the main player records and player details. I will extract the required information, including player name, state, total points, and pre-tournament rating. I will also extract opponent numbers only from games that were actually played (W, L, or D) and exclude rounds without an actual opponent, such as byes.

I will then use the opponent numbers to identify their corresponding pre-tournament ratings and calculate the average opponent pre-rating for each player. Finally, I will combine the extracted and calculated information into a single data frame and export the final results as a CSV file.

Anticipated Challenges

One anticipated challenge is extracting the required information from the structured text file because each player’s information is spread across multiple lines and is not stored in a standard tabular format. Another challenge is identifying which rounds represent games that were actually played. Only wins (W), losses (L), and draws (D) should be included when calculating the average opponent pre-rating, while unplayed rounds such as byes should be excluded.

Code Base

Load the Data

The tournament data are stored in a text file available through GitHub. The file is read directly from the GitHub raw URL so that the data remain accessible when the code is run on another computer.

data_url <- "https://raw.githubusercontent.com/OzgeG01/DATA607-Assignment/refs/heads/main/tournamentinfo.txt"

tournament_data <- readLines(data_url)

head(tournament_data, 10)
##  [1] "-----------------------------------------------------------------------------------------" 
##  [2] " Pair | Player Name                     |Total|Round|Round|Round|Round|Round|Round|Round| "
##  [3] " Num  | USCF ID / Rtg (Pre->Post)       | Pts |  1  |  2  |  3  |  4  |  5  |  6  |  7  | "
##  [4] "-----------------------------------------------------------------------------------------" 
##  [5] "    1 | GARY HUA                        |6.0  |W  39|W  21|W  18|W  14|W   7|D  12|D   4|" 
##  [6] "   ON | 15445895 / R: 1794   ->1817     |N:2  |W    |B    |W    |B    |W    |B    |W    |" 
##  [7] "-----------------------------------------------------------------------------------------" 
##  [8] "    2 | DAKSHESH DARURI                 |6.0  |W  63|W  58|L   4|W  17|W  16|W  20|W   7|" 
##  [9] "   MI | 14598900 / R: 1553   ->1663     |N:2  |B    |W    |B    |W    |B    |W    |B    |" 
## [10] "-----------------------------------------------------------------------------------------"

Identify Player Rows

Each player’s main record begins with a player number followed by a vertical bar. These rows contain the player’s name, total points, and round-by-round opponent information.

player_rows <- grep("^\\s*\\d+\\s*\\|", tournament_data, value = TRUE)

head(player_rows)
## [1] "    1 | GARY HUA                        |6.0  |W  39|W  21|W  18|W  14|W   7|D  12|D   4|"
## [2] "    2 | DAKSHESH DARURI                 |6.0  |W  63|W  58|L   4|W  17|W  16|W  20|W   7|"
## [3] "    3 | ADITYA BAJAJ                    |6.0  |L   8|W  61|W  25|W  21|W  11|W  13|W  12|"
## [4] "    4 | PATRICK H SCHILLING             |5.5  |W  23|D  28|W   2|W  26|D   5|W  19|D   1|"
## [5] "    5 | HANSHI ZUO                      |5.5  |W  45|W  37|D  12|D  13|D   4|W  14|W  17|"
## [6] "    6 | HANSEN SONG                     |5.0  |W  34|D  29|L  11|W  35|D  10|W  27|W  21|"

Extract Player Numbers

The player number is extracted from the first field of each main player row. These numbers will be used to match each opponent with the corresponding player’s pre-tournament rating.

player_numbers <- as.numeric(
  trimws(sapply(strsplit(player_rows, "\\|"), `[`, 1))
)

head(player_numbers)
## [1] 1 2 3 4 5 6

Extract Player Names

The player name is extracted from each main player row by splitting the row at the vertical bar. The second field contains the player’s name, and trimws() is used to remove any extra spaces.

player_names <- trimws(
  sapply(strsplit(player_rows, "\\|"), `[`, 2)
)

head(player_names)
## [1] "GARY HUA"            "DAKSHESH DARURI"     "ADITYA BAJAJ"       
## [4] "PATRICK H SCHILLING" "HANSHI ZUO"          "HANSEN SONG"

Extract Total Points

The total number of points for each player is stored in the third field of the main player row. The rows are split using the vertical bar as the delimiter, and the extracted values are converted to numeric format.

total_points <- as.numeric(
  trimws(sapply(strsplit(player_rows, "\\|"), `[`, 3))
)

head(total_points)
## [1] 6.0 6.0 6.0 5.5 5.5 5.0

Identify Player Detail Rows

The second line of each player record contains the player’s state and rating information. These rows begin with a two-letter state abbreviation followed by a vertical bar.

detail_rows <- grep("^\\s*[A-Z]{2}\\s*\\|", tournament_data, value = TRUE)  

head(detail_rows)
## [1] "   ON | 15445895 / R: 1794   ->1817     |N:2  |W    |B    |W    |B    |W    |B    |W    |"
## [2] "   MI | 14598900 / R: 1553   ->1663     |N:2  |B    |W    |B    |W    |B    |W    |B    |"
## [3] "   MI | 14959604 / R: 1384   ->1640     |N:2  |W    |B    |W    |B    |W    |B    |W    |"
## [4] "   MI | 12616049 / R: 1716   ->1744     |N:2  |W    |B    |W    |B    |W    |B    |B    |"
## [5] "   MI | 14601533 / R: 1655   ->1690     |N:2  |B    |W    |B    |W    |B    |W    |B    |"
## [6] "   OH | 15055204 / R: 1686   ->1687     |N:3  |W    |B    |W    |B    |B    |W    |B    |"

Extract Player State

The player’s state is stored in the first field of the detail row. The detail rows are split using the vertical bar as the delimiter, and extra whitespace is removed from the extracted state values.

player_states <- trimws(
  sapply(strsplit(detail_rows, "\\|"), `[`, 1)
)

head(player_states)
## [1] "ON" "MI" "MI" "MI" "MI" "OH"

Extract Pre-Tournament Rating

The player’s pre-tournament rating appears in the detail row immediately after R:. A regular expression is used to extract this value, and the result is converted to numeric format.

pre_ratings <- as.numeric(
  sub(".*R:\\s*(\\d+).*", "\\1", detail_rows)
)

head(pre_ratings)
## [1] 1794 1553 1384 1716 1655 1686

Extract Opponent Numbers

The round information begins with the fourth field of each main player row. First, the round fields are separated from the rest of the player information. Only games recorded as wins (W), losses (L), or draws (D) are included. Byes and other unplayed rounds are excluded.

round_fields <- lapply(
  strsplit(player_rows, "\\|"),
  function(x) trimws(x[4:length(x)])
)

opponent_numbers <- lapply(round_fields, function(rounds) {
  played_games <- rounds[grepl("^[WLD]\\s+\\d+$", rounds)]
  as.numeric(sub("^[WLD]\\s+", "", played_games))
})

head(opponent_numbers)
## [[1]]
## [1] 39 21 18 14  7 12  4
## 
## [[2]]
## [1] 63 58  4 17 16 20  7
## 
## [[3]]
## [1]  8 61 25 21 11 13 12
## 
## [[4]]
## [1] 23 28  2 26  5 19  1
## 
## [[5]]
## [1] 45 37 12 13  4 14 17
## 
## [[6]]
## [1] 34 29 11 35 10 27 21

Calculate Average Opponent Pre-Rating

The opponent numbers are used to identify the corresponding pre-tournament ratings. For each player, the average pre-tournament rating of the opponents actually played is calculated and rounded to the nearest whole number.

avg_opponent_rating <- sapply(opponent_numbers, function(opponents) {
  opponent_ratings <- pre_ratings[match(opponents, player_numbers)]
  mean(opponent_ratings, na.rm = TRUE)
})

avg_opponent_rating <- round(avg_opponent_rating)

head(avg_opponent_rating)
## [1] 1605 1469 1564 1574 1501 1519

Verify the First Player

The first player, Gary Hua, can be used to verify the opponent rating calculation. His opponents’ pre-tournament ratings are used to calculate his average opponent pre-rating.

player_names[1]
## [1] "GARY HUA"
opponent_numbers[[1]]
## [1] 39 21 18 14  7 12  4
pre_ratings[match(opponent_numbers[[1]], player_numbers)]
## [1] 1436 1563 1600 1610 1649 1663 1716
avg_opponent_rating[1]
## [1] 1605

Verify a Player with Byes

Player 37 includes unplayed rounds such as a bye and a half-point bye. This check confirms that only actual games (W, L or D) are included in the opponent calculation.

player_rows[37]
## [1] "   37 | AMIYATOSH PWNANANDAM            |3.5  |B    |L   5|W  34|L  27|H    |L  23|W  61|"
opponent_numbers[[37]]
## [1]  5 34 27 23 61
avg_opponent_rating[37]
## [1] 1385

Create the Final Data Frame

The extracted and calculated information is combined into a single data frame containing the five fields required for the project.

chess_results <- data.frame(
  Player_Name = player_names,
  State = player_states,
  Total_Points = total_points,
  Pre_Rating = pre_ratings,
  Avg_Opponent_Pre_Rating = avg_opponent_rating
)

head(chess_results)
##           Player_Name State Total_Points Pre_Rating Avg_Opponent_Pre_Rating
## 1            GARY HUA    ON          6.0       1794                    1605
## 2     DAKSHESH DARURI    MI          6.0       1553                    1469
## 3        ADITYA BAJAJ    MI          6.0       1384                    1564
## 4 PATRICK H SCHILLING    MI          5.5       1716                    1574
## 5          HANSHI ZUO    MI          5.5       1655                    1501
## 6         HANSEN SONG    OH          5.0       1686                    1519

Export the Results

The final data frame is exported as a CSV file so that the cleaned tournament data can be easily imported into other applications, such as a SQL database. Row names are excluded from the exported file.

write.csv(
  chess_results,
  "chess_tournament_results.csv",
  row.names = FALSE
)

Conclusion

In this project, I transformed structured chess tournament text data into a clean data frame containing the required information for all players. I extracted each player’s name, state, total points, and pre-tournament rating, identified the opponents they actually played, and calculated the average pre-tournament rating of those opponents.

Rounds without an actual opponent, such as byes, were excluded from the opponent rating calculations. The final dataset was exported as a CSV file for potential use in a database or further analysis.

Citation

I used ChatGPT (GPT-5.6 Luna), developed by OpenAI, to help me understand some of the functions I used in this assignment and to check my work.

OpenAI. (2026). ChatGPT (GPT-5.6 Luna) [Large language model]. [https://chatgpt.com/ Accessed September 26, 2026].

Video Explainer

Link: