79637244

Date: 2025-05-24 23:29:06
Score: 1
Natty:
Report link

Thank you to everyone for the interesting visualizations, it helped me learn a lot. I realized that my original idea for a 'clock-face' creates a confusing and misleading visual when you have activities that span midnight for two nights in a row. I switched instead to a semicircular or fan-like shape instead.

A segment graph also works well in terms of being able to most easily compare days so that you can detect patterns in when the baby is most commonly awake/asleep, while the spiral track most elegantly deals with the issue of activities crossing midnight.

library(ggplot2)
library(lubridate)
library(tidyverse)
library(googlesheets4) #https://googlesheets4.tidyverse.org/
library(aptheme)
library(ggpubr) # allows us to use ggarrange
library(glue)

#Load in sample data from a public Google Sheet 
gs4_deauth() 
url = # insert url here
df = read_sheet(url) 

# Don't want to actually share this Google Sheet so here's reproducible data for seven days:

df = structure(list(date = structure(c(1756512000, 1756512000, 1756512000, 
1756512000, 1756512000, 1756512000, 1756512000, 1756512000, 1756512000, 
1756598400, 1756598400, 1756598400, 1756598400, 1756598400, 1756598400, 
1756598400, 1756598400, 1756684800, 1756684800, 1756684800, 1756684800, 
1756684800, 1756684800, 1756684800, 1756684800, 1756684800, 1756684800, 
1756771200, 1756771200, 1756771200, 1756771200, 1756771200, 1756771200, 
1756771200, 1756771200, 1756771200, 1756857600, 1756857600, 1756857600, 
1756857600, 1756857600, 1756857600, 1756857600, 1756857600, 1756857600, 
1756857600, 1756857600, 1756944000, 1756944000, 1756944000, 1756944000, 
1756944000, 1756944000, 1756944000, 1756944000, 1756944000, 1757030400, 
1757030400, 1757030400, 1757030400, 1757030400, 1757030400, 1757030400, 
1757030400, 1757030400), class = c("POSIXct", "POSIXt"), tzone = "UTC"), 
    activity = c("sleep", "feed", "sleep", "feed", "sleep", "feed", 
    "sleep", "feed", "sleep", "sleep", "sleep", "feed", "sleep", 
    "feed", "sleep", "feed", "sleep", "sleep", "feed", "feed", 
    "sleep", "feed", "sleep", "feed", "sleep", "feed", "sleep", 
    "sleep", "feed", "sleep", "feed", "sleep", "feed", "sleep", 
    "feed", "sleep", "sleep", "feed", "sleep", "feed", "sleep", 
    "feed", "sleep", "feed", "sleep", "feed", "sleep", "feed", 
    "sleep", "feed", "sleep", "feed", "sleep", "feed", "sleep", 
    "feed", "sleep", "feed", "sleep", "feed", "sleep", "feed", 
    "sleep", "feed", "sleep"), start_time = structure(c(-2209158000, 
    -2209152600, -2209150800, -2209139100, -2209132800, -2209122000, 
    -2209113600, -2209096800, -2209089600, -2209152600, -2209145400, 
    -2209139100, -2209132200, -2209122000, -2209114800, -2209100400, 
    -2209095000, -2209150800, -2209148100, -2209146600, -2209132800, 
    -2209123800, -2209122000, -2209113000, -2209101360, -2209096800, 
    -2209093200, -2209158900, -2209153200, -2209150800, -2209139400, 
    -2209134600, -2209122900, -2209115700, -2209097400, -2209089600, 
    -2209157400, -2209151400, -2209149000, -2209138800, -2209132800, 
    -2209120200, -2209114200, -2209096800, -2209091400, -2209083900, 
    -2209082400, -2209153800, -2209150800, -2209140000, -2209135500, 
    -2209123800, -2209114800, -2209097700, -2209089600, -2209080600, 
    -2209157100, -2209150800, -2209149000, -2209138200, -2209132800, 
    -2209122600, -2209113000, -2209096200, -2209089600), class = c("POSIXct", 
    "POSIXt"), tzone = "UTC"), end_time = structure(c(-2209152600, 
    -2209152000, -2209140000, -2209138200, -2209127400, -2209120560, 
    -2209105200, -2209093200, -2209154400, -2209147200, -2209140000, 
    -2209138200, -2209131600, -2209120200, -2209107600, -2209096800, 
    -2209158000, -2209148100, -2209147200, -2209146000, -2209125600, 
    -2209122900, -2209113900, -2209110900, -2209098360, -2209095900, 
    -2209079700, -2209153500, -2209152300, -2209140900, -2209138200, 
    -2209129200, -2209121700, -2209107000, -2209094100, -2209161000, 
    -2209152000, -2209150800, -2209140000, -2209137900, -2209128000, 
    -2209119000, -2209105800, -2209093200, -2209084200, -2209083600, 
    -2209154400, -2209152900, -2209141800, -2209138800, -2209130100, 
    -2209122600, -2209107600, -2209094400, -2209082400, -2209078800, 
    -2209151700, -2209150200, -2209139400, -2209137000, -2209128300, 
    -2209120800, -2209106400, -2209093200, -2209077000), class = c("POSIXct", 
    "POSIXt"), tzone = "UTC")), row.names = c(NA, -65L), class = c("tbl_df", 
"tbl", "data.frame"))

# Adjust so we have the correct days (rather than Google's default of December 30, 1899)
# Midnight flag marks activities that go from one day to the next 

df = df %>%
  mutate(midnight_flag = if_else(end_time - start_time < 0,
                                 TRUE,
                                 FALSE)) %>% 
  mutate(start_time = update(start_time, year = year(date), month = month(date), day = day(date)),
         end_time = update(end_time, year = year(date), month = month(date), day = day(date))) %>% 
  mutate(end_time = if_else(midnight_flag,
                            end_time + days(1),
                            end_time)) %>% 
  mutate(activity = str_to_title(activity))

# Split up days that cross midnight into two rows
df = df %>%
  split(seq_len(nrow(.))) %>% # Split dataframe by row into a list of tibbles
  map_dfr(function(row) { # function to bind rows together into a dataframe again
    if (row$midnight_flag) {
      midnight = ceiling_date(row$start_time, unit = "day")
      just_before_midnight = update(row$date, hour = 23, minute = 59, second = 59)
            tibble( # Creates tibble with two rows
              activity = row$activity,
              date = c(row$date, midnight),
              start_time = c(row$start_time, midnight),
              end_time = c(just_before_midnight, row$end_time)
            )
    } else {
      row %>% select(-midnight_flag)
    }
  }) %>% mutate(date = as_date(date))

# Visualize as semicircles
semicircle_graph = function(starting_date) {
  midnight_start <- ymd_hms(paste(starting_date, "00:00:00"))
  midnight_end <- ymd_hms(paste(starting_date + days(1), "00:00:00"))
  weekday = strftime(starting_date, '%A')
  
  df %>% 
    filter(date == starting_date) %>% 
    mutate(weekday = strftime(date, '%A')) %>% 
    ggplot(aes(xmin = start_time, xmax = end_time,
               ymin = 0, ymax = 1, fill = activity)) +
    geom_rect() +
    scale_x_datetime(limits = c(midnight_start, midnight_end),
                     date_labels = "%l %p",
                     expand = c(0,0), # Comment this out and the shape will be more like a fan
                     breaks = seq(midnight_start, midnight_end, by = "4 hours")) +
    theme_minimal(base_family = "Chalkboard") +
    ggtitle(glue("{weekday}, {starting_date}")) + 
    ggtitle(paste0(weekday, ", ", starting_date)) +
    theme(axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5, face=1),  
          legend.position = "bottom",
          legend.title = element_blank()) + 
    coord_radial(start = -0.5 * pi, end = 0.5 * pi)  
}

# Plot each day and save as a list
plots = map(unique(df$date), semicircle_graph)

ggarrange(plotlist = plots,
          common.legend = TRUE,
          legend = "bottom",
          nrow = 2, ncol = 4)

A visualization of the time segments every 24 hours during which a baby is either eating or sleeping. Each day is represented by one semicircle with blue segments for sleeping and red for eating. The day of the week and date is shown about the semicircles.

Reasons:
  • Blacklisted phrase (0.5): Thank you
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: user3710004