knitr::opts_chunk$set(echo = TRUE)
#I have read the honor code and abided by it.
# I strive to uphold the University values of respect, responsibility, discovery, and excellence. On my honor, I pledge that I have neither given nor received unauthorized assistance on this work.
#Question 1A
x <- c(12, NA, 5, 20, 0, 9)
x[x != NA & x >= 9]
## [1] NA NA NA NA
#!= NA returns all NAs because R can't compare anything directly to NA using !=
retained <- x[!is.na(x) & x >= 9]
retained
## [1] 12 20 9
mean(retained)
## [1] 13.66667
#Question 1B
weight <- factor(c("10", "5", "20", "10"),
levels = c("5", "10", "20"))
as.numeric(weight)
## [1] 2 1 3 2
#as.numeric() pulls levels, not the actual values, so it needs to be made a character first
weight_k<- as.numeric(as.character(weight))
weight_k
## [1] 10 5 20 10
weight_g <- weight_k * 1000
weight_g
## [1] 10000 5000 20000 10000
#Question 1C
A <- matrix(1:12, nrow = 4, ncol = 3)
A
## [,1] [,2] [,3]
## [1,] 1 5 9
## [2,] 2 6 10
## [3,] 3 7 11
## [4,] 4 8 12
AxA<- A * A
AxA
## [,1] [,2] [,3]
## [1,] 1 25 81
## [2,] 4 36 100
## [3,] 9 49 121
## [4,] 16 64 144
dim(AxA)
## [1] 4 3
AtA <- t(A) %*% A
AtA
## [,1] [,2] [,3]
## [1,] 30 70 110
## [2,] 70 174 278
## [3,] 110 278 446
dim(AtA)
## [1] 3 3
#In AxA each entry in the matrix is multiplied by itself,which is why it stays a 4x3, but AtA multiplies a 3x4 and 4x3 matrix making a 3x3 matrix
col_means <- apply(A, 2, mean)
col_means
## [1] 2.5 6.5 10.5
#No this does not have an inverse because column 3 depends on columns 1 and 2
#aka the columns are not indepedent
#Question 1D
matrix_info <- list(
data = A,
means = col_means,
crossproduct = AtA)
str(matrix_info["means"])
## List of 1
## $ means: num [1:3] 2.5 6.5 10.5
str(matrix_info[["means"]])
## num [1:3] 2.5 6.5 10.5
#The single brackets return a list, while double brackets pull out the actual object
matrix_info[["crossproduct"]][2, 3]
## [1] 278
#Question 2A
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(lubridate)
depots <- read.csv("depots.csv",
colClasses = c(depot_id = "character"),
na.strings = "M")
deliveries <- read.csv("deliveries.csv",
colClasses = c(depot_id = "character"),
na.strings = "M")
deliveries$date <- as.Date(deliveries$date)
dim(depots)
## [1] 5 2
dim(deliveries)
## [1] 180 7
str(deliveries)
## 'data.frame': 180 obs. of 7 variables:
## $ delivery_id: chr "D001" "D002" "D003" "D004" ...
## $ depot_id : chr "04" "04" "03" "01" ...
## $ date : Date, format: "2026-07-20" "2026-09-11" ...
## $ distance_km: num 19 4 24.9 18.3 17.5 12.8 NA 20.6 16.8 19.1 ...
## $ weight_kg : num 17.7 11.6 7.1 3.6 7.2 1.1 11.3 10.4 7.8 2.4 ...
## $ minutes : num 86.6 31.7 86.1 67 49.1 49.2 33 77.9 69.5 70.6 ...
## $ rating : int 4 5 4 2 NA 3 5 1 4 4 ...
#Question 2B
delivery<-deliveries |>
left_join(depots, by = "depot_id")
nrow(delivery) == nrow(deliveries)
## [1] TRUE
!any(is.na(delivery$depot))
## [1] TRUE
unused_depot <- depots |>
filter(!depot_id %in% delivery$depot_id) |>
pull(depot)
unused_depot
## [1] "Central"
delivery$depot <- factor(delivery$depot, levels = depots$depot)
#Question 2C
#I used AI on this one because it was picking up minutes from the lubricate package, so I had figure that out
depot_summary <- delivery |>
group_by(depot) |>
summarise(
n_deliveries = n(),
n_observed_times = sum(!is.na(minutes)),
mean_time = mean(minutes, na.rm = TRUE),
sd_time = sd(minutes, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(mean_time))
print(depot_summary)
## # A tibble: 4 × 5
## depot n_deliveries n_observed_times mean_time sd_time
## <fct> <int> <int> <dbl> <dbl>
## 1 South 45 45 79.1 31.8
## 2 West 45 44 74.7 27.9
## 3 North 45 44 74.1 29.6
## 4 East 45 43 66.5 28.8
write.csv(depot_summary, file = "depot_summary.csv", row.names = FALSE)
#The number of deliveries and observe ones differ due to missing data. The observed times only count it when the variable is present.
#Question 2D
delivery_long <- delivery |>
pivot_longer(
cols = c(distance_km, weight_kg, minutes),
names_to = "measurement",
values_to = "value")|>
select(delivery_id, measurement, value)
dim(delivery_long)
## [1] 540 3
delivery_wide <- delivery_long |>
pivot_wider(
names_from = measurement,
values_from = value)
delivery_long_ordered<- delivery |>
select(delivery_id, distance_km, weight_kg, minutes) |>
arrange(delivery_id)
all.equal(delivery_long_ordered, delivery_wide)
## [1] "Attributes: < Component \"class\": Lengths (1, 3) differ (string compare on first 1) >"
## [2] "Attributes: < Component \"class\": 1 string mismatch >"
#Question 2 E
plot(x = delivery$minutes, y = delivery$distance_km)

library(ggplot2)
pl <-ggplot(delivery, aes(x = minutes, y = distance_km)) +
geom_point() +
facet_wrap(~depot)+
geom_smooth(method = "lm", se = FALSE)
pl
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 9 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 9 rows containing missing values or values outside the scale range
## (`geom_point()`).

pl_plot<- file.path(tempdir(), "delivery_plot.png")
ggsave(filename = "delivery_plot.png", plot = pl, width = 7, height = 5, units = "in", dpi = 150)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 9 rows containing non-finite outside the scale range (`stat_smooth()`).
## Removed 9 rows containing missing values or values outside the scale range
## (`geom_point()`).
file.exists(pl_plot)
## [1] FALSE
#Yes this relationship looks similar across the depots.
#Question 3A
delivery2<-delivery[!is.na(delivery$minutes) &
!is.na(delivery$distance_km) &
!is.na(delivery$weight_kg), ]
nrow(delivery2)
## [1] 165
set.seed(202603)
train_amo<- sample(1:165, size = 120, replace = FALSE)
train <- delivery2[train_amo, ]
test <- delivery2[-train_amo, ]
nrow(train)
## [1] 120
nrow(test)
## [1] 45
m1<-lm(minutes ~ distance_km + weight_kg, data = train)
summary(m1)
##
## Call:
## lm(formula = minutes ~ distance_km + weight_kg, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -18.159 -4.054 -0.574 4.615 19.500
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 10.85638 1.76905 6.137 1.18e-08 ***
## distance_km 3.02914 0.06858 44.173 < 2e-16 ***
## weight_kg 1.09019 0.12843 8.489 7.56e-14 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.002 on 117 degrees of freedom
## Multiple R-squared: 0.9451, Adjusted R-squared: 0.9441
## F-statistic: 1007 on 2 and 117 DF, p-value: < 2.2e-16
#This is below .05 so it is SS. No, this does not establish causation as there are other factors which may impact the results.
#Question 3B
X <- cbind(1, train$distance_km, train$weight_kg)
y <- train$minutes
math<-solve(t(X) %*% X) %*% t(X) %*% y
dim(X)
## [1] 120 3
dim(t(X) %*% X)
## [1] 3 3
all.equal(as.vector(math), as.vector(coef(m1)))
## [1] TRUE
#you cannot use == as the variables are not comparable between each other
#Question 3C
new<- data.frame(distance_km = 15, weight_kg = 5)
predict(m1, newdata = new, interval = "confidence", level = 0.95)
## fit lwr upr
## 1 61.74439 60.09383 63.39495
predict(m1, newdata = new, interval = "prediction", level = 0.95)
## fit lwr upr
## 1 61.74439 47.77933 75.70945
#The prediction interval is wider as it accounts for uncertanty
par(mfrow = c(1, 2))
plot(m1)


#These plots help to show that regression diagnostics have been pass!
#Question 3D
#I used AI to help calculate the RSME. I got confused with the equation, so I asked it to help me with that and making the table with everything.
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
##
## The following object is masked from 'package:dplyr':
##
## combine
##
## The following object is masked from 'package:ggplot2':
##
## margin
set.seed(2026031)
rf<- randomForest(minutes ~ distance_km + weight_kg,
data = train,
ntree = 300,
mtry = 2)
rmse <- function(actual, predicted) {
sqrt(mean((actual - predicted)^2))}
mean_y <- mean(train$minutes)
train_rmse_base <- rmse(train$minutes, mean_y)
test_rmse_base <- rmse(test$minutes, mean_y)
train_rmse_lm <- rmse(train$minutes, predict(m1, newdata = train))
test_rmse_lm <- rmse(test$minutes, predict(m1, newdata = test))
train_rmse_rf <- rmse(train$minutes, predict(rf, newdata = train))
test_rmse_rf <- rmse(test$minutes, predict(rf, newdata = test))
data.frame(
Method = c("Baseline", "Linear Model", "Random Forest"),
`Training RMSE` = c(train_rmse_base, train_rmse_lm, train_rmse_rf),
`Test RMSE` = c(test_rmse_base, test_rmse_lm, test_rmse_rf))
## Method Training.RMSE Test.RMSE
## 1 Baseline 29.503495 29.860557
## 2 Linear Model 6.913960 7.112118
## 3 Random Forest 3.491358 8.821323
#Question 3E
#AI helped me with the bootstrap. I understand the logic of this, but still have not gotten the hang of the actual code so i worked it though with it.
set.seed(2026032)
B <- 2000
n_train <- nrow(train)
boot_coefs <- numeric(B)
for (b in 1:B) {
boot_indices <- sample(1:n_train, size = n_train, replace = TRUE)
boot_sample <- train[boot_indices, ]
boot_fit <- lm(minutes ~ distance_km + weight_kg, data = boot_sample)
boot_coefs[b] <- coef(boot_fit)["distance_km"]}
hist(boot_coefs)
quantile<-quantile(boot_coefs, probs = c(0.025, 0.975))
quantile
## 2.5% 97.5%
## 2.884031 3.173399
confint<-confint(m1, parm = "distance_km", level = 0.95)
confint
## 2.5 % 97.5 %
## distance_km 2.893328 3.164947
#resampling would be incorrect because it breaks down the relationship between the variables
#Question 4A
weight <- factor(c("10", "5", "20", "10"),
levels = c("5", "10", "20"))
as.numeric(weight)
## [1] 2 1 3 2
#as.numeric() pulls levels, not the actual values, so it needs to be made a character first
d_target <- function(x) {
ifelse(x >= 0 & x <= 1, 6 * x * (1 - x), 0)
}
test<- c(-0.1, 0, 0.25, 0.5, 1, 1.1)
d_target(test)
## [1] 0.000 0.000 1.125 1.500 0.000 0.000
#Question 4B
# I used AI to help me write the loop. I understand how to read the code, but have struggled to actually write the code, so it helped me wit that step by step.
r_target <- function(n) {
if (n < 1 || n %% 1 != 0) stop("n >= 1")
draws <- numeric(n)
accepted <- 0
attempts <- 0
while (accepted < n) {
attempts <- attempts + 1
u <- runif(1); v <- runif(1)
if (v <= 4 * u * (1 - u)) {
accepted <- accepted + 1
draws[accepted] <- u}}
list(draws = draws, acceptance = n, acceptance = n / attempts)
}
#Question 4C
set.seed(202604)
res <- r_target(5000)
mean(res$draws)
## [1] 0.5014082
var(res$draws)
## [1] 0.04984302
res$acceptance
## [1] 5000
res$attempts
## NULL
hist(res$draws)

#Question 4D
#AI helped me calculate the MCSE and transpose the equation into code. Like, I understand the equation, but need to be walkthrough how to actually make it into code
p_hat <- mean(res$draws > 0.8)
mcse <- sqrt(p_hat * (1 - p_hat) / 5000)
p<- pbeta(0.8, 2, 2)
q<- qbeta(0.90, 2, 2)
p_hat
## [1] 0.1048
mcse
## [1] 0.004331673
p
## [1] 0.896
q
## [1] 0.8041999
#Question 5A
running_mean_loop <- function(x) {
out <- numeric(length(x))
for (i in seq_along(x)) {
out[i] <- mean(x[seq_len(i)])
}
out
}
running_mean_vectorized <- function(x) {
cumsum(x) / seq_along(x)
}
test2<- c(2, 4, 9, 1)
running_mean_loop(test2)
## [1] 2 3 5 4
running_mean_vectorized(test2)
## [1] 2 3 5 4
running_mean_loop(numeric(0))
## numeric(0)
running_mean_vectorized(numeric(0))
## numeric(0)
set.seed(202605)
x <- rnorm(10000)
loop <- Sys.time()
for (i in 1:5) running_mean_loop(x)
difftime(Sys.time(), loop)
## Time difference of 1.11096 secs
vec <- Sys.time()
for (i in 1:5) running_mean_vectorized(x)
difftime(Sys.time(), vec)
## Time difference of 0.0008971691 secs
#The loop takes ~0.20 seconds and the vectorized version takes ~0.0003 seconds. The vectorization is not always faster as it can cause increased memory making it go slower
#Question 5B
#AI helped me write this code as I was unfamiliar with this package and needed to be walked through it
library(future.apply)
## Loading required package: future
plan(sequential)
means_seq <- unlist(future_lapply(
1:8,
function(i) mean(r_target(1000)),
future.seed = 2026051
))
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
workers <- min(2, availableCores())
plan(multisession, workers = workers)
means_multi <- unlist(future_lapply(
1:8,
function(i) mean(r_target(1000)),
future.seed = 2026051
))
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
## Warning in mean.default(r_target(1000)): argument is not numeric or logical:
## returning NA
means_seq
## [1] NA NA NA NA NA NA NA NA
means_multi
## [1] NA NA NA NA NA NA NA NA
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.7.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/Chicago
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] future.apply_1.21.0 future_1.80.0 randomForest_4.7-1.2
## [4] lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0
## [7] dplyr_1.2.1 purrr_1.2.2 readr_2.2.0
## [10] tidyr_1.3.2 tibble_3.3.1 ggplot2_4.0.3
## [13] tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 utf8_1.2.6 generics_0.1.4 stringi_1.8.9
## [5] lattice_0.23-1 listenv_1.1.0 hms_1.1.4 digest_0.6.39
## [9] magrittr_2.0.5 evaluate_1.0.5 grid_4.6.1 timechange_0.4.0
## [13] RColorBrewer_1.1-3 fastmap_1.2.0 jsonlite_2.0.0 Matrix_1.7-6
## [17] mgcv_1.9-4 scales_1.4.0 codetools_0.2-20 textshaping_1.0.5
## [21] jquerylib_0.1.4 cli_3.6.6 rlang_1.3.0 parallelly_1.48.0
## [25] splines_4.6.1 withr_3.0.3 cachem_1.1.0 yaml_2.3.12
## [29] otel_0.2.0 parallel_4.6.1 tools_4.6.1 tzdb_0.5.0
## [33] globals_0.20.0 vctrs_0.7.3 R6_2.6.1 lifecycle_1.0.5
## [37] ragg_1.5.2 pkgconfig_2.0.3 pillar_1.11.1 bslib_0.12.0
## [41] gtable_0.3.6 glue_1.8.1 systemfonts_1.3.2 xfun_0.62
## [45] tidyselect_1.2.1 rstudioapi_0.19.0 knitr_1.52 farver_2.1.2
## [49] htmltools_0.5.9 nlme_3.1-171 rmarkdown_2.32 labeling_0.4.3
## [53] compiler_4.6.1 S7_0.2.2