Overview

This RMarkdown shows how I come up with my daily projections for MR Play. See here for how MR Play contests work.

Source

# Load libraries
library(tidyverse)
library(stringi)
library(nbastatR)
library(reshape2)
library(googlesheets4)
library(knitr)

# Load file that contains helper functions
source("Dropbox/Data_Science/TS_Functions.R")

Load and melt last 10 day stats

The last 10 day stats are needed for every player as that is how the boosts are calculated based upon being compared to.

# Load last 10 game averages
setwd("/Users/billy/Downloads")
tenday <- read_csv("MRplayer10gameavg102822.csv", show_col_types = FALSE)  #EDIT THIS LINE HERE

# Shorten the names of the stat columns and apply fixbballnames() name cleaning function
tenday2 <- tenday %>% 
    select(Name = YahooName, Points, Rebounds, Assists, Threes = ThreePointersMade, Steals, Blocks = BlockedShots, Games, Minutes, Started) %>% 
    mutate(Points = Points/Games, 
      Rebounds = Rebounds/Games,
      Assists = Assists/Games,
      Threes = Threes/Games,
      Steals = Steals/Games,
      Blocks = Blocks/Games) %>% 
    fix_bball_names() %>% 
    na.omit()

# Melt the individual stats variables into 1 new column w/ that variable name as the row
tenday_melt <- melt(tenday2, id = "Name", measure.vars = c("Points", "Assists", "Threes", "Steals", "Blocks"), variable.name = "Stat", value.name = "last10")

Load topshot moments

Load TopShot moment data from OTMNFT.com to determine which players have boosts in which stats.

# Load all TopShot moment data
topshotmoments <- read.csv("https://otmnftapi.com/nbatopshot/create_moments_csv", encoding = "Latin-1") %>% rename(Name = Player.Name)
  
# Convert European characters into English and perform the name cleaning function
topshotmoments <- topshotmoments %>% 
    mutate(Name = stri_trans_general(Name, 'latin-ascii')) %>% 
    fix_bball_names() %>% 
    I

# Function to convert TopShot moment stat categories to MR Play stat categories (e.g. "Layup" into "Points")
topshotmoments2 <- simplify_moments(topshotmoments)

Load BBM Projections

First goal is to load daily stat projections from BasketballMonster (BBM), perform some cleaning, and compute the raw MR total points stat.

# Load BBM Projections from a csv file on the internet
bbmproj <- read_csv("https://basketballmonster.com/dailyprojections.aspx?exportcsv=TbRiHe8ctANfwMvqI6EetQdY4QsKbZvwhTJrUTJudBU=", show_col_types = FALSE) 

# This below is for when I'm projecting for a specific day in advance
# setwd("/Users/billy/Downloads")
# bbmproj <- read_csv("Export_2022_03_15.csv", show_col_types = FALSE)

# Add column for the MR Play point system and sort by that column
bbmproj2 <- bbmproj %>% 
    mutate(MR_raw_proj = points + rebounds + 1.5*assists + 2*steals + 2*blocks, .before = value) %>% 
#    filter(MR_raw_proj > 0) %>% 
    arrange(desc(MR_raw_proj)) %>% 
    mutate(Name = paste(first_name, last_name, sep = " "), .before = MR_raw_proj) %>% 
    select(Name, Team = team, opponent, Injury = injury, MR_raw_proj, Minutes = minutes, Points = points, Rebounds = rebounds, Assists = assists, Threes = threes,
           Steals = steals, Blocks = blocks) %>% 
    fix_bball_names()

Melt BBM stats

bbm_melt <- melt(bbmproj2, id = c("Name", "Team", "opponent", "Injury", "MR_raw_proj"), measure.vars = c("Points", "Assists", "Threes", "Steals", "Blocks"), variable.name = "Stat", value.name = "projection")

# Join Last 10 day stats on to projection table
df1 <- bbm_melt %>%
    left_join(tenday_melt, by = c("Name", "Stat")) %>% 
    replace(is.na(.), 0)

Get Game Logs

To determine the variability (standard devaition) of each player’s statistical performance in each stat category, I’ll use their game logs from the 2021-22 season. These are available through the package nbastatR.

# Increase size of the connection buffer due to large data set about to be loaded (> 50k rows)
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072*2)

# Get game logs from 2021, 2022, 2023 seasons
gamelog <- game_logs(seasons = c(2021:2023), 
                     league = "NBA", 
                     season_types = "Regular Season", 
                     assign_to_environment = FALSE,
                     result_types = "player") 
## Acquiring NBA basic player game logs for the 2020-21 Regular Season
## Acquiring NBA basic player game logs for the 2021-22 Regular Season
## Acquiring NBA basic player game logs for the 2022-23 Regular Season
# Some cleaning to match the BBM Projections and Last 10 day stat data frames
gamelog <- gamelog %>% 
    rename(c(Name = namePlayer, Points = pts, Rebounds = treb, Assists = ast, Threes = fg3m, Steals = stl, Blocks = blk)) %>% 
    data.frame() %>% 
    fix_bball_names() %>% 
    I

# Get the standard deviation for every stat for every player
gamelog_std <- gamelog %>% 
  group_by(Name) %>% 
  summarise(Points = sd(Points), Assists = sd(Assists), Threes = sd(Threes), Steals = sd(Steals), Blocks = sd(Blocks))

# Melt stat variable to prepare for joining
gamelog_std_melt <- melt(gamelog_std, id = "Name", measure.vars = c("Points", "Assists", "Threes", "Steals", "Blocks"), variable.name = "Stat", value.name = "sd")

# Join standard deviations onto the projection table
df2 <- df1 %>% 
  left_join(gamelog_std_melt, by = c("Name", "Stat")) %>% 
  replace(is.na(.), 0) %>% 
  inner_join(topshotmoments2, by = c("Name", "Stat"))

Get projected boosts

# Calculate player ceilings for every stat
df3 <- df2 %>% 
  mutate(ceiling = projection + 3*sd, .after = sd) %>% 
  mutate(Stat = as.character(Stat))
# Function to get boosted projections
boosted_projection <- function(stat, b, c, d, e){

  boost <- NULL
  if(stat == "Points"){boost = 0.5}
  else if(stat == "Assists"){boost = 5}
  else if(stat == "Threes"){boost = 7}
  else if(stat == "Steals"){boost = 12}
  else if(stat == "Blocks"){boost = 16}

  #EV
  boostoutcomes <- c((floor(c)+1):e)
  
  probs <- dnorm(boostoutcomes, b, d)        # b = projection, d = sd
  
  probs <- round(probs, 3)
  
  values <- (boostoutcomes - c)*boost + 5
  
  xpx <- values*probs
  sum(xpx)   
}
# Calculate and attach boosts on to projections
df4 <- df3

for (row in 1:nrow(df3)){
  df4$boost[row] <- boosted_projection(df4$Stat[row], df4$projection[row], df4$last10[row], df4$sd[row], df4$ceiling[row])
}
# Calculate final boosted projection, select relevant variables, sort, and round
df5 <- df4 %>% 
  mutate(MR_boosted_proj = MR_raw_proj + boost, .before = MR_raw_proj) %>% 
  arrange(desc(MR_boosted_proj)) %>% 
  select(Name, Team, opponent, Stat, MR_boosted_proj, MR_raw_proj, boost, projection, last10, Low.Ask, Injury) %>% 
  mutate_at(vars(MR_boosted_proj:last10), funs(round(., 1)))


# Write to google sheets for consumption
mrplay <- "https://docs.google.com/spreadsheets/d/1KBcepiBDe-6YXLc_9HX4LaXto4fu8wzBrhtqPFC2bto/edit?usp=sharing"
df5 %>% sheet_write(ss = mrplay, sheet = "oct-28") %>% range_autofit()


# Preview of the output
kable(head(df5, 30))
Name Team opponent Stat MR_boosted_proj MR_raw_proj boost projection last10 Low.Ask Injury
Nikola Jokic DEN UTA Blocks 68.2 56.5 11.7 0.9 0.6 170 ,
Nikola Jokic DEN UTA Assists 66.4 56.5 9.9 8.0 7.6 3 ,
Rudy Gobert MIN LAL Blocks 66.3 39.0 27.3 2.4 1.1 2 ,
Trae Young ATL @ DET Threes 65.3 49.7 15.5 3.2 1.8 3 ,
James Harden PHI @ TOR Assists 64.7 48.6 16.2 10.4 8.4 3 ,
Trae Young ATL @ DET Assists 63.8 49.7 14.1 9.8 8.2 2 ,
Nikola Jokic DEN UTA Threes 63.8 56.5 7.3 1.0 0.8 4 ,
Giannis Antetokounmpo MIL NYK Blocks 63.2 55.6 7.6 1.2 1.5 4 ,
Kristaps Porzingis WAS IND Blocks 61.5 42.0 19.5 2.2 1.4 48 ,
Jakob Poeltl SAS CHI Blocks 61.3 35.5 25.8 2.0 0.8 39 ,
LeBron James LAL @ MIN Steals 61.1 49.5 11.7 1.1 0.7 9 Probable,
Nikola Jokic DEN UTA Points 60.7 56.5 4.2 26.8 26.7 3 ,
Giannis Antetokounmpo MIL NYK Threes 59.4 55.6 3.8 1.0 1.1 500 ,
Giannis Antetokounmpo MIL NYK Assists 59.2 55.6 3.6 5.0 6.6 10 ,
Giannis Antetokounmpo MIL NYK Points 58.5 55.6 2.9 31.6 34.5 3 ,
Joel Embiid PHI @ TOR Blocks 58.1 46.2 11.9 1.5 1.3 199 ,
LeBron James LAL @ MIN Assists 57.8 49.5 8.3 7.1 6.9 7 Probable,
Trae Young ATL @ DET Points 57.3 49.7 7.6 28.6 22.2 3 ,
Donovan Mitchell CLE @ BOS Threes 56.3 40.7 15.6 3.7 2.2 2 ,
James Harden PHI @ TOR Blocks 55.5 48.6 7.0 0.4 0.6 38 ,
LeBron James LAL @ MIN Threes 55.4 49.5 6.0 2.3 2.6 39 Probable,
Chris Paul PHO NOR Assists 55.4 36.7 18.7 10.2 7.8 2 ,
Dejounte Murray ATL @ DET Steals 55.2 44.8 10.3 2.3 2.0 5 ,
Jayson Tatum BOS CLE Assists 54.6 47.0 7.6 5.8 5.6 3 ,
LeBron James LAL @ MIN Blocks 54.3 49.5 4.9 0.8 1.0 7 Probable,
Anthony Edwards MIN LAL Steals 54.2 40.0 14.2 1.6 0.9 320 ,
Cade Cunningham DET ATL Assists 54.2 41.5 12.7 7.1 5.8 3900 ,
James Harden PHI @ TOR Threes 54.0 48.6 5.5 2.4 2.8 4 ,
Joel Embiid PHI @ TOR Assists 54.0 46.2 7.8 2.8 2.4 30 ,
Joel Embiid PHI @ TOR Threes 54.0 46.2 7.8 1.1 0.8 5 ,