Introduction

The first big project! This project is centered around cleaning up a nonstandard set of data, particularly one for chess tournament results. I am expected to create a organize some of the data that is already in this table as well as generate averages of each player’s opponent ranking. What’s left will be a consise look into who the players are, how they did in the tournament, and the average opponent they faced.

Some of the challenges I expect with this assignment are the cleaning of the data format as well as the augmentation of the columns in order to get the average opponent ranking. This will be a true test of some of the skills we have learned so far in this class, as well as apply new principles in order to clean values or match rows based on column values.

The code

I approached this data entirely in R, while I could see a path to solving this problem using SQL as well.

First we will load in the .txt as is and see what we can do to start chipping away at it. after struggling through read.csv, read_csv, and other similar functions, I found that read.delim was the option that produced the best data load-in, and using “|” as the delimiter allowed us to get a rough graph to start.

Chess_Data <- read.delim("tournamentinfo.txt", header = FALSE, sep = "|")

glimpse(Chess_Data)
## Rows: 196
## Columns: 11
## $ V1  <chr> "-----------------------------------------------------------------…
## $ V2  <chr> "", " Player Name                     ", " USCF ID / Rtg (Pre->Pos…
## $ V3  <chr> "", "Total", " Pts ", "", "6.0  ", "N:2  ", "", "6.0  ", "N:2  ", …
## $ V4  <chr> "", "Round", "  1  ", "", "W  39", "W    ", "", "W  63", "B    ", …
## $ V5  <chr> "", "Round", "  2  ", "", "W  21", "B    ", "", "W  58", "W    ", …
## $ V6  <chr> "", "Round", "  3  ", "", "W  18", "W    ", "", "L   4", "B    ", …
## $ V7  <chr> "", "Round", "  4  ", "", "W  14", "B    ", "", "W  17", "W    ", …
## $ V8  <chr> "", "Round", "  5  ", "", "W   7", "W    ", "", "W  16", "B    ", …
## $ V9  <chr> "", "Round", "  6  ", "", "D  12", "B    ", "", "W  20", "W    ", …
## $ V10 <chr> "", "Round", "  7  ", "", "D   4", "W    ", "", "W   7", "B    ", …
## $ V11 <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…

As you can see, the data, while now imported into R, is very much in need of some cleaning. we have rows that provide no value, and a lot of data with no strong corresponding columns. I noticed a pattern where after removing the “—–…” rows, there were different data sets on the even and odd rows. I added an index and used this along with a modulo operator (%), which allows you to use the remainder as a filtering condition. ThiS along with some other polishing I was able to get two tables that hold different sets of data we will need to complete the finished product.

## Rows: 64
## Columns: 3
## $ Area   <chr> "   ON ", "   MI ", "   MI ", "   MI ", "   MI ", "   OH ", "  …
## $ Rating <chr> " 15445895 / R: 1794   ->1817     ", " 14598900 / R: 1553   ->1…
## $ index  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## Rows: 64
## Columns: 11
## $ Rank   <chr> "    1 ", "    2 ", "    3 ", "    4 ", "    5 ", "    6 ", "  …
## $ Name   <chr> " GARY HUA                        ", " DAKSHESH DARURI         …
## $ Points <chr> "6.0  ", "6.0  ", "6.0  ", "5.5  ", "5.5  ", "5.0  ", "5.0  ", …
## $ R1     <chr> "W  39", "W  63", "L   8", "W  23", "W  45", "W  34", "W  57", …
## $ R2     <chr> "W  21", "W  58", "W  61", "D  28", "W  37", "D  29", "W  46", …
## $ R3     <chr> "W  18", "L   4", "W  25", "W   2", "D  12", "L  11", "W  13", …
## $ R4     <chr> "W  14", "W  17", "W  21", "W  26", "D  13", "W  35", "W  11", …
## $ R5     <chr> "W   7", "W  16", "W  11", "D   5", "D   4", "D  10", "L   1", …
## $ R6     <chr> "D  12", "W  20", "W  13", "W  19", "W  14", "W  27", "W   9", …
## $ R7     <chr> "D   4", "W   7", "W  12", "D   1", "W  17", "W  21", "L   2", …
## $ index  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …

Now that I have both of these tables, I will join them together and start cleaning the values within the tables, particularly the Elo and the Round results. I need these to be integers so that they can be manipulated. There is one caveat to this that I will go into later, and I still don’t fully understand it.

Full_Clean_Data <- full_join(Chess_Data_Even,Chess_Data_Odd) 
## Joining with `by = join_by(index)`
glimpse(Full_Clean_Data)
## Rows: 64
## Columns: 13
## $ Area   <chr> "   ON ", "   MI ", "   MI ", "   MI ", "   MI ", "   OH ", "  …
## $ Rating <chr> " 15445895 / R: 1794   ->1817     ", " 14598900 / R: 1553   ->1…
## $ index  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## $ Rank   <chr> "    1 ", "    2 ", "    3 ", "    4 ", "    5 ", "    6 ", "  …
## $ Name   <chr> " GARY HUA                        ", " DAKSHESH DARURI         …
## $ Points <chr> "6.0  ", "6.0  ", "6.0  ", "5.5  ", "5.5  ", "5.0  ", "5.0  ", …
## $ R1     <chr> "W  39", "W  63", "L   8", "W  23", "W  45", "W  34", "W  57", …
## $ R2     <chr> "W  21", "W  58", "W  61", "D  28", "W  37", "D  29", "W  46", …
## $ R3     <chr> "W  18", "L   4", "W  25", "W   2", "D  12", "L  11", "W  13", …
## $ R4     <chr> "W  14", "W  17", "W  21", "W  26", "D  13", "W  35", "W  11", …
## $ R5     <chr> "W   7", "W  16", "W  11", "D   5", "D   4", "D  10", "L   1", …
## $ R6     <chr> "D  12", "W  20", "W  13", "W  19", "W  14", "W  27", "W   9", …
## $ R7     <chr> "D   4", "W   7", "W  12", "D   1", "W  17", "W  21", "L   2", …
Full_Clean_Data2 <- Full_Clean_Data |>
  mutate(
    across(
      c(R1, R2, R3, R4, R5, R6, R7),
         ~str_remove_all(.x, "W|L|D")
      )
  ) |>
    mutate(
    across(
      c(R1, R2, R3, R4, R5, R6, R7, Rank),
      ~as.integer(.x)
    )
  ) |>
  mutate(
    Rating = str_remove(Rating, ".*:"),
    Rating = str_remove(Rating, "-.*"),
    Rating = str_remove(Rating, "P.*")
  )
## Warning: There were 7 warnings in `mutate()`.
## The first warning was:
## ℹ In argument: `across(c(R1, R2, R3, R4, R5, R6, R7, Rank), ~as.integer(.x))`.
## Caused by warning:
## ! NAs introduced by coercion
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 6 remaining warnings.
glimpse(Full_Clean_Data2)
## Rows: 64
## Columns: 13
## $ Area   <chr> "   ON ", "   MI ", "   MI ", "   MI ", "   MI ", "   OH ", "  …
## $ Rating <chr> " 1794   ", " 1553   ", " 1384   ", " 1716   ", " 1655   ", " 1…
## $ index  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## $ Rank   <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## $ Name   <chr> " GARY HUA                        ", " DAKSHESH DARURI         …
## $ Points <chr> "6.0  ", "6.0  ", "6.0  ", "5.5  ", "5.5  ", "5.0  ", "5.0  ", …
## $ R1     <int> 39, 63, 8, 23, 45, 34, 57, 3, 25, 16, 38, 42, 36, 54, 19, 10, 4…
## $ R2     <int> 21, 58, 61, 28, 37, 29, 46, 32, 18, 19, 56, 33, 27, 44, 16, 15,…
## $ R3     <int> 18, 4, 25, 2, 12, 11, 13, 14, 59, 55, 6, 5, 7, 8, 30, NA, 26, 1…
## $ R4     <int> 14, 17, 21, 26, 13, 35, 11, 9, 8, 31, 7, 38, 5, 1, 22, 39, 2, 3…
## $ R5     <int> 7, 16, 11, 5, 4, 10, 1, 47, 26, 6, 3, NA, 33, 27, 54, 2, 23, 19…
## $ R6     <int> 12, 20, 13, 19, 14, 27, 9, 28, 7, 25, 34, 1, 3, 5, 33, 36, 22, …
## $ R7     <int> 4, 7, 12, 1, 17, 21, 2, 19, 20, 18, 26, 3, 32, 31, 38, NA, 5, 1…

Now here is where I really hit a roadblock with this assignment. My next goal was to use the cleaned up match results to pull in the Elo of the player they played in order to eventually generate an average. I am well aware of xlookup in excel, but trying to figure out how to get an operation that mirrors it in R was quite difficult for me. Through searching around and attempting different joins, I found that the most effective method of getting these was to use a more native solution through Rating[match(Rank from Match, Rank of player they played)].

The Caveat I described above was that this match function would not work if the RX values were integers. I have no Idea why this was the case but I rolled with it, and my work around was to just convert them after the match function.

With these data points matched, I was able to calculate the newest column that finds the average opponent Elo. Thanks to assignment 3A I had the know-how of using rowwise() to get accurate averages for each row. With this being done, all that was needed was a little bit of cleaning, and then I was done!

Chess_Data_Joins <- Full_Clean_Data2 |>
  select(!index) |>
  mutate(R1elo = Rating[match(R1, Rank)],
         R2elo = Rating[match(R2, Rank)],
         R3elo = Rating[match(R3, Rank)],
         R4elo = Rating[match(R4, Rank)],
         R5elo = Rating[match(R5, Rank)],
         R6elo = Rating[match(R6, Rank)],
         R7elo = Rating[match(R7, Rank)]) |>
      mutate(
    across(
      c(R1elo, R2elo, R3elo, R4elo, R5elo, R6elo, R7elo),
      ~as.integer(.x)
    )
  ) |>
  rowwise() |>
  mutate(Opponent_Avg_Elo = mean(
      c(R1elo, R2elo, R3elo, R4elo, R5elo, R6elo, R7elo
      )
      , na.rm = TRUE)) |>
  relocate(Name, Area, Points, Rating, Opponent_Avg_Elo) |>
  arrange(Rank) |>
  select(Name, Area, Points, Rating, Opponent_Avg_Elo) |>
  mutate(Opponent_Avg_Elo = round(Opponent_Avg_Elo, digits = 0))
  
   kable(Chess_Data_Joins)
Name Area Points Rating Opponent_Avg_Elo
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
CAMERON WILLIAM MC LEMAN MI 4.5 1712 1468
KENNETH J TACK MI 4.5 1663 1506
TORRANCE HENRY JR MI 4.5 1666 1498
BRADLEY SHAW MI 4.5 1610 1515
ZACHARY JAMES HOUGHTON MI 4.5 1220 1484
MIKE NIKITIN MI 4.0 1604 1386
RONALD GRZEGORCZYK MI 4.0 1629 1499
DAVID SUNDEEN MI 4.0 1600 1480
DIPANKAR ROY MI 4.0 1564 1426
JASON ZHENG MI 4.0 1595 1411
DINH DANG BUI ON 4.0 1563 1470
EUGENE L MCCLURE MI 4.0 1555 1300
ALAN BUI ON 4.0 1363 1214
MICHAEL R ALDRICH MI 4.0 1229 1357
LOREN SCHWIEBERT MI 3.5 1745 1363
MAX ZHU ON 3.5 1579 1507
GAURAV GIDWANI MI 3.5 1552 1222
SOFIA ADINA STANESCU-BELLU MI 3.5 1507 1522
CHIEDOZIE OKORIE MI 3.5 1602 1314
GEORGE AVERY JONES ON 3.5 1522 1144
RISHI SHETTY MI 3.5 1494 1260
JOSHUA PHILIP MATHEWS ON 3.5 1441 1379
JADE GE MI 3.5 1449 1277
MICHAEL JEFFERY THOMAS MI 3.5 1399 1375
JOSHUA DAVID LEE MI 3.5 1438 1150
SIDDHARTH JHA MI 3.5 1355 1388
AMIYATOSH PWNANANDAM MI 3.5 980 1385
BRIAN LIU MI 3.0 1423 1539
JOEL R HENDON MI 3.0 1436 1430
FOREST ZHANG MI 3.0 1348 1391
KYLE WILLIAM MURPHY MI 3.0 1403 1248
JARED GE MI 3.0 1332 1150
ROBERT GLEN VASEY MI 3.0 1283 1107
JUSTIN D SCHILLING MI 3.0 1199 1327
DEREK YAN MI 3.0 1242 1152
JACOB ALEXANDER LAVALLEY MI 3.0 377 1358
ERIC WRIGHT MI 2.5 1362 1392
DANIEL KHAIN MI 2.5 1382 1356
MICHAEL J MARTIN MI 2.5 1291 1286
SHIVAM JHA MI 2.5 1056 1296
TEJAS AYYAGARI MI 2.5 1011 1356
ETHAN GUO MI 2.5 935 1495
JOSE C YBARRA MI 2.0 1393 1345
LARRY HODGE MI 2.0 1270 1206
ALEX KONG MI 2.0 1186 1406
MARISA RICCI MI 2.0 1153 1414
MICHAEL LU MI 2.0 1092 1363
VIRAJ MOHILE MI 2.0 917 1391
SEAN M MC CORMICK MI 2.0 853 1319
JULIA SHEN MI 1.5 967 1330
JEZZEL FARKAS ON 1.5 955 1327
ASHWIN BALAJI MI 1.0 1530 1186
THOMAS JOSEPH HOSMER MI 1.0 1175 1350
BEN LI MI 1.0 1163 1263

Conclusion

In conclusion, this was a well rounded test of being able clean non-standard data formats. After spending the first few weeks of this class focused on the basics of the different IDE’s and the building blocks of R writing, this assignment allowed me to bring everything together while still learning new critical skills for building. I was able to take the raw data, convert it to a readable and workable format, and then apply further logic to use the existing data to generate new data.

The areas I expected to be the most difficult ended up working out to be some of the simpler problems. For example, the actual reading of the document and being able to separate the data into two working tables I thought would take a lot longer to do. The hardest part for me was the generating of the average and understanding the best way to join tables. There are a lot of different joins and matches, and knowing which ones to use for which situation is difficult initially. also, I am not as comfortable with native R functions as I am with tidyverse functions, so I always feel less confident resorting to a native function to solve a problem.

In thinking about what could be done differently or what could be improved I can think of two things. One, I think there is room for some of this data manipulation to be done in SQL in potentially a more concise manner. Although more concise, it would also require an added layer of uploading the data to SQL and then exporting back out. Second, I think there is room to optimize the code. A lot of my code for this assignment was scrappy, and I feel that there are better ways to apply functions over a range of data differently and more compact than I did. I also think I could get more done within a function than I do now, such as taking care of multiple mutations under the same function. As I continue to figure out how everything interacts within the R language I feel more comfortable performing operations independent of eachother, but I know ultimately there are ways to make it neater and more concise.