TidyTuesday: Long Beach Animal Shelter

tidy tuesday
data visualization
Author

Jess Graves

Published

March 1, 2025

This week’s dataset (2025-03-04)

This week’s tidytuesday included a published dataset from Long Beach Animal Shelter. Here’s what their README says:

This week we’re exploring the Long Beach Animal Shelter Data!

The dataset comes from the City of Long Beach Animal Care Services via the {animalshelter} R package.

This dataset comprises of the intake and outcome record from Long Beach Animal Shelter.

  • How has the number of pet adoptions changed over the years?

  • Which type of pets are adopted most often?

What I hope to visualize

  • What pets are represented in the dataset?

  • What outcomes are most likely to occur in different pets?

  • Have visit rates changed over time?

Data

Libraries
library(tidyverse)
library(styler)
library(patchwork)
library(ggmosaic)
library(colorspace)
library(paletteer)
library(ggstream)
library(gtsummary)

# c1 <- "#FDFBE4FF"
c2 <- lighten("#FFFBF2", amount = 0.3)

choice <- c2
# setting ggplot theme
my_theme <- theme_classic() +
  theme(
    axis.title = element_text(size = 16, color = "grey30", 
                              face = "bold"),
    axis.text = element_text(size = 14, color = "grey30"),
    axis.line = element_line(color = "grey50"),
    strip.text = element_text(size = 14),
    plot.background = element_rect(
      color =  choice,
      fill = choice
    ),
    panel.background = element_rect(
      color = choice,
      fill = choice
    )
  ) 

palette <- "lisa::FridaKahlo"
alpha = 0.9

theme_set(my_theme)
today <- Sys.Date()

longbeach <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-03-04/longbeach.csv') 
Tabulating animal types and outcome types
longbeach %>% 
  dplyr::select(animal_type, outcome_type)  %>%
  tbl_summary()
Characteristic N = 29,7871
animal_type
    amphibian 3 (<0.1%)
    bird 2,075 (7.0%)
    cat 14,145 (47%)
    dog 9,768 (33%)
    guinea pig 172 (0.6%)
    livestock 10 (<0.1%)
    other 1,332 (4.5%)
    rabbit 526 (1.8%)
    reptile 344 (1.2%)
    wild 1,412 (4.7%)
outcome_type
    adoption 6,290 (21%)
    community cat 386 (1.3%)
    died 763 (2.6%)
    disposal 71 (0.2%)
    duplicate 31 (0.1%)
    euthanasia 5,451 (18%)
    foster 10 (<0.1%)
    foster to adopt 166 (0.6%)
    homefirst 88 (0.3%)
    missing 59 (0.2%)
    rescue 6,680 (23%)
    return to owner 3,214 (11%)
    return to rescue 46 (0.2%)
    return to wild habitat 294 (1.0%)
    shelter, neuter, return 919 (3.1%)
    transfer 4,869 (16%)
    transport 206 (0.7%)
    trap, neuter, release 57 (0.2%)
    Unknown 187
1 n (%)

There seem to be some redundant outcome types, so I am going to combine a few categories

longbeach <- longbeach %>%
  filter(animal_type !='other', 
         outcome_type != 'duplicate')  %>%
  mutate(age = difftime(intake_date, dob, units='days')/365.25, 
         outcome_type_clean = case_when(grepl('neuter', outcome_type) ~ 'neuter', 
                                        grepl('return', outcome_type) ~ 'return', 
                                        grepl('foster', outcome_type) ~ 'foster', 
                                        grepl('disposal', outcome_type) ~ 'died', 
                                        TRUE ~ outcome_type )) %>%
  mutate(across(c(outcome_type_clean, animal_type), str_to_sentence))

For the sake of de-cluttering, I’m only going to look at the top 5 most frequent pets and top 5 most frequent outcome types.

Top 5 most frequent pets and outcomes
animal_levels <- longbeach %>%
  group_by(animal_type) %>%
  tally() %>%
  arrange(desc(n)) %>%
  slice(1:5)

outcome_levels <-longbeach %>%
  group_by(outcome_type_clean) %>%
  tally() %>%
  arrange(desc(n)) %>%
  slice(1:5)

Most frequent visits & outcome types

Code
data_clean <- longbeach %>%
  filter(animal_type %in% animal_levels$animal_type, 
         outcome_type_clean %in% outcome_levels$outcome_type_clean) %>%
  mutate(animal_type = factor(animal_type, 
                              levels = animal_levels$animal_type), 
         outcome_type_clean = factor(outcome_type_clean, 
                              levels = outcome_levels$outcome_type_clean), 
         )
Code
p_mosaic <- data_clean %>%
  ggplot() +
  geom_mosaic(aes(x=product(outcome_type_clean), 
                  fill=animal_type), 
              offset=0.001, alpha=alpha)+
  theme(legend.position ='none', 
        axis.text.x= element_blank(), 
        axis.ticks = element_blank(), 
        axis.title.x = element_blank(),
        axis.title.y = element_text(margin=margin(r=5, 
                                                  l=5))) + 
  scale_x_productlist(expand=c(0, 0)) + 
  scale_y_productlist(expand=c(0, 0)) + 
  coord_flip() + 
  labs(y='Pet', x='Visit outcome') +
  scale_fill_paletteer_d(palette)  
p_mosaic

Code
p_hist <- data_clean %>% 
  group_by(animal_type) %>% 
  tally() %>% 
  mutate(percent = paste0(round(100*n/sum(n), 0), '%'), 
         n_pct = paste0(format(n, big.mark = ','), ' (', percent, ')')) %>%
  ggplot(aes(x=animal_type, y=n, fill=animal_type)) + 
  geom_bar(stat='identity', 
           alpha=alpha) +
  scale_fill_paletteer_d(palette) + 
  guides(fill = 'none') + 
  geom_text(aes(label = n_pct, y = n), 
            vjust = -1, 
            color = 'grey30', 
            size=4.5) + 
  theme(axis.line = element_blank(), 
        axis.text.y = element_blank(), 
        axis.title = element_blank(), 
        axis.ticks = element_blank(), 
        axis.text.x = element_text(vjust=5), 
        plot.title = element_text(size=18, color = 'grey30', 
                                  hjust=1)) + 
  scale_y_continuous(limits = c(0, 13000)) 
p_hist

Number of adoptions over time

Code
p_stream <- data_clean %>%
  filter(outcome_type_clean == 'Adoption') %>%
  mutate(year_mo = floor_date(intake_date, "month")) %>%
  group_by(year_mo, animal_type) %>%
  tally() %>% 
  mutate(animal_type = factor(animal_type, levels = animal_levels$animal_type)) %>%
  ggplot(aes(x=year_mo, 
             y = n, 
             fill=animal_type)) + 
  geom_stream(alpha=alpha) +
  scale_fill_paletteer_d(palette, drop = FALSE) + 
  scale_x_date(date_breaks = "1 year", 
               date_labels = '%Y', 
               expand = c(0, 0)) + 
  theme(axis.text.y=element_blank(), 
        axis.ticks=element_blank(), 
        axis.line.y=element_blank()) + 
  labs(x='Year', y='Number of adoptions') + 
  guides(fill = FALSE)
p_stream

Combining

Version 1

Code
p_bottom <- (p_hist + plot_spacer() + p_mosaic + theme(axis.title.y=element_blank())) + 
    plot_layout(nrow = 1, widths = c(2, 0.1, 1))
(p_stream / p_bottom )+ 
  plot_layout(nrow=2, 
              heights = c(1, 0.5))

Version 2 (Final version)

Code
right_side <- (p_hist +
    theme(axis.text.x = element_text(vjust=1))) /
  (p_mosaic) +
  plot_layout(ncol = 1, 
              heights = c(1, 1))
Code
final <- (p_stream | right_side) + plot_layout(widths = c(2.5, 1.1))+ 
  plot_annotation(title = 'Long Beach Animal Shelter', 
                  caption = 'Data from: Long Beach Animal Shelter (tidytuesday Week 8); Vis by: github.com/jesslgraves') & 
  theme(plot.title = element_text(size=28, face = 'bold'))

final 

Code
ggsave('preview-image.png', final, 
       units='cm', 
       width = 50, 
       height = 25)

Citation

BibTeX citation:
@online{graves2025,
  author = {Graves, Jess},
  title = {TidyTuesday: {Long} {Beach} {Animal} {Shelter}},
  date = {2025-03-01},
  url = {https://JessLGraves.github.io/posts/2025-03-01-tidytuesday/},
  langid = {en}
}
For attribution, please cite this work as:
Graves, Jess. 2025. “TidyTuesday: Long Beach Animal Shelter.” March 1, 2025. https://JessLGraves.github.io/posts/2025-03-01-tidytuesday/.