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
.)