DATA 607 Project 1 – Chess Tournament Code Base

Author

Patricio Romero

Published

September 25, 2026

Introduction

Chess tournament cross-tables contain information about players, ratings, scores, opponents, and game results in a semi-structured text format. Although the file is readable to a person, it is not immediately suitable for analysis or database storage.

The objective of this project is to use R to read and transform the provided tournamentinfo.txt file into a structured CSV dataset. The final dataset will contain each player’s name, state, total points, pre-tournament rating, and average pre-tournament rating of the opponents faced.

The project will also validate the transformed data and perform a small exploratory data analysis before generating the final chess_tournament_results.csv file.

Data Source

The data were provided by the professor in the file tournamentinfo.txt. The file contains the results of a seven-round chess tournament with 64 players.

Each player is represented by two lines. The first line contains the player’s pairing number, name, total points, and results for each round. The second line contains the player’s state, USCF identification number, pre-tournament rating, post-tournament rating, and color played in each round.

Round results such as W 39, L 8, or D 12 identify a win, loss, or draw and the pairing number of the opponent. Codes without an opponent number, including B, H, U, and X, represent special tournament outcomes and are not used when calculating the average opponent rating.

Data Dictionary

The final CSV file contains one row for each player and the following variables:

Variable Description
player_name Full name of the chess player
state Player’s state or province abbreviation
total_points Total tournament points earned by the player
pre_rating Player’s rating before the tournament
average_opponent_pre_rating Average pre-tournament rating of the player’s numbered opponents

The average opponent rating is calculated by identifying the opponent pairing numbers in the round results, matching those numbers to the corresponding players, retrieving their pre-tournament ratings, and calculating the arithmetic mean.

Planned Approach

The project follows these steps:

  1. Read all lines from tournamentinfo.txt into R.
  2. Identify the two lines associated with each player.
  3. Extract the pairing number, player name, state, total points, and pre-tournament rating.
  4. Extract the numbered opponents from the seven round-result fields.
  5. Exclude special tournament codes that do not identify an opponent.
  6. Match each opponent number to the corresponding player’s pre-tournament rating.
  7. Calculate and round the average opponent pre-rating for every player.
  8. Create a tidy dataset with one row per player.
  9. Validate the transformed data.
  10. Confirm that Gary Hua’s average opponent pre-rating is 1605.
  11. Export the final dataset as chess_tournament_results.csv.
  12. Perform a small exploratory data analysis.

The original text file remains unchanged. All transformations, calculations, validations, and exports are performed reproducibly in this Quarto document.

Data Import and Initial Inspection

The required packages are loaded and the original tournament text file is read into R.

library(readr)
library(dplyr)
library(stringr)
library(ggplot2)
library(knitr)

raw_lines <- read_lines(
  "tournamentinfo.txt",
  progress = FALSE
)

file_summary <- data.frame(
  measure = c(
    "Source file",
    "Raw text lines"
  ),
  value = c(
    "tournamentinfo.txt",
    length(raw_lines)
  )
)

knitr::kable(
  file_summary,
  col.names = c("Measure", "Value"),
  align = c("l", "l"),
  caption = "Initial inspection of the source file"
)
Initial inspection of the source file
Measure Value
Source file tournamentinfo.txt
Raw text lines 196

Identifying Player Records

Player records are identified by lines that begin with a pairing number followed by a vertical separator. The following line contains the player’s state and rating information.

player_line_indices <- which(
  str_detect(
    raw_lines,
    "^\\s*[0-9]+\\s*\\|"
  )
)

number_of_players <- length(player_line_indices)

player_record_summary <- data.frame(
  measure = "Identified player records",
  value = number_of_players
)

knitr::kable(
  player_record_summary,
  col.names = c("Measure", "Value"),
  align = c("l", "r"),
  caption = "Player record identification summary"
)
Player record identification summary
Measure Value
Identified player records 64
player_line_preview <- data.frame(
  pairing_number = seq_len(6),
  source_line = head(player_line_indices, 6)
)

knitr::kable(
  player_line_preview,
  col.names = c(
    "Pairing No.",
    "Source Line"
  ),
  align = c("r", "r"),
  caption = "Location of the first six player records"
)
Location of the first six player records
Pairing No. Source Line
1 5
2 8
3 11
4 14
5 17
6 20

Preliminary Data Inspection

The preliminary inspection confirms that the source file contains 196 text lines and 64 identifiable player records.

inspection_summary <- data.frame(
  measure = c(
    "Raw text lines",
    "Identified player records"
  ),
  value = c(
    length(raw_lines),
    number_of_players
  )
)

knitr::kable(
  inspection_summary,
  col.names = c("Measure", "Value"),
  align = c("l", "r"),
  caption = "Preliminary inspection of the tournament file"
)
Preliminary inspection of the tournament file
Measure Value
Raw text lines 196
Identified player records 64

Extracting Basic Player Information

The first transformation extracts the pairing number, player name, state, total points, and pre-tournament rating for each player.

player_lines <- raw_lines[player_line_indices]
detail_lines <- raw_lines[player_line_indices + 1]

player_fields <- str_split_fixed(
  player_lines,
  "\\|",
  11
)

detail_fields <- str_split_fixed(
  detail_lines,
  "\\|",
  11
)

basic_players <- data.frame(
  pairing_number = parse_integer(
    player_fields[, 1]
  ),
  player_name = str_to_title(
    str_squish(player_fields[, 2])
  ),
  state = str_squish(
    detail_fields[, 1]
  ),
  total_points = parse_double(
    player_fields[, 3]
  ),
  pre_rating = parse_integer(
    str_match(
      detail_lines,
      "R:\\s*([0-9]+)"
    )[, 2]
  )
)

knitr::kable(
  head(basic_players),
  col.names = c(
    "Pairing No.",
    "Player",
    "State",
    "Points",
    "Pre-Rating"
  ),
  align = c("r", "l", "c", "r", "r"),
  digits = c(0, NA, NA, 1, 0),
  caption = "Basic information for the first six players"
)
Basic information for the first six players
Pairing No. Player State Points Pre-Rating
1 Gary Hua ON 6.0 1794
2 Dakshesh Daruri MI 6.0 1553
3 Aditya Bajaj MI 6.0 1384
4 Patrick H Schilling MI 5.5 1716
5 Hanshi Zuo MI 5.5 1655
6 Hansen Song OH 5.0 1686

Extracting Opponent Pairing Numbers

The seven round fields contain game results and, when applicable, the pairing number of the opponent. Special results without an opponent number are converted to missing values and excluded.

round_results <- player_fields[, 4:10]

opponent_numbers <- lapply(
  seq_len(nrow(round_results)),
  function(player_row) {
    opponents <- parse_integer(
      str_extract(
        round_results[player_row, ],
        "[0-9]+"
      )
    )

    opponents[!is.na(opponents)]
  }
)

names(opponent_numbers) <-
  basic_players$pairing_number

gary_opponents <- data.frame(
  round = seq_along(opponent_numbers[[1]]),
  opponent_pairing_number =
    opponent_numbers[[1]]
)

knitr::kable(
  gary_opponents,
  col.names = c(
    "Round",
    "Opponent Pairing No."
  ),
  align = c("r", "r"),
  caption = "Gary Hua's identified opponents"
)
Gary Hua’s identified opponents
Round Opponent Pairing No.
1 39
2 21
3 18
4 14
5 7
6 12
7 4
opponent_count_summary <- data.frame(
  pairing_number =
    basic_players$pairing_number[1:10],
  player_name =
    basic_players$player_name[1:10],
  opponents_identified =
    lengths(opponent_numbers)[1:10]
)

knitr::kable(
  opponent_count_summary,
  col.names = c(
    "Pairing No.",
    "Player",
    "Opponents Identified"
  ),
  align = c("r", "l", "r"),
  caption = paste(
    "Opponent counts for",
    "the first ten players"
  )
)
Opponent counts for the first ten players
Pairing No. Player Opponents Identified
1 Gary Hua 7
2 Dakshesh Daruri 7
3 Aditya Bajaj 7
4 Patrick H Schilling 7
5 Hanshi Zuo 7
6 Hansen Song 7
7 Gary Dee Swathell 7
8 Ezekiel Houghton 7
9 Stefano Lee 7
10 Anvit Rao 7

Calculating Average Opponent Ratings

Each opponent pairing number is matched to the corresponding player’s pre-tournament rating. The identified ratings are averaged and rounded to the nearest whole number.

pre_ratings_by_pairing <- setNames(
  basic_players$pre_rating,
  basic_players$pairing_number
)

average_opponent_ratings <- sapply(
  opponent_numbers,
  function(opponents) {
    opponent_ratings <-
      pre_ratings_by_pairing[
        as.character(opponents)
      ]

    round(
      mean(
        opponent_ratings,
        na.rm = TRUE
      )
    )
  }
)

basic_players$average_opponent_pre_rating <-
  as.integer(average_opponent_ratings)

gary_validation <- basic_players |>
  filter(player_name == "Gary Hua")

gary_vertical <- data.frame(
  measure = c(
    "Pairing number",
    "Player name",
    "State",
    "Total points",
    "Pre-tournament rating",
    "Average opponent pre-rating"
  ),
  value = c(
    gary_validation$pairing_number,
    gary_validation$player_name,
    gary_validation$state,
    sprintf(
      "%.1f",
      gary_validation$total_points
    ),
    gary_validation$pre_rating,
    gary_validation$
      average_opponent_pre_rating
  )
)

knitr::kable(
  gary_vertical,
  col.names = c("Measure", "Value"),
  align = c("l", "l"),
  caption = paste(
    "Validation of Gary Hua's",
    "calculated result"
  )
)
Validation of Gary Hua’s calculated result
Measure Value
Pairing number 1
Player name Gary Hua
State ON
Total points 6.0
Pre-tournament rating 1794
Average opponent pre-rating 1605

Creating and Validating the Final Dataset

The required variables are selected and arranged into the final dataset. Validation checks confirm the number of players, required columns, missing values, duplicate pairing numbers, valid opponent references, and Gary Hua’s expected result.

chess_tournament_results <- basic_players |>
  select(
    player_name,
    state,
    total_points,
    pre_rating,
    average_opponent_pre_rating
  )

invalid_opponent_numbers <- sum(
  !unlist(opponent_numbers) %in%
    basic_players$pairing_number
)

validation_summary <- data.frame(
  check = c(
    "Player records",
    "Final variables",
    "Missing player names",
    "Missing states",
    "Missing total points",
    "Missing pre-ratings",
    "Missing opponent averages",
    "Duplicate pairing numbers",
    "Invalid opponent numbers",
    "Gary Hua opponent average"
  ),
  result = c(
    nrow(chess_tournament_results),
    ncol(chess_tournament_results),
    sum(is.na(
      chess_tournament_results$player_name
    )),
    sum(is.na(
      chess_tournament_results$state
    )),
    sum(is.na(
      chess_tournament_results$total_points
    )),
    sum(is.na(
      chess_tournament_results$pre_rating
    )),
    sum(is.na(
      chess_tournament_results$
        average_opponent_pre_rating
    )),
    sum(duplicated(
      basic_players$pairing_number
    )),
    invalid_opponent_numbers,
    chess_tournament_results |>
      filter(player_name == "Gary Hua") |>
      pull(average_opponent_pre_rating)
  )
)

knitr::kable(
  validation_summary,
  col.names = c(
    "Validation Check",
    "Result"
  ),
  align = c("l", "r"),
  caption = paste(
    "Validation results for",
    "the transformed dataset"
  )
)
Validation results for the transformed dataset
Validation Check Result
Player records 64
Final variables 5
Missing player names 0
Missing states 0
Missing total points 0
Missing pre-ratings 0
Missing opponent averages 0
Duplicate pairing numbers 0
Invalid opponent numbers 0
Gary Hua opponent average 1605
stopifnot(
  nrow(chess_tournament_results) == 64,
  ncol(chess_tournament_results) == 5,
  !anyNA(chess_tournament_results),
  !anyDuplicated(basic_players$pairing_number),
  invalid_opponent_numbers == 0,
  gary_validation$
    average_opponent_pre_rating == 1605
)

Exporting the Final Dataset

The validated dataset is exported as a CSV file and then imported back into R to confirm that its structure was preserved.

write_csv(
  chess_tournament_results,
  "chess_tournament_results.csv"
)

csv_check <- read_csv(
  "chess_tournament_results.csv",
  show_col_types = FALSE
)

csv_validation <- data.frame(
  measure = c(
    "CSV file exists",
    "Player records",
    "Variables",
    "Column names preserved"
  ),
  value = c(
    file.exists(
      "chess_tournament_results.csv"
    ),
    nrow(csv_check),
    ncol(csv_check),
    identical(
      names(csv_check),
      names(chess_tournament_results)
    )
  )
)

knitr::kable(
  csv_validation,
  col.names = c("Validation Check", "Result"),
  align = c("l", "l"),
  caption = "Validation of the exported CSV file"
)
Validation of the exported CSV file
Validation Check Result
CSV file exists 1
Player records 64
Variables 5
Column names preserved 1

Exploratory Data Analysis

Summary statistics describe the tournament participants, their pre-tournament ratings, and the average strength of their opponents.

tournament_summary <- data.frame(
  measure = c(
    "Number of players",
    "Mean pre-rating",
    "Median pre-rating",
    "Minimum pre-rating",
    "Maximum pre-rating",
    "Mean opponent rating"
  ),
  value = c(
    nrow(chess_tournament_results),
    round(
      mean(chess_tournament_results$pre_rating),
      1
    ),
    median(
      chess_tournament_results$pre_rating
    ),
    min(
      chess_tournament_results$pre_rating
    ),
    max(
      chess_tournament_results$pre_rating
    ),
    round(
      mean(
        chess_tournament_results$
          average_opponent_pre_rating
      ),
      1
    )
  )
)

knitr::kable(
  tournament_summary,
  col.names = c("Measure", "Value"),
  align = c("l", "r"),
  caption = "Summary statistics for the tournament"
)
Summary statistics for the tournament
Measure Value
Number of players 64.0
Mean pre-rating 1378.5
Median pre-rating 1407.0
Minimum pre-rating 377.0
Maximum pre-rating 1794.0
Mean opponent rating 1378.6

The tournament included 64 players. The mean pre-tournament rating was approximately 1378.5, while the median was 1407. Ratings ranged from 377 to 1794. The overall mean opponent rating was approximately 1378.6.

Player Rating and Opponent Strength

The following visualization compares each player’s pre-tournament rating with the average pre-tournament rating of the opponents faced.

ggplot(
  chess_tournament_results,
  aes(
    x = pre_rating,
    y = average_opponent_pre_rating
  )
) +
  geom_point(
    color = "#2C7FB8",
    size = 2.5,
    alpha = 0.75
  ) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    color = "#D95F0E",
    linewidth = 0.9
  ) +
  labs(
    title = paste(
      "Player Rating and",
      "Average Opponent Rating"
    ),
    x = "Player Pre-Tournament Rating",
    y = "Average Opponent Pre-Tournament Rating"
  ) +
  theme_minimal()

The upward trend indicates that players with higher pre-tournament ratings generally faced stronger opponents during the tournament.

Leading Tournament Performances

Players are ranked by total tournament points. Pre-tournament rating is used to order players with the same number of points.

leading_players <- chess_tournament_results |>
  arrange(
    desc(total_points),
    desc(pre_rating)
  ) |>
  slice_head(n = 10)

knitr::kable(
  leading_players,
  col.names = c(
    "Player",
    "State",
    "Points",
    "Rating",
    "Opponent Avg."
  ),
  align = c("l", "c", "r", "r", "r"),
  digits = c(NA, NA, 1, 0, 0),
  caption = "Ten leading tournament performances"
)
Ten leading tournament performances
Player State Points Rating Opponent Avg.
Gary Hua ON 6.0 1794 1605
Dakshesh Daruri MI 6.0 1553 1469
Aditya Bajaj MI 6.0 1384 1564
Patrick H Schilling MI 5.5 1716 1574
Hanshi Zuo MI 5.5 1655 1501
Hansen Song OH 5.0 1686 1519
Gary Dee Swathell MI 5.0 1649 1372
Ezekiel Houghton MI 5.0 1641 1468
Stefano Lee ON 5.0 1411 1523
Anvit Rao MI 5.0 1365 1554

Conclusion

The original semi-structured tournament cross-table was successfully transformed into a tidy dataset containing 64 player records and the five variables required by the assignment.

The validation found no missing required values, duplicate pairing numbers, or invalid opponent references. Gary Hua’s calculated average opponent pre-rating was 1605, which matches the expected assignment result.

The completed dataset was exported as chess_tournament_results.csv and successfully read back into R with its 64 rows, five columns, and original column names preserved.

Expected Output

The generated file is:

chess_tournament_results.csv

The first record is:

Measure Value
Player name Gary Hua
State ON
Total points 6.0
Pre-tournament rating 1794
Average opponent pre-rating 1605

Deliverables

The completed project includes:

  • tournamentinfo.txt
  • DATA607-Project1-Chess-Tournament-Code-Base.qmd
  • DATA607-Project1-Chess-Tournament-Code-Base.html
  • chess_tournament_results.csv

AI Use

ChatGPT was used to help interpret the assignment requirements, organize the workflow, improve the English writing, explain the tournament cross-table structure, and provide coding guidance. I ran the code, reviewed the transformed data, validated the results, and confirmed the conclusions myself.