Library Event Attendance and Performance Analysis

Author

Andrew Ledet

Published

September 5, 2025

Welcome to the Library Event Attendance and Performance Analysis! This report was designed to provide a clear, data-driven look into our library’s adult event programming. The goal is to help us better understand what makes an event successful, so we can make more informed decisions about future programming.

We began by taking our raw event data and cleaning it up. To focus on the adult audience, we filtered out all events intended for children or youth, as well as any events that were cancelled, had zero attendees, or lacked a valid date or registration count. We also removed any events that started before 9 a.m. This process left us with a clean dataset of our true adult programs.

The core of our analysis is a series of visual heatmaps that show us when people are most likely to register for events. By looking at a simple grid of days and times, we can immediately spot patterns and trends. We created a large, overall heatmap to see a general picture of attendance across all adult programs.

To dig deeper, we also generated subject-specific and room-specific heatmaps. This allows us to answer questions like, “Do our technology workshops perform better on a certain day of the week?” or “Is a room more popular in the morning or evening?” We then supported these visuals with summary tables that highlight the top-performing subjects and rooms based on the number of events and total registrations, giving us a quick reference for our most successful programs and locations.

This analysis provides a new way to look at our event data, offering clear insights into when and where our adult programs are most successful. By leveraging this information, we can strategically plan our future events to better meet the needs of our community and maximize attendance.

Show code
# Load required libraries
library(tidyverse)
library(janitor)
library(lubridate)
library(kableExtra)
library(viridis)

# Load and prepare the data
event_data <- read_csv("event_data_with_analysis.csv", 
                       col_types = cols(), 
                       na = c("", "NA", "N/A")) %>%
  clean_names() %>%
  mutate(
    # Parse event dates and times
    event_start_time = ymd_hms(event_start_time),
    event_date = as.Date(event_start_time),
    event_year = year(event_date),
    event_month = month(event_date),
    event_day_of_week = wday(event_date, label = TRUE),
    event_hour_decimal = hour(event_start_time) + minute(event_start_time) / 60,
    
    # Filter out children/youth events and cancelled events
    is_valid_event = 
      !str_detect(tolower(event_title %||% ""), "child|kid|family|youth") &
      !str_detect(tolower(event_subjects %||% ""), "child|kid|family|youth") &
      !str_detect(tolower(event_title %||% ""), "cancel") &
      !is.na(event_date) &
      !is.na(actual_registrations) & 
      actual_registrations > 0 &
      hour(event_start_time) >= 9  # Remove pre-opening events
  ) %>%
  filter(is_valid_event)

Overall Event Registration Heatmap

Show code
# Create overall heatmap
overall_heatmap_data <- event_data %>%
  group_by(event_day_of_week, event_hour_decimal = floor(event_hour_decimal)) %>%
  summarise(
    avg_registrations = mean(actual_registrations, na.rm = TRUE),
    event_count = n(),
    .groups = "drop"
  )

ggplot(overall_heatmap_data, 
       aes(x = event_day_of_week, 
           y = event_hour_decimal, 
           fill = avg_registrations)) +
  geom_tile(color = "white") +
  scale_fill_viridis_c(name = "Avg Registrations", option = "plasma") +
  labs(title = "Overall Registration Patterns by Day and Time",
       x = "Day of Week", y = "Hour of Day") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Subject-Specific Heatmaps

Show code
# Function to create heatmap for a specific subject
create_subject_heatmap <- function(subject) {
  # Prepare data for the specific subject
  subject_heatmap_data <- event_data %>%
    filter(event_subjects == subject) %>%
    group_by(event_day_of_week, event_hour_decimal = floor(event_hour_decimal)) %>%
    summarise(
      avg_registrations = mean(actual_registrations, na.rm = TRUE),
      event_count = n(),
      .groups = "drop"
    )
  
  # Create the heatmap
  p <- ggplot(subject_heatmap_data, 
         aes(x = event_day_of_week, 
             y = event_hour_decimal, 
             fill = avg_registrations)) +
    geom_tile(color = "white") +
    scale_fill_viridis_c(name = "Avg Registrations", option = "plasma") +
    labs(title = paste("Registration Patterns for", subject),
         x = "Day of Week", y = "Hour of Day") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  
  # Print the plot with a header
  cat("\n\n###", subject, "Events\n\n")
  print(p)
}

# Generate heatmaps for each unique subject
# Limit to top 10 subjects to prevent overwhelming output
top_subjects <- event_data %>%
  group_by(event_subjects) %>%
  summarise(
    total_events = n(),
    total_registrations = sum(actual_registrations, na.rm = TRUE)
  ) %>%
  slice_max(total_events, n = 10) %>%
  pull(event_subjects)

# Create heatmaps for top subjects
lapply(top_subjects, create_subject_heatmap)

ESL Events

No Subjects Listed Events

Makerplace Events

Technology Classes Events

Senior Center; Technology Classes Events

3 Events

Storytime Events

Books & Authors; Senior Center Events

Genealogy Events

Senior Center Events

[[1]] [[2]] [[3]] [[4]] [[5]] [[6]] [[7]] [[8]] [[9]] [[10]]

Room-Specific Heatmaps

Show code
# Function to create heatmap for a specific room
create_room_heatmap <- function(room) {
  # Prepare data for the specific room
  room_heatmap_data <- event_data %>%
    filter(event_room_name == room) %>%
    group_by(event_day_of_week, event_hour_decimal = floor(event_hour_decimal)) %>%
    summarise(
      avg_registrations = mean(actual_registrations, na.rm = TRUE),
      event_count = n(),
      .groups = "drop"
    )
  
  # Create the heatmap
  p <- ggplot(room_heatmap_data, 
         aes(x = event_day_of_week, 
             y = event_hour_decimal, 
             fill = avg_registrations)) +
    geom_tile(color = "white") +
    scale_fill_viridis_c(name = "Avg Registrations", option = "plasma") +
    labs(title = paste("Registration Patterns for", room),
         x = "Day of Week", y = "Hour of Day") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  
  # Print the plot with a header
  cat("\n\n###", room, "Room\n\n")
  print(p)
}

# Generate heatmaps for top rooms
top_rooms <- event_data %>%
  group_by(event_room_name) %>%
  summarise(
    total_events = n(),
    total_registrations = sum(actual_registrations, na.rm = TRUE)
  ) %>%
  slice_max(total_events, n = 10) %>%
  pull(event_room_name)

# Create heatmaps for top rooms
lapply(top_rooms, create_room_heatmap)

Zoom Event Room

Training Center Room

Hendrickson South Room

Cardinal Room Room

Sr Center Computer Room Room

Makerplace - Kitchen Room

ESL/Literacy Office Room

Lindsey Room Room

Makerplace - Creative Arts Room

Makerplace - Flex Space 2 Room

[[1]] [[2]] [[3]] [[4]] [[5]] [[6]] [[7]] [[8]] [[9]] [[10]]

Additional Insights

Show code
# Summary of top subjects
subject_summary <- event_data %>%
  group_by(event_subjects) %>%
  summarise(
    total_events = n(),
    total_registrations = sum(actual_registrations, na.rm = TRUE),
    avg_registrations = mean(actual_registrations, na.rm = TRUE)
  ) %>%
  slice_max(total_events, n = 10) %>%
  arrange(desc(total_events))

subject_summary %>%
  kable(caption = "Top 10 Subjects by Number of Events") %>%
  kable_styling(bootstrap_options = "striped")
Top 10 Subjects by Number of Events
event_subjects total_events total_registrations avg_registrations
ESL 1469 15880 10.810075
No Subjects Listed 1033 35579 34.442401
Makerplace 682 20810 30.513196
Technology Classes 639 4254 6.657277
Senior Center; Technology Classes 389 4486 11.532134
3 159 2554 16.062893
Storytime 154 1611 10.461039
Books & Authors; Senior Center 112 1616 14.428571
Genealogy 63 1657 26.301587
Senior Center 61 464 7.606557
Show code
# Summary of top rooms
room_summary <- event_data %>%
  group_by(event_room_name) %>%
  summarise(
    total_events = n(),
    total_registrations = sum(actual_registrations, na.rm = TRUE),
    avg_registrations = mean(actual_registrations, na.rm = TRUE)
  ) %>%
  slice_max(total_events, n = 10) %>%
  arrange(desc(total_events))

room_summary %>%
  kable(caption = "Top 10 Rooms by Number of Events") %>%
  kable_styling(bootstrap_options = "striped")
Top 10 Rooms by Number of Events
event_room_name total_events total_registrations avg_registrations
Zoom Event 1309 16920 12.925898
Training Center 652 4418 6.776074
Hendrickson South 550 47046 85.538182
Cardinal Room 457 6562 14.358862
Sr Center Computer Room 342 3999 11.692982
Makerplace - Kitchen 301 13910 46.212625
ESL/Literacy Office 282 2675 9.485816
Lindsey Room 254 3760 14.803150
Makerplace - Creative Arts 180 3930 21.833333
Makerplace - Flex Space 2 159 2553 16.056604