Q1

data(cars)

median(cars[, 1])
## [1] 15

Q2

library(jsonlite)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
btc <- "https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=100"

btc <- fromJSON(btc)
df_btc  <- btc$Data$Data

max_close <- max(df_btc$close, na.rm = TRUE)

cat("Maximum daily CLOSE (BTC-USD) over last 100 days:", max_close, "\n")
## Maximum daily CLOSE (BTC-USD) over last 100 days: 124723

Q3

Research Questions

1. Which Chicago Cubs hitters produced the most offense in 2025 (in terms of HR, RBI, and total bases)?

2. Who were the team’s most efficient hitters based on rate stats (AVG, OBP, SLG, OPS)?

3. How do plate-discipline measures such as walks (BB%) and strikeouts (K%) vary across players?

4. Which players demonstrated the most power independent of batting average (ISO)?

5. Are there any hitters whose strong rate metrics suggest breakout potential despite lower counting stats?

Extraction

library(jsonlite)
library(dplyr)
library(tidyr)
library(ggplot2)

url <- "https://statsapi.mlb.com/api/v1/stats?stats=season&group=hitting&season=2025&teamId=112&sportId=1"


raw_2025 <- fromJSON(url)


str(raw_2025)
## List of 2
##  $ copyright: chr "Copyright 2025 MLB Advanced Media, L.P.  Use of any content on this page acknowledges agreement to the terms po"| __truncated__
##  $ stats    :'data.frame':   1 obs. of  8 variables:
##   ..$ type                :'data.frame': 1 obs. of  1 variable:
##   .. ..$ displayName: chr "season"
##   ..$ group               :'data.frame': 1 obs. of  1 variable:
##   .. ..$ displayName: chr "hitting"
##   ..$ totalSplits         : int 7
##   ..$ exemptions          :List of 1
##   .. ..$ : list()
##   ..$ splits              :List of 1
##   .. ..$ :'data.frame':  7 obs. of  8 variables:
##   .. .. ..$ season  : chr [1:7] "2025" "2025" "2025" "2025" ...
##   .. .. ..$ stat    :'data.frame':   7 obs. of  34 variables:
##   .. .. .. ..$ age                     : int [1:7] 28 28 27 23 30 31 30
##   .. .. .. ..$ gamesPlayed             : int [1:7] 156 136 155 157 151 159 150
##   .. .. .. ..$ groundOuts              : int [1:7] 179 114 111 116 98 137 120
##   .. .. .. ..$ airOuts                 : int [1:7] 197 169 139 192 177 147 165
##   .. .. .. ..$ runs                    : int [1:7] 89 91 78 91 75 84 87
##   .. .. .. ..$ doubles                 : int [1:7] 29 25 25 37 31 24 32
##   .. .. .. ..$ triples                 : int [1:7] 4 4 5 4 3 3 0
##   .. .. .. ..$ homeRuns                : int [1:7] 7 22 34 31 32 24 23
##   .. .. .. ..$ strikeOuts              : int [1:7] 49 88 139 155 164 168 151
##   .. .. .. ..$ baseOnBalls             : int [1:7] 39 87 56 29 71 47 87
##   .. .. .. ..$ intentionalWalks        : int [1:7] 1 7 2 1 1 1 2
##   .. .. .. ..$ hits                    : int [1:7] 178 133 137 146 140 144 138
##   .. .. .. ..$ hitByPitch              : int [1:7] 7 4 10 9 1 2 2
##   .. .. .. ..$ avg                     : chr [1:7] ".297" ".266" ".261" ".247" ...
##   .. .. .. ..$ atBats                  : int [1:7] 599 500 524 591 571 590 569
##   .. .. .. ..$ obp                     : chr [1:7] ".345" ".377" ".343" ".287" ...
##   .. .. .. ..$ slg                     : chr [1:7] ".394" ".464" ".523" ".481" ...
##   .. .. .. ..$ ops                     : chr [1:7] ".739" ".841" ".866" ".768" ...
##   .. .. .. ..$ caughtStealing          : int [1:7] 6 3 0 8 2 3 3
##   .. .. .. ..$ stolenBases             : int [1:7] 29 25 4 35 5 20 6
##   .. .. .. ..$ stolenBasePercentage    : chr [1:7] ".829" ".893" "1.000" ".814" ...
##   .. .. .. ..$ caughtStealingPercentage: chr [1:7] ".171" ".107" ".000" ".186" ...
##   .. .. .. ..$ groundIntoDoublePlay    : int [1:7] 4 8 2 1 13 9 8
##   .. .. .. ..$ numberOfPitches         : int [1:7] 2305 2306 2324 2312 2727 2521 2717
##   .. .. .. ..$ plateAppearances        : int [1:7] 649 597 592 647 651 645 663
##   .. .. .. ..$ totalBases              : int [1:7] 236 232 274 284 273 246 239
##   .. .. .. ..$ rbi                     : int [1:7] 61 73 90 95 103 77 79
##   .. .. .. ..$ leftOnBase              : int [1:7] 233 180 191 274 246 264 207
##   .. .. .. ..$ sacBunts                : int [1:7] 0 1 0 6 0 1 0
##   .. .. .. ..$ sacFlies                : int [1:7] 4 3 2 12 8 5 5
##   .. .. .. ..$ babip                   : chr [1:7] ".313" ".282" ".292" ".276" ...
##   .. .. .. ..$ groundOutsToAirouts     : chr [1:7] "0.91" "0.67" "0.80" "0.60" ...
##   .. .. .. ..$ catchersInterference    : int [1:7] 0 2 0 0 0 0 0
##   .. .. .. ..$ atBatsPerHomeRun        : chr [1:7] "85.57" "22.73" "15.41" "19.06" ...
##   .. .. ..$ team    :'data.frame':   7 obs. of  3 variables:
##   .. .. .. ..$ id  : int [1:7] 112 112 112 112 112 112 112
##   .. .. .. ..$ name: chr [1:7] "Chicago Cubs" "Chicago Cubs" "Chicago Cubs" "Chicago Cubs" ...
##   .. .. .. ..$ link: chr [1:7] "/api/v1/teams/112" "/api/v1/teams/112" "/api/v1/teams/112" "/api/v1/teams/112" ...
##   .. .. ..$ player  :'data.frame':   7 obs. of  5 variables:
##   .. .. .. ..$ id       : int [1:7] 663538 663656 683737 691718 673548 621020 664023
##   .. .. .. ..$ fullName : chr [1:7] "Nico Hoerner" "Kyle Tucker" "Michael Busch" "Pete Crow-Armstrong" ...
##   .. .. .. ..$ link     : chr [1:7] "/api/v1/people/663538" "/api/v1/people/663656" "/api/v1/people/683737" "/api/v1/people/691718" ...
##   .. .. .. ..$ firstName: chr [1:7] "Nico" "Kyle" "Michael" "Pete" ...
##   .. .. .. ..$ lastName : chr [1:7] "Hoerner" "Tucker" "Busch" "Crow-Armstrong" ...
##   .. .. ..$ league  :'data.frame':   7 obs. of  3 variables:
##   .. .. .. ..$ id  : int [1:7] 104 104 104 104 104 104 104
##   .. .. .. ..$ name: chr [1:7] "NL" "NL" "NL" "NL" ...
##   .. .. .. ..$ link: chr [1:7] "/api/v1/league/104" "/api/v1/league/104" "/api/v1/league/104" "/api/v1/league/104" ...
##   .. .. ..$ sport   :'data.frame':   7 obs. of  3 variables:
##   .. .. .. ..$ id          : int [1:7] 1 1 1 1 1 1 1
##   .. .. .. ..$ link        : chr [1:7] "/api/v1/sports/1" "/api/v1/sports/1" "/api/v1/sports/1" "/api/v1/sports/1" ...
##   .. .. .. ..$ abbreviation: chr [1:7] "MLB" "MLB" "MLB" "MLB" ...
##   .. .. ..$ rank    : int [1:7] 1 2 3 4 5 6 7
##   .. .. ..$ position:'data.frame':   7 obs. of  4 variables:
##   .. .. .. ..$ code        : chr [1:7] "4" "9" "3" "8" ...
##   .. .. .. ..$ name        : chr [1:7] "Second Base" "Outfielder" "First Base" "Outfielder" ...
##   .. .. .. ..$ type        : chr [1:7] "Infielder" "Outfielder" "Infielder" "Outfielder" ...
##   .. .. .. ..$ abbreviation: chr [1:7] "2B" "RF" "1B" "CF" ...
##   ..$ splitsTiedWithOffset:List of 1
##   .. ..$ : list()
##   ..$ splitsTiedWithLimit :List of 1
##   .. ..$ : list()
##   ..$ playerPool          : chr "QUALIFIED"
cubs_hit_2025_raw <- raw_2025$stats$splits[[1]]

Data Description, Cleaning, and Manipulation

cubs_hit_2025 <- cubs_hit_2025_raw %>%
  unnest_wider(stat,     names_sep = ".") %>%
  unnest_wider(player,   names_sep = ".") %>%
  unnest_wider(position, names_sep = ".") %>%
  unnest_wider(team,     names_sep = ".")
cubs_hit_2025 <- cubs_hit_2025 %>%
  mutate(
    across(
      c(stat.avg, stat.obp, stat.slg, stat.ops, stat.babip,
        stat.stolenBasePercentage, stat.caughtStealingPercentage,
        stat.groundOutsToAirouts, stat.atBatsPerHomeRun),
      ~ suppressWarnings(as.numeric(sub("^\\.", "0.", .)))
    )
  )
cubs_hit_2025 <- cubs_hit_2025 %>%
  rename(
    player_id = player.id,
    player    = player.fullName,
    pos_code  = position.abbreviation,
    pos_name  = position.name,
    team_id   = team.id,
    team_name = team.name
  )
cubs_hit_2025 <- cubs_hit_2025 %>%
  select(
    rank, season, player_id, player, pos_code, pos_name, team_name,
    stat.age, stat.gamesPlayed, stat.plateAppearances, stat.atBats,
    stat.hits, stat.doubles, stat.triples, stat.homeRuns, stat.rbi,
    stat.runs, stat.baseOnBalls, stat.strikeOuts, stat.stolenBases,
    stat.caughtStealing, stat.totalBases,
    stat.avg, stat.obp, stat.slg, stat.ops, stat.babip
  ) %>%
  distinct(player_id, .keep_all = TRUE)
# Guard against division by zero
safe_div <- function(n, d) ifelse(is.na(d) | d == 0, NA_real_, n / d)

cubs_hit_2025 <- cubs_hit_2025 %>%
  mutate(
    # Power independent of average
    ISO = stat.slg - stat.avg,
    # Rates per plate appearance
    BB_rate = safe_div(stat.baseOnBalls, stat.plateAppearances),  # walk rate
    K_rate  = safe_div(stat.strikeOuts,   stat.plateAppearances)   # strikeout rate
  )
# Basic shape
n_players   <- nrow(cubs_hit_2025)
n_cols      <- ncol(cubs_hit_2025)

# Missingness snapshot
na_counts <- sapply(cubs_hit_2025, function(x) sum(is.na(x)))

# Summary of key numeric columns
num_summary <- cubs_hit_2025 %>%
  select(stat.plateAppearances, stat.atBats, stat.hits, stat.doubles, stat.triples,
         stat.homeRuns, stat.rbi, stat.runs, stat.totalBases,
         stat.avg, stat.obp, stat.slg, stat.ops, stat.babip, ISO, BB_rate, K_rate) %>%
  summary()

n_players; n_cols
## [1] 7
## [1] 30
na_counts
##                  rank                season             player_id 
##                     0                     0                     0 
##                player              pos_code              pos_name 
##                     0                     0                     0 
##             team_name              stat.age      stat.gamesPlayed 
##                     0                     0                     0 
## stat.plateAppearances           stat.atBats             stat.hits 
##                     0                     0                     0 
##          stat.doubles          stat.triples         stat.homeRuns 
##                     0                     0                     0 
##              stat.rbi             stat.runs      stat.baseOnBalls 
##                     0                     0                     0 
##       stat.strikeOuts      stat.stolenBases   stat.caughtStealing 
##                     0                     0                     0 
##       stat.totalBases              stat.avg              stat.obp 
##                     0                     0                     0 
##              stat.slg              stat.ops            stat.babip 
##                     0                     0                     0 
##                   ISO               BB_rate                K_rate 
##                     0                     0                     0
num_summary
##  stat.plateAppearances  stat.atBats      stat.hits      stat.doubles 
##  Min.   :592.0         Min.   :500.0   Min.   :133.0   Min.   :24.0  
##  1st Qu.:621.0         1st Qu.:546.5   1st Qu.:137.5   1st Qu.:25.0  
##  Median :647.0         Median :571.0   Median :140.0   Median :29.0  
##  Mean   :634.9         Mean   :563.4   Mean   :145.1   Mean   :29.0  
##  3rd Qu.:650.0         3rd Qu.:590.5   3rd Qu.:145.0   3rd Qu.:31.5  
##  Max.   :663.0         Max.   :599.0   Max.   :178.0   Max.   :37.0  
##   stat.triples   stat.homeRuns      stat.rbi        stat.runs  stat.totalBases
##  Min.   :0.000   Min.   : 7.00   Min.   : 61.00   Min.   :75   Min.   :232.0  
##  1st Qu.:3.000   1st Qu.:22.50   1st Qu.: 75.00   1st Qu.:81   1st Qu.:237.5  
##  Median :4.000   Median :24.00   Median : 79.00   Median :87   Median :246.0  
##  Mean   :3.286   Mean   :24.71   Mean   : 82.57   Mean   :85   Mean   :254.9  
##  3rd Qu.:4.000   3rd Qu.:31.50   3rd Qu.: 92.50   3rd Qu.:90   3rd Qu.:273.5  
##  Max.   :5.000   Max.   :34.00   Max.   :103.00   Max.   :91   Max.   :284.0  
##     stat.avg         stat.obp         stat.slg         stat.ops     
##  Min.   :0.2430   Min.   :0.2870   Min.   :0.3940   Min.   :0.7170  
##  1st Qu.:0.2445   1st Qu.:0.3130   1st Qu.:0.4185   1st Qu.:0.7505  
##  Median :0.2470   Median :0.3420   Median :0.4640   Median :0.7680  
##  Mean   :0.2576   Mean   :0.3314   Mean   :0.4539   Mean   :0.7853  
##  3rd Qu.:0.2635   3rd Qu.:0.3440   3rd Qu.:0.4795   3rd Qu.:0.8225  
##  Max.   :0.2970   Max.   :0.3770   Max.   :0.5230   Max.   :0.8660  
##    stat.babip          ISO            BB_rate            K_rate      
##  Min.   :0.2760   Min.   :0.0970   Min.   :0.04482   Min.   :0.0755  
##  1st Qu.:0.2820   1st Qu.:0.1750   1st Qu.:0.06648   1st Qu.:0.1876  
##  Median :0.2880   Median :0.1980   Median :0.09459   Median :0.2348  
##  Mean   :0.2901   Mean   :0.1963   Mean   :0.09406   Mean   :0.2053  
##  3rd Qu.:0.2950   3rd Qu.:0.2335   3rd Qu.:0.12014   3rd Qu.:0.2457  
##  Max.   :0.3130   Max.   :0.2620   Max.   :0.14573   Max.   :0.2605
# Top 10 by OPS
top_ops <- cubs_hit_2025 %>%
  filter(!is.na(stat.ops), stat.plateAppearances >= 50) %>%
  arrange(desc(stat.ops)) %>%
  select(player, pos_code, stat.plateAppearances, stat.avg, stat.obp, stat.slg, stat.ops) %>%
  head(10)

# Top 10 by HR
top_hr <- cubs_hit_2025 %>%
  arrange(desc(stat.homeRuns)) %>%
  select(player, pos_code, stat.plateAppearances, stat.homeRuns, stat.rbi, stat.totalBases) %>%
  head(10)

top_ops
## # A tibble: 7 × 7
##   player      pos_code stat.plateAppearances stat.avg stat.obp stat.slg stat.ops
##   <chr>       <chr>                    <int>    <dbl>    <dbl>    <dbl>    <dbl>
## 1 Michael Bu… 1B                         592    0.261    0.343    0.523    0.866
## 2 Kyle Tucker RF                         597    0.266    0.377    0.464    0.841
## 3 Seiya Suzu… DH                         651    0.245    0.326    0.478    0.804
## 4 Pete Crow-… CF                         647    0.247    0.287    0.481    0.768
## 5 Ian Happ    LF                         663    0.243    0.342    0.42     0.762
## 6 Nico Hoern… 2B                         649    0.297    0.345    0.394    0.739
## 7 Dansby Swa… SS                         645    0.244    0.3      0.417    0.717
top_hr
## # A tibble: 7 × 6
##   player   pos_code stat.plateAppearances stat.homeRuns stat.rbi stat.totalBases
##   <chr>    <chr>                    <int>         <int>    <int>           <int>
## 1 Michael… 1B                         592            34       90             274
## 2 Seiya S… DH                         651            32      103             273
## 3 Pete Cr… CF                         647            31       95             284
## 4 Dansby … SS                         645            24       77             246
## 5 Ian Happ LF                         663            23       79             239
## 6 Kyle Tu… RF                         597            22       73             232
## 7 Nico Ho… 2B                         649             7       61             236

Visualization

# Order players by HR for a nicer bar chart
plot_df_hr <- cubs_hit_2025 %>%
  mutate(player = reorder(player, stat.homeRuns))

ggplot(plot_df_hr, aes(x = player, y = stat.homeRuns)) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Chicago Cubs 2025 — Home Runs",
    x = "Player",
    y = "Home Runs"
  ) +
  theme_minimal()

# ISO by player bar chart
plot_df_iso <- cubs_hit_2025 %>%
  filter(!is.na(ISO)) %>%
  mutate(player = reorder(player, ISO))

ggplot(plot_df_iso, aes(x = player, y = ISO)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(
    title = "Chicago Cubs 2025 — Isolated Power (ISO) by Player",
    x = "Player",
    y = "ISO (Slugging - Batting Average)"
  ) +
  theme_minimal(base_size = 13)