Virginie
Virginie

Reputation: 67

hours scale in ggplot2

I'm working with bedtime and waketime, so I would like to create a graph with a 24h x axis, starting at 12pm on day 1 and ending at 12pm on day 2. Meaning that after 11:59pm, it should start at 0 again.

Same question with number only, I'd like to create a scale from 10 to 20 and after 20 start at 1 again until 10.

This is the code that I have for now:

ggplot(SLEEP2, aes(x=as.POSIXct(bedT, format="%H:%M"), y=Jour))+ 
geom_rect(aes(xmin=as.POSIXct(sleepT, format="%H:%M"), xmax=as.POSIXct(wakeT, 
format="%H:%M"),ymin=(Jour-0.4), ymax=(Jour+0.4)),fill="orange", 
color="black")+ scale_x_datetime()

I'd like to represent their sleep time with bars...

How can I do that? I tried several option but nothing is working. Thank you.

Upvotes: 4

Views: 1542

Answers (3)

tjebo
tjebo

Reputation: 23807

To turn my comment into an answer - I think this is one of the few moments when a circular / polar coordinate system may be appropriate.

I shamelessly used the data and good deal of the plotting code from user keithbjolley's answer - thanks and + 1.

The main change is the call to coord_polar().

library(tidyverse)
library(lubridate)

start <- ymd_hm("20210101-0000")
secsday <- 60 * 60 * 12
df <- tibble(date_time = seq(start, start %m+% weeks(2), by = 200)) %>%
  mutate(
    time = hms::as_hms(date_time),
    date = factor(date(date_time)),
    values = jitter(cos(day(date_time) / 30 + pi * as.numeric(time) / secsday), amount = 1 / 40),
    values = ifelse(values > 0, values, 0)
  )

reds <- grDevices::hcl.colors(length(unique(df$date)), "Reds")

ggplot(df, aes(x = time, y = values, color = date)) +
  geom_line() +
  coord_polar(start = 0) +
  scale_color_manual(values = reds) +
  theme_minimal()

Created on 2021-01-19 by the reprex package (v0.3.0)

Upvotes: 0

keithpjolley
keithpjolley

Reputation: 2273

Edit at bottom to include given data.

If there's a way to get the graph's axis transformed like maybe is more intuitive I don't know. I couldn't figure that out.

As other's mentioned having some sample data in your code would be helpful so that solutions could use it instead of having to guess and/or make up our own. Including a picture of what you have and/or want is also good.

Something like:

R> head(SLEEP2)
...

Anyways, here's one way of doing it. It's not at all elegant but it works.

First, make some "untransformed" data that peaks at midnight and plot it. I'm guessing this looks something like what you have already.

library(tidyverse)
library(lubridate)

start <- ymd_hm("20210101-0000")
# Make some pretend data. It's value is zero during the "day" and peaks around midnight.
secsday <- 60*60*12
df <- tibble(date_time = seq(start, start %m+% weeks(2), by=200)) %>%
    mutate(time = hms::as_hms(date_time),
           date = factor(date(date_time)),
           values = jitter(cos(day(date_time)/30+pi*as.numeric(time)/secsday), amount=1/40),
           values = ifelse(values>0, values, 0)
    )

# Plot original data.
ggplot(df, aes(time, values, colour=date)) +
    geom_line() +
    scale_x_time(breaks=hours(seq(0,24,6)), labels=c("midnight","6AM","noon","6pm","midnight")) +
    ggtitle('pre-transform')

pre-transform ggplot

Next, translate AM to PM and PM to AM.

# Transform times so AM is PM and PM is AM (swap around noon).
dt <- df %>% mutate(time=
            ifelse(time>hm("12:00"), hms::as_hms(time)-secsday, hms::as_hms(time)+secsday)
        )
ggplot(dt, aes(time, values, colour=date)) +
    geom_line() +
    scale_x_time(breaks=hours(seq(0,24,6)), labels=c("noon", "6PM", "midnight", "6AM", "noon")) +
    ggtitle('post-transform')

post-transform ggplot

I'd like to know if there's a way of doing this by only adjusting the axis instead of the data.

With the data you provided and using bars:

dat <- data.frame(Jour=1:5,
                  date=seq.Date(as.Date("2020-10-01"), as.Date("2020-10-05"), "day"),
                  sleeptime=c("22:30","21:10","23:00","23:00", "23:20"),
                  waketime= c("6:30", "7:00", "7:30", "6:25","7:10"))

# make fake dates for sleep and wake
dat <- dat %>% mutate(
            sleep = ymd_hm(paste(dat[1,]$date, sleeptime)),
            wake = ymd_hm(paste(dat[1,]$date, waketime)),
            wake = wake + ddays(ifelse(lubridate::hm(waketime)<lubridate::hm(sleeptime), 1, 0))
        )

ggplot(dat, aes(xmin=sleep, xmax=wake)) +
    scale_x_datetime(breaks=ymd(dat[1,]$date) + hours(seq(12,36,length.out=5)),
                     labels=c("noon", "6PM", "midnight", "6AM", "noon"),
                     limits=ymd(dat[1,]$date) + hours(seq(12,36,length.out=2))
                     ) +
    geom_rect(ymin=0.25, ymax=0.75, fill='darkgreen') +
    ylim(0,1) +
    facet_grid(rows = vars(date)) +
    theme(axis.title = element_blank()) +
    theme(axis.text.y = element_blank(),
          axis.ticks = element_blank()
    )

ggplot with bars and user supplied data

Upvotes: 2

Sinh Nguyen
Sinh Nguyen

Reputation: 4497

You can use scales_x_... function with custom label functions to achieved what you want. Here is the example, as you didn't provide a reproducible example I just create my own version which may not perfect, please check and adjust as you go.

library(dplyr)
library(lubridate)
library(ggplot2)
df <- tibble(time = seq(from = as.POSIXct("2020-01-01 00:00:00"),
                        by = "1 hour", length.out = 48),
             number = seq(1, by = 1, length.out = 48),
             y_value = runif(48, min = 0, max = 60))

format_time_label <- function(x) {
  hour(x)
}

df[["hour"]] <- day(df[["time"]]) * hour(df[["time"]])
ggplot(data = df) +
  geom_bar(aes(x = time, y = y_value), stat = "identity") +
  scale_x_datetime(date_breaks = "1 hour", labels = format_time_label)

enter image description here

For the number, I am not sure what you want so I restart back to 1 after every 20.

format_number_label <- function(x) {
  x[x %% 20 == 0] <- 20
  x[x > 20] <- x[x > 20] - (x[x > 20] %/% 20) * 20
  x
}

ggplot(data = df) +
  geom_bar(aes(x = number, y = y_value), stat = "identity") +
  scale_x_continuous(breaks = seq(1, by = 1, length.out = 48),
                     labels = format_number_label)

enter image description here

Upvotes: 1

Related Questions