# packages
library(rvest)
library(googlesheets4)
library(cronR)
library(kableExtra)
library(dplyr)
library(tidyr)
Masters Fantasy Golf Tournament Dashboard
This blog posts shows how I use the cronR
package to automate a fantasy golf pool with some friends for the golf major tournaments.
The process takes four steps:
- Collect the leader board data
- Prepare the data
- Publish the data to GoogleSheets
- Automate the script with cronR
Lets use the 2022 Masters tournament as an example of how we set up the pipeline.
Collecting Leaderboard Data
I’ve found success using two different methods here. Either through rvest
to web scrape the leader board data off the web, i.e. ESPN Leaderboard, or through an API. The API route takes more work, but I’ve found the ESPN Developer API to work well during the tournament. Here is the endpoint: https://site.api.espn.com/apis/site/v2/sports/golf/pga/scoreboard
.
For this demo, we will use the web scraping approach, as there is no guarantee the API will work in the future.
= "https://www.espn.com/golf/leaderboard?tournamentId=401353232"
url
= read_html(url)
content = content %>%
scores html_table() %>%
bind_rows() %>%
select(-1)
%>%
scores kbl() %>%
kable_paper("striped") %>%
scroll_box(height = "500px")
POS | PLAYER | SCORE | R1 | R2 | R3 | R4 | TOT | EARNINGS | FEDEX PTS |
---|---|---|---|---|---|---|---|---|---|
1 | Scottie Scheffler | -10 | 69 | 67 | 71 | 71 | 278 | $2,700,000 | 600 |
2 | Rory McIlroy | -7 | 73 | 73 | 71 | 64 | 281 | $1,620,000 | 330 |
T3 | Shane Lowry | -5 | 73 | 68 | 73 | 69 | 283 | $870,000 | 180 |
T3 | Cameron Smith | -5 | 68 | 74 | 68 | 73 | 283 | $870,000 | 180 |
5 | Collin Morikawa | -4 | 73 | 70 | 74 | 67 | 284 | $600,000 | 120 |
T6 | Corey Conners | -3 | 70 | 73 | 72 | 70 | 285 | $521,250 | 105 |
T6 | Will Zalatoris | -3 | 71 | 72 | 75 | 67 | 285 | $521,250 | 105 |
T8 | Sungjae Im | -1 | 67 | 74 | 71 | 75 | 287 | $450,000 | 91 |
T8 | Justin Thomas | -1 | 76 | 67 | 72 | 72 | 287 | $450,000 | 91 |
T10 | Charl Schwartzel | E | 72 | 69 | 73 | 74 | 288 | $390,000 | 80 |
T10 | Cameron Champ | E | 72 | 75 | 71 | 70 | 288 | $390,000 | 80 |
T12 | Dustin Johnson | +1 | 69 | 73 | 75 | 72 | 289 | $330,000 | 70 |
T12 | Danny Willett | +1 | 69 | 74 | 73 | 73 | 289 | $330,000 | 70 |
T14 | Kevin Na | +2 | 71 | 71 | 79 | 69 | 290 | $225,333 | 55 |
T14 | Jason Kokrak | +2 | 70 | 76 | 71 | 73 | 290 | $225,333 | 55 |
T14 | Min Woo Lee | +2 | 73 | 75 | 72 | 70 | 290 | $225,333 | 0 |
T14 | Lee Westwood | +2 | 72 | 74 | 73 | 71 | 290 | $225,333 | 55 |
T14 | Harry Higgs | +2 | 71 | 75 | 73 | 71 | 290 | $225,333 | 55 |
T14 | Tommy Fleetwood | +2 | 75 | 72 | 70 | 73 | 290 | $225,333 | 55 |
T14 | Hideki Matsuyama | +2 | 72 | 69 | 77 | 72 | 290 | $225,333 | 55 |
T14 | Matt Fitzpatrick | +2 | 71 | 73 | 76 | 70 | 290 | $225,333 | 55 |
T14 | Talor Gooch | +2 | 72 | 74 | 73 | 71 | 290 | $225,333 | 55 |
T23 | J.J. Spaun | +3 | 74 | 70 | 75 | 72 | 291 | $138,000 | 41 |
T23 | Robert MacIntyre | +3 | 73 | 73 | 76 | 69 | 291 | $138,000 | 0 |
T23 | Sergio Garcia | +3 | 72 | 74 | 74 | 71 | 291 | $138,000 | 41 |
T23 | Harold Varner III | +3 | 71 | 71 | 80 | 69 | 291 | $138,000 | 41 |
T27 | Viktor Hovland | +4 | 72 | 76 | 71 | 73 | 292 | $111,000 | 35 |
T27 | Séamus Power | +4 | 74 | 74 | 74 | 70 | 292 | $111,000 | 35 |
T27 | Jon Rahm | +4 | 74 | 72 | 77 | 69 | 292 | $111,000 | 35 |
T30 | Marc Leishman | +5 | 73 | 75 | 71 | 74 | 293 | $93,150 | 28 |
T30 | Russell Henley | +5 | 73 | 74 | 76 | 70 | 293 | $93,150 | 28 |
T30 | Hudson Swafford | +5 | 77 | 69 | 73 | 74 | 293 | $93,150 | 28 |
T30 | Lucas Glover | +5 | 72 | 76 | 72 | 73 | 293 | $93,150 | 28 |
T30 | Sepp Straka | +5 | 74 | 72 | 76 | 71 | 293 | $93,150 | 28 |
T35 | Joaquin Niemann | +6 | 69 | 74 | 77 | 74 | 294 | $75,563 | 22 |
T35 | Webb Simpson | +6 | 71 | 74 | 73 | 76 | 294 | $75,563 | 22 |
T35 | Tony Finau | +6 | 71 | 75 | 74 | 74 | 294 | $75,563 | 22 |
T35 | Patrick Reed | +6 | 74 | 73 | 73 | 74 | 294 | $75,563 | 22 |
T39 | Patrick Cantlay | +7 | 70 | 75 | 79 | 71 | 295 | $63,000 | 18 |
T39 | Tom Hoge | +7 | 73 | 74 | 75 | 73 | 295 | $63,000 | 18 |
T39 | Si Woo Kim | +7 | 76 | 70 | 73 | 76 | 295 | $63,000 | 18 |
T39 | Bubba Watson | +7 | 73 | 73 | 78 | 71 | 295 | $63,000 | 18 |
43 | Billy Horschel | +8 | 74 | 73 | 79 | 70 | 296 | $55,500 | 15 |
T44 | Kevin Kisner | +9 | 75 | 70 | 75 | 77 | 297 | $51,000 | 13 |
T44 | Christiaan Bezuidenhout | +9 | 73 | 71 | 77 | 76 | 297 | $51,000 | 13 |
46 | Cam Davis | +12 | 75 | 73 | 79 | 73 | 300 | $46,500 | 12 |
47 | Tiger Woods | +13 | 71 | 74 | 78 | 78 | 301 | $43,500 | 11 |
T48 | Adam Scott | +14 | 74 | 74 | 80 | 74 | 302 | $40,050 | 10 |
T48 | Max Homa | +14 | 74 | 73 | 77 | 78 | 302 | $40,050 | 10 |
T50 | Mackenzie Hughes | +15 | 73 | 75 | 77 | 78 | 303 | $37,350 | 9 |
T50 | Daniel Berger | +15 | 71 | 75 | 77 | 80 | 303 | $37,350 | 9 |
52 | Tyrrell Hatton | +17 | 72 | 74 | 79 | 80 | 305 | $36,000 | 8 |
- | Brian Harman | CUT | 74 | 75 | -- | -- | 149 | -- | 0 |
- | Padraig Harrington | CUT | 74 | 75 | -- | -- | 149 | -- | 0 |
- | Takumi Kanaya | CUT | 75 | 74 | -- | -- | 149 | -- | 0 |
- | Zach Johnson | CUT | 74 | 75 | -- | -- | 149 | -- | 0 |
- | K.H. Lee | CUT | 74 | 75 | -- | -- | 149 | -- | 0 |
- | Sam Burns | CUT | 75 | 74 | -- | -- | 149 | -- | 0 |
- | Lucas Herbert | CUT | 74 | 76 | -- | -- | 150 | -- | 0 |
- | Mike Weir | CUT | 74 | 76 | -- | -- | 150 | -- | 0 |
- | Jordan Spieth | CUT | 74 | 76 | -- | -- | 150 | -- | 0 |
- | Brooks Koepka | CUT | 75 | 75 | -- | -- | 150 | -- | 0 |
- | Ryan Palmer | CUT | 75 | 75 | -- | -- | 150 | -- | 0 |
- | Xander Schauffele | CUT | 74 | 77 | -- | -- | 151 | -- | 0 |
- | Keita Nakajima | CUT | 72 | 79 | -- | -- | 151 | -- | 0 |
- | Austin Greaser (a) | CUT | 74 | 77 | -- | -- | 151 | -- | 0 |
- | Stewart Cink | CUT | 76 | 75 | -- | -- | 151 | -- | 0 |
- | Abraham Ancer | CUT | 72 | 79 | -- | -- | 151 | -- | 0 |
- | Luke List | CUT | 77 | 75 | -- | -- | 152 | -- | 0 |
- | Francesco Molinari | CUT | 78 | 74 | -- | -- | 152 | -- | 0 |
- | Bernhard Langer | CUT | 76 | 76 | -- | -- | 152 | -- | 0 |
- | Gary Woodland | CUT | 75 | 77 | -- | -- | 152 | -- | 0 |
- | Justin Rose | CUT | 76 | 76 | -- | -- | 152 | -- | 0 |
- | Erik van Rooyen | CUT | 73 | 79 | -- | -- | 152 | -- | 0 |
- | Guido Migliozzi | CUT | 75 | 77 | -- | -- | 152 | -- | 0 |
- | Cameron Young | CUT | 77 | 77 | -- | -- | 154 | -- | 0 |
- | Fred Couples | CUT | 75 | 79 | -- | -- | 154 | -- | 0 |
- | Garrick Higgo | CUT | 72 | 83 | -- | -- | 155 | -- | 0 |
- | Larry Mize | CUT | 77 | 78 | -- | -- | 155 | -- | 0 |
- | James Piot | CUT | 81 | 74 | -- | -- | 155 | -- | 0 |
- | Aaron Jarvis (a) | CUT | 81 | 74 | -- | -- | 155 | -- | 0 |
- | Bryson DeChambeau | CUT | 76 | 80 | -- | -- | 156 | -- | 0 |
- | Sandy Lyle | CUT | 82 | 76 | -- | -- | 158 | -- | 0 |
- | Vijay Singh | CUT | 78 | 80 | -- | -- | 158 | -- | 0 |
- | Matthew Wolff | CUT | 81 | 78 | -- | -- | 159 | -- | 0 |
- | Thomas Pieters | CUT | 79 | 80 | -- | -- | 159 | -- | 0 |
- | Stewart Hagestad (a) | CUT | 79 | 81 | -- | -- | 160 | -- | 0 |
- | José María Olazábal | CUT | 77 | 84 | -- | -- | 161 | -- | 0 |
- | Laird Shepherd | CUT | 81 | 85 | -- | -- | 166 | -- | 0 |
- | Louis Oosthuizen | WD | 76 | -- | -- | -- | 76 | -- | 0 |
- | Paul Casey | WD | -- | -- | -- | -- | -- | -- | 0 |
The rules of our pool were to each pick 6 players. We would count the four lowest scores each day, and otherwise assign a score of +8 if you failed to have four players make the cut. The winner is the one with the lowest score for their team at the end of the tournament.
Join fantasy picks
We each fill out our teams by a snake draft often on Zoom. For the 2021 Masters, we filled our our results in a GoogleSheet. We can use the googlesheets4
package to read in data from our GoogleSheet of picks into R.
= "https://docs.google.com/spreadsheets/d/14nW_AWYil-jBQ2lC54k_lETGw_O031no-Q3FYvtwsUs/edit#gid=0"
ss = read_sheet(ss, sheet = "draft")
picks
%>%
picks pivot_wider(id_cols = Round, names_from = team, values_from = player) %>%
kbl() %>%
kable_paper("striped")
Round | Bray | Bret | Craig | Jacko | John | Jordan | Mike | Rocco | Trev |
---|---|---|---|---|---|---|---|---|---|
1 | Justin Thomas | Rory McIlroy | Collin Morikawa | Brooks Koepka | Scottie Scheffler | Cameron Smith | Viktor Hovland | Jon Rahm | Dustin Johnson |
3 | Shane Lowry | Corey Conners | Paul Casey | Daniel Berger | Tommy Fleetwood | Sam Burns | Abraham Ancer | Bryson DeChambeau | Matt Fitzpatrick |
2 | Louis Oosthuizen | Jordan Spieth | Hideki Matsuyama | Will Zalatoris | Tiger Woods | Joaquin Niemann | Patrick Cantlay | Tony Finau | Xander Schauffele |
4 | Tyrrell Hatton | Sungjae Im | Max Homa | Kevin Kisner | Adam Scott | Justin Rose | Patrick Reed | Billy Horschel | Russell Henley |
5 | Bubba Watson | Jason Kokrak | Kevin Na | Cameron Champ | Marc Leishman | Sergio Garcia | Lee Westwood | Webb Simpson | Talor Gooch |
6 | Seamus Power | Tom Hoge | Thomas Pieters | Mackenzie Hughes | Gary Woodland | Si Woo Kim | Lucas Herbert | Francesco Molinari | Cameron Young |
# join scoreboard data & reshape
= picks %>%
results select(-Round) %>%
left_join(scores %>% select(-c("POS", "SCORE", "TOT", "EARNINGS", "FEDEX PTS") ),
by = c("player" = "PLAYER")) %>%
pivot_longer(c("R1", "R2", "R3", "R4"), names_to = "round", values_to = "score") %>%
mutate(score = ifelse(score == "--", 80, as.numeric(score))) # set MCs to 80
%>%
results kbl() %>%
kable_paper("striped") %>%
scroll_box(height = "500px")
team | player | round | score |
---|---|---|---|
Bray | Justin Thomas | R1 | 76 |
Bray | Justin Thomas | R2 | 67 |
Bray | Justin Thomas | R3 | 72 |
Bray | Justin Thomas | R4 | 72 |
Bray | Shane Lowry | R1 | 73 |
Bray | Shane Lowry | R2 | 68 |
Bray | Shane Lowry | R3 | 73 |
Bray | Shane Lowry | R4 | 69 |
Bray | Louis Oosthuizen | R1 | 76 |
Bray | Louis Oosthuizen | R2 | 80 |
Bray | Louis Oosthuizen | R3 | 80 |
Bray | Louis Oosthuizen | R4 | 80 |
Bray | Tyrrell Hatton | R1 | 72 |
Bray | Tyrrell Hatton | R2 | 74 |
Bray | Tyrrell Hatton | R3 | 79 |
Bray | Tyrrell Hatton | R4 | 80 |
Bray | Bubba Watson | R1 | 73 |
Bray | Bubba Watson | R2 | 73 |
Bray | Bubba Watson | R3 | 78 |
Bray | Bubba Watson | R4 | 71 |
Bray | Seamus Power | R1 | NA |
Bray | Seamus Power | R2 | NA |
Bray | Seamus Power | R3 | NA |
Bray | Seamus Power | R4 | NA |
Bret | Jordan Spieth | R1 | 74 |
Bret | Jordan Spieth | R2 | 76 |
Bret | Jordan Spieth | R3 | 80 |
Bret | Jordan Spieth | R4 | 80 |
Bret | Rory McIlroy | R1 | 73 |
Bret | Rory McIlroy | R2 | 73 |
Bret | Rory McIlroy | R3 | 71 |
Bret | Rory McIlroy | R4 | 64 |
Bret | Corey Conners | R1 | 70 |
Bret | Corey Conners | R2 | 73 |
Bret | Corey Conners | R3 | 72 |
Bret | Corey Conners | R4 | 70 |
Bret | Sungjae Im | R1 | 67 |
Bret | Sungjae Im | R2 | 74 |
Bret | Sungjae Im | R3 | 71 |
Bret | Sungjae Im | R4 | 75 |
Bret | Tom Hoge | R1 | 73 |
Bret | Tom Hoge | R2 | 74 |
Bret | Tom Hoge | R3 | 75 |
Bret | Tom Hoge | R4 | 73 |
Bret | Jason Kokrak | R1 | 70 |
Bret | Jason Kokrak | R2 | 76 |
Bret | Jason Kokrak | R3 | 71 |
Bret | Jason Kokrak | R4 | 73 |
Craig | Collin Morikawa | R1 | 73 |
Craig | Collin Morikawa | R2 | 70 |
Craig | Collin Morikawa | R3 | 74 |
Craig | Collin Morikawa | R4 | 67 |
Craig | Hideki Matsuyama | R1 | 72 |
Craig | Hideki Matsuyama | R2 | 69 |
Craig | Hideki Matsuyama | R3 | 77 |
Craig | Hideki Matsuyama | R4 | 72 |
Craig | Paul Casey | R1 | 80 |
Craig | Paul Casey | R2 | 80 |
Craig | Paul Casey | R3 | 80 |
Craig | Paul Casey | R4 | 80 |
Craig | Max Homa | R1 | 74 |
Craig | Max Homa | R2 | 73 |
Craig | Max Homa | R3 | 77 |
Craig | Max Homa | R4 | 78 |
Craig | Thomas Pieters | R1 | 79 |
Craig | Thomas Pieters | R2 | 80 |
Craig | Thomas Pieters | R3 | 80 |
Craig | Thomas Pieters | R4 | 80 |
Craig | Kevin Na | R1 | 71 |
Craig | Kevin Na | R2 | 71 |
Craig | Kevin Na | R3 | 79 |
Craig | Kevin Na | R4 | 69 |
Jacko | Brooks Koepka | R1 | 75 |
Jacko | Brooks Koepka | R2 | 75 |
Jacko | Brooks Koepka | R3 | 80 |
Jacko | Brooks Koepka | R4 | 80 |
Jacko | Will Zalatoris | R1 | 71 |
Jacko | Will Zalatoris | R2 | 72 |
Jacko | Will Zalatoris | R3 | 75 |
Jacko | Will Zalatoris | R4 | 67 |
Jacko | Daniel Berger | R1 | 71 |
Jacko | Daniel Berger | R2 | 75 |
Jacko | Daniel Berger | R3 | 77 |
Jacko | Daniel Berger | R4 | 80 |
Jacko | Kevin Kisner | R1 | 75 |
Jacko | Kevin Kisner | R2 | 70 |
Jacko | Kevin Kisner | R3 | 75 |
Jacko | Kevin Kisner | R4 | 77 |
Jacko | Cameron Champ | R1 | 72 |
Jacko | Cameron Champ | R2 | 75 |
Jacko | Cameron Champ | R3 | 71 |
Jacko | Cameron Champ | R4 | 70 |
Jacko | Mackenzie Hughes | R1 | 73 |
Jacko | Mackenzie Hughes | R2 | 75 |
Jacko | Mackenzie Hughes | R3 | 77 |
Jacko | Mackenzie Hughes | R4 | 78 |
John | Scottie Scheffler | R1 | 69 |
John | Scottie Scheffler | R2 | 67 |
John | Scottie Scheffler | R3 | 71 |
John | Scottie Scheffler | R4 | 71 |
John | Tiger Woods | R1 | 71 |
John | Tiger Woods | R2 | 74 |
John | Tiger Woods | R3 | 78 |
John | Tiger Woods | R4 | 78 |
John | Adam Scott | R1 | 74 |
John | Adam Scott | R2 | 74 |
John | Adam Scott | R3 | 80 |
John | Adam Scott | R4 | 74 |
John | Tommy Fleetwood | R1 | 75 |
John | Tommy Fleetwood | R2 | 72 |
John | Tommy Fleetwood | R3 | 70 |
John | Tommy Fleetwood | R4 | 73 |
John | Marc Leishman | R1 | 73 |
John | Marc Leishman | R2 | 75 |
John | Marc Leishman | R3 | 71 |
John | Marc Leishman | R4 | 74 |
John | Gary Woodland | R1 | 75 |
John | Gary Woodland | R2 | 77 |
John | Gary Woodland | R3 | 80 |
John | Gary Woodland | R4 | 80 |
Jordan | Cameron Smith | R1 | 68 |
Jordan | Cameron Smith | R2 | 74 |
Jordan | Cameron Smith | R3 | 68 |
Jordan | Cameron Smith | R4 | 73 |
Jordan | Sam Burns | R1 | 75 |
Jordan | Sam Burns | R2 | 74 |
Jordan | Sam Burns | R3 | 80 |
Jordan | Sam Burns | R4 | 80 |
Jordan | Joaquin Niemann | R1 | 69 |
Jordan | Joaquin Niemann | R2 | 74 |
Jordan | Joaquin Niemann | R3 | 77 |
Jordan | Joaquin Niemann | R4 | 74 |
Jordan | Sergio Garcia | R1 | 72 |
Jordan | Sergio Garcia | R2 | 74 |
Jordan | Sergio Garcia | R3 | 74 |
Jordan | Sergio Garcia | R4 | 71 |
Jordan | Justin Rose | R1 | 76 |
Jordan | Justin Rose | R2 | 76 |
Jordan | Justin Rose | R3 | 80 |
Jordan | Justin Rose | R4 | 80 |
Jordan | Si Woo Kim | R1 | 76 |
Jordan | Si Woo Kim | R2 | 70 |
Jordan | Si Woo Kim | R3 | 73 |
Jordan | Si Woo Kim | R4 | 76 |
Mike | Viktor Hovland | R1 | 72 |
Mike | Viktor Hovland | R2 | 76 |
Mike | Viktor Hovland | R3 | 71 |
Mike | Viktor Hovland | R4 | 73 |
Mike | Patrick Cantlay | R1 | 70 |
Mike | Patrick Cantlay | R2 | 75 |
Mike | Patrick Cantlay | R3 | 79 |
Mike | Patrick Cantlay | R4 | 71 |
Mike | Patrick Reed | R1 | 74 |
Mike | Patrick Reed | R2 | 73 |
Mike | Patrick Reed | R3 | 73 |
Mike | Patrick Reed | R4 | 74 |
Mike | Abraham Ancer | R1 | 72 |
Mike | Abraham Ancer | R2 | 79 |
Mike | Abraham Ancer | R3 | 80 |
Mike | Abraham Ancer | R4 | 80 |
Mike | Lee Westwood | R1 | 72 |
Mike | Lee Westwood | R2 | 74 |
Mike | Lee Westwood | R3 | 73 |
Mike | Lee Westwood | R4 | 71 |
Mike | Lucas Herbert | R1 | 74 |
Mike | Lucas Herbert | R2 | 76 |
Mike | Lucas Herbert | R3 | 80 |
Mike | Lucas Herbert | R4 | 80 |
Rocco | Francesco Molinari | R1 | 78 |
Rocco | Francesco Molinari | R2 | 74 |
Rocco | Francesco Molinari | R3 | 80 |
Rocco | Francesco Molinari | R4 | 80 |
Rocco | Jon Rahm | R1 | 74 |
Rocco | Jon Rahm | R2 | 72 |
Rocco | Jon Rahm | R3 | 77 |
Rocco | Jon Rahm | R4 | 69 |
Rocco | Tony Finau | R1 | 71 |
Rocco | Tony Finau | R2 | 75 |
Rocco | Tony Finau | R3 | 74 |
Rocco | Tony Finau | R4 | 74 |
Rocco | Bryson DeChambeau | R1 | 76 |
Rocco | Bryson DeChambeau | R2 | 80 |
Rocco | Bryson DeChambeau | R3 | 80 |
Rocco | Bryson DeChambeau | R4 | 80 |
Rocco | Billy Horschel | R1 | 74 |
Rocco | Billy Horschel | R2 | 73 |
Rocco | Billy Horschel | R3 | 79 |
Rocco | Billy Horschel | R4 | 70 |
Rocco | Webb Simpson | R1 | 71 |
Rocco | Webb Simpson | R2 | 74 |
Rocco | Webb Simpson | R3 | 73 |
Rocco | Webb Simpson | R4 | 76 |
Trev | Dustin Johnson | R1 | 69 |
Trev | Dustin Johnson | R2 | 73 |
Trev | Dustin Johnson | R3 | 75 |
Trev | Dustin Johnson | R4 | 72 |
Trev | Xander Schauffele | R1 | 74 |
Trev | Xander Schauffele | R2 | 77 |
Trev | Xander Schauffele | R3 | 80 |
Trev | Xander Schauffele | R4 | 80 |
Trev | Matt Fitzpatrick | R1 | 71 |
Trev | Matt Fitzpatrick | R2 | 73 |
Trev | Matt Fitzpatrick | R3 | 76 |
Trev | Matt Fitzpatrick | R4 | 70 |
Trev | Russell Henley | R1 | 73 |
Trev | Russell Henley | R2 | 74 |
Trev | Russell Henley | R3 | 76 |
Trev | Russell Henley | R4 | 70 |
Trev | Talor Gooch | R1 | 72 |
Trev | Talor Gooch | R2 | 74 |
Trev | Talor Gooch | R3 | 73 |
Trev | Talor Gooch | R4 | 71 |
Trev | Cameron Young | R1 | 77 |
Trev | Cameron Young | R2 | 77 |
Trev | Cameron Young | R3 | 80 |
Trev | Cameron Young | R4 | 80 |
Prepare Data & Compute Sidebets
Now we have the scores read in, lets compute the day-by-day scores for each team. Recall, this is the four lowest scores per team per round.
= results %>%
pool_results group_by(team, round) %>%
arrange(team, round, score) %>%
mutate(count_score = ifelse(rank(score, ties.method = "first") <= 4, 1, 0)) %>%
summarise(value = sum(score * count_score) - 72 * 4) %>%
ungroup()
= pool_results %>%
total_scores pivot_wider(id_cols = team, names_from = round, values_from = value) %>%
group_by(team) %>%
mutate(Total = sum(across())) %>%
arrange(Total)
%>%
total_scores kbl() %>%
kable_paper("striped")
team | R1 | R2 | R3 | R4 | Total |
---|---|---|---|---|---|
Bret | -8 | 6 | -3 | -8 | -13 |
John | -1 | -1 | 2 | 4 | 4 |
Jordan | -4 | 4 | 4 | 6 | 10 |
Trev | -3 | 6 | 12 | -5 | 10 |
Craig | 2 | -5 | 19 | -2 | 14 |
Jacko | -1 | 4 | 10 | 4 | 17 |
Mike | -2 | 10 | 8 | 1 | 17 |
Rocco | 2 | 5 | 15 | 1 | 23 |
Bray | NA | NA | NA | NA | NA |
= results %>%
player_scores pivot_wider(id_cols = c("player", "team"), names_from = round, values_from = score)
%>%
player_scores select(-team) %>%
kbl(col.names = c("Team", "Round 1", "Round 2", "Round 3", "Round 4")) %>%
pack_rows(index = table(player_scores$team)) %>%
kable_paper() %>%
scroll_box(height = "500px")
Team | Round 1 | Round 2 | Round 3 | Round 4 |
---|---|---|---|---|
Bray | ||||
Justin Thomas | 76 | 67 | 72 | 72 |
Shane Lowry | 73 | 68 | 73 | 69 |
Louis Oosthuizen | 76 | 80 | 80 | 80 |
Tyrrell Hatton | 72 | 74 | 79 | 80 |
Bubba Watson | 73 | 73 | 78 | 71 |
Seamus Power | NA | NA | NA | NA |
Bret | ||||
Jordan Spieth | 74 | 76 | 80 | 80 |
Rory McIlroy | 73 | 73 | 71 | 64 |
Corey Conners | 70 | 73 | 72 | 70 |
Sungjae Im | 67 | 74 | 71 | 75 |
Tom Hoge | 73 | 74 | 75 | 73 |
Jason Kokrak | 70 | 76 | 71 | 73 |
Craig | ||||
Collin Morikawa | 73 | 70 | 74 | 67 |
Hideki Matsuyama | 72 | 69 | 77 | 72 |
Paul Casey | 80 | 80 | 80 | 80 |
Max Homa | 74 | 73 | 77 | 78 |
Thomas Pieters | 79 | 80 | 80 | 80 |
Kevin Na | 71 | 71 | 79 | 69 |
Jacko | ||||
Brooks Koepka | 75 | 75 | 80 | 80 |
Will Zalatoris | 71 | 72 | 75 | 67 |
Daniel Berger | 71 | 75 | 77 | 80 |
Kevin Kisner | 75 | 70 | 75 | 77 |
Cameron Champ | 72 | 75 | 71 | 70 |
Mackenzie Hughes | 73 | 75 | 77 | 78 |
John | ||||
Scottie Scheffler | 69 | 67 | 71 | 71 |
Tiger Woods | 71 | 74 | 78 | 78 |
Adam Scott | 74 | 74 | 80 | 74 |
Tommy Fleetwood | 75 | 72 | 70 | 73 |
Marc Leishman | 73 | 75 | 71 | 74 |
Gary Woodland | 75 | 77 | 80 | 80 |
Jordan | ||||
Cameron Smith | 68 | 74 | 68 | 73 |
Sam Burns | 75 | 74 | 80 | 80 |
Joaquin Niemann | 69 | 74 | 77 | 74 |
Sergio Garcia | 72 | 74 | 74 | 71 |
Justin Rose | 76 | 76 | 80 | 80 |
Si Woo Kim | 76 | 70 | 73 | 76 |
Mike | ||||
Viktor Hovland | 72 | 76 | 71 | 73 |
Patrick Cantlay | 70 | 75 | 79 | 71 |
Patrick Reed | 74 | 73 | 73 | 74 |
Abraham Ancer | 72 | 79 | 80 | 80 |
Lee Westwood | 72 | 74 | 73 | 71 |
Lucas Herbert | 74 | 76 | 80 | 80 |
Rocco | ||||
Francesco Molinari | 78 | 74 | 80 | 80 |
Jon Rahm | 74 | 72 | 77 | 69 |
Tony Finau | 71 | 75 | 74 | 74 |
Bryson DeChambeau | 76 | 80 | 80 | 80 |
Billy Horschel | 74 | 73 | 79 | 70 |
Webb Simpson | 71 | 74 | 73 | 76 |
Trev | ||||
Dustin Johnson | 69 | 73 | 75 | 72 |
Xander Schauffele | 74 | 77 | 80 | 80 |
Matt Fitzpatrick | 71 | 73 | 76 | 70 |
Russell Henley | 73 | 74 | 76 | 70 |
Talor Gooch | 72 | 74 | 73 | 71 |
Cameron Young | 77 | 77 | 80 | 80 |
Write to GoogleSheets
Lets write our total table to a GoogleSheet where everyone in the competition can view. The googlesheet can be viewed here.
# add data to sheet
%>%
player_scores select(team, player, R1, R2, R3, R4) %>%
write_sheet(ss, sheet = "leaderboard")
# add a total leader board table
%>%
ss range_write(total_scores, sheet = "leaderboard", range = "H1")
# add date last updated
%>%
ss range_write(data = data.frame(Last_updated = as.character(Sys.time())),
sheet = "leaderboard",
range = "O2")
From here, we can build plots off of the data ranges in our GoogleSheet, and update the formatting however we would like.
Automate with cronR
cronR
is a unix/linux tool that allows us to schedule R scripts. We are able to set jobs that will run on specific intervals. For example, we can have our leader board update every 15 minutes during the tournament to provide live updates.
Setting the cron job
We use cron_add()
to set the cron job. I found when using MacOS that I had to allow cron permissions in system preferences. I found this tutorial to be helpful.
Once you install the package, you can also use the RStudio addin, found at Addins > Schedule R scripts on Linux/Unix
.
= getwd()
wd = file.path(wd, "index.rmd")
script = cron_rscript(script,
cmd log_append = TRUE,
log_timestamp = TRUE)
cron_add(command = cmd,
frequency = "*/15 * * * *",
id = "2021-masters-pool",
description = "update masters pool every 15 mintues")
Viewing and Removing cron jobs
This is as simple as using cron_ls()
to list the cron jobs, and cron_rm()
to remove jobs by their id.
cron_ls()
cron_rm("2021-masters-pool")
Other Comments
While we web scraped the leader board data, I find the API can allow you to get richer data. Specifically hole-by-hole scores. This data allows for other side bets. Specifically having skins or a horse race. The downside is the API changes based on the status of the current event, therefore often requires daily checking.
Additionally, you can pull the individual scorecard data from the ESPN player pages. For example, see Rory McIlroy here.
GoogleSheets also have a read_html()
function, which allow you to read the raw data straight into the GoogleSheet. This can remove needing to use R, however you lose the flexibility of being able to update the sheet at whatever period you wish, and you are limited if you wish to do more complex analysis.