Ответ 1
Как из dplyr 0.2 (я думаю) rowwise()
реализовано, поэтому ответ на эту проблему становится:
iris %>%
rowwise() %>%
mutate(Max.Len= max(Sepal.Length,Petal.Length))
При работе с plyr
мне часто было полезно использовать adply
для скалярных функций, которые я должен применять к каждой строке.
например.
data(iris)
library(plyr)
head(
adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length))
)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1 5.1 3.5 1.4 0.2 setosa 5.1
2 4.9 3.0 1.4 0.2 setosa 4.9
3 4.7 3.2 1.3 0.2 setosa 4.7
4 4.6 3.1 1.5 0.2 setosa 4.6
5 5.0 3.6 1.4 0.2 setosa 5.0
6 5.4 3.9 1.7 0.4 setosa 5.4
Теперь я использую dplyr
больше, мне интересно, есть ли способ аккуратный/естественный? Поскольку это НЕ, что я хочу:
library(dplyr)
head(
mutate(iris, Max.Len= max(Sepal.Length,Petal.Length))
)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1 5.1 3.5 1.4 0.2 setosa 7.9
2 4.9 3.0 1.4 0.2 setosa 7.9
3 4.7 3.2 1.3 0.2 setosa 7.9
4 4.6 3.1 1.5 0.2 setosa 7.9
5 5.0 3.6 1.4 0.2 setosa 7.9
6 5.4 3.9 1.7 0.4 setosa 7.9
Как из dplyr 0.2 (я думаю) rowwise()
реализовано, поэтому ответ на эту проблему становится:
iris %>%
rowwise() %>%
mutate(Max.Len= max(Sepal.Length,Petal.Length))
Вам нужно группировать по строкам:
iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))
Это то, что сделал 1
в adply
.
Идиоматический подход будет заключаться в создании соответствующей векторной функции.
R
предоставляет pmax
, который подходит здесь, однако он также предоставляет Vectorize
в качестве обертки для mapply
, чтобы вы могли создать произвольную версию произвольной функции с векторизованной версией.
library(dplyr)
# use base R pmax (vectorized in C)
iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length))
# use vectorize to create your own function
# for example, a horribly inefficient get first non-Na value function
# a version that is not vectorized
coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]}
# a vectorized version
Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b'))
# some example data
df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8))
df %>% mutate(ab =Coalesce(a,b))
Обратите внимание, что реализация векторизации в C/С++ будет быстрее, но не существует пакета magicPony
, который будет писать эту функцию для вас.
После написания этого, Хэдли снова изменил некоторые вещи. Функции, которые раньше были в purrr, теперь находятся в новом смешанном пакете с именем purrrlyr, который описывается как:
purrrlyr содержит некоторые функции, которые лежат на пересечении purrr и dplyr. Они были удалены из purrr, чтобы сделать упаковку более легкой и потому, что они были заменены другими решениями в tidyverse.
Итак, вам нужно будет установить + загрузить этот пакет, чтобы сделать код ниже.
Хэдли часто меняет свое мнение о том, что мы должны использовать, но я думаю, что мы должны переключиться на функции в purrr, чтобы получить функциональность по ряду строк. По крайней мере, они предлагают ту же функциональность и имеют почти тот же интерфейс, что и adply
от plyr.
Существуют две связанные функции: by_row
и invoke_rows
. Я понимаю, что вы используете by_row
, когда хотите перебрать строки и добавить результаты в файл data.frame. invoke_rows
используется, когда вы перебираете строки data.frame и передаете каждый col в качестве аргумента функции. Мы будем использовать только первый.
library(tidyverse)
iris %>%
by_row(..f = function(this_row) {
browser()
})
Это позволяет нам видеть внутренности (чтобы мы могли видеть, что мы делаем), что аналогично тому, как это делается с adply
.
Called from: ..f(.d[[i]], ...)
Browse[1]> this_row
# A tibble: 1 × 5
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<dbl> <dbl> <dbl> <dbl> <fctr>
1 5.1 3.5 1.4 0.2 setosa
Browse[1]> Q
По умолчанию by_row
добавляет столбец списка на основе вывода:
iris %>%
by_row(..f = function(this_row) {
this_row[1:4] %>% unlist %>% mean
})
дает:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <list>
1 5.1 3.5 1.4 0.2 setosa <dbl [1]>
2 4.9 3.0 1.4 0.2 setosa <dbl [1]>
3 4.7 3.2 1.3 0.2 setosa <dbl [1]>
4 4.6 3.1 1.5 0.2 setosa <dbl [1]>
5 5.0 3.6 1.4 0.2 setosa <dbl [1]>
6 5.4 3.9 1.7 0.4 setosa <dbl [1]>
7 4.6 3.4 1.4 0.3 setosa <dbl [1]>
8 5.0 3.4 1.5 0.2 setosa <dbl [1]>
9 4.4 2.9 1.4 0.2 setosa <dbl [1]>
10 4.9 3.1 1.5 0.1 setosa <dbl [1]>
# ... with 140 more rows
если вместо этого мы вернем a data.frame
, получим список с data.frame
s:
iris %>%
by_row( ..f = function(this_row) {
data.frame(
new_col_mean = this_row[1:4] %>% unlist %>% mean,
new_col_median = this_row[1:4] %>% unlist %>% median
)
})
дает:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <list>
1 5.1 3.5 1.4 0.2 setosa <data.frame [1 × 2]>
2 4.9 3.0 1.4 0.2 setosa <data.frame [1 × 2]>
3 4.7 3.2 1.3 0.2 setosa <data.frame [1 × 2]>
4 4.6 3.1 1.5 0.2 setosa <data.frame [1 × 2]>
5 5.0 3.6 1.4 0.2 setosa <data.frame [1 × 2]>
6 5.4 3.9 1.7 0.4 setosa <data.frame [1 × 2]>
7 4.6 3.4 1.4 0.3 setosa <data.frame [1 × 2]>
8 5.0 3.4 1.5 0.2 setosa <data.frame [1 × 2]>
9 4.4 2.9 1.4 0.2 setosa <data.frame [1 × 2]>
10 4.9 3.1 1.5 0.1 setosa <data.frame [1 × 2]>
# ... with 140 more rows
Как мы добавляем выход функции, управляется параметром .collate
. Там три варианта: список, строки, столбцы. Когда наш вывод имеет длину 1, не имеет значения, будем ли мы использовать строки или столбцы.
iris %>%
by_row(.collate = "cols", ..f = function(this_row) {
this_row[1:4] %>% unlist %>% mean
})
iris %>%
by_row(.collate = "rows", ..f = function(this_row) {
this_row[1:4] %>% unlist %>% mean
})
оба производят:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <dbl>
1 5.1 3.5 1.4 0.2 setosa 2.550
2 4.9 3.0 1.4 0.2 setosa 2.375
3 4.7 3.2 1.3 0.2 setosa 2.350
4 4.6 3.1 1.5 0.2 setosa 2.350
5 5.0 3.6 1.4 0.2 setosa 2.550
6 5.4 3.9 1.7 0.4 setosa 2.850
7 4.6 3.4 1.4 0.3 setosa 2.425
8 5.0 3.4 1.5 0.2 setosa 2.525
9 4.4 2.9 1.4 0.2 setosa 2.225
10 4.9 3.1 1.5 0.1 setosa 2.400
# ... with 140 more rows
Если вывести data.frame с 1 строкой, это имеет значение только немного, что мы используем:
iris %>%
by_row(.collate = "cols", ..f = function(this_row) {
data.frame(
new_col_mean = this_row[1:4] %>% unlist %>% mean,
new_col_median = this_row[1:4] %>% unlist %>% median
)
})
iris %>%
by_row(.collate = "rows", ..f = function(this_row) {
data.frame(
new_col_mean = this_row[1:4] %>% unlist %>% mean,
new_col_median = this_row[1:4] %>% unlist %>% median
)
})
оба дают:
# A tibble: 150 × 8
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .row new_col_mean new_col_median
<dbl> <dbl> <dbl> <dbl> <fctr> <int> <dbl> <dbl>
1 5.1 3.5 1.4 0.2 setosa 1 2.550 2.45
2 4.9 3.0 1.4 0.2 setosa 2 2.375 2.20
3 4.7 3.2 1.3 0.2 setosa 3 2.350 2.25
4 4.6 3.1 1.5 0.2 setosa 4 2.350 2.30
5 5.0 3.6 1.4 0.2 setosa 5 2.550 2.50
6 5.4 3.9 1.7 0.4 setosa 6 2.850 2.80
7 4.6 3.4 1.4 0.3 setosa 7 2.425 2.40
8 5.0 3.4 1.5 0.2 setosa 8 2.525 2.45
9 4.4 2.9 1.4 0.2 setosa 9 2.225 2.15
10 4.9 3.1 1.5 0.1 setosa 10 2.400 2.30
# ... with 140 more rows
за исключением того, что второй имеет столбец с именем .row
, а первый - нет.
Наконец, если наш вывод длиннее длины 1 либо как vector
, либо как data.frame
со строками, то имеет значение, будем ли мы использовать строки или столбцы для .collate
:
mtcars[1:2] %>% by_row(function(x) 1:5)
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows")
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")
производит соответственно:
# A tibble: 32 × 3
mpg cyl .out
<dbl> <dbl> <list>
1 21.0 6 <int [5]>
2 21.0 6 <int [5]>
3 22.8 4 <int [5]>
4 21.4 6 <int [5]>
5 18.7 8 <int [5]>
6 18.1 6 <int [5]>
7 14.3 8 <int [5]>
8 24.4 4 <int [5]>
9 22.8 4 <int [5]>
10 19.2 6 <int [5]>
# ... with 22 more rows
# A tibble: 160 × 4
mpg cyl .row .out
<dbl> <dbl> <int> <int>
1 21 6 1 1
2 21 6 1 2
3 21 6 1 3
4 21 6 1 4
5 21 6 1 5
6 21 6 2 1
7 21 6 2 2
8 21 6 2 3
9 21 6 2 4
10 21 6 2 5
# ... with 150 more rows
# A tibble: 32 × 7
mpg cyl .out1 .out2 .out3 .out4 .out5
<dbl> <dbl> <int> <int> <int> <int> <int>
1 21.0 6 1 2 3 4 5
2 21.0 6 1 2 3 4 5
3 22.8 4 1 2 3 4 5
4 21.4 6 1 2 3 4 5
5 18.7 8 1 2 3 4 5
6 18.1 6 1 2 3 4 5
7 14.3 8 1 2 3 4 5
8 24.4 4 1 2 3 4 5
9 22.8 4 1 2 3 4 5
10 19.2 6 1 2 3 4 5
# ... with 22 more rows
Итак, нижняя строка. Если вы хотите функциональность adply(.margins = 1, ...)
, вы можете использовать by_row
.
Расширяя ответ BrodieG,
Если функция возвращает более одной строки, то вместо mutate()
do()
должна использоваться do()
. Затем, чтобы объединить его вместе, используйте rbind_all()
из пакета dplyr
.
В dplyr
версии dplyr_0.1.2
использование 1:n()
в предложении group_by()
не работает для меня. Надеюсь, Хэдли скоро будет реализовывать rowwise()
.
iris %>%
group_by(1:nrow(iris)) %>%
do(do_fn) %>%
rbind_all()
Тестирование производительности,
library(plyr) # plyr_1.8.4.9000
library(dplyr) # dplyr_0.8.0.9000
library(purrr) # purrr_0.2.99.9000
library(microbenchmark)
d1_count <- 1000
d2_count <- 10
d1 <- data.frame(a=runif(d1_count))
do_fn <- function(row){data.frame(a=row$a, b=runif(d2_count))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}
op <- microbenchmark(
plyr_version = plyr::adply(d1, 1, do_fn),
dplyr_version = d1 %>%
dplyr::group_by(1:nrow(d1)) %>%
dplyr::do(do_fn(.)) %>%
dplyr::bind_rows(),
purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
times=50)
он имеет следующие результаты:
Unit: milliseconds
expr min lq mean median uq max neval
plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449 50
dplyr_version 977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978 50
purrr_version 609.5790 629.7565 643.8498 644.2505 656.1959 686.8128 50
Это показывает, что новая версия purrr
является самой быстрой
Что-то вроде этого?
iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)