Import Dataset
library(readxl)
NBA <- read_excel("NBA 2022-23 Salary Data - Independent Project.xlsx")
## New names:
## • `` -> `...1`
Clean Data
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
NBA_clean <- NBA %>%
rename(
FG_pct = `FG%`,
X3P_pct = `3P%`,
X2P_pct = `2P%`,
FT_pct = `FT%`,
TS_pct = `TS%`,
ORB_pct = `ORB%`,
DRB_pct = `DRB%`,
STL_pct = `STL%`,
BLK_pct = `BLK%`
)
Summary Statistics
library(dplyr)
# Define the variables
variables <- c("FG_pct", "X3P_pct", "X2P_pct", "FT_pct", "TS_pct", "ORB_pct", "DRB_pct", "STL_pct", "BLK_pct")
# Create an empty data frame to store the results
results <- data.frame(
variable = character(),
n = integer(),
mean = numeric(),
sd = numeric(),
min = numeric(),
p25 = numeric(),
median = numeric(),
p75 = numeric(),
max = numeric(),
stringsAsFactors = FALSE
)
# Loop through each variable and calculate the statistics
for (var in variables) {
stats <- c(
var,
sum(!is.na(NBA_clean[[var]])),
mean(NBA_clean[[var]], na.rm = TRUE),
sd(NBA_clean[[var]], na.rm = TRUE),
min(NBA_clean[[var]], na.rm = TRUE),
quantile(NBA_clean[[var]], 0.25, na.rm = TRUE),
median(NBA_clean[[var]], na.rm = TRUE),
quantile(NBA_clean[[var]], 0.75, na.rm = TRUE),
max(NBA_clean[[var]], na.rm = TRUE)
)
# Add the statistics to the results data frame
results <- rbind(results, stats)
}
# Rename the columns
colnames(results) <- c("variable", "n", "mean", "sd", "min", "p25", "median", "p75", "max")
# Print the results
print(results)
## variable n mean sd min p25 median p75
## 1 FG_pct 466 0.465008583690987 0.109546749147304 0 0.417 0.455 0.5075
## 2 X3P_pct 454 0.325090308370044 0.131624250278105 0 0.288 0.3445 0.387
## 3 X2P_pct 463 0.533464362850972 0.136046331250516 0 0.487 0.537 0.5945
## 4 FT_pct 444 0.753488738738739 0.148659145812224 0 0.691 0.769 0.844
## 5 TS_pct 466 0.563023605150215 0.100740940416532 0 0.524 0.567 0.61
## 6 ORB_pct 467 5.24346895074946 4.3322544112627 0 2.1 3.8 7.1
## 7 DRB_pct 467 14.8845824411135 6.47809266185433 0 10.7 13.5 18.35
## 8 STL_pct 467 1.52355460385439 1.36886038513293 0 1 1.4 1.7
## 9 BLK_pct 467 1.8321199143469 2.52433571820543 0 0.7 1.2 2.35
## max
## 1 1
## 2 1
## 3 1
## 4 1
## 5 1.064
## 6 28.8
## 7 55.4
## 8 24.2
## 9 44.1
NBA_clean <- NBA %>%
rename(
FG_pct = `FG%`,
X3P_pct = `3P%`,
X2P_pct = `2P%`,
FT_pct = `FT%`,
TS_pct = `TS%`,
ORB_pct = `ORB%`,
DRB_pct = `DRB%`,
STL_pct = `STL%`,
BLK_pct = `BLK%`
)
Visuals
# Load necessary libraries for plotting
library(ggplot2)
# Histogram for FG%
ggplot(NBA_clean, aes(x = FG_pct)) +
geom_histogram(binwidth = 0.05, fill = "skyblue", color = "black", alpha = 0.7) +
labs(title = "Histogram of Field Goal Percentage (FG%)", x = "FG%", y = "Frequency")
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_bin()`).

# Histogram for 3P%
ggplot(NBA_clean, aes(x = X3P_pct)) +
geom_histogram(binwidth = 0.05, fill = "lightgreen", color = "black", alpha = 0.7) +
labs(title = "Histogram of Three-Point Percentage (3P%)", x = "3P%", y = "Frequency")
## Warning: Removed 13 rows containing non-finite outside the scale range
## (`stat_bin()`).

# Histogram for TS%
ggplot(NBA_clean, aes(x = TS_pct)) +
geom_histogram(binwidth = 0.05, fill = "lightcoral", color = "black", alpha = 0.7) +
labs(title = "Histogram of True Shooting Percentage (TS%)", x = "TS%", y = "Frequency")
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_bin()`).

# Boxplot of TS% by Position
ggplot(NBA_clean, aes(x = Position, y = TS_pct)) +
geom_boxplot(fill = "lightblue", color = "black") +
labs(title = "Boxplot of TS% by Position", x = "Position", y = "True Shooting Percentage")
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_boxplot()`).

# Boxplot of FG% by Position
ggplot(NBA_clean, aes(x = Position, y = FG_pct)) +
geom_boxplot(fill = "lightgreen", color = "black") +
labs(title = "Boxplot of FG% by Position", x = "Position", y = "Field Goal Percentage")
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_boxplot()`).

# Density Plot of TS%
ggplot(NBA_clean, aes(x = TS_pct)) +
geom_density(fill = "purple", alpha = 0.6) +
labs(title = "Density Plot of TS%", x = "True Shooting Percentage", y = "Density")
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_density()`).

Dummy Variable Mutate - Position
NBA_clean <- NBA_clean %>%
mutate(PG_dummy = ifelse(grepl("PG", Position), 1, 0))
# Create dummy variables for Position using model.matrix()
dummy_vars <- model.matrix(~ Position - 1, data = NBA_clean) # -1 removes the intercept column
head(dummy_vars)
## PositionC PositionPF PositionPG PositionPG-SG PositionSF PositionSF-PF
## 1 0 0 1 0 0 0
## 2 0 0 1 0 0 0
## 3 0 0 1 0 0 0
## 4 0 1 0 0 0 0
## 5 0 1 0 0 0 0
## 6 0 0 0 0 0 0
## PositionSF-SG PositionSG PositionSG-PG
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 0 0 0
## 5 0 0 0
## 6 0 1 0
# Replace hyphens with underscores to split hybrid positions
NBA_clean$Position <- gsub("-", "_", NBA_clean$Position)
# Convert Position into a factor after replacing hyphens with underscores
model <- lm(Salary ~ factor(Position) * TS_pct, data = NBA_clean)
summary(model)
##
## Call:
## lm(formula = Salary ~ factor(Position) * TS_pct, data = NBA_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -16682287 -6084663 -3418626 2295531 37245871
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.609e+06 8.882e+06 -0.294 0.7691
## factor(Position)PF 3.646e+06 1.078e+07 0.338 0.7352
## factor(Position)PG -1.377e+07 1.106e+07 -1.245 0.2139
## factor(Position)PG_SG -7.093e+09 2.981e+09 -2.379 0.0178 *
## factor(Position)SF 4.448e+06 1.007e+07 0.442 0.6590
## factor(Position)SF_PF -3.671e+06 1.041e+07 -0.353 0.7246
## factor(Position)SF_SG -2.409e+08 2.382e+08 -1.011 0.3124
## factor(Position)SG -3.564e+06 1.157e+07 -0.308 0.7581
## factor(Position)SG_PG -6.161e+07 2.079e+08 -0.296 0.7671
## TS_pct 1.603e+07 1.428e+07 1.122 0.2624
## factor(Position)PF:TS_pct -2.287e+06 1.773e+07 -0.129 0.8974
## factor(Position)PG:TS_pct 3.714e+07 1.888e+07 1.968 0.0497 *
## factor(Position)PG_SG:TS_pct 1.162e+10 4.875e+09 2.384 0.0175 *
## factor(Position)SF:TS_pct -4.531e+06 1.660e+07 -0.273 0.7850
## factor(Position)SF_PF:TS_pct NA NA NA NA
## factor(Position)SF_SG:TS_pct 4.331e+08 4.181e+08 1.036 0.3008
## factor(Position)SG:TS_pct 7.429e+06 1.955e+07 0.380 0.7041
## factor(Position)SG_PG:TS_pct 1.301e+08 3.753e+08 0.347 0.7290
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10340000 on 449 degrees of freedom
## (1 observation deleted due to missingness)
## Multiple R-squared: 0.1003, Adjusted R-squared: 0.06819
## F-statistic: 3.127 on 16 and 449 DF, p-value: 4.479e-05
Dummy Statistics Table
library(dplyr)
# Summary statistics by position (cross-section)
summary_table <- NBA_clean %>%
group_by(Position) %>%
summarise(
count = n(),
mean_TS_pct = mean(TS_pct, na.rm = TRUE),
sd_TS_pct = sd(TS_pct, na.rm = TRUE),
mean_Salary = mean(Salary, na.rm = TRUE),
sd_Salary = sd(Salary, na.rm = TRUE)
)
# View the summary table
print(summary_table)
## # A tibble: 9 × 6
## Position count mean_TS_pct sd_TS_pct mean_Salary sd_Salary
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 C 91 0.617 0.0763 7282722. 8983558.
## 2 PF 86 0.571 0.107 8885045. 10897789.
## 3 PG 77 0.526 0.0961 11579573. 13889342.
## 4 PG_SG 2 0.611 0.00212 21458528. 24690088.
## 5 SF 91 0.547 0.129 8132253. 11055604.
## 6 SF_PF 1 0.579 NA 3000000 NA
## 7 SF_SG 2 0.570 0.0247 12239763 11116054.
## 8 SG 115 0.550 0.0729 6681301. 8308597.
## 9 SG_PG 2 0.554 0.0276 16650807 4029367.
Dummy Summary Statistics?
NBA_clean %>%
group_by(PG_dummy) %>%
summarise(across(
c(FG_pct, X3P_pct, X2P_pct, FT_pct, TS_pct, ORB_pct, DRB_pct, STL_pct, BLK_pct),
list(
n = ~sum(!is.na(.)),
mean = ~mean(., na.rm = TRUE),
sd = ~sd(., na.rm = TRUE),
min = ~min(., na.rm = TRUE),
p25 = ~quantile(., 0.25, na.rm = TRUE),
median = ~median(., na.rm = TRUE),
p75 = ~quantile(., 0.75, na.rm = TRUE),
max = ~max(., na.rm = TRUE)
),
.names = "{.col}_{.fn}"
))
## FG% 3P% 2P% FT%
## Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.4170 1st Qu.:0.2880 1st Qu.:0.4870 1st Qu.:0.6910
## Median :0.4550 Median :0.3445 Median :0.5370 Median :0.7690
## Mean :0.4650 Mean :0.3251 Mean :0.5335 Mean :0.7535
## 3rd Qu.:0.5075 3rd Qu.:0.3870 3rd Qu.:0.5945 3rd Qu.:0.8440
## Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
## NA's :1 NA's :13 NA's :4 NA's :23
## TS% ORB% DRB% STL%
## Min. :0.000 Min. : 0.000 Min. : 0.00 Min. : 0.000
## 1st Qu.:0.524 1st Qu.: 2.100 1st Qu.:10.70 1st Qu.: 1.000
## Median :0.567 Median : 3.800 Median :13.50 Median : 1.400
## Mean :0.563 Mean : 5.243 Mean :14.88 Mean : 1.524
## 3rd Qu.:0.610 3rd Qu.: 7.100 3rd Qu.:18.35 3rd Qu.: 1.700
## Max. :1.064 Max. :28.800 Max. :55.40 Max. :24.200
## NA's :1
## BLK% Position
## Min. : 0.000 Length:467
## 1st Qu.: 0.700 Class :character
## Median : 1.200 Mode :character
## Mean : 1.832
## 3rd Qu.: 2.350
## Max. :44.100
##
NBA %>%
count(Position) %>%
arrange(desc(n))
Model 1
# Run the regression model
model1 <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct, data = NBA_clean)
# View regression summary
summary(model1)
##
## Call:
## lm(formula = log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct +
## TS_pct, data = NBA_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -7.0569 -0.7697 0.0658 1.0265 2.7434
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 12.58889 0.65061 19.349 < 2e-16 ***
## FG_pct 3.74117 2.15987 1.732 0.08397 .
## X3P_pct 0.54887 0.70586 0.778 0.43725
## X2P_pct -0.91744 0.93726 -0.979 0.32821
## FT_pct 1.60896 0.59654 2.697 0.00727 **
## TS_pct -0.05345 2.50499 -0.021 0.98299
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.428 on 427 degrees of freedom
## (34 observations deleted due to missingness)
## Multiple R-squared: 0.04677, Adjusted R-squared: 0.03561
## F-statistic: 4.191 on 5 and 427 DF, p-value: 0.0009952
Model 2
# Expanded regression model with rebounding and defensive metrics
model2 <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct +
ORB_pct + DRB_pct + STL_pct + BLK_pct,
data = NBA_clean)
# View model 2 summary
summary(model2)
##
## Call:
## lm(formula = log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct +
## TS_pct + ORB_pct + DRB_pct + STL_pct + BLK_pct, data = NBA_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.4452 -0.7203 0.1708 0.9214 2.5618
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 12.46866 0.69409 17.964 < 2e-16 ***
## FG_pct 7.24099 2.39354 3.025 0.002636 **
## X3P_pct -0.29452 0.71243 -0.413 0.679522
## X2P_pct -1.46659 0.92770 -1.581 0.114650
## FT_pct 1.47923 0.58458 2.530 0.011755 *
## TS_pct -1.78091 2.51831 -0.707 0.479842
## ORB_pct -0.12683 0.02764 -4.588 5.91e-06 ***
## DRB_pct 0.05211 0.01527 3.412 0.000708 ***
## STL_pct 0.06441 0.09544 0.675 0.500107
## BLK_pct -0.04669 0.05669 -0.824 0.410673
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.387 on 423 degrees of freedom
## (34 observations deleted due to missingness)
## Multiple R-squared: 0.1082, Adjusted R-squared: 0.08927
## F-statistic: 5.705 on 9 and 423 DF, p-value: 1.766e-07
Model 3
# Make sure Position is treated as a categorical variable
NBA_clean$Position <- as.factor(NBA_clean$Position)
# Model 3: Include position and its interaction with performance metrics
model3 <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct +
ORB_pct + DRB_pct + STL_pct + BLK_pct + Position,
data = NBA_clean)
# Example: including interaction between Position and TS_pct
model3_interaction <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct +
ORB_pct + DRB_pct + STL_pct + BLK_pct +
Position * TS_pct,
data = NBA_clean)
summary(model3) # Or summary(model3_interaction) for the version with interaction
##
## Call:
## lm(formula = log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct +
## TS_pct + ORB_pct + DRB_pct + STL_pct + BLK_pct + Position,
## data = NBA_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.4781 -0.6820 0.1322 0.9817 2.6163
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 12.67354 0.81692 15.514 < 2e-16 ***
## FG_pct 7.45252 2.43873 3.056 0.00239 **
## X3P_pct -0.24576 0.71279 -0.345 0.73043
## X2P_pct -1.65959 0.95038 -1.746 0.08151 .
## FT_pct 1.55359 0.58672 2.648 0.00841 **
## TS_pct -1.90815 2.53341 -0.753 0.45176
## ORB_pct -0.13123 0.03058 -4.292 2.21e-05 ***
## DRB_pct 0.04829 0.01632 2.958 0.00327 **
## STL_pct 0.07738 0.10048 0.770 0.44170
## BLK_pct -0.06281 0.05968 -1.053 0.29317
## PositionPF -0.02821 0.26120 -0.108 0.91405
## PositionPG -0.03392 0.35222 -0.096 0.92333
## PositionPG_SG 0.80893 1.02505 0.789 0.43047
## PositionSF -0.05907 0.30421 -0.194 0.84613
## PositionSF_PF -0.53190 1.40985 -0.377 0.70616
## PositionSF_SG 0.73417 1.02983 0.713 0.47631
## PositionSG -0.40514 0.31922 -1.269 0.20509
## PositionSG_PG 1.31510 1.02591 1.282 0.20060
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.386 on 415 degrees of freedom
## (34 observations deleted due to missingness)
## Multiple R-squared: 0.1272, Adjusted R-squared: 0.0915
## F-statistic: 3.559 on 17 and 415 DF, p-value: 2.712e-06
summary(model3_interaction)
##
## Call:
## lm(formula = log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct +
## TS_pct + ORB_pct + DRB_pct + STL_pct + BLK_pct + Position *
## TS_pct, data = NBA_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.3055 -0.6682 0.0720 0.9388 2.6614
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 14.49125 1.33823 10.829 < 2e-16 ***
## FG_pct 7.67139 2.46371 3.114 0.00198 **
## X3P_pct -0.28380 0.71364 -0.398 0.69108
## X2P_pct -1.91425 0.95904 -1.996 0.04660 *
## FT_pct 1.23117 0.60439 2.037 0.04229 *
## TS_pct -4.32340 3.27025 -1.322 0.18690
## ORB_pct -0.13188 0.03062 -4.307 2.08e-05 ***
## DRB_pct 0.04620 0.01643 2.812 0.00516 **
## STL_pct 0.09230 0.10201 0.905 0.36607
## BLK_pct -0.07171 0.06053 -1.185 0.23683
## PositionPF -0.87197 1.80805 -0.482 0.62987
## PositionPG -5.33226 2.04017 -2.614 0.00929 **
## PositionPG_SG -385.93769 399.30468 -0.967 0.33435
## PositionSF -2.89270 1.81794 -1.591 0.11234
## PositionSF_PF -0.60849 1.40567 -0.433 0.66533
## PositionSF_SG -16.87729 32.04857 -0.527 0.59875
## PositionSG -1.24898 1.69644 -0.736 0.46201
## PositionSG_PG 2.44243 27.81545 0.088 0.93007
## TS_pct:PositionPF 1.27857 3.05250 0.419 0.67554
## TS_pct:PositionPG 9.41064 3.56767 2.638 0.00866 **
## TS_pct:PositionPG_SG 632.45709 652.97318 0.969 0.33333
## TS_pct:PositionSF 4.84007 3.14689 1.538 0.12481
## TS_pct:PositionSF_PF NA NA NA NA
## TS_pct:PositionSF_SG 30.68907 56.21705 0.546 0.58543
## TS_pct:PositionSG 1.22761 2.94226 0.417 0.67673
## TS_pct:PositionSG_PG -2.33604 50.22195 -0.047 0.96292
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.381 on 408 degrees of freedom
## (34 observations deleted due to missingness)
## Multiple R-squared: 0.1477, Adjusted R-squared: 0.09756
## F-statistic: 2.946 on 24 and 408 DF, p-value: 6.323e-06
Models Summary
library(modelsummary)
modelsummary(
list("Model 1" = model1, "Model 2" = model2, "Model 3" = model3),
output = "gt", # Opens a pretty table in the Viewer pane
title = "Regression Results: Determinants of Log Salary",
statistic = c("({std.error})", "{p.value}"), # Show SE and p-values
stars = TRUE, # Adds significance stars
gof_omit = "AIC|BIC|Log.Lik|RMSE|Deviance", # Clean up extra stats
coef_rename = c(
"FG_pct" = "FG%",
"X3P_pct" = "3P%",
"TS_pct" = "TS%",
"ORB_pct" = "ORB%",
"DRB_pct" = "DRB%",
"STL_pct" = "STL%",
"BLK_pct" = "BLK%",
"positionF" = "Position: Forward",
"positionG" = "Position: Guard"
)
)
## Warning: Can't find generic `as.gtable` in package gtable to register S3 method.
## ℹ This message is only shown to developers using devtools.
## ℹ Do you need to update gtable to the latest version?
Regression Results: Determinants of Log Salary
| |
Model 1 |
Model 2 |
Model 3 |
| (Intercept) |
12.589*** |
12.469*** |
12.674*** |
|
(0.651) |
(0.694) |
(0.817) |
|
<0.001 |
<0.001 |
<0.001 |
| FG% |
3.741+ |
7.241** |
7.453** |
|
(2.160) |
(2.394) |
(2.439) |
|
0.084 |
0.003 |
0.002 |
| 3P% |
0.549 |
-0.295 |
-0.246 |
|
(0.706) |
(0.712) |
(0.713) |
|
0.437 |
0.680 |
0.730 |
| X2P_pct |
-0.917 |
-1.467 |
-1.660+ |
|
(0.937) |
(0.928) |
(0.950) |
|
0.328 |
0.115 |
0.082 |
| FT_pct |
1.609** |
1.479* |
1.554** |
|
(0.597) |
(0.585) |
(0.587) |
|
0.007 |
0.012 |
0.008 |
| TS% |
-0.053 |
-1.781 |
-1.908 |
|
(2.505) |
(2.518) |
(2.533) |
|
0.983 |
0.480 |
0.452 |
| ORB% |
|
-0.127*** |
-0.131*** |
|
|
(0.028) |
(0.031) |
|
|
<0.001 |
<0.001 |
| DRB% |
|
0.052*** |
0.048** |
|
|
(0.015) |
(0.016) |
|
|
<0.001 |
0.003 |
| STL% |
|
0.064 |
0.077 |
|
|
(0.095) |
(0.100) |
|
|
0.500 |
0.442 |
| BLK% |
|
-0.047 |
-0.063 |
|
|
(0.057) |
(0.060) |
|
|
0.411 |
0.293 |
| PositionPF |
|
|
-0.028 |
|
|
|
(0.261) |
|
|
|
0.914 |
| PositionPG |
|
|
-0.034 |
|
|
|
(0.352) |
|
|
|
0.923 |
| PositionPG_SG |
|
|
0.809 |
|
|
|
(1.025) |
|
|
|
0.430 |
| PositionSF |
|
|
-0.059 |
|
|
|
(0.304) |
|
|
|
0.846 |
| PositionSF_PF |
|
|
-0.532 |
|
|
|
(1.410) |
|
|
|
0.706 |
| PositionSF_SG |
|
|
0.734 |
|
|
|
(1.030) |
|
|
|
0.476 |
| PositionSG |
|
|
-0.405 |
|
|
|
(0.319) |
|
|
|
0.205 |
| PositionSG_PG |
|
|
1.315 |
|
|
|
(1.026) |
|
|
|
0.201 |
| Num.Obs. |
433 |
433 |
433 |
| R2 |
0.047 |
0.108 |
0.127 |
| R2 Adj. |
0.036 |
0.089 |
0.091 |
| F |
4.191 |
5.705 |
3.559 |
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 |
Outlier Test - Model 1
# Filter to complete cases used in model1
NBA_model1_data <- NBA_clean[complete.cases(NBA_clean[, c("Salary", "FG_pct", "X3P_pct", "X2P_pct", "FT_pct", "TS_pct")]), ]
# Refit the model on this data
model1 <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct, data = NBA_model1_data)
# Calculate studentized residuals
NBA_model1_data$stud_resid <- rstudent(model1)
# Show the top 10 absolute outliers
NBA_model1_data %>%
arrange(desc(abs(stud_resid))) %>%
select(Salary, stud_resid) %>%
head(10)
Comparison - R^2 and Coefficients
NBA_no_outliers <- NBA_model1_data %>% filter(abs(stud_resid) <= 3)
# Re-run model
model1_no_outliers <- lm(log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct + TS_pct, data = NBA_no_outliers)
summary(model1_no_outliers)
##
## Call:
## lm(formula = log(Salary) ~ FG_pct + X3P_pct + X2P_pct + FT_pct +
## TS_pct, data = NBA_no_outliers)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4.3435 -0.8489 0.0601 0.9147 2.7978
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 11.3220 0.5948 19.035 < 2e-16 ***
## FG_pct 2.5174 1.9331 1.302 0.193543
## X3P_pct 0.9245 0.6429 1.438 0.151136
## X2P_pct -1.0784 0.8536 -1.263 0.207207
## FT_pct 1.9028 0.5304 3.587 0.000374 ***
## TS_pct 2.9018 2.2555 1.287 0.198974
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.253 on 419 degrees of freedom
## Multiple R-squared: 0.1144, Adjusted R-squared: 0.1038
## F-statistic: 10.82 on 5 and 419 DF, p-value: 8.337e-10
Robustness Checks
# Run Ramsey RESET test for model specification
library(AER)
## Loading required package: car
## Loading required package: carData
##
## Attaching package: 'car'
## The following object is masked from 'package:dplyr':
##
## recode
## Loading required package: lmtest
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Loading required package: sandwich
## Loading required package: survival
reset_test_model1 <- resettest(model1)
reset_test_model2 <- resettest(model2)
reset_test_model3 <- resettest(model3)
# Print RESET test results
print(reset_test_model1)
##
## RESET test
##
## data: model1
## RESET = 22.397, df1 = 2, df2 = 425, p-value = 5.652e-10
print(reset_test_model2)
##
## RESET test
##
## data: model2
## RESET = 13.614, df1 = 2, df2 = 421, p-value = 1.866e-06
print(reset_test_model3)
##
## RESET test
##
## data: model3
## RESET = 6.7181, df1 = 2, df2 = 413, p-value = 0.001345
plot(model$fitted.values, model$residuals)
abline(h = 0, col = "red")

Multicoliniearity
library(car)
# For model1
vif(model1)
## FG_pct X3P_pct X2P_pct FT_pct TS_pct
## 6.985771 1.595839 2.058965 1.399115 6.821114
# For model2
vif(model2)
## FG_pct X3P_pct X2P_pct FT_pct TS_pct ORB_pct DRB_pct STL_pct
## 9.084527 1.721436 2.136026 1.422780 7.299971 2.532740 1.727078 1.068789
## BLK_pct
## 1.644400
# For model3
vif(model3)
## GVIF Df GVIF^(1/(2*Df))
## FG_pct 9.453949 1 3.074727
## X3P_pct 1.727398 1 1.314305
## X2P_pct 2.247225 1 1.499075
## FT_pct 1.436700 1 1.198624
## TS_pct 7.405893 1 2.721377
## ORB_pct 3.106087 1 1.762409
## DRB_pct 1.977576 1 1.406263
## STL_pct 1.187517 1 1.089732
## BLK_pct 1.826562 1 1.351504
## Position 3.636224 8 1.084028
Heteroskatdasity
library(lmtest)
# Breusch-Pagan test for heteroskedasticity
bptest(model1)
##
## studentized Breusch-Pagan test
##
## data: model1
## BP = 19.5, df = 5, p-value = 0.00155
bptest(model2)
##
## studentized Breusch-Pagan test
##
## data: model2
## BP = 21.318, df = 9, p-value = 0.01131
bptest(model3)
##
## studentized Breusch-Pagan test
##
## data: model3
## BP = 29.738, df = 17, p-value = 0.0283
Shapiro
# Q-Q plot
qqnorm(residuals(model1)); qqline(residuals(model1), col = "red")

# Shapiro-Wilk test
shapiro.test(residuals(model1))
##
## Shapiro-Wilk normality test
##
## data: residuals(model1)
## W = 0.95312, p-value = 1.714e-10
shapiro.test(residuals(model2))
##
## Shapiro-Wilk normality test
##
## data: residuals(model2)
## W = 0.93792, p-value = 1.844e-12
shapiro.test(residuals(model3))
##
## Shapiro-Wilk normality test
##
## data: residuals(model3)
## W = 0.93981, p-value = 3.107e-12