# had to install this way due to a change in statcast functions 

#devtools::install_github("BillPetti/baseballr")

library(baseballr)
library(dplyr)
library(ggplot2)

##### FIRST DECIDE WHICH PLAYERS' ATBAT DATA WE WANT TO DOWNLOAD
### LET'S TAKE THE 2023 Top players for Total Bases, and All of Cleveland's 2024 Hitters

top_total_bases_2023=mlb_stats_leaders(leader_categories = 'totalBases',
                                       sport_id=1,
                                       stat_group='hitting',
                                       season=2023)

cleveland_hitters_2024=mlb_team_leaders(team_id=114, #cleveland's ID number
                                        leader_categories='totalBases',
                                        season=2024  
) %>%
  filter(stat_group=='hitting')

glimpse(top_total_bases_2023) 
## Rows: 102
## Columns: 23
## $ leader_category       <chr> "totalBases", "totalBases", "totalBases", "total…
## $ rank                  <int> 1, 2, 3, 4, 5, 6, 7, 8, 8, 10, 11, 12, 12, 14, 1…
## $ value                 <chr> "383", "367", "361", "338", "328", "325", "320",…
## $ season                <chr> "2023", "2023", "2023", "2023", "2023", "2023", …
## $ num_teams             <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ team_id               <int> 144, 144, 119, 119, 144, 108, 140, 136, 118, 144…
## $ team_name             <chr> "Atlanta Braves", "Atlanta Braves", "Los Angeles…
## $ team_link             <chr> "/api/v1/teams/144", "/api/v1/teams/144", "/api/…
## $ league_id             <int> 104, 104, 104, 104, 104, 103, 103, 103, 103, 104…
## $ league_name           <chr> "NL", "NL", "NL", "NL", "NL", "AL", "AL", "AL", …
## $ league_link           <chr> "/api/v1/league/104", "/api/v1/league/104", "/ap…
## $ person_id             <int> 660670, 621566, 518692, 605141, 663586, 660271, …
## $ person_full_name      <chr> "Ronald Acuña Jr.", "Matt Olson", "Freddie Freem…
## $ person_link           <chr> "/api/v1/people/660670", "/api/v1/people/621566"…
## $ person_first_name     <chr> "Ronald", "Matt", "Freddie", "Mookie", "Austin",…
## $ person_last_name      <chr> "Acuña", "Olson", "Freeman", "Betts", "Riley", "…
## $ sport_id              <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ sport_link            <chr> "/api/v1/sports/1", "/api/v1/sports/1", "/api/v1…
## $ sport_abbreviation    <chr> "MLB", "MLB", "MLB", "MLB", "MLB", "MLB", "MLB",…
## $ stat_group            <chr> "hitting", "hitting", "hitting", "hitting", "hit…
## $ total_splits          <int> 633, 633, 633, 633, 633, 633, 633, 633, 633, 633…
## $ game_type_id          <chr> "R", "R", "R", "R", "R", "R", "R", "R", "R", "R"…
## $ game_type_description <chr> "Regular Season", "Regular Season", "Regular Sea…
glimpse(cleveland_hitters_2024) 
## Rows: 15
## Columns: 22
## $ leader_category       <chr> "totalBases", "totalBases", "totalBases", "total…
## $ rank                  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
## $ value                 <chr> "98", "86", "71", "66", "54", "53", "49", "37", …
## $ season                <chr> "2024", "2024", "2024", "2024", "2024", "2024", …
## $ team_id               <int> 114, 114, 114, 114, 114, 114, 114, 114, 114, 114…
## $ team_name             <chr> "Cleveland Guardians", "Cleveland Guardians", "C…
## $ team_link             <chr> "/api/v1/teams/114", "/api/v1/teams/114", "/api/…
## $ league_id             <int> 103, 103, 103, 103, 103, 103, 103, 103, 103, 103…
## $ league_name           <chr> "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", …
## $ league_link           <chr> "/api/v1/league/103", "/api/v1/league/103", "/ap…
## $ person_id             <int> 608070, 647304, 665926, 680757, 686823, 671289, …
## $ person_full_name      <chr> "José Ramírez", "Josh Naylor", "Andrés Giménez",…
## $ person_link           <chr> "/api/v1/people/608070", "/api/v1/people/647304"…
## $ person_first_name     <chr> "José", "Josh", "Andrés", "Steven", "Will", "Tyl…
## $ person_last_name      <chr> "Ramírez", "Naylor", "Giménez", "Kwan", "Brennan…
## $ sport_id              <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
## $ sport_link            <chr> "/api/v1/sports/1", "/api/v1/sports/1", "/api/v1…
## $ sport_abbreviation    <chr> "MLB", "MLB", "MLB", "MLB", "MLB", "MLB", "MLB",…
## $ stat_group            <chr> "hitting", "hitting", "hitting", "hitting", "hit…
## $ total_splits          <int> 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, …
## $ game_type_id          <chr> "R", "R", "R", "R", "R", "R", "R", "R", "R", "R"…
## $ game_type_description <chr> "Regular Season", "Regular Season", "Regular Sea…
### Grab the player info (player Id, name, debut date ) for the group of players above
player_ids=rbind(top_total_bases_2023 %>% select(person_id),
                 cleveland_hitters_2024 %>% select(person_id)) %>%
  distinct(person_id)

head(player_ids)
##    person_id
## 1:    660670
## 2:    621566
## 3:    518692
## 4:    605141
## 5:    663586
## 6:    660271
function_player_db=function(x) mlb_people(person_ids=x) %>% 
  select(id, full_name, mlb_debut_date, active)

library(data.table)
player_db=rbindlist(lapply(player_ids$person_id, function_player_db))

head(player_db)
##        id        full_name mlb_debut_date active
## 1: 660670 Ronald Acuña Jr.     2018-04-25   TRUE
## 2: 621566       Matt Olson     2016-09-12   TRUE
## 3: 518692  Freddie Freeman     2010-09-01   TRUE
## 4: 605141     Mookie Betts     2014-06-29   TRUE
## 5: 663586     Austin Riley     2019-05-15   TRUE
## 6: 660271    Shohei Ohtani     2018-03-29   TRUE
#for each player, I have to give a date range for which to pull statcast_data from
# so I am going to make a data fram with start and end dates, then join it to player_db
first_debut=year(min(player_db$mlb_debut_date))

library(tidyr)
start_end_years_dates=data.frame(crossing(startyear=c(first_debut:2024),
                                 endyear=c(first_debut:2024)) ) %>%
  filter(endyear>=startyear) %>%
  mutate(start_date=paste(endyear,"-03-01", sep=""),
         end_date=paste(endyear, "-11-10", sep=""))

head(start_end_years_dates)
##   startyear endyear start_date   end_date
## 1      2009    2009 2009-03-01 2009-11-10
## 2      2009    2010 2010-03-01 2010-11-10
## 3      2009    2011 2011-03-01 2011-11-10
## 4      2009    2012 2012-03-01 2012-11-10
## 5      2009    2013 2013-03-01 2013-11-10
## 6      2009    2014 2014-03-01 2014-11-10
## Now join apppend this table to player_db (each player will have a row for each year he played in)
#the warning message will state that multiple matches are made--that is what we want
player_db2=player_db %>% mutate(debut_year=year(mlb_debut_date)) %>%
  left_join(start_end_years_dates, by=c('debut_year'='startyear')) %>%
  mutate(start_date=if_else(year(mlb_debut_date)==endyear, mlb_debut_date, start_date))
## Warning in left_join(., start_end_years_dates, by = c(debut_year = "startyear")): Each row in `x` is expected to match at most 1 row in `y`.
## ℹ Row 1 of `x` matches multiple rows.
## ℹ If multiple matches are expected, set `multiple = "all"` to silence this
##   warning.
head(player_db2)
##        id        full_name mlb_debut_date active debut_year endyear start_date
## 1: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2018 2018-04-25
## 2: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2019 2019-03-01
## 3: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2020 2020-03-01
## 4: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2021 2021-03-01
## 5: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2022 2022-03-01
## 6: 660670 Ronald Acuña Jr.     2018-04-25   TRUE       2018    2023 2023-03-01
##      end_date
## 1: 2018-11-10
## 2: 2019-11-10
## 3: 2020-11-10
## 4: 2021-11-10
## 5: 2022-11-10
## 6: 2023-11-10
### Now we can grab the atbat data for each player between his start_date and end_date

function_atbatdata=function(x,y,z) scrape_statcast_savant_batter(as.Date(x),as.Date(y),
                                                                 batterid=z)

t=function_atbatdata("2024-05-12", "2024-05-21", 660670)


# ^^ this was just an example of one player's data pull between two dates

I am not going to run this in the Markdown, because it took about 2 hours to run. But this will grab every player’s (in our player_db) at bat data for his ENTIRE CAREER!

#this may take a while to run, it is pulling a dataframe for each unique row in player_db2
# you can restrict your desired players and date ranges how you wish
# you just need to run scrape_statcast_savant_batter(as.Date(x),as.Date(y),batterid=z)

atbatlist=mapply(function_atbatdata, player_db2$start_date, player_db2$end_date, player_db2$id)

atbatlist_rowsofinterest=subset(atbatlist, 
                                rownames(atbatlist) %in% c('game_date', 'pitch_type', 'pitch_name',
                                                           'pitch_number',
                                                           'release_speed', 'batter', 'player_name', 
                                                           'events', 'description',
                                                           'hit_distance_sc', 'launch_angle', 'launch_speed', 
                                                           'at_bat_number')) 


#now take all of the 900+ dataframes stores in the atbatlist
#bind them together
#choose only the rows where there is an event (i.e. dont pick "Strike 1")
# select just a few of the 180+ columns
#Group by player, and order their at-bats (this will allow for running totals)


func_dataframe=function(x) as.data.frame(atbatlist_rowsofinterest[,x])



big_atbat_df=plyr::rbind.fill(
  lapply(1:length(colnames(atbatlist_rowsofinterest)), func_dataframe)) %>%
  filter(events!='') %>%
  ### We have to remove events that dont signify a PLATE APP (caught stealing, pickoff attempts, etc)
  filter(!grepl('pickoff|other_out|wild_pitch|stolen_base|game_advisory|passed_ball|caught_stealing', events)) %>%
  arrange(batter, game_date, at_bat_number ) %>%
  select(game_date, pitch_type, pitch_name, pitch_number,
         release_speed, batter, player_name, events, description,
         hit_distance_sc, launch_angle, launch_speed, at_bat_number) %>%
  rename(ab_number_in_game=at_bat_number) %>%
  group_by(batter) %>%
  mutate(abnumber=row_number())

Instead I will infile the big_atbat_df data set that I made above

big_atbat_df =fread('E:/R Baseball Files/Career Atbat Logs_2023s Top Hitters and CLE 24 Hitters UPDATED 2024-05-23 09_07_21.csv')


events_numerator_denom=data.frame(events=unique(big_atbat_df$events)) %>%
  mutate(hit=if_else(events %in% c('single', 'double', 'triple', 'home_run'), 1, 0)) %>%
  mutate(atbats=if_else(events %in% c('single', 'double', 'triple', 'home_run',
                                      'field_out', 'strikeout', 'force_out',
                                      'grounded_into_double_play', 'field_error',
                                      'double_play', 'fileders_choice', 'fielders_choice_out',
                                      'strikeout_double_play', 'triple_play'), 1, 0)) %>%
  mutate(on_base_num=if_else(events %in% c('single', 'double', 'triple', 'home_run',
                                           'walk', 'hit_by_pitch', 'intent_walk'), 1, 0)) %>%
  mutate(on_base_denom=if_else(events %in% c('single', 'double', 'triple', 'home_run',
                                             'walk', 'hit_by_pitch', 'intent_walk', 'sac_fly',
                                             'field_out', 'strikeout', 'force_out',
                                             'grounded_into_double_play', 'field_error',
                                             'double_play', 'fileders_choice', 'fielders_choice_out',
                                             'strikeout_double_play', 'triple_play'), 1, 0)) %>%
  mutate(slug_num=if_else(events %in% c('single'), 1, 
                          if_else(events %in% c('double'), 2, 
                                  if_else(events %in% c('triple'), 3,
                                          if_else(events %in% c('home_run'), 4, 0)))))

### Now we want to take rolling-100-AB totals for each player.

library(zoo)
## Warning: package 'zoo' was built under R version 4.2.3
atbats_with_rolling_totals=big_atbat_df %>%
  left_join(events_numerator_denom, by=c('events')) %>%
  group_by(batter) %>% 
  arrange(batter, abnumber) %>%
  mutate(rolling_100_hit=rollsumr(hit, k = 100, fill = NA),
         rolling_100_atbats=rollsumr(atbats, k = 100, fill = NA),
         rolling_100_on_base_num=rollsumr(on_base_num, k = 100, fill = NA),
         rolling_100_on_base_denom=rollsumr(on_base_denom, k = 100, fill = NA),
         rolling_100_slug_num=rollsumr(slug_num, k = 100, fill = NA)) %>%
  mutate(rolling_100_avg=rolling_100_hit/rolling_100_atbats,
         rolling_100_obp=rolling_100_on_base_num/rolling_100_on_base_denom,
         rolling_100_slg=rolling_100_slug_num/rolling_100_atbats,
         rolling_100_ops=rolling_100_obp+rolling_100_slg)

best_stretches_byplayer=atbats_with_rolling_totals %>%
  mutate(player_rolling_100_avg_rank=rank(-rolling_100_avg, ties.method='first'),
         player_rolling_100_obp_rank=rank(-rolling_100_obp, ties.method='first'),
         player_rolling_100_slg_rank=rank(-rolling_100_slg, ties.method='first'),
         player_rolling_100_ops_rank=rank(-rolling_100_ops, ties.method='first')) 

ranked_best_stretches_obp=best_stretches_byplayer %>%
  filter(player_rolling_100_obp_rank==1) %>%
  ungroup() %>%
  mutate(player_rank_best_obp_stretch=rank(-rolling_100_obp, ties.method='min')) %>%
  arrange(player_rank_best_obp_stretch)
ranked_best_stretches_obp %>% filter(!is.na(rolling_100_obp)) %>%
  mutate(facetgroup=if_else(player_rank_best_obp_stretch<=50, "Players 1-50", "Remaining Players"))%>%
  mutate(fillvariable=if_else(batter %in% unique(cleveland_hitters_2024$person_id), "cle", "non cle")) %>%
  ggplot(aes(x=reorder(player_name, rolling_100_obp), y=rolling_100_obp, 
             label=paste(game_date, format(round(rolling_100_obp, 3), digits=3), sep=":  ")))+
  geom_bar(stat='identity', aes(fill=fillvariable))+
  scale_fill_manual(values=c('dodgerblue', 'grey'), guide="none")+
  geom_hline(yintercept=0.4, linetype='dotted')+
  geom_hline(yintercept=0.5, linetype='dotted')+
  geom_hline(yintercept=0.6, linetype='dotted')+
  geom_text(size=2.5, fontface='bold', color='black', hjust=1.2)+
  scale_y_continuous(
    labels = scales::number_format(accuracy = 0.001))+
  coord_flip()+
  theme_bw()+
  theme(axis.text=element_text(size=6))+
  facet_wrap(~facetgroup, scales='free_y')+
  ggtitle('Juan Soto was in another galaxy at the end the 2021 season')+
  labs(y='Best Rolling 100-PA OBP of Career', x='Player (Grouped for Aesthetic)')