library(dplyr)
library(lmtest)
library(ggpmisc)
library(foreach)
library(ggpubr)
library(ggplot2)
theme_set(theme_minimal()+theme(
plot.title = element_text(size = 12,hjust = 0.5),
plot.subtitle = element_text(face="bold",
size = 12)
))
update_geom_defaults("col", list(fill = "skyblue"))
update_geom_defaults("point", list(colour = "skyblue3"))
update_geom_defaults("smooth", list(colour = "magenta3", linetype= "dashed" ,
linewidth = .75))
stat_poly_eq2 <- function(...) {
stat_poly_eq(formula = y ~ x, coef.digits = 10,rr.digits = 10,p.digits = 10,
eq.x.rhs = "~`*`~Age", eq.with.lhs = "`#`~Charges~~`=`~~", ...)
}
plot_start <- function(df,w,...) {
ggplot(df, aes(x = Age, y = AvNumCharges))+labs(y = "Average # Charges", x="Age (years)",
title ="Age-Averaged Number\n of Charges per Arrest")+w+
geom_smooth(method = "lm",formula = y ~ x, ...) +
stat_poly_eq2(use_label("R2"), label.y = 0.9, label.x = 0.05)
}
This project utilizes police arrest data generated from the police department (Record Management System (RMS)) in Scottsdale, Arizona.
df=read.csv("Police_Arrests.csv")
names(df)
## [1] "OBJECTID" "Arrestee" "Race"
## [4] "Sex" "Age" "District"
## [7] "Beat" "Zone" "ArrestDate"
## [10] "ArrestTime" "ArrestNum" "DrNum"
## [13] "ArrestType" "ArizonaStatuteCode" "ChargeDescription"
## [16] "ArrestLocation" "CityOfArrestee" "OfficerSerialNum"
Each row represents one charge, and charges belonging to the same arrest incident share an arrest number.
For each arrest number: combine all charges into an overall count and retain arrest details.
df=df %>%
mutate(
NumCharges=n(),
.by= ArrestNum) %>%
filter(!duplicated(df$ArrestNum))%>%
select(-c("ChargeDescription","ArizonaStatuteCode"))
Fit a linear regression model to estimate the number of charges from other arrest data.
In this analysis the arrestees age will be used to estimate the number of charges. Records with uncommon ages are removed.
df=df %>% select(c("NumCharges","Age"))%>%
filter(Age<=83 & Age > 23)
Now group the arrest records by age and compute the mean number of charges for each group.
df2=df %>% summarise(AvNumCharges = mean(NumCharges),.by=Age)
head(df2)
plot_start(df2,geom_point()) + expand_limits(y = 2.5)+stat_poly_eq2(use_label("eq")) +
stat_poly_eq2( use_label("P"), label.y = .85, label.x = .05)
Heteroskedasticity appears to be present in the model.
Heteroskedasticity happens when the error values from a regression model have unequal variability across the range of another variable. This violates the mathematical assumptions of least ordinary squares regression.
Check for heteroskedasticity by plotting the residuals of the linear model against the explanatory variable, in this case age.
df2$ols=lm(AvNumCharges ~ Age, data = df2)
df2$Residuals <- df2$ols$residuals
ggplot(data = df2, aes(y = Residuals, x = Age)) +
geom_point() +
geom_abline(slope = 0)
There appears to be more variation in the residual value for older adults, further suggesting heteroskedasticity is present.
To formally detect heteroskedasticity, conduct a Breusch-Pagan test.
pval=bptest(df2$ols)$p.value
print(pval)
## BP
## 9.865546e-05
The p-value 9.8655461^{-5} is less than \(\alpha=0.05\), suggesting that
heteroskedasticity is present.
This data set can be restructured to minimize heteroskedasticity.
Data binning is the restructing technique chosen due to its ability to average out local charges in variability.
Steps:
Group the records based on arestees age into discrete intervals (bins) then compute the mean number of charges for each bin.
Compute the R-Squared statistic of linear fit and test the model for heteroskedasticity with the Breusch-Pagan test (BP Score).
Repeat for each bin size under consideration.
results <- foreach(i = 1:6) %do% {
data = df %>% mutate(Age = Age-(Age %% i)) %>%
summarise(AvNumCharges = mean(NumCharges),.by=Age)
bpscore=round(bptest(lm(AvNumCharges ~ Age, data = data))$p.value,5)
list(plt = plot_start(data, geom_col(),se=F)+
labs(subtitle=paste("(bin width:",i,"years)\nBP Score:", bpscore)) +
expand_limits(y = 3),
bpscore = bpscore)
}
ggarrange(plotlist=lapply(results, function(x) x$plt),
labels = c("A)", "B)", "C)", "D)", "E)", "F)"),
ncol = 3, nrow = 2)
The bin width is set to 4 years.
df=df %>% mutate(Age = Age-(Age %% 4)) %>%
summarise(AvNumCharges = mean(NumCharges),.by=Age)
Since the p-value 0.11477 is greater than \(\alpha=0.05\) the evidence no longer suggests that heteroskedasticity is present in the model.
plot_start(df,geom_col())+ expand_limits(y = 3)+stat_poly_eq2(use_label("eq")) +
stat_poly_eq2( use_label("P"), label.y = .85, label.x = .05)
When arrest records are grouped by the arestees age into 4 year bins, and the mean number of charges per arrest is computed for each group:
We find that 88% of the variation in average charge number can be explained from a linear relationship with age.
Age may not be the cause of variations in charge number, but it presents strong evidence of being correlated.