2

I am trying to use an ifelse statement to provide a "message" based on the value of a calculated mean.

Here is the code I have. I'm not getting an error message but the level of sleep is not in the correct place.

summary_sleep<-sleep_day %>%
group_by(Id)%>%
summarise(max_slept = max(TotalMinutesAsleep), 
mean_sleep=mean(TotalMinutesAsleep), 
 sum_sleep=sum(TotalMinutesAsleep),
number_sleep_entries=length(TotalMinutesAsleep))


summary_sleep$level_of_sleep = 
ifelse(summary_sleep$mean_sleep >= 460, "Too much sleep (OVER 460)",
ifelse(summary_sleep$mean_sleep >= 360 && summary_sleep$mean_sleep 
<=459, "Good sleep (360-460)",
ifelse(summary_sleep$mean_sleep >= 200 && summary_sleep$mean_sleep 
<=360, "Bad sleep (200-360)",
ifelse(summary_sleep$mean_sleep >0 && summary_sleep$mean_sleep <200, 
 "Bad sleep (0-200)"))))
ID mean_sleep level_of_sleep
1 294.0000 Good sleep (360-460)
2 61.000 Good sleep (360-460)
3 506.1786 Too much sleep (OVER 460)

2 Answers2

3

For complex conditions, case_when (or ifelse) may be necessary. In your case, where the conditions are just ranges of a single numeric column, you can use the cut function:

summary_sleep$level_of_sleep = cut(summary_sleep$mean_sleep, 
                                   breaks = c(0, 200, 360, 460, Inf),
                                   labels = c("Bad sleep (0-200)", 
                                              "Bad sleep (200-360)", 
                                              "Good sleep (360-460)", 
                                              "Too much sleep (OVER 460)"),
                                   include.lowest=TRUE)

The breaks will be closed on the right and open on the left by default. To reverse this, use the argument right=FALSE.

Regarding your ifelse statements: I think you're getting the incorrect values due to using && rather than &. You want a separate logical test for each individual value of mean_sleep, but && runs the logical test only on the first value of a vector and returns a single TRUE or FALSE result.

Look at the examples below. In Example 1, the & version gives us a separate logical test for each value of x, but && gives us only a single logical test using the first value of x. Since x[1]=1, the result is FALSE. In Example 2, I've rearranged x so that the value 5 comes first. 5 fulfills the conditions, so && now returns TRUE.

# Example 1
x = 1:10

x >= 4 & x <= 6
#>  [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE

x >= 4 && x <= 6
#> [1] FALSE

# Example 2
x = c(5, 1:4, 6:10)

x >= 4 & x <= 6
#>  [1]  TRUE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

x >= 4 && x <= 6
#> [1] TRUE

And an example when used in a data frame. Note that in the second case we sort the data frame first:

library(tidyverse)

mtcars %>% 
  select(mpg) %>% 
  slice(1:10) %>% 
  mutate(
    `&&` = mpg >= 19 && mpg <= 21,
    `&` = mpg >= 19 & mpg <= 21,
    `mpg&&` = ifelse(mpg >= 19 && mpg <= 21, "Mid MPG", "Other"),
    `mpg&` = ifelse(mpg >= 19 & mpg <= 21, "Mid MPG", "Other")
  )
#>                    mpg   &&     &   mpg&&    mpg&
#> Mazda RX4         21.0 TRUE  TRUE Mid MPG Mid MPG
#> Mazda RX4 Wag     21.0 TRUE  TRUE Mid MPG Mid MPG
#> Datsun 710        22.8 TRUE FALSE Mid MPG   Other
#> Hornet 4 Drive    21.4 TRUE FALSE Mid MPG   Other
#> Hornet Sportabout 18.7 TRUE FALSE Mid MPG   Other
#> Valiant           18.1 TRUE FALSE Mid MPG   Other
#> Duster 360        14.3 TRUE FALSE Mid MPG   Other
#> Merc 240D         24.4 TRUE FALSE Mid MPG   Other
#> Merc 230          22.8 TRUE FALSE Mid MPG   Other
#> Merc 280          19.2 TRUE  TRUE Mid MPG Mid MPG

mtcars %>% 
  select(mpg) %>% 
  slice(1:10) %>% 
  arrange(mpg) %>%
  mutate(
    `&&` = mpg >= 19 && mpg <= 21,
    `&` = mpg >= 19 & mpg <= 21,
    `mpg&&` = ifelse(mpg >= 19 && mpg <= 21, "Mid MPG", "Other"),
    `mpg&` = ifelse(mpg >= 19 & mpg <= 21, "Mid MPG", "Other")
  )
#>                    mpg    &&     & mpg&&    mpg&
#> Duster 360        14.3 FALSE FALSE Other   Other
#> Valiant           18.1 FALSE FALSE Other   Other
#> Hornet Sportabout 18.7 FALSE FALSE Other   Other
#> Merc 280          19.2 FALSE  TRUE Other Mid MPG
#> Mazda RX4         21.0 FALSE  TRUE Other Mid MPG
#> Mazda RX4 Wag     21.0 FALSE  TRUE Other Mid MPG
#> Hornet 4 Drive    21.4 FALSE FALSE Other   Other
#> Datsun 710        22.8 FALSE FALSE Other   Other
#> Merc 230          22.8 FALSE FALSE Other   Other
#> Merc 240D         24.4 FALSE FALSE Other   Other

See this SO answer for additional detail on why you should use &, rather than && when operating on a vector of values where you want a separate logical test for each value. (Also, note that in your example, it looks like the third condition should be < 360, rather than <= 360.)

eipi10
  • 91,525
  • 24
  • 209
  • 285
2

You may be looking for a case_when:

library(dplyr)
summary_sleep %>% mutate(
 level_of_sleep = case_when(
    mean_sleep >= 460 ~ "Too much sleep (OVER 460)",
    mean_sleep >= 360 & mean_sleep <=459 ~ "Good sleep (360-460)",
    mean_sleep >= 200 & mean_sleep <360 ~ "Bad sleep (200-360)",
    mean_sleep > 0 & mean_sleep <200 ~ "Bad sleep (0-200)"))
Gabe Solomon
  • 365
  • 3
  • 12