Sep 23, 2026stats <- read.csv("~/Desktop/stats/archive (3)/NBA Player Stats(1950 - 2022).csv")
salaries <- read.csv("~/Desktop/stats/archive (3)/NBA Salaries(1990-2023).csv")
#Cleaning and Merging the Data into one Dataset
# convert salary from text to in millions
# comparable salaries
salaries$salary <- as.numeric(gsub("[$,]", "", salaries$inflationAdjSalary)) / 1000000
# Salaries file uses the year the season started, stats file uses the year it ended
# Add 1 so the seasons match
salaries$Season <- salaries$seasonStartYear + 1
# Salary data starts in 1990, so drop older seasons from the stats file
stats <- stats[stats$Season >= 1991, ]
# Players traded mid-season have one row per team plus a "TOT" (total) row
# Keep only the TOT row for those players so each player-season appears once
key <- paste(stats$Player, stats$Season)
traded <- key[stats$Tm == "TOT"]
stats <- stats[!(key %in% traded) | stats$Tm == "TOT", ]
#remove duplicate player-seasons
salaries <- salaries[!duplicated(salaries[, c("playerName", "Season")]), ]
# Merge files by player name and season
nba <- merge(stats, salaries[, c("playerName", "Season", "salary")],
by.x = c("Player", "Season"), by.y = c("playerName", "Season"))
# check
nrow(nba)
## [1] 13018
#Part A: Descriptive Statistics: ## 1: Categorical Variable: Position
# players w two positions, keep primary
nba$Position <- sub("-.*", "", nba$Pos)
#pos order alph.
nba$Position <- factor(nba$Position, levels = c("PG", "SG", "SF", "PF", "C"))
#freq table
pos_freq <- table(nba$Position)
pos_freq
##
## PG SG SF PF C
## 2507 2684 2426 2784 2617
# relative frequency (prop of player-seasons at each position)
round(prop.table(pos_freq), 3)
##
## PG SG SF PF C
## 0.193 0.206 0.186 0.214 0.201
# bar chart
barplot(pos_freq,
main = "Player-Seasons by Position (1991-2022)",
xlab = "Position", ylab = "Number of Player-Seasons",
col = "navy")
## 2: Quantitative Variable: Salary
# Descriptive statistics for salary (in millions)
min(nba$salary)
## [1] 0.008074
median(nba$salary)
## [1] 2.947918
max(nba$salary)
## [1] 61.25856
mean(nba$salary)
## [1] 5.607961
sd(nba$salary)
## [1] 6.894125
# Range = max - min
max(nba$salary) - min(nba$salary)
## [1] 61.25048
# histogram
hist(nba$salary,
breaks = 30,
main = "Distribution of NBA Salaries (1991-2022)",
xlab = "Salary (millions, inflation-adjusted $)",
col = "gold")
## 3: Salary by Position
# Split salary group for each position
pg <- nba$salary[nba$Position == "PG"]
sg <- nba$salary[nba$Position == "SG"]
sf <- nba$salary[nba$Position == "SF"]
pf <- nba$salary[nba$Position == "PF"]
c_ <- nba$salary[nba$Position == "C"]
# Table --> descript stats
salary_table <- data.frame(
Position = c("PG", "SG", "SF", "PF", "C"),
n = c(length(pg), length(sg), length(sf), length(pf), length(c_)),
Min = c(min(pg), min(sg), min(sf), min(pf), min(c_)),
Median = c(median(pg), median(sg), median(sf), median(pf), median(c_)),
Max = c(max(pg), max(sg), max(sf), max(pf), max(c_)),
Mean = c(mean(pg), mean(sg), mean(sf), mean(pf), mean(c_)),
SD = c(sd(pg), sd(sg), sd(sf), sd(pf), sd(c_))
)
salary_table$Range <- salary_table$Max - salary_table$Min
# round
salary_table[, -1] <- round(salary_table[, -1], 2)
salary_table
## Position n Min Median Max Mean SD Range
## 1 PG 2507 0.03 2.63 49.93 5.28 7.18 49.90
## 2 SG 2684 0.01 2.68 61.26 5.18 6.51 61.25
## 3 SF 2426 0.01 2.84 41.93 5.35 6.51 41.91
## 4 PF 2784 0.01 3.08 46.10 5.89 7.04 46.09
## 5 C 2617 0.03 3.53 44.91 6.31 7.12 44.88
#boxplot
boxplot(salary ~ Position, data = nba,
main = "Salary by Position",
xlab = "Position", ylab = "Salary (millions$)",
col = "blue")
#Part B: Confidence Interval:
# points conf interval
t.test(stats$PTS, conf.level = 0.95)
##
## One Sample t-test
##
## data: stats$PTS
## t = 131.3, df = 14706, p-value < 2.2e-16
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## 504.9807 520.2865
## sample estimates:
## mean of x
## 512.6336
# assists conf interval
t.test(stats$AST, conf.level = 0.95)
##
## One Sample t-test
##
## data: stats$AST
## t = 102.37, df = 14706, p-value < 2.2e-16
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## 112.1022 116.4789
## sample estimates:
## mean of x
## 114.2905
# conf in by position
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
##
## 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
position_stats = stats %>%
filter(Pos %in% c("PG", "SG", "SF", "PF", "C")) %>%
group_by(Pos) %>%
summarise(
n = sum(!is.na(PTS)),
mean = mean(PTS, na.rm = TRUE),
sd = sd(PTS, na.rm = TRUE),
SE = sd / sqrt(n),
lower_CI = mean - qt(0.975, df = n - 1) * SE,
upper_CI = mean + qt(0.975, df = n - 1) * SE
)
position_stats
## # A tibble: 5 × 7
## Pos n mean sd SE lower_CI upper_CI
## <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 C 2974 421. 421. 7.71 406. 436.
## 2 PF 3024 504. 470. 8.55 487. 521.
## 3 PG 2829 536. 467. 8.78 518. 553.
## 4 SF 2677 549. 489. 9.45 531. 568.
## 5 SG 2966 565. 510. 9.37 547. 584.