Reputation: 49
I have two columns stored as character data that represent 24-hour time.
Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", "0100")
I would like to subtract these times and get the desired column below (as numeric).
Hours_slept <- c(7, 6.75, 7.25)
I would like to use the tidyverse.
Upvotes: 1
Views: 469
Reputation: 8800
Here's a base R implementation. Like Andy mentioned, there is an assumption that the Bedtime
must be before Wakeup
, and by no more than a day:
Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", "0100")
Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')
Bedtime[Bedtime > Wakeup] <- Bedtime[Bedtime > Wakeup] - as.difftime(1, units='days')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
# [1] 7.00 6.75 7.25
The input hours are converted into datetimes, (which by default will take the current date). Subtract one day from any times where the bed time is greater than the corresponding wakeup. Then subtract the datetimes with difftime
and get the data in numeric hours.
OP brought up the issue of missing values in the comments. My initial solution fails with Error in NextMethod(.Generic) : NAs are not allowed in subscripted assignments
. Following here, one fix is to check for NAs when subscripting:
Wakeup <- c("0700", NA, "0815")
Bedtime <- c("2400", "2230", "0100")
IsNA <- is.na(Wakeup) | is.na(Bedtime) # FALSE TRUE FALSE
Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')
# subscripting accounts for NAs now
Bedtime[Bedtime > Wakeup & ! IsNA] <- Bedtime[Bedtime > Wakeup & ! IsNA] - as.difftime(1, units='days')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
# 7.00 NA 7.25
But this also led me to another solution, which I maybe prefer in general. Calculate the difference in the automatically generated date times, and add 24 (hours) to any negative values. This seems to work with NA
s without any additional fuss (it uses ifelse
and avoids the subscripting with NA
):
Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", NA)
Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
Hours_slept <- ifelse(Hours_slept < 0, Hours_slept + 24, Hours_slept)
# 7.00 6.75 NA
Upvotes: 3
Reputation: 7636
There's a few interesting things to think through here, such as how to convert to a time object (if that's what you're aiming for) and how to work out time distance without a date (we can tell that 2230 is day before wakeup and 0100 is same day, but R doesn't).
If you want to stick to very tidyversey ways of doing things, then you can make it a tibble and mutate
using lubridate
functions:
library(tidyverse)
library(lubridate)
Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", "0100")
tibble(Bedtime, Wakeup) %>%
mutate(
# First change makes each a time, formatting time string by inserting space
across(.fns = ~ str_replace(.x, "(?=\\d{2}$)", " ") %>% hm()),
# Then time difference
time_diff = Wakeup - Bedtime,
# Some are negative - a quick fix may be just to add 24h
time_diff = if_else(time_diff < 0, hours(24) + time_diff, time_diff),
# Then convert to decimal number of hours
time_diff = as.numeric(time_diff) / (60 * 60)
)
#> # A tibble: 3 × 3
#> Bedtime Wakeup time_diff
#> <Period> <Period> <dbl>
#> 1 24H 0M 0S 7H 0M 0S 7
#> 2 22H 30M 0S 5H 15M 0S 6.75
#> 3 1H 0M 0S 8H 15M 0S 7.25
Created on 2022-05-26 by the reprex package (v2.0.1)
Upvotes: 4