Самое большое различие между {dplyr} и {purrr} заключается в том, что {dplyr} предназначен для работы только с data.frames, а {purrr} предназначен для работы со всеми видами списков. Data.frames являются списками, вы также можете использовать {purrr} для итерации data.frame.
map_chr(iris, class)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
"numeric" "numeric" "numeric" "numeric" "factor"
summarise_at
и map_at
не ведут себя точно так же: summarise_at
просто возвращает искомую сводку, map_at
возвращает все данные. Фрейм в виде списка, с изменениями, выполненными там, где вы просили это:
> library(purrr)
> library(dplyr)
> small_iris <- sample_n(iris, 5)
> map_at(small_iris, c("Sepal.Length", "Sepal.Width"), mean)
$Sepal.Length
[1] 6.58
$Sepal.Width
[1] 3.2
$Petal.Length
[1] 6.7 1.3 5.7 4.3 4.7
$Petal.Width
[1] 2.0 0.4 2.1 1.3 1.5
$Species
[1] virginica setosa virginica versicolor versicolor
Levels: setosa versicolor virginica
> summarise_at(small_iris, c("Sepal.Length", "Sepal.Width"), mean)
Sepal.Length Sepal.Width
1 6.58 3.2
map_at
всегда возвращает список, mutate_at
всегда data.frame:
> map_at(small_iris, c("Sepal.Length", "Sepal.Width"), ~ .x / 10)
$Sepal.Length
[1] 0.77 0.54 0.67 0.64 0.67
$Sepal.Width
[1] 0.28 0.39 0.33 0.29 0.31
$Petal.Length
[1] 6.7 1.3 5.7 4.3 4.7
$Petal.Width
[1] 2.0 0.4 2.1 1.3 1.5
$Species
[1] virginica setosa virginica versicolor versicolor
Levels: setosa versicolor virginica
> mutate_at(small_iris, c("Sepal.Length", "Sepal.Width"), ~ .x / 10)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 0.77 0.28 6.7 2.0 virginica
2 0.54 0.39 1.3 0.4 setosa
3 0.67 0.33 5.7 2.1 virginica
4 0.64 0.29 4.3 1.3 versicolor
5 0.67 0.31 4.7 1.5 versicolor
Итак, подведем итоги по первому вопросу: если вы думаете о выполнении операции «по столбцам» для не вложенного df и хотите в результате получить data.frame, вам нужно перейти к {dplyr}.
Что касается вложенного столбца, вы должны объединить group_by()
, nest()
из {tidyr}, mutate()
и map()
. Здесь вы создаете уменьшенную версию вашего фрейма данных, который будет содержать столбец, представляющий собой список фреймов данных. Затем вы будете использовать map()
для перебора элементов внутри этого нового столбца.
Вот пример с нашим любимым ирисом:
library(tidyr)
iris_n <- iris %>%
group_by(Species) %>%
nest()
iris_n
# A tibble: 3 x 2
Species data
<fct> <list>
1 setosa <tibble [50 × 4]>
2 versicolor <tibble [50 × 4]>
3 virginica <tibble [50 × 4]>
Здесь новый объект - это data.frame с столбцом data
, представляющим собой список меньших data.frames, один по виду (коэффициент, который мы указали в group_by()
). Затем мы можем перебрать этот столбец, выполнив:
map(iris_n$data, ~ lm(Sepal.Length ~ Sepal.Width, data = .x))
[[1]]
Call:
lm(formula = Sepal.Length ~ Sepal.Width, data = .x)
Coefficients:
(Intercept) Sepal.Width
2.6390 0.6905
[[2]]
Call:
lm(formula = Sepal.Length ~ Sepal.Width, data = .x)
Coefficients:
(Intercept) Sepal.Width
3.5397 0.8651
[[3]]
Call:
lm(formula = Sepal.Length ~ Sepal.Width, data = .x)
Coefficients:
(Intercept) Sepal.Width
3.9068 0.9015
Но идея состоит в том, чтобы хранить все внутри data.frame, поэтому мы можем использовать mutate
для создания столбца, в котором будет храниться этот новый список lm
результатов:
iris_n %>%
mutate(lm = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = .x)))
# A tibble: 3 x 3
Species data lm
<fct> <list> <list>
1 setosa <tibble [50 × 4]> <S3: lm>
2 versicolor <tibble [50 × 4]> <S3: lm>
3 virginica <tibble [50 × 4]> <S3: lm>
Таким образом, вы можете запустить несколько mutate()
, чтобы получить r.squared
, например:
iris_n %>%
mutate(lm = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = .x)),
lm = map(lm, summary),
r_squared = map_dbl(lm, "r.squared"))
# A tibble: 3 x 4
Species data lm r_squared
<fct> <list> <list> <dbl>
1 setosa <tibble [50 × 4]> <S3: summary.lm> 0.551
2 versicolor <tibble [50 × 4]> <S3: summary.lm> 0.277
3 virginica <tibble [50 × 4]> <S3: summary.lm> 0.209
Но более эффективный способ - использовать compose()
из {purrr} для создания функции, которая сделает это один раз, вместо того, чтобы повторять mutate()
.
get_rsquared <- compose(as_mapper("r.squared"), summary, lm)
iris_n %>%
mutate(lm = map_dbl(data, ~ get_rsquared(Sepal.Length ~ Sepal.Width, data = .x)))
# A tibble: 3 x 3
Species data lm
<fct> <list> <dbl>
1 setosa <tibble [50 × 4]> 0.551
2 versicolor <tibble [50 × 4]> 0.277
3 virginica <tibble [50 × 4]> 0.209
Если вы знаете, что всегда будете использовать Sepal.Length ~ Sepal.Width
, вы можете даже предварительно заполнить lm()
с помощью partial()
:
pr_lm <- partial(lm, formula = Sepal.Length ~ Sepal.Width)
get_rsquared <- compose(as_mapper("r.squared"), summary, pr_lm)
iris_n %>%
mutate(lm = map_dbl(data, get_rsquared))
# A tibble: 3 x 3
Species data lm
<fct> <list> <dbl>
1 setosa <tibble [50 × 4]> 0.551
2 versicolor <tibble [50 × 4]> 0.277
3 virginica <tibble [50 × 4]> 0.209
Что касается ресурсов, я написал серию постов на {purrr}, которые вы можете проверить: https://colinfay.me/tags/#purrr