1. Is there such a thing as ‘hot hand’?

Does momentum in sports really exists, or is it an illusion?

YT video (20m) - Momentum Exists - A Hot Hand Analysis / Michael MacKelvie

Paper: Glazer2020

Shiny App for consecutive shots made

Run this code in R, and it will launch integractive app for simulating consecutive shots made.

Steph Curry takes about 17 shots per game. 82 games in NBA season. Overall, his 2-pointer is about 47%, 3-pointer is about 42% successful.

library(shiny)
library(plotly)

# ---- Simulation core --------------------------------------------------------
# For one simulated sequence of `numFlips` coin flips (Heads w.p. p),
# count occurrences of a *maximal run of exactly length k* of heads, and
# separately of tails, for k = 2..10. Uses run-length encoding (rle), so a
# run of length 5, e.g., counts once toward k=5 -- it is not also counted
# toward k=2, k=3, k=4.
K_VALS <- 2:10

simulate_once <- function(numFlips, p) {
  flips <- sample(c("H", "T"), numFlips, replace = TRUE, prob = c(p, 1 - p))
  r <- rle(flips)
  head_lens <- r$lengths[r$values == "H"]
  tail_lens <- r$lengths[r$values == "T"]
  list(
    H = as.integer(table(factor(head_lens, levels = K_VALS))),
    T = as.integer(table(factor(tail_lens, levels = K_VALS)))
  )
}

run_simulation <- function(numFlips, p, nSim, progress = NULL) {
  countsH <- matrix(0L, nrow = nSim, ncol = length(K_VALS))
  countsT <- matrix(0L, nrow = nSim, ncol = length(K_VALS))

  chunk <- max(1, nSim %/% 20)
  for (i in seq_len(nSim)) {
    res <- simulate_once(numFlips, p)
    countsH[i, ] <- res$H
    countsT[i, ] <- res$T
    if (!is.null(progress) && i %% chunk == 0) {
      progress(i / nSim)
    }
  }

  data.frame(
    k      = K_VALS,
    mean_H = colMeans(countsH),
    p5_H   = apply(countsH, 2, quantile, probs = 0.05, names = FALSE),
    p95_H  = apply(countsH, 2, quantile, probs = 0.95, names = FALSE),
    mean_T = colMeans(countsT),
    p5_T   = apply(countsT, 2, quantile, probs = 0.05, names = FALSE),
    p95_T  = apply(countsT, 2, quantile, probs = 0.95, names = FALSE)
  )
}

# ---- UI ----------------------------------------------------------------------
ui <- fluidPage(
  titlePanel("Consecutive Heads / Tails Run Counts Over Repeated Simulations"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("p", "Probability of Heads (p)",
                  min = 0, max = 1, value = 0.45, step = 0.01),
      sliderInput("numFlips", "Number of Coin Flips (numFlips)",
                  min = 1, max = 1500, value = 170, step = 1),
      actionButton("run", "Run Simulation (5000 reps)", class = "btn-primary"),
      helpText(
        "For each of 5000 independent simulations of numFlips coin flips, ",
        "counts how many times a run of EXACTLY k consecutive heads occurs ",
        "(numConH) and exactly k consecutive tails (numConT), for ",
        "k = 2, 3, ..., 10. Reports the mean, 5th percentile, and 95th ",
        "percentile of numConH and numConT across the 5000 repetitions, ",
        "for each k. Click the button to (re)run after changing p or numFlips."
      )
    ),
    mainPanel(
      plotlyOutput("barPlot", height = "500px"),
      br(),
      tableOutput("summaryTable")
    )
  )
)

# ---- Server --------------------------------------------------------------
server <- function(input, output, session) {

  sim_results <- eventReactive(input$run, {
    p <- isolate(input$p)
    numFlips <- isolate(input$numFlips)
    nSim <- 5000

    summary_df <- withProgress(message = "Running 5000 simulations...", value = 0, {
      run_simulation(numFlips, p, nSim, progress = function(frac) setProgress(frac))
    })

    list(summary = summary_df, p = p, numFlips = numFlips, nSim = nSim)
  })

  output$barPlot <- renderPlotly({
    res <- sim_results()
    df <- res$summary

    plot_ly(df, x = ~k, name = "Heads (exact run = k)") %>%
      add_trace(
        y = ~mean_H, type = "bar",
        marker = list(color = "#3b82f6"),
        error_y = list(
          type = "data", symmetric = FALSE,
          array = ~(p95_H - mean_H), arrayminus = ~(mean_H - p5_H),
          color = "#1e3a8a"
        ),
        name = "Heads (exact run = k)"
      ) %>%
      add_trace(
        y = ~mean_T, type = "bar",
        marker = list(color = "#ef4444"),
        error_y = list(
          type = "data", symmetric = FALSE,
          array = ~(p95_T - mean_T), arrayminus = ~(mean_T - p5_T),
          color = "#7f1d1d"
        ),
        name = "Tails (exact run = k)"
      ) %>%
      layout(
        barmode = "group",
        title = sprintf(
          "p = %.2f, numFlips = %d, %d repetitions  (error bars: 5th-95th percentile)",
          res$p, res$numFlips, res$nSim
        ),
        xaxis = list(title = "k (exact run length)", tickmode = "linear", dtick = 1),
        yaxis = list(title = "Number of occurrences (mean)"),
        legend = list(orientation = "h", x = 0, y = 1.12)
      )
  })

  output$summaryTable <- renderTable({
    res <- sim_results()
    df <- res$summary
    out <- data.frame(
      k = df$k,
      `Mean numConH` = round(df$mean_H, 3),
      `5th pct H` = df$p5_H,
      `95th pct H` = df$p95_H,
      `Mean numConT` = round(df$mean_T, 3),
      `5th pct T` = df$p5_T,
      `95th pct T` = df$p95_T,
      check.names = FALSE
    )
    out
  })
}

shinyApp(ui = ui, server = server)

Sample Picture of App

knitr::include_graphics("flipInRow-v01.jpg")