data(cars)
median(cars[, 1])
## [1] 15
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
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]]
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
# 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)