# Inladen van de benodigde libraries
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.2.2
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.5.1 ✔ purrr 0.3.4
## ✔ tibble 3.2.1 ✔ dplyr 1.1.4
## ✔ tidyr 1.2.1 ✔ stringr 1.4.1
## ✔ readr 2.1.2 ✔ forcats 0.5.2
## Warning: package 'ggplot2' was built under R version 4.2.3
## Warning: package 'tibble' was built under R version 4.2.3
## Warning: package 'dplyr' was built under R version 4.2.3
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
library(tsibble) # Voor tijdreeksanalyse
## Registered S3 method overwritten by 'tsibble':
## method from
## as_tibble.grouped_df dplyr
##
## Attaching package: 'tsibble'
##
## The following objects are masked from 'package:base':
##
## intersect, setdiff, union
library(feasts) # Voor tijdreekscomponentanalyse
## Loading required package: fabletools
library(lubridate) # Voor datummanipulatie
##
## Attaching package: 'lubridate'
##
## The following object is masked from 'package:tsibble':
##
## interval
##
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(zoo) # Voor na.approx()
## Warning: package 'zoo' was built under R version 4.2.3
##
## Attaching package: 'zoo'
##
## The following object is masked from 'package:tsibble':
##
## index
##
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(dplyr)
library(ggcorrplot)
## Warning: package 'ggcorrplot' was built under R version 4.2.3
library(fable)
library(lmtest)
## Warning: package 'lmtest' was built under R version 4.2.3
library(gridExtra)
## Warning: package 'gridExtra' was built under R version 4.2.3
##
## Attaching package: 'gridExtra'
##
## The following object is masked from 'package:dplyr':
##
## combine
library(forecast)
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
# path_filedata <- "/Users/hannahbrands/Downloads/datasets/filedata.csv"
# path_weer <- "/Users/hannahbrands/Downloads/datasets/weer.csv"
path_filedata <- "C:/Users/oleja/Downloads/datasets/filedata.csv"
path_weer <- "C:/Users/oleja/Downloads/weer.csv"
# Inlezen en voorbereiden van de filedataset
# We beginnen met het inlezen van de dataset en het opschonen van de data.
# Ongeldige of negatieve waarden worden gefilterd om fouten in de analyse te voorkomen.
filedata <- read.csv(path_filedata, sep = ";") %>%
# Filter: Verwijder niet-numerieke en negatieve waarden
filter(
!grepl("[^0-9.]", duurMinuten), # Controleer op niet-numerieke tekens in duurMinuten
!grepl("[^0-9.]", zwaarteKmMin) # Controleer op niet-numerieke tekens in zwaarteKmMin
) %>%
# Formaatconversie: Zet kolommen om naar juiste typen
mutate(
BeginDatum = as_date(BeginDatum), # Zet BeginDatum om naar datumformaat
duurMinuten = abs(as.numeric(duurMinuten)), # Zet duurMinuten om naar absolute numerieke waarden
zwaarteKmMin = abs(as.numeric(zwaarteKmMin)) # Zet zwaarteKmMin om naar absolute numerieke waarden
) %>%
# Filter: Verwijder irrelevante of foutieve waarnemingen
filter(
zwaarteKmMin > 0, # Filezwaarte moet groter dan 0 zijn
duurMinuten > 0, # Fileduur moet groter dan 0 zijn
zwaarteKmMin / 2 >= duurMinuten # Filelengte moet minimaal 2 keer de duur zijn
)
# Berekening van dagelijkse filezwaarte
# De totale filezwaarte per dag wordt berekend door de waarden per datum te sommeren.
dag_zwaarte <- filedata %>%
group_by(BeginDatum) %>%
summarize(filezwaarte = sum(zwaarteKmMin), .groups = "drop") %>% # Bereken de som per dag
as_tsibble(index = BeginDatum) %>% # Zet om naar een tsibble (tijdreeks)
fill_gaps(filezwaarte = NA) %>% # Vul ontbrekende dagen in met NA
mutate(filezwaarte = na.approx(filezwaarte)) # Vervang NA's met lineaire interpolatie
# Visualisatie van de dagelijkse filezwaarte
# Een lijnplot wordt gemaakt om de dagelijkse trends in filezwaarte te visualiseren.
dag_zwaarte %>%
autoplot(filezwaarte) +
labs(
title = "Dagelijkse Filezwaarte",
x = "Datum",
y = "Filezwaarte (km∙min)"
)

# STL-decompositie uitvoeren
# De tijdreeks wordt gedecomposeerd in trend, seizoen en restcomponenten.
# Er is gekozen voor een seizoensperiode van 365 dagen om jaarlijkse patronen te analyseren.
decomp <- dag_zwaarte %>%
model(STL(filezwaarte ~ season(period = 365), robust = TRUE)) %>%
components()
# Identificatie van uitbijters
# Uitbijters worden geïdentificeerd in de restcomponent met behulp van een boxplot-methode (IQR-regel).
filezwaarte_outliers <- decomp %>%
filter(
remainder < quantile(remainder, 0.25) - 3 * IQR(remainder) | # Ondergrens
remainder > quantile(remainder, 0.75) + 3 * IQR(remainder) # Bovengrens
)
# Visualisatie van de uitbijters
# Een boxplot toont de restcomponent, waarbij uitbijters worden gemarkeerd.
decomp %>%
ggplot(aes(x = "", y = remainder)) + # Gebruik een lege x-as voor een enkele boxplot
geom_boxplot(outlier.color = "red", coef = 3, fill = "skyblue", color = "black") +
labs(
title = "Boxplot van de restcomponent",
x = NULL,
y = "Restcomponent"
) +
theme_minimal()

# Verwijderen van uitbijters
# Uitbijters worden verwijderd en de data wordt opnieuw geïnterpoleerd.
dag_zwaarte_clean <- dag_zwaarte %>%
anti_join(filezwaarte_outliers, by = "BeginDatum") %>% # Verwijder uitbijters
fill_gaps(filezwaarte = NA) %>% # Vul opnieuw hiaten in met NA
mutate(filezwaarte = na.approx(filezwaarte)) # Vervang NA's met lineaire interpolatie
# Nieuwe STL-decompositie
# Na het opschonen van de data wordt opnieuw een STL-decompositie uitgevoerd.
decomp_clean <- dag_zwaarte_clean %>%
model(STL(filezwaarte ~ season(period = 365), robust = TRUE)) %>%
components()
# Visualisatie van de nieuwe decompositie
# De decompositie wordt opnieuw gevisualiseerd, nu zonder de invloed van uitbijters.
decomp_clean %>%
autoplot() +
labs(
title = "STL-decompositie van dagelijkse filezwaarte (zonder uitbijters)",
subtitle = "Filezwaarte = trend + seizoen (365 dagen) + restcomponent",
x = "Datum",
y = "Filezwaarte"
) +
theme_minimal()

# Analyse van de restcomponent
# De restcomponent wordt verder geanalyseerd op statistische validiteit.
remainder <- decomp_clean$remainder
# ACF van de restcomponent
# Controleer of er autocorrelatie aanwezig is in de restcomponent.
acf(remainder, main = "ACF van de restcomponent")

# Ljung-Box-test
# Test voor significante autocorrelatie.
Box.test(remainder, lag = 20, type = "Ljung-Box")
##
## Box-Ljung test
##
## data: remainder
## X-squared = 16146, df = 20, p-value < 2.2e-16
# Q-Q-plot voor normaalverdeling
# Controleer visueel of de restcomponent normaal verdeeld is.
qq_plot <- ggplot(data.frame(sample = remainder), aes(sample = sample)) +
stat_qq() +
stat_qq_line(color = "red") +
labs(title = "Q-Q-plot van de Restcomponent") +
theme_minimal()
grid.arrange(qq_plot, ncol = 1)

# Shapiro-Wilk-test voor normaalverdeling
# Neem een steekproef om de test uit te voeren op een grote dataset.
set.seed(123) # Zorg voor reproduceerbaarheid
sampled_remainder <- sample(remainder, size = 5000)
shapiro.test(sampled_remainder)
##
## Shapiro-Wilk normality test
##
## data: sampled_remainder
## W = 0.98297, p-value < 2.2e-16
# Breusch-Pagan-test voor homoscedasticiteit
# Controleer of de variantie van de restcomponent constant is.
bp_test <- bptest(remainder ~ seq_along(remainder))
print(bp_test)
##
## studentized Breusch-Pagan test
##
## data: remainder ~ seq_along(remainder)
## BP = 1.2641, df = 1, p-value = 0.2609
### boxplot maken om inzicht te krijgen in de algemene data ###
# Bereken de mediaan
median_value <- median(dag_zwaarte_clean$filezwaarte, na.rm = TRUE)
# Bereken de grenzen
lower_bound <- median_value - 20000
upper_bound <- median_value + 20000
# Bepaal welke waarden buiten het bereik liggen
outside_bounds <- dag_zwaarte_clean$filezwaarte < lower_bound |
dag_zwaarte_clean$filezwaarte > upper_bound
# Bereken het percentage buiten het bereik
percentage_outside <- sum(outside_bounds, na.rm = TRUE) / nrow(dag_zwaarte_clean) * 100
cat("Percentage buiten bereik:", percentage_outside, "%\n")
## Percentage buiten bereik: 53.53639 %
# Maak de boxplot met rode grenzen
ggplot(dag_zwaarte_clean, aes(y = filezwaarte)) +
geom_boxplot(fill = "skyblue", color = "darkblue", outlier.color = "red") +
geom_hline(yintercept = lower_bound, color = "red") + # Ondergrens
geom_hline(yintercept = upper_bound, color = "red") + # Bovengrens
labs(
title = "Boxplot van Dagelijkse Filezwaarte met Grenzen",
y = "Dagelijkse Filezwaarte (km·min)"
) +
theme_minimal()

### Bereken het percentage buiten de grens van 20000 ###
percentage_outside_remainder <- sum(decomp$remainder < -20000 | decomp$remainder > 20000) / nrow(decomp) * 100
cat("Percentage buiten bereik remainder:", percentage_outside_remainder, "%\n")
## Percentage buiten bereik remainder: 35.37531 %
### Maak de boxplot van de remainders (al eerder gedaan) ###
ggplot(decomp_clean, aes(y = remainder)) +
geom_boxplot(fill = "skyblue", color = "darkblue", outlier.color = "red") +
geom_hline(yintercept = -20000, color = "red") + # Ondergrens
geom_hline(yintercept = 20000, color = "red") + # Bovengrens
labs(
title = "Boxplot van Dagelijkse remainders met Grenzen",
y = "Dagelijkse remainders (km·min)"
) +
theme_minimal()

### algemene data opslpitsen in dagen aangezien daar waarschijnlijk een verschil tussen zit
# hier eerst een lijn diagram van maken ###
# Voeg kolommen toe voor weekdagen en jaar
dag_zwaarte_clean_dagen <- dag_zwaarte_clean %>%
mutate(
Weekdag = weekdays(BeginDatum),
Jaar = as.numeric(format(BeginDatum, "%Y"))
)
dag_zwaarte_clean_dagen$BeginDatum <- NULL
# Zet de weekdagen in de juiste volgorde
dag_zwaarte_clean_dagen$Weekdag <- factor(dag_zwaarte_clean_dagen$Weekdag,
levels = c("maandag", "dinsdag", "woensdag",
"donderdag", "vrijdag", "zaterdag", "zondag"))
# Bereken gemiddelde filezwaarte per jaar en weekdag
seasonal_summary <- dag_zwaarte_clean_dagen %>%
group_by(Jaar, Weekdag) %>%
summarise(mean_zwaarte = mean(filezwaarte), .groups = "drop")
# Maak de seizoensplot
ggplot(seasonal_summary, aes(x = Weekdag, y = mean_zwaarte, color = factor(Jaar), group = Jaar)) +
geom_line(linewidth = 1) +
scale_color_viridis_d() + # Mooie kleuren voor jaren
labs(
title = "Seizoensplot: Gemiddelde filezwaarte per weekdag",
x = "Weekdag",
y = "Gemiddelde filezwaarte (km-min)",
color = "Jaar"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

### Boxplot maken met grenzen van RWS per dag ###
# Bereken de stats per dag
stats_per_day <- dag_zwaarte_clean_dagen %>%
group_by(Weekdag) %>%
summarise(median_value = median(filezwaarte),
total_count = n(),
outside_count = sum(filezwaarte < (median(filezwaarte) - 20000) |
filezwaarte > (median(filezwaarte) + 20000))) %>%
mutate(lower_bound = median_value - 20000,
upper_bound = median_value + 20000,
percentage_outside = (outside_count / total_count) * 100)
# plot maken
ggplot(dag_zwaarte_clean_dagen, aes(x = Weekdag, y = filezwaarte)) +
geom_boxplot() +
geom_segment(data = stats_per_day,
aes(x = as.numeric(Weekdag) - 0.4, xend = as.numeric(Weekdag) + 0.4,
y = lower_bound, yend = lower_bound),
color = "red") +
geom_segment(data = stats_per_day,
aes(x = as.numeric(Weekdag) - 0.4, xend = as.numeric(Weekdag) + 0.4,
y = upper_bound, yend = upper_bound),
color = "red") +
labs(title = "Daily Traffic per Day of the Week",
x = "Day of the Week",
y = "Daily Traffic") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

### Percentage buiten de grens van RWS per dag
ggplot(stats_per_day, aes(x = Weekdag, y = percentage_outside)) +
geom_bar(stat = "identity", fill = "skyblue") +
labs(
title = "Percentage van data buiten mediaan ± 20.000 per dag",
x = "Dag van de week",
y = "Percentage buiten grenzen (%)"
) +
theme_minimal()

### Alles wat we hiervoor hebben gedaan voor de algemene data ook doen voor alleen de remainders ###
decomp_clean <- dag_zwaarte_clean %>%
model(STL(filezwaarte ~ season(period = 365), robust = TRUE)) %>%
components()
# Voeg kolommen toe voor weekdag en jaar
decomp_clean_dagen <- decomp_clean %>%
mutate(
Weekdag = weekdays(BeginDatum),
Jaar = as.numeric(format(BeginDatum, "%Y"))
)
decomp_clean_dagen <- decomp_clean_dagen %>%
select(-.model, -filezwaarte, -trend, -season_365, -season_adjust)
decomp_clean_dagen$BeginDatum <- NULL
# Zet de weekdagen in de juiste volgorde
decomp_clean_dagen$Weekdag <- factor(decomp_clean_dagen$Weekdag,
levels = c("maandag", "dinsdag", "woensdag",
"donderdag", "vrijdag", "zaterdag", "zondag"))
# Bereken gemiddelde filezwaarte per jaar en weekdag
seasonal_summary <- decomp_clean_dagen %>%
group_by(Jaar, Weekdag) %>%
summarise(mean_zwaarte = mean(remainder), .groups = "drop")
# Maak de seizoensplot
ggplot(seasonal_summary, aes(x = Weekdag, y = mean_zwaarte, color = factor(Jaar), group = Jaar)) +
geom_line(linewidth = 1) +
scale_color_viridis_d() + # Mooie kleuren voor jaren
labs(
title = "Seizoensplot: Gemiddelde remainders per weekdag",
x = "Weekdag",
y = "Gemiddelde remainders (km-min)",
color = "Jaar"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Bereken de stats per dag
stats_per_day <- decomp_clean_dagen %>%
group_by(Weekdag) %>%
summarise(total_count = n(),
outside_count = sum(remainder < -20000 | remainder > 20000)) %>%
mutate(percentage_outside = (outside_count / total_count) * 100)
ggplot(decomp_clean_dagen, aes(x = Weekdag, y = remainder)) +
geom_boxplot() +
geom_segment(data = stats_per_day,
aes(x = as.numeric(Weekdag) - 0.4, xend = as.numeric(Weekdag) + 0.4,
y = -20000, yend = -20000),
color = "red") +
geom_segment(data = stats_per_day,
aes(x = as.numeric(Weekdag) - 0.4, xend = as.numeric(Weekdag) + 0.4,
y = 20000, yend = 20000),
color = "red") +
labs(title = "Daily remainders per Day of the Week",
x = "Day of the Week",
y = "Daily remainders") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplot(stats_per_day, aes(x = Weekdag, y = percentage_outside)) +
geom_bar(stat = "identity", fill = "skyblue") +
labs(
title = "Percentage remainders ± 20.000 per dag",
x = "Dag van de week",
y = "Percentage buiten remainders (%)"
) +
theme_minimal()

# Maak trainings- en testset aan
train <- dag_zwaarte_clean %>% filter_index(. ~ "2022-12-31")
test <- dag_zwaarte_clean %>% filter_index("2023-01-01" ~ .)
# Maak meerdere trainingsets aan (sliding window van 3 jaar)
train_sets <- train %>% slide_tsibble(.size = 1095, .step = 365, .id = "id") # 3 jaar = 1095 dagen)
# fit de verschillende modellen en genereer forecasts; dit duurt ook wel even
forecasts <- train_sets %>%
model(
Drift = RW(filezwaarte ~ drift()),
Mean = MEAN(filezwaarte),
Naive = NAIVE(filezwaarte),
`Seasonal naive` = SNAIVE(filezwaarte),
ETS = ETS(filezwaarte),
ARIMA = ARIMA(filezwaarte)) %>%
forecast(h = 30)
# Voeg kolom toe met forecast horizon
forecasts <- forecasts %>%
group_by(.model, id) %>%
mutate(h = row_number()) %>%
ungroup()
# Voeg werkelijke waarden toe aan de voorspellingen
forecasts <- forecasts %>%
left_join(dag_zwaarte_clean, by = c("BeginDatum"))
# Bereken absolute fouten
forecasts <- forecasts %>%
mutate(error = .mean - filezwaarte.y) # Gebruik de werkelijke waarden (filezwaarte.y)
# rood als buiten de 20000 grens
forecasts <- forecasts %>%
group_by(.model, h) %>%
mutate(exceeded = ifelse(min(error, na.rm = TRUE) < -20000 | max(error, na.rm = TRUE) > 20000, TRUE, FALSE)) %>%
ungroup()
# tenslotte plotten we alles
forecasts %>%
ggplot(aes(color = exceeded, x = as.factor(h), y = error)) +
geom_boxplot() +
geom_hline(yintercept = 20000, linetype = "dashed", colour = "blue") +
geom_hline(yintercept = -20000, linetype = "dashed", colour = "blue") +
scale_color_manual(values = c("black", "red")) +
theme(legend.position = "none") +
labs(x = "Forecast horizon (in days)",
y = "Absolute error",
title = "Absolute errors depending on forecast horizon",
subtitle = "") +
facet_wrap(vars(.model))

# Bereken de wekelijkse filezwaarte, omdat we 1 week moeten voorspellen
# We transformeren de dagelijkse filezwaarte naar wekelijkse data door de totale filezwaarte per week te berekenen.
week_zwaarte <- filedata %>%
mutate(Week = floor_date(BeginDatum, unit = "week")) %>% # Rond de datum af naar het begin van de week
group_by(Week) %>%
summarize(filezwaarte = sum(zwaarteKmMin, na.rm = TRUE), .groups = "drop") %>% # Som filezwaarte per week
as_tsibble(index = Week) %>% # Zet de data om naar een tijdreeksformaat (tsibble)
fill_gaps(filezwaarte = NA) %>% # Vul ontbrekende weken in met NA
mutate(filezwaarte = na.approx(filezwaarte)) # Vervang de NA's met lineaire interpolatie
# Splits de wekelijkse data in training- en testsets
# We verdelen de data in een trainingsset (80%) en een testset (20%) om onze modellen te trainen en valideren.
n_total <- nrow(week_zwaarte) # Totaal aantal weken
n_train <- floor(0.8 * n_total) # Bereken de omvang van de trainingsset (80%)
file_train <- week_zwaarte[1:n_train, ] # Trainingsset
file_test <- week_zwaarte[(n_train + 1):n_total, ] # Testset
# Zet de data om naar tijdreeksformaat
# Dit is nodig voor het trainen van tijdreeksmodellen zoals ARIMA en ETS.
file_train_ts <- ts(file_train$filezwaarte, frequency = 52) # Tijdreeks met wekelijkse frequentie
file_test_ts <- ts(file_test$filezwaarte, frequency = 52) # Tijdreeks voor de testset
# Train een ARIMA-model
# We gebruiken een automatisch ARIMA-model omdat het flexibel is en goed presteert bij veel soorten tijdreeksen.
auto_model <- auto.arima(file_train_ts)
# Train een ETS-model
# We trainen een ETS-model met behulp van stlf() om seizoensvoorspellingen te genereren.
ets_stlf_model <- stlf(file_train_ts, method = "ets", h = length(file_test_ts))
# Voorspellingen maken voor de testperiode
# We maken voorspellingen voor zowel ARIMA als ETS en vergelijken de resultaten.
forecast_auto <- forecast(auto_model, h = length(file_test_ts)) # Voorspellingen met ARIMA
forecast_ets_stlf <- forecast(ets_stlf_model, h = length(file_test_ts)) # Voorspellingen met ETS
# RMSE berekenen
# RMSE (Root Mean Square Error) meet de nauwkeurigheid van de voorspellingen.
rmse_auto <- sqrt(mean((as.numeric(forecast_auto$mean) - as.numeric(file_test_ts))^2))
rmse_ets_stlf <- sqrt(mean((as.numeric(forecast_ets_stlf$mean) - as.numeric(file_test_ts))^2))
# Print RMSE-waarden en selecteer het beste model
cat("RMSE ARIMA:", rmse_auto, "\n") # RMSE van het ARIMA-model
## RMSE ARIMA: 143513.5
cat("RMSE ETS (stlf):", rmse_ets_stlf, "\n") # RMSE van het ETS-model
## RMSE ETS (stlf): 146666.5
# Kies het beste model op basis van de laagste RMSE
if (rmse_auto < rmse_ets_stlf) {
cat("ARIMA heeft een lagere RMSE dan ETS. Daarom kiezen we ARIMA voor de voorspellingen.\n")
best_model <- "ARIMA"
} else {
cat("ETS heeft een lagere RMSE dan ARIMA. Daarom kiezen we ETS voor de voorspellingen.\n")
best_model <- "ETS"
}
## ARIMA heeft een lagere RMSE dan ETS. Daarom kiezen we ARIMA voor de voorspellingen.
# Combineer voorspellingen en werkelijke waarden
# Om de prestaties van de modellen te visualiseren, combineren we de voorspellingen met de werkelijke waarden.
test_data <- data.frame(
Date = file_test$Week, # Datums van de testperiode
Actual = file_test$filezwaarte, # Werkelijke waarden
ARIMA = as.numeric(forecast_auto$mean), # Voorspellingen van ARIMA
ETS = as.numeric(forecast_ets_stlf$mean) # Voorspellingen van ETS
)
# Visualisatie van de voorspellingen versus de werkelijke waarden
# De lijnplot toont hoe goed de modellen de werkelijke waarden voorspellen.
ggplot(test_data, aes(x = Date)) +
geom_line(aes(y = Actual, color = "Actual"), linewidth = 1) + # Werkelijke waarden
geom_line(aes(y = ARIMA, color = "ARIMA"), linewidth = 1) + # Voorspellingen van ARIMA
geom_line(aes(y = ETS, color = "ETS"), linewidth = 1) + # Voorspellingen van ETS
labs(
title = "Voorspellingen versus werkelijke waarden",
y = "Filezwaarte",
x = "Datum",
color = "Legenda"
) +
theme_minimal()

# Analyse van residuen (alleen voor ARIMA)
# We analyseren de residuen van ARIMA om te controleren of er nog patronen of systematische afwijkingen zijn.
residuals_auto <- residuals(auto_model)
# ACF van de residuen
# Controleer op autocorrelatie in de residuen om de validiteit van het model te evalueren.
acf(residuals_auto, main = "ACF van residuen (ARIMA)")

# Bereken het gemiddelde van de residuen
# Een gemiddeld residu van ongeveer nul wijst op een goed fit model.
mean_residuals_auto <- mean(residuals_auto, na.rm = TRUE)
cat("Gemiddelde residuen (ARIMA):", mean_residuals_auto, "\n")
## Gemiddelde residuen (ARIMA): -741.5857
# Voorspelling maken voor 1 week vooruit met ARIMA
# Omdat ARIMA het beste model is, maken we een voorspelling voor 1 week vooruit.
forecast_auto_one_week <- forecast(auto_model, h = 1)
# Rond de voorspelling af op duizendtallen
# Dit maakt de voorspelling gemakkelijker te interpreteren.
forecasts_auto <- round(as.numeric(forecast_auto_one_week$mean) / 1000) * 1000
# Label de voorspelling met de datum van de volgende week
forecast_week_auto <- seq(from = max(file_test$Week) + 7, by = "week", length.out = 1)
names(forecasts_auto) <- format(forecast_week_auto, "%d %B %Y")
# Print de voorspelling
cat("Voorspelling voor 1 week vooruit (ARIMA):\n")
## Voorspelling voor 1 week vooruit (ARIMA):
print(forecasts_auto)
## 07 januari 2024
## 250000
# Weersdataset inlezen en voorbereiden
# De dataset met weergegevens wordt ingelezen en voorbereid om te combineren met de filezwaartegegevens.
weer <- read.csv(path_weer, sep = ";")
# Kolomnamen omvormen en data transformeren
# We zetten de datum om naar het juiste formaat en maken de dataset breder door metingen per locatie te pivoteren.
weersdata <- weer %>%
mutate(YYYYMMDD = as.Date(as.character(YYYYMMDD), format = "%Y%m%d")) %>% # Datum omzetten naar correct formaat
pivot_wider(names_from = meting, values_from = meetwaarde) %>% # Metingen per kolom groeperen
group_by(YYYYMMDD) %>%
filter(!(locatie == "De Bilt" & as.Date(YYYYMMDD) < as.Date("1906-01-01"))) %>% # Data vóór 1906 voor De Bilt verwijderen voor consistentie
ungroup()
# Verwijderen van kolommen met teveel missende waarden
# Kolommen met meer dan 40% missende waarden worden verwijderd om de dataset schoon en bruikbaar te houden.
na_percentage <- colMeans(is.na(weersdata)) * 100
kolommen_te_verwijderen <- names(weersdata)[na_percentage >= 40]
weersdata <- weersdata[, na_percentage < 40]
# Invullen van missende waarden
# Overgebleven missende waarden worden opgevuld met het gemiddelde van de betreffende kolom.
weersdata <- weersdata %>%
mutate(across(where(is.numeric), ~ ifelse(is.na(.), mean(., na.rm = TRUE), .)))
# Bereken gemiddelde per dag (indien meerdere locaties per dag)
# Om consistente dagwaarden te krijgen, berekenen we het gemiddelde van numerieke kolommen per dag.
weersdata <- weersdata %>%
group_by(YYYYMMDD) %>%
summarize(across(where(is.numeric), mean, na.rm = TRUE)) %>%
ungroup()
## Warning: There was 1 warning in `summarize()`.
## ℹ In argument: `across(where(is.numeric), mean, na.rm = TRUE)`.
## ℹ In group 1: `YYYYMMDD = 1906-01-01`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
##
## # Previously
## across(a:b, mean, na.rm = TRUE)
##
## # Now
## across(a:b, \(x) mean(x, na.rm = TRUE))
# Filter weersdata voor overlappende periode
# We beperken de data tot het bereik van 2000-01-01 tot 2023-12-31 om consistent te blijven met de filezwaartegegevens.
weersdata <- weersdata %>%
filter(YYYYMMDD >= as.Date("2000-01-01") & YYYYMMDD <= as.Date("2023-12-31"))
# Maak van de filezwaartegegevens een tijdreeks
# Dit is nodig om later te combineren met de weersgegevens.
dag_zwaarte <- dag_zwaarte %>%
as_tsibble(index = BeginDatum)
# Combineer de weersgegevens met de filezwaartegegevens
# Door de datasets te koppelen op de datumkolom, voegen we weersgegevens toe aan de filezwaartedata.
combined_data <- dag_zwaarte %>%
left_join(weersdata, by = c("BeginDatum" = "YYYYMMDD"))
# Bereken correlaties met filezwaarte
# We onderzoeken welke weergerelateerde variabelen een relatie hebben met de filezwaarte.
cor_data <- combined_data %>%
as_tibble() %>%
select(-BeginDatum, filezwaarte, FG, TG, SQ, DR, RH, NG, UG) # Selecteer relevante variabelen
# We berekenen de correlatie van elke variabele met filezwaarte.
cor_values <- cor(cor_data, use = "complete.obs")["filezwaarte", ]
# Selecteer variabelen met significante correlatie
# We kiezen variabelen met een absolute correlatie > 0.08, gebaseerd op onze drempel voor relevantie.
relevante_variabelen <- names(cor_values[abs(cor_values) > 0.08])
# Visualisatie van de relatie tussen filezwaarte en neerslagduur (DR)
# We gebruiken scatterplots om de verbanden tussen weerfactoren en filezwaarte te illustreren.
combined_data %>%
ggplot(aes(x = DR, y = filezwaarte)) +
geom_point(alpha = 0.5) + # Puntenplot met transparantie
geom_smooth(method = "lm", se = FALSE, color = "blue") + # Lineaire trendlijn toevoegen
labs(
title = "Relatie tussen DR en filezwaarte",
x = "Duur neerslag in 0,1 uur",
y = "Filezwaarte (km∙min)"
)
## `geom_smooth()` using formula = 'y ~ x'

# Visualisatie van de relatie tussen filezwaarte en neerslaghoeveelheid (RH)
combined_data %>%
ggplot(aes(x = RH, y = filezwaarte)) +
geom_point(alpha = 0.5) +
geom_smooth(method = "lm", se = FALSE, color = "blue") +
labs(
title = "Relatie tussen neerslag (RH) en filezwaarte",
x = "Neerslag (0.1 mm)",
y = "Filezwaarte (km∙min)"
)
## `geom_smooth()` using formula = 'y ~ x'

# Lineaire regressie met TSLM
# We voeren een regressieanalyse uit waarbij filezwaarte de afhankelijke variabele is en weerfactoren de voorspellers.
combined_data %>%
model(TSLM(filezwaarte ~ DR + RH + RHX + PG + PN + UG)) %>% # TSLM met geselecteerde variabelen
report() # Samenvatting van het model
## Series: filezwaarte
## Model: TSLM
##
## Residuals:
## Min 1Q Median 3Q Max
## -68600 -24396 -4986 18481 323153
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 92512.868 37134.063 2.491 0.01275 *
## DR 57.095 27.592 2.069 0.03855 *
## RH 142.458 30.449 4.679 2.93e-06 ***
## RHX -270.259 58.711 -4.603 4.22e-06 ***
## PG -8.833 14.570 -0.606 0.54438
## PN 1.682 13.675 0.123 0.90213
## UG 124.502 39.557 3.147 0.00165 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 28530 on 8759 degrees of freedom
## Multiple R-squared: 0.02561, Adjusted R-squared: 0.02495
## F-statistic: 38.38 on 6 and 8759 DF, p-value: < 2.22e-16
# Combineer filedata met weersgegevens
model_data <- dag_zwaarte %>%
left_join(weersdata, by = c("BeginDatum" = "YYYYMMDD"))
# De dataset met filezwaarte wordt gecombineerd met weersdata op basis van de datum.
# Dit is nodig om weersinvloeden op de filezwaarte te kunnen analyseren.
# Splits de data in training- en testsets (80% voor training)
n_total <- nrow(model_data)
n_train <- floor(0.8 * n_total) # 80% van de data wordt gebruikt voor het trainen van het model.
train <- model_data[1:n_train, ]
test <- model_data[(n_train + 1):n_total, ]
# De training- en testdata worden gescheiden, zodat het model eerst kan leren en daarna wordt geëvalueerd.
# Zet training- en testsets om naar tijdreeksen (tsibble-formaat)
train <- train %>%
as_tsibble(index = BeginDatum)
test <- test %>%
as_tsibble(index = BeginDatum)
# Het omzetten naar tsibble-formaat is nodig voor tijdreeksanalyse in R.
# SARIMA-model met regressoren trainen
sarima_model <- train %>%
model(
SARIMA = ARIMA(filezwaarte ~ RH + RHX + UG) # ARIMA-model met relevante variabelen
)
# Hier gebruiken we het SARIMA-model om filezwaarte te voorspellen, rekening houdend met significante weersvariabelen
# (RH: dagelijkse regen, RHX: maximale regen in een uur, UG: gemiddelde windsnelheid).
# Dynamische regressie (TSLM) model trainen
tslm_model <- train %>%
model(
TSLM = TSLM(filezwaarte ~ RH + RHX + UG) # Lineair model met dezelfde variabelen als SARIMA
)
# TSLM-model wordt gebruikt als vergelijkingsmodel. Dit model werkt lineair en is minder complex dan SARIMA.
# Voorspellingen maken met SARIMA
sarima_forecast <- sarima_model %>%
forecast(new_data = test %>% select(BeginDatum, RH, RHX, UG, filezwaarte))
# SARIMA voorspelt de filezwaarte voor de testset, gebaseerd op de geselecteerde weersvariabelen.
# Bereken RMSE (Root Mean Square Error) voor SARIMA
sarima_accuracy <- accuracy(sarima_forecast, test)
cat("SARIMA RMSE:", sarima_accuracy$RMSE, "\n")
## SARIMA RMSE: 29860.17
# De RMSE voor SARIMA wordt berekend om de nauwkeurigheid van het model te evalueren. Hoe lager de RMSE, hoe beter het model.
# Voorspellingen maken met TSLM
tslm_forecast <- tslm_model %>%
forecast(new_data = test %>% select(BeginDatum, RH, RHX, UG, filezwaarte))
# TSLM voorspelt de filezwaarte voor dezelfde testset.
# Bereken RMSE voor TSLM
tslm_accuracy <- accuracy(tslm_forecast, test)
cat("Dynamic TSLM RMSE:", tslm_accuracy$RMSE, "\n")
## Dynamic TSLM RMSE: 32942.95
# De RMSE voor TSLM wordt berekend om te vergelijken met SARIMA.
# Vergelijk de RMSE-waarden
rmse_results <- tibble(
Model = c("SARIMA met regressoren", "TSLM met regressoren"),
RMSE = c(sarima_accuracy$RMSE, tslm_accuracy$RMSE)
)
print(rmse_results)
## # A tibble: 2 × 2
## Model RMSE
## <chr> <dbl>
## 1 SARIMA met regressoren 29860.
## 2 TSLM met regressoren 32943.
# De RMSE-waarden van beide modellen worden in een tabel gezet, zodat we eenvoudig kunnen zien welk model beter presteert.
# Selecteer het beste model op basis van de laagste RMSE
best_model <- rmse_results %>%
filter(RMSE == min(RMSE)) %>%
pull(Model)
cat("Het beste model is:", best_model, "\n")
## Het beste model is: SARIMA met regressoren
# Het model met de laagste RMSE wordt als het beste model geselecteerd. Dit wordt ook vermeld in de uitvoer.
# Voorspellingen maken met het beste model
if (best_model == "SARIMA met regressoren") {
best_forecast <- sarima_model %>%
forecast(new_data = test %>% select(BeginDatum, RH, RHX, UG, filezwaarte))
} else {
best_forecast <- tslm_model %>%
forecast(new_data = test %>% select(BeginDatum, RH, RHX, UG, filezwaarte))
}
# Het geselecteerde model (SARIMA of TSLM) wordt gebruikt om voorspellingen te maken op basis van de testdata.
# Beste model uit opgave 3 blijkt ARIMA zonder regressoren
# Splits de data in trainings- en testset
train <- dag_zwaarte %>%
filter(BeginDatum < as.Date("2020-03-01")) # Data vóór maart 2020
test <- dag_zwaarte %>%
filter(BeginDatum >= as.Date("2020-03-01") & BeginDatum <= as.Date("2020-03-31")) # Data van maart 2020
rest <- dag_zwaarte %>%
filter(BeginDatum >= as.Date("2020-04-01")) # Data na maart 2020
# Zet data om naar tsibble-formaat
train <- train %>% as_tsibble(index = BeginDatum)
test <- test %>% as_tsibble(index = BeginDatum)
# Pas ARIMA en ETS modellen toe
fit_ARIMA <- train %>%
model(ARIMA = ARIMA(filezwaarte))
# Genereer voorspellingen voor testset
forecasts <- fit_ARIMA %>% forecast(h = nrow(test))
# Bereken nauwkeurigheid voor beide modellen
accuracy_results <- forecasts %>%
accuracy(test) %>%
select(.model, RMSE, MAPE)
print(accuracy_results)
## # A tibble: 1 × 3
## .model RMSE MAPE
## <chr> <dbl> <dbl>
## 1 ARIMA 27978. 2020.
# Plot alleen de gefilterde data
forecasts %>%
autoplot(train, level = 95) +
scale_x_date(limits = c(as.Date("2019-10-01"), as.Date("2020-03-31"))) +
geom_line(data = test, aes(x = BeginDatum, y = filezwaarte)) + # Voeg de testlijn toe
#geom_line(data = rest, aes(x = BeginDatum, y = filezwaarte)) + # Voeg de testlijn toe
labs(
title = "Voorspelling van dagelijkse filezwaarte in maart 2020",
x = "Datum",
y = "Filezwaarte (km·min)",
caption = "Inclusief 95%-voorspellingsinterval"
) +
theme_minimal()
## Warning: Removed 7213 rows containing missing values or values outside the scale range
## (`geom_line()`).

# Voeg een kolom toe met de dag van de maand
train_march <- train %>%
filter(month(BeginDatum) == 3 & year(BeginDatum) < 2020 & year(BeginDatum) > 2015) %>%
mutate(Day = day(BeginDatum)) # Dag van de maand
forecast_march <- forecasts %>%
filter(lubridate::month(BeginDatum) == 3) %>%
mutate(Day = day(BeginDatum)) # Dag van de maand
forecast_march_95 <- forecast_march %>%
mutate(
.lower = .mean - 1.96 * sd(test$filezwaarte),
.upper = .mean + 1.96 * sd(test$filezwaarte)
)
# Plot de data
ggplot() +
geom_line(data = train_march, aes(x = Day, y = filezwaarte, color = as.factor(year(BeginDatum))), alpha = 1) + # Trainingsdata
geom_ribbon(data = forecast_march_95, aes(x = Day, ymin = .lower, ymax = .upper), fill = "blue", alpha = 0.1) +
geom_line(data = forecast_march_95, aes(x = Day, y = .mean)) + # Voorspellingen
labs(
title = "Vergelijking van dagelijkse filezwaarte in maart (2000-2012)",
x = "Dag van maart",
y = "Filezwaarte (km·min)",
color = "Jaar",
caption = "Voorspelde maart 2020 weergegeven in rood"
) +
theme_minimal() +
scale_x_continuous(breaks = 1:31) # Zorg dat de x-as alleen 1 t/m 31 toont
