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.
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.
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.
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] "-----------------------------------------------------------------------------------------"
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|"
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
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"
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
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 |"
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"
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
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
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
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
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
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
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
)
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.
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].
Link: