This project aims to understand and predict daily bank customer visits (CUST) using:
# # Load packages
library(tidyverse) # # data wrangling + ggplot2
library(readxl) # # read Excel
library(lubridate) # # date functions (month(), wday(), etc.)
library(janitor) # # clean_names()
library(broom) # # tidy model outputs
library(knitr) # # kable tables
library(kableExtra) # # nicer tables
library(see)
# =========================
# # 1A) LOAD DATA
# =========================
# # Read Excel file (make sure it's in the same folder as this .Rmd)
raw <- read_excel("MiM811Quiz4MiniProj1Data.xlsx")
# # Clean column names (removes weird spaces like " CUST")
data <- raw %>%
janitor::clean_names()
# # Quick look
glimpse(data)## Rows: 254
## Columns: 13
## $ cust <dbl> 1825, 1257, 969, 1672, 1098, 691, 672, 754, 972, 816, 717, 72…
## $ daycat <chr> "Tuesday", "Wednesday", "Thursday", "Friday", "Monday", "Tues…
## $ date <dttm> 2007-01-02, 2007-01-03, 2007-01-04, 2007-01-05, 2007-01-08, …
## $ month <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ daymon <dbl> 2, 3, 4, 5, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 22, 23, 24,…
## $ dayweek <dbl> 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2…
## $ special <chr> "SP,FAC,AH", "0", "0", "SP", "0", "0", "0", "0", "0", "0", "0…
## $ payday <dbl> 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0…
## $ sp <dbl> 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0…
## $ fac <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ holidays <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ bh <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ ah <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
# =========================
# # 1B) FIX TYPES + CREATE TIME INDEX
# =========================
data <- data %>%
mutate(
# # Convert date to Date class (even if it already looks like a date)
date = as.Date(date),
# # Time trend index: 1, 2, 3, ..., n
time_index = row_number(),
# # DAYCAT as ordered factor (important for weekday comparisons)
daycat = factor(
daycat,
levels = c("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"),
ordered = TRUE
),
# # Month as a factor (optional but useful)
month_f = factor(month, levels = 1:12, labels = month.name)
)
# # Review structure + summary (required “review structure” idea)
str(data)## tibble [254 × 15] (S3: tbl_df/tbl/data.frame)
## $ cust : num [1:254] 1825 1257 969 1672 1098 ...
## $ daycat : Ord.factor w/ 7 levels "Monday"<"Tuesday"<..: 2 3 4 5 1 2 3 4 5 1 ...
## $ date : Date[1:254], format: "2007-01-02" "2007-01-03" ...
## $ month : num [1:254] 1 1 1 1 1 1 1 1 1 1 ...
## $ daymon : num [1:254] 2 3 4 5 8 9 10 11 12 15 ...
## $ dayweek : num [1:254] 2 3 4 5 1 2 3 4 5 1 ...
## $ special : chr [1:254] "SP,FAC,AH" "0" "0" "SP" ...
## $ payday : num [1:254] 1 0 0 1 0 0 0 0 0 0 ...
## $ sp : num [1:254] 1 0 0 1 0 0 0 0 0 0 ...
## $ fac : num [1:254] 1 0 0 0 0 0 0 0 0 0 ...
## $ holidays : num [1:254] 1 0 0 0 0 0 0 0 0 0 ...
## $ bh : num [1:254] 0 0 0 0 0 0 0 0 0 0 ...
## $ ah : num [1:254] 1 0 0 0 0 0 0 0 0 0 ...
## $ time_index: int [1:254] 1 2 3 4 5 6 7 8 9 10 ...
## $ month_f : Factor w/ 12 levels "January","February",..: 1 1 1 1 1 1 1 1 1 1 ...
## cust daycat date month
## Min. : 404.0 Monday :50 Min. :2007-01-02 Min. : 1.000
## 1st Qu.: 785.8 Tuesday :51 1st Qu.:2007-03-30 1st Qu.: 3.250
## Median : 930.5 Wednesday:50 Median :2007-06-30 Median : 6.500
## Mean :1037.5 Thursday :51 Mean :2007-06-30 Mean : 6.476
## 3rd Qu.:1183.5 Friday :52 3rd Qu.:2007-09-30 3rd Qu.: 9.750
## Max. :2068.0 Saturday : 0 Max. :2007-12-31 Max. :12.000
## Sunday : 0
## daymon dayweek special payday
## Min. : 1.00 Min. :1.000 Length:254 Min. :0.000
## 1st Qu.: 8.00 1st Qu.:2.000 Class :character 1st Qu.:0.000
## Median :16.00 Median :3.000 Mode :character Median :0.000
## Mean :15.83 Mean :3.016 Mean :0.122
## 3rd Qu.:23.00 3rd Qu.:4.000 3rd Qu.:0.000
## Max. :31.00 Max. :5.000 Max. :1.000
##
## sp fac holidays bh
## Min. :0.00000 Min. :0.00000 Min. :0.00000 Min. :0.00000
## 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:0.00000
## Median :0.00000 Median :0.00000 Median :0.00000 Median :0.00000
## Mean :0.09843 Mean :0.03937 Mean :0.04724 Mean :0.01969
## 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:0.00000
## Max. :1.00000 Max. :1.00000 Max. :1.00000 Max. :1.00000
##
## ah time_index month_f
## Min. :0.00000 Min. : 1.00 August : 23
## 1st Qu.:0.00000 1st Qu.: 64.25 October: 23
## Median :0.00000 Median :127.50 January: 22
## Mean :0.02756 Mean :127.50 March : 22
## 3rd Qu.:0.00000 3rd Qu.:190.75 May : 22
## Max. :1.00000 Max. :254.00 April : 21
## (Other):121
# # 1C) UNDERSTAND EVENT FLAGS
# =========================
# # These event columns are your “special events” predictors.
# # Based on names, they typically mean:
# # payday = payday indicator (1/0)
# # sp = staff payday (1/0) (guess from abbreviation)
# # fac = faculty payday (1/0)
# # holidays = holiday day (1/0)
# # bh = before-holiday (1/0)
# # ah = after-holiday (1/0)
# # If your class notes define them differently, use the official meaning.
# # Count how many days are flagged as 1 (quick sanity check)
data %>%
summarise(across(c(payday, sp, fac, holidays, bh, ah), ~sum(.x, na.rm = TRUE))) %>%
kable(caption = "How many days are flagged for each event?") %>%
kableExtra::kable_styling(full_width = FALSE)| payday | sp | fac | holidays | bh | ah |
|---|---|---|---|---|---|
| 31 | 25 | 10 | 12 | 5 | 7 |
# =========================================
# # 2.3) OPTIONAL EXTRA EVENTS
# =========================================
data <- data %>%
mutate(
year = year(date),
# # New Year (Jan 1)
is_new_year = if_else(month(date) == 1 & day(date) == 1, 1, 0),
# # Christmas (Dec 25)
is_christmas = if_else(month(date) == 12 & day(date) == 25, 1, 0)
)
# # Black Friday approximation:
# # US definition: 4th Friday of November.
black_fridays <- data %>%
filter(month(date) == 11, wday(date, label = TRUE) == "Fri") %>%
group_by(year) %>%
arrange(date) %>%
mutate(friday_number = row_number()) %>%
filter(friday_number == 4) %>%
pull(date)
data <- data %>%
mutate(is_black_friday = if_else(date %in% black_fridays, 1, 0))
data %>%
filter(is_new_year == 1 | is_christmas == 1 | is_black_friday == 1) %>%
select(date, is_new_year, is_christmas, is_black_friday) %>%
arrange(date) %>%
kable(caption = "Detected dates for optional extra events")| date | is_new_year | is_christmas | is_black_friday |
|---|---|---|---|
| 2007-11-23 | 0 | 0 | 1 |
ggplot(data, aes(x = date, y = cust)) +
geom_line() +
labs(
title = "Daily Customer Visits Over Time",
x = "Date",
y = "CUST (Visits)"
)ggplot(data, aes(x = daycat, y = cust)) +
geom_boxplot() +
labs(
title = "Customer Visits by Day of Week (DAYCAT)",
x = "Day of Week",
y = "CUST (Visits)"
)plot_data <- data %>%
mutate(
payday_label = factor(payday, levels = c(0,1), labels = c("Not Payday","Payday")),
holiday_label = factor(holidays, levels = c(0,1), labels = c("Not Holiday","Holiday"))
)
ggplot(plot_data, aes(x = date, y = cust, color = payday_label)) +
geom_line() +
facet_wrap(~ holiday_label, ncol = 1) +
labs(
title = "Daily Visits — Colored by Payday, Faceted by Holiday",
x = "Date",
y = "CUST (Visits)",
color = "Payday?"
)event_dates <- data %>%
filter(is_new_year == 1 | is_christmas == 1 | is_black_friday == 1) %>%
select(date) %>%
distinct()
ggplot(data, aes(x = date, y = cust)) +
geom_line() +
geom_vline(
data = event_dates,
aes(xintercept = as.numeric(date)),
linetype = "dashed"
) +
labs(
title = "Daily Visits with Special Event Markers (dashed lines)",
x = "Date",
y = "CUST (Visits)"
)##
## Call:
## lm(formula = cust ~ time_index + daycat, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -572.91 -144.51 -35.06 92.18 962.35
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.024e+03 2.924e+01 35.008 < 2e-16 ***
## time_index 8.908e-02 1.988e-01 0.448 0.654551
## daycat.L 2.605e+02 3.253e+01 8.009 4.51e-14 ***
## daycat.Q 4.747e+02 3.263e+01 14.551 < 2e-16 ***
## daycat.C 1.205e+02 3.252e+01 3.706 0.000259 ***
## daycat^4 5.887e+01 3.269e+01 1.801 0.072949 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 232.3 on 248 degrees of freedom
## Multiple R-squared: 0.5456, Adjusted R-squared: 0.5365
## F-statistic: 59.56 on 5 and 248 DF, p-value: < 2.2e-16
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 1023.7204995 | 29.2426951 | 35.0077343 | 0.0000000 |
| time_index | 0.0890758 | 0.1988343 | 0.4479902 | 0.6545514 |
| daycat.L | 260.4968532 | 32.5268084 | 8.0086816 | 0.0000000 |
| daycat.Q | 474.7217560 | 32.6256270 | 14.5505788 | 0.0000000 |
| daycat.C | 120.5438976 | 32.5248257 | 3.7062120 | 0.0002595 |
| daycat^4 | 58.8745043 | 32.6934556 | 1.8008040 | 0.0729487 |
m2 <- lm(cust ~ time_index + daycat + payday + holidays + sp + fac + bh + ah, data = data)
summary(m2)##
## Call:
## lm(formula = cust ~ time_index + daycat + payday + holidays +
## sp + fac + bh + ah, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -484.93 -123.98 -12.84 93.32 807.98
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 956.34903 23.46298 40.760 < 2e-16 ***
## time_index 0.06242 0.15627 0.399 0.689914
## daycat.L 127.06006 28.86490 4.402 1.61e-05 ***
## daycat.Q 363.95776 27.94200 13.025 < 2e-16 ***
## daycat.C 47.74734 26.40794 1.808 0.071832 .
## daycat^4 57.95678 25.57645 2.266 0.024330 *
## payday 443.14322 128.13516 3.458 0.000641 ***
## holidays 302.35215 70.83869 4.268 2.83e-05 ***
## sp 0.97173 122.05212 0.008 0.993654
## fac 82.61443 103.05425 0.802 0.423533
## bh 6.55590 105.86856 0.062 0.950674
## ah NA NA NA NA
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 179.7 on 243 degrees of freedom
## Multiple R-squared: 0.7335, Adjusted R-squared: 0.7226
## F-statistic: 66.9 on 10 and 243 DF, p-value: < 2.2e-16
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 956.3490313 | 23.4629831 | 40.7599080 | 0.0000000 |
| time_index | 0.0624229 | 0.1562731 | 0.3994474 | 0.6899144 |
| daycat.L | 127.0600608 | 28.8649022 | 4.4018878 | 0.0000161 |
| daycat.Q | 363.9577594 | 27.9419969 | 13.0254742 | 0.0000000 |
| daycat.C | 47.7473440 | 26.4079355 | 1.8080680 | 0.0718322 |
| daycat^4 | 57.9567812 | 25.5764467 | 2.2660216 | 0.0243304 |
| payday | 443.1432183 | 128.1351649 | 3.4584044 | 0.0006415 |
| holidays | 302.3521510 | 70.8386880 | 4.2681783 | 0.0000283 |
| sp | 0.9717298 | 122.0521181 | 0.0079616 | 0.9936542 |
| fac | 82.6144252 | 103.0542468 | 0.8016596 | 0.4235331 |
| bh | 6.5558990 | 105.8685622 | 0.0619249 | 0.9506735 |
| ah | NA | NA | NA | NA |
m3 <- lm(
cust ~ time_index + month_f + daycat * payday + holidays * fac + sp + bh + ah,
data = data
)
summary(m3)##
## Call:
## lm(formula = cust ~ time_index + month_f + daycat * payday +
## holidays * fac + sp + bh + ah, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -405.80 -89.02 -15.41 68.44 546.85
##
## Coefficients: (2 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1003.131 37.660 26.636 < 2e-16 ***
## time_index -12.032 1.596 -7.538 1.12e-12 ***
## month_fFebruary 277.368 57.165 4.852 2.26e-06 ***
## month_fMarch 576.918 80.713 7.148 1.18e-11 ***
## month_fApril 867.141 110.434 7.852 1.59e-13 ***
## month_fMay 1213.237 142.457 8.517 2.24e-15 ***
## month_fJune 1511.428 174.634 8.655 9.03e-16 ***
## month_fJuly 1734.265 206.059 8.416 4.31e-15 ***
## month_fAugust 1925.714 239.655 8.035 5.00e-14 ***
## month_fSeptember 2051.795 273.620 7.499 1.42e-12 ***
## month_fOctober 2339.013 307.425 7.608 7.25e-13 ***
## month_fNovember 2604.374 341.224 7.632 6.25e-13 ***
## month_fDecember 2940.767 373.585 7.872 1.41e-13 ***
## daycat.L 140.937 24.401 5.776 2.49e-08 ***
## daycat.Q 362.260 23.462 15.440 < 2e-16 ***
## daycat.C 47.703 22.134 2.155 0.03220 *
## daycat^4 68.904 21.205 3.249 0.00133 **
## payday 375.837 130.811 2.873 0.00445 **
## holidays 297.878 61.687 4.829 2.52e-06 ***
## fac 115.894 111.898 1.036 0.30143
## sp 56.356 170.155 0.331 0.74080
## bh 15.484 91.287 0.170 0.86546
## ah NA NA NA NA
## daycat.L:payday -26.654 133.276 -0.200 0.84166
## daycat.Q:payday -19.789 150.232 -0.132 0.89532
## daycat.C:payday 77.822 129.226 0.602 0.54763
## daycat^4:payday NA NA NA NA
## holidays:fac -34.927 180.755 -0.193 0.84695
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 147.6 on 228 degrees of freedom
## Multiple R-squared: 0.8313, Adjusted R-squared: 0.8128
## F-statistic: 44.93 on 25 and 228 DF, p-value: < 2.2e-16
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 1003.13099 | 37.660147 | 26.6364063 | 0.0000000 |
| time_index | -12.03244 | 1.596212 | -7.5381213 | 0.0000000 |
| month_fFebruary | 277.36798 | 57.164786 | 4.8520777 | 0.0000023 |
| month_fMarch | 576.91819 | 80.712549 | 7.1478128 | 0.0000000 |
| month_fApril | 867.14071 | 110.433875 | 7.8521261 | 0.0000000 |
| month_fMay | 1213.23683 | 142.456517 | 8.5165414 | 0.0000000 |
| month_fJune | 1511.42795 | 174.634334 | 8.6548156 | 0.0000000 |
| month_fJuly | 1734.26505 | 206.058605 | 8.4163680 | 0.0000000 |
| month_fAugust | 1925.71396 | 239.654723 | 8.0353683 | 0.0000000 |
| month_fSeptember | 2051.79494 | 273.619898 | 7.4987051 | 0.0000000 |
| month_fOctober | 2339.01257 | 307.425347 | 7.6083921 | 0.0000000 |
| month_fNovember | 2604.37428 | 341.223612 | 7.6324562 | 0.0000000 |
| month_fDecember | 2940.76705 | 373.585047 | 7.8717472 | 0.0000000 |
| daycat.L | 140.93724 | 24.401140 | 5.7758464 | 0.0000000 |
| daycat.Q | 362.26042 | 23.462429 | 15.4400223 | 0.0000000 |
| daycat.C | 47.70280 | 22.133998 | 2.1551824 | 0.0321951 |
| daycat^4 | 68.90354 | 21.205033 | 3.2493958 | 0.0013311 |
| payday | 375.83676 | 130.811376 | 2.8731199 | 0.0044481 |
| holidays | 297.87763 | 61.687237 | 4.8288373 | 0.0000025 |
| fac | 115.89402 | 111.898080 | 1.0357105 | 0.3014344 |
| sp | 56.35569 | 170.155324 | 0.3312015 | 0.7407965 |
| bh | 15.48419 | 91.286798 | 0.1696213 | 0.8654583 |
| ah | NA | NA | NA | NA |
| daycat.L:payday | -26.65448 | 133.276496 | -0.1999938 | 0.8416637 |
| daycat.Q:payday | -19.78862 | 150.232377 | -0.1317201 | 0.8953219 |
| daycat.C:payday | 77.82195 | 129.226210 | 0.6022149 | 0.5476295 |
| daycat^4:payday | NA | NA | NA | NA |
| holidays:fac | -34.92723 | 180.754683 | -0.1932300 | 0.8469509 |
model_compare <- tibble(
model = c("m1: trend + weekday", "m2: + events", "m3: + month + interactions"),
r2 = c(summary(m1)$r.squared, summary(m2)$r.squared, summary(m3)$r.squared),
adj_r2 = c(summary(m1)$adj.r.squared, summary(m2)$adj.r.squared, summary(m3)$adj.r.squared)
)
kable(model_compare, caption = "Model comparison (R² / Adjusted R²)")| model | r2 | adj_r2 |
|---|---|---|
| m1: trend + weekday | 0.5456273 | 0.5364666 |
| m2: + events | 0.7335429 | 0.7225776 |
| m3: + month + interactions | 0.8312662 | 0.8127647 |
data <- data %>%
arrange(date) %>%
mutate(
payday_tomorrow = lead(payday, 1, default = 0), # # 1 means: tomorrow is payday
payday_yesterday = lag(payday, 1, default = 0) # # 1 means: yesterday was payday
)
m_lag <- lm(cust ~ time_index + daycat + payday + payday_tomorrow + payday_yesterday + holidays, data = data)
summary(m_lag)##
## Call:
## lm(formula = cust ~ time_index + daycat + payday + payday_tomorrow +
## payday_yesterday + holidays, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -528.89 -103.69 -22.70 85.12 823.50
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 930.01802 23.74766 39.163 < 2e-16 ***
## time_index 0.08548 0.15035 0.569 0.5702
## daycat.L 163.54276 30.14359 5.425 1.39e-07 ***
## daycat.Q 323.63235 28.78388 11.244 < 2e-16 ***
## daycat.C 72.68755 28.66353 2.536 0.0118 *
## daycat^4 48.85463 25.86246 1.889 0.0601 .
## payday 468.13677 40.34813 11.602 < 2e-16 ***
## payday_tomorrow 32.36819 41.59557 0.778 0.4372
## payday_yesterday 161.41237 39.85161 4.050 6.88e-05 ***
## holidays 304.67794 52.12808 5.845 1.62e-08 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 173.8 on 244 degrees of freedom
## Multiple R-squared: 0.7497, Adjusted R-squared: 0.7405
## F-statistic: 81.21 on 9 and 244 DF, p-value: < 2.2e-16
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 930.0180150 | 23.7476625 | 39.1625077 | 0.0000000 |
| time_index | 0.0854849 | 0.1503468 | 0.5685846 | 0.5701610 |
| daycat.L | 163.5427613 | 30.1435912 | 5.4254571 | 0.0000001 |
| daycat.Q | 323.6323492 | 28.7838825 | 11.2435266 | 0.0000000 |
| daycat.C | 72.6875457 | 28.6635301 | 2.5358895 | 0.0118412 |
| daycat^4 | 48.8546260 | 25.8624555 | 1.8890173 | 0.0600754 |
| payday | 468.1367725 | 40.3481276 | 11.6024411 | 0.0000000 |
| payday_tomorrow | 32.3681899 | 41.5955650 | 0.7781644 | 0.4372260 |
| payday_yesterday | 161.4123691 | 39.8516054 | 4.0503354 | 0.0000688 |
| holidays | 304.6779437 | 52.1280786 | 5.8447952 | 0.0000000 |
“Explain how to interpret an interaction term in R regression (DAYCAT * Payday) in simple words.”
What I learned:
Interaction means the payday effect changes depending on weekday.
For example, a term like daycatFriday:payday means “extra payday impact on Fridays.”
“Write ggplot code to plot daily visits over time, colored by Payday, and faceted by Holidays.”
What worked:
How my prompt improved:
This study examined daily customer visits to a bank branch using event-based regression models that incorporate a time trend, weekday effects, and special events such as paydays and holidays.
Model 2 already explains a substantial portion of the variation in customer visits (Adjusted R² ≈ 0.723), indicating that weekday patterns and event flags provide strong predictive value. When monthly seasonality and interaction terms are added in Model 3, model fit improves further (Adjusted R² ≈ 0.813). This shows that incorporating seasonality significantly strengthens the model’s ability to explain daily visit behavior.
In Model 2, the time trend (time_index) is not statistically significant, suggesting there is no simple linear increase/decrease over time once weekday and event effects are controlled. However, after controlling for seasonality in Model 3, the time trend becomes negative and highly significant (time_index ≈ -12.0, p < 0.001), implying a modest underlying downward trend in visits within the seasonal pattern.
Weekday effects are strongly significant in both models, confirming that customer visits follow a systematic weekly rhythm rather than being evenly distributed across days. This indicates that operational planning should consider recurring weekday traffic differences.
Event variables provide clear managerial insights. Paydays have a large and statistically significant positive effect on visits in both models (about +443 in Model 2; about +376 in Model 3), meaning branches experience substantially higher traffic on payday periods. Holidays are also statistically significant (about +302 in Model 2; about +298 in Model 3), showing that customer visits differ meaningfully on holidays compared to normal days in the dataset.
In contrast, staff payday (sp), faculty payday (fac), and before-holiday (bh) are not statistically significant, suggesting these factors do not materially affect total visits after controlling for the main payday/holiday effects. Additionally, the interaction terms (weekday × payday and holiday × faculty payday) are not statistically significant, implying the payday effect is relatively consistent across weekdays rather than being concentrated on a specific day of the week.
Finally, monthly seasonality is one of the strongest drivers in Model 3, with month effects increasing substantially from February through December. This indicates pronounced seasonal variation in branch traffic and suggests that staffing and service capacity planning should consider both payday peaks and high-traffic months.
Overall, the results demonstrate that an event-based modeling approach combined with seasonality provides a strong framework for understanding and predicting bank customer visits, supporting better operational and staffing decisions.