Overview
The goal of this lab is to practice parsing through a text file to
obtain chess tournament data, divided by individual players. Some skills
used are regex matching, file handling, and data handling functions.
library(tidyverse)
library(openintro)
library(RCurl)
library(stringr)
Load tournament info txt from cloud hosted source
Start by loading the text file called “tournamentinfo.txt” to be
parsed. Split the text into lines to make it easier to identify each
player. Indices are saved to denote which lines belong to a player and
simplify the look up process later. The regex matches lines that start
with a pair number and has “|” in between each piece of information.
Note that the pair number is not saved because as long as the lines are
parsed in order, it can be derived.
tournament_info_url <- 'https://raw.githubusercontent.com/Megabuster/Data607/refs/heads/main/data/project1/tournamentinfo.txt'
raw_text <- getURL(tournament_info_url)
lines <- readLines(textConnection((raw_text)))
indices <- str_which(lines, '([1-9])(.)+(|)(.)+(|)(.)+([.])(.)+(|)(.)+(|)(.)+(|)(.)+(|)(.)+(|)(.)+(|)')
Create data frame columns
As the tournament info text file is not in a usable data set form, we
need to parse it for Player’s Name, Player’s State, Total Number of
Points, Player’s Pre-Rating, and Average Pre Chess Rating of Opponents.
Each category can be saved with its own vector. The “opponent_ids”
variable is notably not part of the columns of the final csv file. It is
there to help fascilitate the creating of the average opponent
pre-ratings column.
players <- vector()
states <- vector()
points <- numeric()
pre_ratings <- numeric()
opp_pre_ratings <- numeric()
opponent_ids <- list()
Parse raw text
These steps are regex heavy to identify each key piece of
information. The loop iterates once per player using the “indices” saved
in the initial step. “Player_index” is incremented manually to match
each player’s pair number. A “line” is equal to “lines[i]” which means
the index of a line containing a player’s pair number, name, and match
data. The format of the original file has the remaining player info
including Elo on the following line which is labeled below as
“line2”.
The ratings section of the player’s information always has a “->”
to denote the pre and post ratings. This means the desired number, the
pre-rating, will be on the left of the arrow. The next regex
consideration is that some ratings have a “P” in them. We only want the
number that comes before the P. A notable difference between the “P” and
non-P ratings is that “P” ratings are always attached to the Elo rating
directly while non-P ratings have a variable amount of space in between.
The “(\s)*” pattern match accounts for that.
The last 7 digits of the first player line contains all the matches
and the opponents’ pair numbers. The results are outside of the scope of
this project, so collect just the numbers and store them all in
“opponent_ids”.
player_index = 1
for (i in indices) {
line <- lines[i]
line2 <- lines[i+1]
new_split <- trimws(unlist(strsplit(line, '\\|')))
new_split2 <- trimws(unlist(strsplit(line2, '\\|')))
players <- append(players, new_split[2])
states <- append(states, new_split2[1])
points <- append(points, new_split[3])
if (str_detect(new_split2[2], '[0-9]+?(?=P)')) {
pre_ratings <- append(pre_ratings, str_extract(new_split2[2], '[0-9]+?(?=P)'))
} else {
pre_ratings <- append(pre_ratings, str_extract(new_split2[2], '[0-9]+?(?=(\\s)*->)'))
}
new_opponent_ids <- new_split[4:10]
new_opponent_ids_vec <- numeric()
for (match in new_opponent_ids) {
if (str_detect(match, '[0-9]+')) {
new_opponent_ids_vec <- append(new_opponent_ids_vec, str_extract(match, '[0-9]+'))
}
}
opponent_ids[[player_index]] <- new_opponent_ids_vec
player_index <- player_index + 1
}
Calculate average opponent ratings
Using “opponent_ids”, look up the ratings for every opponent
associated with a player’s pair number. For example, Gary Hua is the
first player and faced 7 opponents. None of the ratings had decimals in
them, so I opted to round each mean and convert them into integers
before saving the column.
for (player_opp_ids in 1:length(opponent_ids)) {
ratings_vec <- numeric()
for (opp_id in opponent_ids[player_opp_ids]) {
ratings_vec <- append(ratings_vec, as.numeric(pre_ratings[as.numeric(opp_id)]))
}
avg_opp_rating <- as.integer(round(mean(ratings_vec)))
opp_pre_ratings <- append(opp_pre_ratings, avg_opp_rating)
}
Collect the data in a data frame
With all of the data already organized into individual columns,
create the final data frame. A sample of the data is shown below.
tournament_players <- data.frame(
name = players,
state = states,
total_points = points,
pre_rating = pre_ratings,
avg_opp_pre_rating = opp_pre_ratings
)
head(tournament_players, 10)
## name state total_points pre_rating avg_opp_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
## 7 GARY DEE SWATHELL MI 5.0 1649 1372
## 8 EZEKIEL HOUGHTON MI 5.0 1641 1468
## 9 STEFANO LEE ON 5.0 1411 1523
## 10 ANVIT RAO MI 5.0 1365 1554
Save to csv
Save the results to a csv without any quotes around the strings and
removing the pair number via the row.names argument.
write.csv(x = tournament_players, file = 'tournament_players.csv', quote = FALSE, row.names = FALSE)
Conclusions
Many assumptions were made according to the exact layout of
“tournamentinfo.txt”. Rows were laid out consistently for each player.
Columns always had a “|” between them. Total points always had a “.”
regardless of the number.
This kind of parsing is not good for reusability, but it also
represents a realistic scenario. When scraping data, the format it is in
might not always be convenient. There will often be times where a custom
solution is needed to collect that information.
LS0tDQp0aXRsZTogIkRBVEEgNjA3IFByb2plY3QgMSINCmF1dGhvcjogIkxhd3JlbmNlIFl1Ig0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiBvcGVuaW50cm86OmxhYl9yZXBvcnQNCi0tLQ0KDQojIyMgT3ZlcnZpZXcNCg0KVGhlIGdvYWwgb2YgdGhpcyBsYWIgaXMgdG8gcHJhY3RpY2UgcGFyc2luZyB0aHJvdWdoIGEgdGV4dCBmaWxlIHRvIG9idGFpbiBjaGVzcyB0b3VybmFtZW50IGRhdGEsIGRpdmlkZWQgYnkgaW5kaXZpZHVhbCBwbGF5ZXJzLiBTb21lIHNraWxscyB1c2VkIGFyZSByZWdleCBtYXRjaGluZywgZmlsZSBoYW5kbGluZywgYW5kIGRhdGEgaGFuZGxpbmcgZnVuY3Rpb25zLg0KYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShvcGVuaW50cm8pDQpsaWJyYXJ5KFJDdXJsKQ0KbGlicmFyeShzdHJpbmdyKQ0KYGBgDQoNCiMjIyBMb2FkIHRvdXJuYW1lbnQgaW5mbyB0eHQgZnJvbSBjbG91ZCBob3N0ZWQgc291cmNlDQoNClN0YXJ0IGJ5IGxvYWRpbmcgdGhlIHRleHQgZmlsZSBjYWxsZWQgInRvdXJuYW1lbnRpbmZvLnR4dCIgdG8gYmUgcGFyc2VkLiBTcGxpdCB0aGUgdGV4dCBpbnRvIGxpbmVzIHRvIG1ha2UgaXQgZWFzaWVyIHRvIGlkZW50aWZ5IGVhY2ggcGxheWVyLiBJbmRpY2VzIGFyZSBzYXZlZCB0byBkZW5vdGUgd2hpY2ggbGluZXMgYmVsb25nIHRvIGEgcGxheWVyIGFuZCBzaW1wbGlmeSB0aGUgbG9vayB1cCBwcm9jZXNzIGxhdGVyLiBUaGUgcmVnZXggbWF0Y2hlcyBsaW5lcyB0aGF0IHN0YXJ0IHdpdGggYSBwYWlyIG51bWJlciBhbmQgaGFzICJ8IiBpbiBiZXR3ZWVuIGVhY2ggcGllY2Ugb2YgaW5mb3JtYXRpb24uIE5vdGUgdGhhdCB0aGUgcGFpciBudW1iZXIgaXMgbm90IHNhdmVkIGJlY2F1c2UgYXMgbG9uZyBhcyB0aGUgbGluZXMgYXJlIHBhcnNlZCBpbiBvcmRlciwgaXQgY2FuIGJlIGRlcml2ZWQuDQpgYGB7ciBnZXQtdG91cm5hbWVudC1pbmZvfQ0KdG91cm5hbWVudF9pbmZvX3VybCA8LSAnaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL01lZ2FidXN0ZXIvRGF0YTYwNy9yZWZzL2hlYWRzL21haW4vZGF0YS9wcm9qZWN0MS90b3VybmFtZW50aW5mby50eHQnDQpyYXdfdGV4dCA8LSBnZXRVUkwodG91cm5hbWVudF9pbmZvX3VybCkNCmxpbmVzIDwtIHJlYWRMaW5lcyh0ZXh0Q29ubmVjdGlvbigocmF3X3RleHQpKSkNCmluZGljZXMgPC0gc3RyX3doaWNoKGxpbmVzLCAnKFsxLTldKSguKSsofCkoLikrKHwpKC4pKyhbLl0pKC4pKyh8KSguKSsofCkoLikrKHwpKC4pKyh8KSguKSsofCkoLikrKHwpJykNCmBgYA0KDQojIyMgQ3JlYXRlIGRhdGEgZnJhbWUgY29sdW1ucw0KDQpBcyB0aGUgdG91cm5hbWVudCBpbmZvIHRleHQgZmlsZSBpcyBub3QgaW4gYSB1c2FibGUgZGF0YSBzZXQgZm9ybSwgd2UgbmVlZCB0byBwYXJzZSBpdCBmb3IgUGxheWVy4oCZcyBOYW1lLCBQbGF5ZXLigJlzIFN0YXRlLCBUb3RhbCBOdW1iZXIgb2YgUG9pbnRzLCBQbGF5ZXLigJlzIFByZS1SYXRpbmcsIGFuZCBBdmVyYWdlIFByZSBDaGVzcyBSYXRpbmcgb2YgT3Bwb25lbnRzLiBFYWNoIGNhdGVnb3J5IGNhbiBiZSBzYXZlZCB3aXRoIGl0cyBvd24gdmVjdG9yLiBUaGUgIm9wcG9uZW50X2lkcyIgdmFyaWFibGUgaXMgbm90YWJseSBub3QgcGFydCBvZiB0aGUgY29sdW1ucyBvZiB0aGUgZmluYWwgY3N2IGZpbGUuIEl0IGlzIHRoZXJlIHRvIGhlbHAgZmFzY2lsaXRhdGUgdGhlIGNyZWF0aW5nIG9mIHRoZSBhdmVyYWdlIG9wcG9uZW50IHByZS1yYXRpbmdzIGNvbHVtbi4NCmBgYHtyIGNyZWF0ZS1jb2x1bW5zfQ0KcGxheWVycyA8LSB2ZWN0b3IoKQ0Kc3RhdGVzIDwtIHZlY3RvcigpDQpwb2ludHMgPC0gbnVtZXJpYygpDQpwcmVfcmF0aW5ncyA8LSBudW1lcmljKCkNCm9wcF9wcmVfcmF0aW5ncyA8LSBudW1lcmljKCkNCg0Kb3Bwb25lbnRfaWRzIDwtIGxpc3QoKQ0KYGBgDQoNCiMjIyBQYXJzZSByYXcgdGV4dA0KDQpUaGVzZSBzdGVwcyBhcmUgcmVnZXggaGVhdnkgdG8gaWRlbnRpZnkgZWFjaCBrZXkgcGllY2Ugb2YgaW5mb3JtYXRpb24uIFRoZSBsb29wIGl0ZXJhdGVzIG9uY2UgcGVyIHBsYXllciB1c2luZyB0aGUgImluZGljZXMiIHNhdmVkIGluIHRoZSBpbml0aWFsIHN0ZXAuICJQbGF5ZXJfaW5kZXgiIGlzIGluY3JlbWVudGVkIG1hbnVhbGx5IHRvIG1hdGNoIGVhY2ggcGxheWVyJ3MgcGFpciBudW1iZXIuIEEgImxpbmUiIGlzIGVxdWFsIHRvICJsaW5lc1tpXSIgd2hpY2ggbWVhbnMgdGhlIGluZGV4IG9mIGEgbGluZSBjb250YWluaW5nIGEgcGxheWVyJ3MgcGFpciBudW1iZXIsIG5hbWUsIGFuZCBtYXRjaCBkYXRhLiBUaGUgZm9ybWF0IG9mIHRoZSBvcmlnaW5hbCBmaWxlIGhhcyB0aGUgcmVtYWluaW5nIHBsYXllciBpbmZvIGluY2x1ZGluZyBFbG8gb24gdGhlIGZvbGxvd2luZyBsaW5lIHdoaWNoIGlzIGxhYmVsZWQgYmVsb3cgYXMgImxpbmUyIi4gDQoNClRoZSByYXRpbmdzIHNlY3Rpb24gb2YgdGhlIHBsYXllcidzIGluZm9ybWF0aW9uIGFsd2F5cyBoYXMgYSAiLT4iIHRvIGRlbm90ZSB0aGUgcHJlIGFuZCBwb3N0IHJhdGluZ3MuIFRoaXMgbWVhbnMgdGhlIGRlc2lyZWQgbnVtYmVyLCB0aGUgcHJlLXJhdGluZywgd2lsbCBiZSBvbiB0aGUgbGVmdCBvZiB0aGUgYXJyb3cuIFRoZSBuZXh0IHJlZ2V4IGNvbnNpZGVyYXRpb24gaXMgdGhhdCBzb21lIHJhdGluZ3MgaGF2ZSBhICJQIiBpbiB0aGVtLiBXZSBvbmx5IHdhbnQgdGhlIG51bWJlciB0aGF0IGNvbWVzIGJlZm9yZSB0aGUgUC4gQSBub3RhYmxlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgIlAiIGFuZCBub24tUCByYXRpbmdzIGlzIHRoYXQgIlAiIHJhdGluZ3MgYXJlIGFsd2F5cyBhdHRhY2hlZCB0byB0aGUgRWxvIHJhdGluZyBkaXJlY3RseSB3aGlsZSBub24tUCByYXRpbmdzIGhhdmUgYSB2YXJpYWJsZSBhbW91bnQgb2Ygc3BhY2UgaW4gYmV0d2Vlbi4gVGhlICIoXFxzKSoiIHBhdHRlcm4gbWF0Y2ggYWNjb3VudHMgZm9yIHRoYXQuDQoNClRoZSBsYXN0IDcgZGlnaXRzIG9mIHRoZSBmaXJzdCBwbGF5ZXIgbGluZSBjb250YWlucyBhbGwgdGhlIG1hdGNoZXMgYW5kIHRoZSBvcHBvbmVudHMnIHBhaXIgbnVtYmVycy4gVGhlIHJlc3VsdHMgYXJlIG91dHNpZGUgb2YgdGhlIHNjb3BlIG9mIHRoaXMgcHJvamVjdCwgc28gY29sbGVjdCBqdXN0IHRoZSBudW1iZXJzIGFuZCBzdG9yZSB0aGVtIGFsbCBpbiAib3Bwb25lbnRfaWRzIi4NCmBgYHtyIHRvdXJuYW1lbnQtaW5mby1jb2xsZWN0fQ0KcGxheWVyX2luZGV4ID0gMQ0KZm9yIChpIGluIGluZGljZXMpIHsNCiAgbGluZSA8LSBsaW5lc1tpXQ0KICBsaW5lMiA8LSBsaW5lc1tpKzFdDQogIG5ld19zcGxpdCA8LSB0cmltd3ModW5saXN0KHN0cnNwbGl0KGxpbmUsICdcXHwnKSkpDQogIG5ld19zcGxpdDIgPC0gdHJpbXdzKHVubGlzdChzdHJzcGxpdChsaW5lMiwgJ1xcfCcpKSkNCg0KICBwbGF5ZXJzIDwtIGFwcGVuZChwbGF5ZXJzLCBuZXdfc3BsaXRbMl0pDQogIHN0YXRlcyA8LSBhcHBlbmQoc3RhdGVzLCBuZXdfc3BsaXQyWzFdKQ0KICBwb2ludHMgPC0gYXBwZW5kKHBvaW50cywgbmV3X3NwbGl0WzNdKQ0KDQogIGlmIChzdHJfZGV0ZWN0KG5ld19zcGxpdDJbMl0sICdbMC05XSs/KD89UCknKSkgew0KICAgIHByZV9yYXRpbmdzIDwtIGFwcGVuZChwcmVfcmF0aW5ncywgc3RyX2V4dHJhY3QobmV3X3NwbGl0MlsyXSwgJ1swLTldKz8oPz1QKScpKQ0KICB9IGVsc2Ugew0KICAgIHByZV9yYXRpbmdzIDwtIGFwcGVuZChwcmVfcmF0aW5ncywgc3RyX2V4dHJhY3QobmV3X3NwbGl0MlsyXSwgJ1swLTldKz8oPz0oXFxzKSotPiknKSkNCiAgfQ0KICANCiAgbmV3X29wcG9uZW50X2lkcyA8LSBuZXdfc3BsaXRbNDoxMF0NCiAgbmV3X29wcG9uZW50X2lkc192ZWMgPC0gbnVtZXJpYygpDQoNCiAgZm9yIChtYXRjaCBpbiBuZXdfb3Bwb25lbnRfaWRzKSB7DQogICAgaWYgKHN0cl9kZXRlY3QobWF0Y2gsICdbMC05XSsnKSkgew0KICAgICAgbmV3X29wcG9uZW50X2lkc192ZWMgPC0gYXBwZW5kKG5ld19vcHBvbmVudF9pZHNfdmVjLCBzdHJfZXh0cmFjdChtYXRjaCwgJ1swLTldKycpKQ0KICAgIH0NCiAgfQ0KDQogIG9wcG9uZW50X2lkc1tbcGxheWVyX2luZGV4XV0gPC0gbmV3X29wcG9uZW50X2lkc192ZWMNCiAgcGxheWVyX2luZGV4IDwtIHBsYXllcl9pbmRleCArIDENCn0NCmBgYA0KDQojIyMgQ2FsY3VsYXRlIGF2ZXJhZ2Ugb3Bwb25lbnQgcmF0aW5ncw0KDQpVc2luZyAib3Bwb25lbnRfaWRzIiwgbG9vayB1cCB0aGUgcmF0aW5ncyBmb3IgZXZlcnkgb3Bwb25lbnQgYXNzb2NpYXRlZCB3aXRoIGEgcGxheWVyJ3MgcGFpciBudW1iZXIuIEZvciBleGFtcGxlLCBHYXJ5IEh1YSBpcyB0aGUgZmlyc3QgcGxheWVyIGFuZCBmYWNlZCA3IG9wcG9uZW50cy4gTm9uZSBvZiB0aGUgcmF0aW5ncyBoYWQgZGVjaW1hbHMgaW4gdGhlbSwgc28gSSBvcHRlZCB0byByb3VuZCBlYWNoIG1lYW4gYW5kIGNvbnZlcnQgdGhlbSBpbnRvIGludGVnZXJzIGJlZm9yZSBzYXZpbmcgdGhlIGNvbHVtbi4NCmBgYHtyIGNhbGN1bGF0ZS1hdmctb3BwLXJhdGluZ3N9DQpmb3IgKHBsYXllcl9vcHBfaWRzIGluIDE6bGVuZ3RoKG9wcG9uZW50X2lkcykpIHsNCg0KICByYXRpbmdzX3ZlYyA8LSBudW1lcmljKCkNCiAgZm9yIChvcHBfaWQgaW4gb3Bwb25lbnRfaWRzW3BsYXllcl9vcHBfaWRzXSkgew0KICAgIHJhdGluZ3NfdmVjIDwtIGFwcGVuZChyYXRpbmdzX3ZlYywgYXMubnVtZXJpYyhwcmVfcmF0aW5nc1thcy5udW1lcmljKG9wcF9pZCldKSkNCiAgfQ0KICBhdmdfb3BwX3JhdGluZyA8LSBhcy5pbnRlZ2VyKHJvdW5kKG1lYW4ocmF0aW5nc192ZWMpKSkNCiAgb3BwX3ByZV9yYXRpbmdzIDwtIGFwcGVuZChvcHBfcHJlX3JhdGluZ3MsIGF2Z19vcHBfcmF0aW5nKQ0KfQ0KYGBgDQoNCiMjIyBDb2xsZWN0IHRoZSBkYXRhIGluIGEgZGF0YSBmcmFtZQ0KDQpXaXRoIGFsbCBvZiB0aGUgZGF0YSBhbHJlYWR5IG9yZ2FuaXplZCBpbnRvIGluZGl2aWR1YWwgY29sdW1ucywgY3JlYXRlIHRoZSBmaW5hbCBkYXRhIGZyYW1lLiBBIHNhbXBsZSBvZiB0aGUgZGF0YSBpcyBzaG93biBiZWxvdy4NCmBgYHtyIG1ha2UtZGF0YS1mcmFtZX0NCnRvdXJuYW1lbnRfcGxheWVycyA8LSBkYXRhLmZyYW1lKA0KICBuYW1lID0gcGxheWVycywNCiAgc3RhdGUgPSBzdGF0ZXMsDQogIHRvdGFsX3BvaW50cyA9IHBvaW50cywNCiAgcHJlX3JhdGluZyA9IHByZV9yYXRpbmdzLA0KICBhdmdfb3BwX3ByZV9yYXRpbmcgPSBvcHBfcHJlX3JhdGluZ3MNCikNCmhlYWQodG91cm5hbWVudF9wbGF5ZXJzLCAxMCkNCmBgYA0KDQojIyMgU2F2ZSB0byBjc3YNCg0KU2F2ZSB0aGUgcmVzdWx0cyB0byBhIGNzdiB3aXRob3V0IGFueSBxdW90ZXMgYXJvdW5kIHRoZSBzdHJpbmdzIGFuZCByZW1vdmluZyB0aGUgcGFpciBudW1iZXIgdmlhIHRoZSByb3cubmFtZXMgYXJndW1lbnQuIA0KYGBge3Igc2F2ZS10by1jc3Z9DQoNCndyaXRlLmNzdih4ID0gdG91cm5hbWVudF9wbGF5ZXJzLCBmaWxlID0gJ3RvdXJuYW1lbnRfcGxheWVycy5jc3YnLCBxdW90ZSA9IEZBTFNFLCByb3cubmFtZXMgPSBGQUxTRSkNCg0KYGBgDQoNCg0KIyMjIENvbmNsdXNpb25zDQoNCk1hbnkgYXNzdW1wdGlvbnMgd2VyZSBtYWRlIGFjY29yZGluZyB0byB0aGUgZXhhY3QgbGF5b3V0IG9mICJ0b3VybmFtZW50aW5mby50eHQiLiBSb3dzIHdlcmUgbGFpZCBvdXQgY29uc2lzdGVudGx5IGZvciBlYWNoIHBsYXllci4gQ29sdW1ucyBhbHdheXMgaGFkIGEgInwiIGJldHdlZW4gdGhlbS4gVG90YWwgcG9pbnRzIGFsd2F5cyBoYWQgYSAiLiIgcmVnYXJkbGVzcyBvZiB0aGUgbnVtYmVyLiANCg0KVGhpcyBraW5kIG9mIHBhcnNpbmcgaXMgbm90IGdvb2QgZm9yIHJldXNhYmlsaXR5LCBidXQgaXQgYWxzbyByZXByZXNlbnRzIGEgcmVhbGlzdGljIHNjZW5hcmlvLiBXaGVuIHNjcmFwaW5nIGRhdGEsIHRoZSBmb3JtYXQgaXQgaXMgaW4gbWlnaHQgbm90IGFsd2F5cyBiZSBjb252ZW5pZW50LiBUaGVyZSB3aWxsIG9mdGVuIGJlIHRpbWVzIHdoZXJlIGEgY3VzdG9tIHNvbHV0aW9uIGlzIG5lZWRlZCB0byBjb2xsZWN0IHRoYXQgaW5mb3JtYXRpb24uDQo=