Why does map %>% as.data.frame give different result than map_df?

Viewed 458

I'm new to R and trying to understand how to use map and map_df. Consider the following:

iris %>% split(.$Species) %>% map_df(function (x) apply(x[, 1:4], 2, mean))

And compare to

iris %>% split(.$Species) %>% map(function (x) apply(x[, 1:4], 2, mean)) %>% as.data.frame

The former gives the following output:

  Sepal.Length Sepal.Width Petal.Length Petal.Width
         <dbl>       <dbl>        <dbl>       <dbl>
1         5.01        3.43         1.46       0.246
2         5.94        2.77         4.26       1.33
3         6.59        2.97         5.55       2.03

The latter gives:

             setosa versicolor virginica
Sepal.Length  5.006      5.936     6.588
Sepal.Width   3.428      2.770     2.974
Petal.Length  1.462      4.260     5.552
Petal.Width   0.246      1.326     2.026

My question is: why? I would expect these two commands to give the same output. How can I get the second output with map_df function?

2 Answers

map_df() seems to bind the list elements by rows (same as map_dfr()) although it is not explicitly stated in its documentation. If you would like to bind by column, use map_dfc() instead. Note that the output is a tibble in which the use of rownames is discouraged. This document suggests ways to work with rownames in tibbles.

iris %>% 
  split(.$Species) %>% 
  map_dfc(function (x) apply(x[, 1:4], 2, mean))

# # A tibble: 4 x 3
#   setosa versicolor virginica
#    <dbl>      <dbl>     <dbl>
# 1  5.01        5.94      6.59
# 2  3.43        2.77      2.97
# 3  1.46        4.26      5.55
# 4  0.246       1.33      2.03
  • Firstly, you have used split instead of group_split which shows that you are creating a named list instead.
  • secondly, after you have created a named list (having three items), you are taking mean of first columns in all three items in list using apply
  • After the mean calculation, the result that remains is again a three item list with a single row containing mean of all four columns.
  • Now here comes the difference-
    • As you have created a named list in first step, if you use map_df or map_dfr (the result will b same in both), map function just row binds the output.
    • However, using map leaves the list as a list (named) which as.data.frame binds column wise.
library(tidyverse)
#1st case
iris %>% split(.$Species) %>% map_dfr(function (x) apply(x[, 1:4], 2, mean))
#> # A tibble: 3 x 4
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#>          <dbl>       <dbl>        <dbl>       <dbl>
#> 1         5.01        3.43         1.46       0.246
#> 2         5.94        2.77         4.26       1.33 
#> 3         6.59        2.97         5.55       2.03

#OR
iris %>% split(.$Species) %>% map_df(function (x) apply(x[, 1:4], 2, mean))
#> # A tibble: 3 x 4
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#>          <dbl>       <dbl>        <dbl>       <dbl>
#> 1         5.01        3.43         1.46       0.246
#> 2         5.94        2.77         4.26       1.33 
#> 3         6.59        2.97         5.55       2.03

#2nd case
iris %>% split(.$Species) %>% map(function (x) apply(x[, 1:4], 2, mean)) %>% as.data.frame
#>              setosa versicolor virginica
#> Sepal.Length  5.006      5.936     6.588
#> Sepal.Width   3.428      2.770     2.974
#> Petal.Length  1.462      4.260     5.552
#> Petal.Width   0.246      1.326     2.026

#OR (with a slight difference of dropping row names)
iris %>% split(.$Species) %>% map_dfc(function (x) apply(x[, 1:4], 2, mean))
#> # A tibble: 4 x 3
#>   setosa versicolor virginica
#>    <dbl>      <dbl>     <dbl>
#> 1  5.01        5.94      6.59
#> 2  3.43        2.77      2.97
#> 3  1.46        4.26      5.55
#> 4  0.246       1.33      2.03

#If group_split would have been used instead
iris %>% group_split(Species) %>% map(function (x) apply(x[, 1:4], 2, mean)) %>% as.data.frame
#>              c.Sepal.Length...5.006..Sepal.Width...3.428..Petal.Length...1.462..
#> Sepal.Length                                                               5.006
#> Sepal.Width                                                                3.428
#> Petal.Length                                                               1.462
#> Petal.Width                                                                0.246
#>              c.Sepal.Length...5.936..Sepal.Width...2.77..Petal.Length...4.26..
#> Sepal.Length                                                             5.936
#> Sepal.Width                                                              2.770
#> Petal.Length                                                             4.260
#> Petal.Width                                                              1.326
#>              c.Sepal.Length...6.588..Sepal.Width...2.974..Petal.Length...5.552..
#> Sepal.Length                                                               6.588
#> Sepal.Width                                                                2.974
#> Petal.Length                                                               5.552
#> Petal.Width                                                                2.026

#OR
iris %>% group_split(Species) %>% map_dfc(function (x) apply(x[, 1:4], 2, mean))
#> New names:
#> * NA -> ...1
#> * NA -> ...2
#> * NA -> ...3
#> # A tibble: 4 x 3
#>    ...1  ...2  ...3
#>   <dbl> <dbl> <dbl>
#> 1 5.01   5.94  6.59
#> 2 3.43   2.77  2.97
#> 3 1.46   4.26  5.55
#> 4 0.246  1.33  2.03

Thus, default binding of list into a dataframe is columnwise and if list is named, item names will be used as output df's column names. On the other hand if you are giving specific command through map_df or map_dfr then list items will be bind row-wise and name of list items are therefore not required.

iris %>% group_split(Species) %>% map_dfr(function (x) apply(x[, 1:4], 2, mean))

# A tibble: 3 x 4
  Sepal.Length Sepal.Width Petal.Length Petal.Width
         <dbl>       <dbl>        <dbl>       <dbl>
1         5.01        3.43         1.46       0.246
2         5.94        2.77         4.26       1.33 
3         6.59        2.97         5.55       2.03 

Hope this is clear.

Related