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)

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

Load BBM Projections

First goal is to load daily stat projections from BasketballMonster (BBM), perform some cleaning, and compute their 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
# Combine First and Last Name columns into "Name"
# fixbballnames() is a cleaning function that makes sure names like Steph Curry and Stephen Curry match up
bbmproj2 <- bbmproj %>% 
    mutate(unboosted_projection = points + rebounds + 1.5*assists + 2*steals + 2*blocks, .before = value) %>% 
    arrange(desc(unboosted_projection)) %>% 
    mutate(Name = paste(first_name, last_name, sep = " "), .before = unboosted_projection) %>% 
    select(Name, Team = team, opponent, Injury = injury, unboosted_projection, Minutes = minutes, Points = points, Rebounds = rebounds, Assists = assists, Threes = threes,
           Steals = steals, Blocks = blocks) %>% 
    fix_bball_names()

Load Last10 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 
setwd("/Users/billy/Downloads")
tenday <- read_csv("MRplayer10gameavg061522.csv", show_col_types = FALSE)  #EDIT THIS LINE HERE

# Shorten the names of the stat columns
# 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()

# Join Last 10 day stats with BBM Projections
df1 <- bbmproj2 %>% 
    left_join(tenday2, by = "Name", suffix = c(".proj", ".last10")) 

# Filter to only include players with projections of 10 points or more
# Unselect irrelevant columns
df2 <- df1 %>% 
    select(-c(Games, Minutes.last10, Started))
# Test to see if any players have non-existent last-10-day stats, to be aware of them
df2[which(is.na(df2$Points.last10)),"Name"]

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")
options <- simplify_moments(topshotmoments)

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.

# Get game logs
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072*2)

gamelog <- game_logs(seasons = 2022, 
                     league = "NBA", 
                     season_types = "Regular Season", 
                     assign_to_environment = FALSE,
                     result_types = "player") 

# 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

Calculate Boosts

options(digits = 2)

# Filter to only include players with projections of 10 points or more
tonight <- df2 %>% filter(unboosted_projection > 10)

# Apply the function "boosted_projection()" which calculates the expected value of an input stat for every NBA Player, given the MR Play boosts.
tonight$Points_boost = sapply(tonight$Name, boosted_projection, "Points")
tonight$Assists_boost = sapply(tonight$Name, boosted_projection, "Assists")
tonight$Threes_boost = sapply(tonight$Name, boosted_projection, "Threes")
tonight$Steals_boost = sapply(tonight$Name, boosted_projection, "Steals")
tonight$Blocks_boost = sapply(tonight$Name, boosted_projection, "Blocks")
# Any issues in this chunk of code have to do with name spelling inconsistencies, need to be added to fix_bball_names()

# Find the highest boost for each player and stor them into x
x<- NULL
for(i in 1:nrow(tonight)){
    x[i] <- max(tonight[i,19:23], na.rm = TRUE)
}

# Add the highest boost into the main dataframe
tonight2 <- tonight %>% 
    mutate(best_boost = x, .before = Points_boost) %>% 
    mutate(boosted_projection = unboosted_projection + best_boost, .before = unboosted_projection) %>% 
    arrange(desc(boosted_projection))

tonight2 <- tonight2 %>% 
    relocate(best_boost:Blocks_boost, .before = Minutes.proj) %>% 
    mutate_if(is.numeric, round, 2)

# Publish the final dataframe to google sheets
library(googlesheets4)
tonight2 %>% sheet_write(ss = mrplay, sheet = "june-16")

Ideas for Improvement

  1. Rearrange final output to look more like: Harden - assist - 65.7, Tatum - 3ptm - 60.1, Harden - 3ptm - 54.5, and …

  2. Include the circulation counts and price to purchase each moment