This project converts a semi-structured chess tournament text file into a tidy CSV dataset suitable for import into a relational database. Each player’s name, state, tournament points, pre-tournament rating, and average pre-tournament rating of opponents are extracted from the original records.
The input is tournamentinfo.txt, supplied with the
assignment. Place it in the same folder as this R Markdown file before
knitting. The tournament contains two lines per player: the first holds
the player number, name, points, and seven round results; the second
holds state, USCF ID, and pre/post rating. Separator lines are
ignored.
# This project uses only base R; no package installation is required.
options(stringsAsFactors = FALSE)
input_file <- "tournamentinfo.txt"
if (!file.exists(input_file)) {
stop("Place tournamentinfo.txt in the same folder as the Rmd file.")
}
lines <- readLines(input_file, warn = FALSE)
cat("Number of text lines:", length(lines), "\n")
## Number of text lines: 196
head(lines, 8)
## [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|"
Split each row at the vertical bars. A player row begins with a
numeric player number and is immediately followed by a state/rating row.
The first integer after R: is the
pre-tournament rating, including provisional ratings
such as 1641P17.
split_fields <- function(line) trimws(strsplit(line, "|", fixed = TRUE)[[1]])
records <- list()
for (i in seq_len(length(lines) - 1L)) {
first <- split_fields(lines[i])
second <- split_fields(lines[i + 1L])
if (length(first) < 10L || !grepl("^[0-9]+$", first[1])) next
if (length(second) < 2L || !grepl("^[A-Z]{2}$", second[1])) next
rating_match <- regexec("R:[[:space:]]*([0-9]+)", second[2])
rating_parts <- regmatches(second[2], rating_match)[[1]]
if (length(rating_parts) < 2L) stop("Missing pre-rating for player ", first[1])
round_cells <- first[4:10]
# Only W/L/D followed by a player number represents a played opponent.
opponent_ids <- vapply(round_cells, function(cell) {
m <- regexec("^[WLD][[:space:]]+([0-9]+)$", cell)
parts <- regmatches(cell, m)[[1]]
if (length(parts) == 2L) as.integer(parts[2]) else NA_integer_
}, integer(1))
records[[length(records) + 1L]] <- list(
player_id = as.integer(first[1]),
player_name = tools::toTitleCase(tolower(first[2])),
state = second[1],
points = as.numeric(first[3]),
pre_rating = as.integer(rating_parts[2]),
opponents = opponent_ids
)
}
players <- data.frame(
player_id = vapply(records, `[[`, integer(1), "player_id"),
player_name = vapply(records, `[[`, character(1), "player_name"),
state = vapply(records, `[[`, character(1), "state"),
points = vapply(records, `[[`, numeric(1), "points"),
pre_rating = vapply(records, `[[`, integer(1), "pre_rating")
)
cat("Number of players:", nrow(players), "\n")
## Number of players: 64
stopifnot(nrow(players) == 64L, !anyDuplicated(players$player_id))
head(players)
## player_id player_name state points pre_rating
## 1 1 Gary Hua ON 6.0 1794
## 2 2 Dakshesh Daruri MI 6.0 1553
## 3 3 Aditya Bajaj MI 6.0 1384
## 4 4 Patrick h Schilling MI 5.5 1716
## 5 5 Hanshi Zuo MI 5.5 1655
## 6 6 Hansen Song OH 5.0 1686
For each played round, the opponent number refers to the player
number in the tournament. Byes (B), half-point byes
(H), unplayed rounds (U), and forfeits
(X) do not identify a played opponent and are excluded. The
denominator is therefore the number of identifiable played opponents,
not necessarily seven.
ratings_by_id <- setNames(players$pre_rating, as.character(players$player_id))
average_opponent_rating <- vapply(records, function(record) {
ids <- record$opponents[!is.na(record$opponents)]
if (length(ids) == 0L) return(NA_real_)
ratings <- unname(ratings_by_id[as.character(ids)])
if (anyNA(ratings)) stop("Opponent ID not found for ", record$player_name)
round(mean(ratings), 0)
}, numeric(1))
players$average_opponent_pre_rating <- average_opponent_rating
players$games_played <- vapply(records, function(x) sum(!is.na(x$opponents)), integer(1))
Gary Hua played opponents 39, 21, 18, 14, 7, 12, and 4. Their pre-ratings are 1436, 1563, 1600, 1610, 1649, 1663, and 1716.
gary <- players[players$player_id == 1L, ]
gary_opponents <- records[[which(players$player_id == 1L)]]$opponents
gary_ratings <- unname(ratings_by_id[as.character(gary_opponents)])
print(gary_ratings)
## [1] 1436 1563 1600 1610 1649 1663 1716
cat("Unrounded mean:", mean(gary_ratings), "\n")
## Unrounded mean: 1605.286
cat("Rounded mean:", round(mean(gary_ratings)), "\n")
## Rounded mean: 1605
stopifnot(
gary$player_name == "Gary Hua",
gary$state == "ON",
gary$points == 6,
gary$pre_rating == 1794,
gary$average_opponent_pre_rating == 1605
)
The exported file has exactly five columns, in the order requested by the assignment.
final_data <- data.frame(
"Player Name" = players$player_name,
"Player State" = players$state,
"Total Points" = players$points,
"Pre-Rating" = players$pre_rating,
"Average Opponent Pre-Rating" = players$average_opponent_pre_rating,
check.names = FALSE
)
output_file <- "DATA607_Chess_Tournament_Results.csv"
write.csv(final_data, output_file, row.names = FALSE, na = "")
cat("Saved:", normalizePath(output_file), "\n")
## Saved: C:\Users\Reggie\OneDrive\Documentos\DATA607_Chess_Tournament_Results.csv
knitr::kable(head(final_data, 10), caption = "First 10 chess tournament records")
| Player Name | Player State | Total Points | Pre-Rating | Average Opponent Pre-Rating |
|---|---|---|---|---|
| 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 |
stopifnot(nrow(final_data) == 64L, ncol(final_data) == 5L)
stopifnot(!anyNA(final_data))
summary(players[, c("points", "pre_rating", "average_opponent_pre_rating", "games_played")])
## points pre_rating average_opponent_pre_rating games_played
## Min. :1.000 Min. : 377 Min. :1107 Min. :1.000
## 1st Qu.:2.500 1st Qu.:1227 1st Qu.:1310 1st Qu.:6.000
## Median :3.500 Median :1407 Median :1382 Median :7.000
## Mean :3.438 Mean :1378 Mean :1379 Mean :6.375
## 3rd Qu.:4.000 3rd Qu.:1583 3rd Qu.:1481 3rd Qu.:7.000
## Max. :6.000 Max. :1794 Max. :1605 Max. :7.000
table(players$state)
##
## MI OH ON
## 55 1 8
plot(players$pre_rating, players$average_opponent_pre_rating,
pch = 19, col = "steelblue",
xlab = "Player pre-tournament rating",
ylab = "Average opponent pre-tournament rating",
main = "Player Rating vs. Average Opponent Rating")
abline(0, 1, lty = 2, col = "gray50")
The semi-structured tournament report was converted into a five-column CSV for all 64 players. Opponent pre-ratings were linked through player numbers and averaged over identifiable games. The result for Gary Hua was validated against the assignment example (1605). The generated CSV can be imported into a SQL database or used for further statistical analysis.
Keep tournamentinfo.txt alongside this .Rmd
file. Click Knit in RStudio to generate the HTML report
and CSV. To publish the report, choose Publish in the
HTML viewer and select RPubs. Make the source text file accessible to
anyone who needs to rerun the analysis (for example, in the same public
project repository). RPubs publishes the report, not the input file or
CSV automatically.