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)