Nested Dataframe loses format after mutating with ifelse-condition

Viewed 101

I'm trying to mutate a new variable in a nested dataframe with an ifelse-condition. But the problem is that after implementing the ifelse-condition the nested dataframe turns into a list. I want to show this problem with the iris dataset:

Here you can see the original nested format:

iris %>% nest(data = -Species)

# A tibble: 3 x 2
  Species    data             
  <fct>      <list>           
1 setosa     <tibble [50 x 4]>
2 versicolor <tibble [50 x 4]>
3 virginica  <tibble [50 x 4]>

And now I want to mutate a new variable in the nested dataframes:

iris %>%
  nest(data = -Species) %>%
  mutate(data = map(data, function(x)
    x %>% mutate(`Sepal.Length^2` = Sepal.Length^2)))

# A tibble: 3 x 2
  Species    data             
  <fct>      <list>           
1 setosa     <tibble [50 x 5]>
2 versicolor <tibble [50 x 5]>
3 virginica  <tibble [50 x 5]>

This code works. The data-column is as desired in the tibble-format.

But if I insert the ifelse-condition now, the tibble-format is lost:

iris %>%
  nest(data = -Species) %>%
  mutate(data = map(data, function(x)
    ifelse(!is.na(x), x %>% mutate(`Sepal.Length^2` = Sepal.Length^2), NA)))

# A tibble: 3 x 2
  Species    data        
  <fct>      <list>      
1 setosa     <list [200]>
2 versicolor <list [200]>
3 virginica  <list [200]>

I want to keep the tibble-format even with the ifelse-condition.

Can anyone help me?

1 Answers

In the first step of the map() computation, i.e. data in setosa, the input x of your custom function is actually

x <- iris[1:50, 1:4]

Then you put x into ifelse()

ifelse(!is.na(x),                                        # part 1
       x %>% mutate(`Sepal.Length^2` = Sepal.Length^2),  # part 2
       NA)                                               # part 3

The first part is !is.na(x), which returns 50x4=200 logical values. Hence, the second and third parts will be recycled to length 200. However, the second part, i.e.

x %>% mutate(`Sepal.Length^2` = Sepal.Length^2)

is a tibble with 5 variables, which is also a list with length 5, so each variable in this tibble will be recycled 40 times and subsequently a list with length 200 will be created. That is why you get 3 lists of length 200.


In your case, ifelse() may not be applicable. You can adjust it to

iris %>%
  nest(data = -Species) %>%
  add_row(Species = "example", data = NA) %>% 
  mutate(data = map(data, function(x) {
    if(is.data.frame(x))
      x %>% mutate(`Sepal.Length^2` = Sepal.Length^2)
    else
      NULL
  }))

# # A tibble: 4 x 2
#   Species    data             
#   <chr>      <list>           
# 1 setosa     <tibble [50 × 5]>
# 2 versicolor <tibble [50 × 5]>
# 3 virginica  <tibble [50 × 5]>
# 4 example    <NULL>   

Make sure that the condition in if() must be a single logical value.


Grateful to @27ϕ9 for a neater version with map_if():

iris %>%
  nest(data = -Species) %>%
  add_row(Species = "example", data = NA) %>% 
  mutate(data = map_if(data, is_tibble,
                       ~ mutate(.x, `Sepal.Length^2` = Sepal.Length^2),
                       .else = NULL))
Related