dplyr을 사용하여 테이블의 모든 행에 함수를 적용합니까?


121

작업 할 때 각 행에 적용해야하는 스칼라 함수 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

나는 최근 mdply에 dplyr에 동등한 것이 있는지 물었고 hadley는 do. 여기서도 작동 할 것 같아요.
baptiste

4
결국 dplyr은 rowwise()각 개별 행별로 그룹화되는 것과 같은 것을 갖게 될 것입니다
hadley

@hadley thx, adply그래도 그룹화를 사용하지 않을 때 처럼 행동해야하지 않습니까? 그 긴밀하게 통합 된 함수가 호출 될 때 group_byNOTsplit_by
스티븐 헨더슨

@StephenHenderson 아니요, 테이블 전체에서 작업하는 방법도 필요하기 때문입니다.
hadley

1
@HowYaDoing 예,하지만 그 방법은 일반화되지 않습니다. 예를 들어 psum, pmean 또는 pmedian이 없습니다.
Stephen Henderson

답변:


202

dplyr 0.2 (내 생각에) rowwise()가 구현 되었으므로이 문제에 대한 대답은 다음과 같습니다.

iris %>% 
  rowwise() %>% 
  mutate(Max.Len= max(Sepal.Length,Petal.Length))

rowwise대안

5 년 (!) 후에도이 답변은 여전히 ​​많은 트래픽을 얻습니다. 주어진 이후로 rowwise많은 사람들이 직관적이라고 생각하지만 점점 권장되지 않습니다. 이 주제를 잘 다루기 위해 R에서 제니 브라이언의 행 중심 워크 플로우를 깔끔한 자료 와 함께 살펴보십시오.

내가 찾은 가장 간단한 방법은 다음을 사용하는 Hadley의 예제 중 하나를 기반으로합니다 pmap.

iris %>% 
  mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))

이 접근 방식을 사용하면 .f내부 함수 ( )에 임의의 수의 인수를 제공 할 수 있습니다 pmap.

pmap 행 현명한 작업을 수행 할 때 실제로 벡터 목록 (데이터 프레임의 열)에서 튜플을 사용한다는 사실을 반영하기 때문에 좋은 개념적 접근 방식입니다.


나는 이것이 의도 된 사용법이라고 생각하므로 이것을 (위에서) 이상적인 대답으로 변경했습니다.
Stephen Henderson

1
동적으로 형성된 데이터 프레임의 값을 추가 할 수 있습니까? 따라서이 데이터 프레임에서는 열 이름을 알 수 없습니다. 열 이름이 알려진 경우 추가 할 수 있습니다.
Arun Raja

stackoverflow.com/questions/28807266/… 방금 답을 찾았습니다. 이것에서 그들은 합계 대신 상관 관계를 사용하고 있습니다. 그러나 같은 개념.
Arun Raja

13
너트 저를 몰았다 - 그것은하지 작업을 수행하는 경우, 당신은 실제로 plyr ::의 mutate하지 dplyr :: 개의 mutate를 사용하고 있는지 확인
얀 - GLX

감사합니다 YAK, 이것도 저를 물었습니다. plyrdplyr패키지를 모두 포함하는 경우 mutate명시 적으로 scope를 제공하지 않는 한 거의 확실하게 잘못된 것을 사용하고 있습니다 dplyr::mutate.
Chris Warth 2015 년

22

관용적 접근 방식은 적절하게 벡터화 된 함수를 만드는 것입니다.

Rpmax여기에 적합한 것을 제공 하십시오. 그러나 임의 함수의 벡터화 된 임의 버전을 만들 수 있도록 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 / C ++에서 벡터화를 구현하는 것이 더 빠르지 만 magicPony함수를 작성 하는 패키지 는 없습니다 .


thx, 이것은 훌륭한 대답입니다. 당신이 말한 것처럼 훌륭한 일반 R 스타일 -idiomatic이지만 dplyr방법 이 있는지 여부에 대한 내 질문을 실제로 해결한다고 생각하지 않습니다 ... dplyr 없이는 더 간단 할 것입니다. 예를 들어 with(df, Coalesce(a,b))아마도, 그것은 그래도 대답의 종류-사용하지 마십시오 dplyr?
Stephen Henderson

4
내가 magicPony패키지 가 없다는 것을 두 번 확인했음을 인정해야 합니다. 너무 나쁜
rsoren

21

행별로 그룹화해야합니다.

iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))

이것은 무엇입니까 1에서 한 adply.


더 간단하거나 "더 좋은"구문이 있어야 할 것 같습니다.
Stephen Henderson

@StephenHenderson,있을 수 있습니다, 나는 dplyr전문가가 아닙니다 . 바라건대 다른 누군가가 더 나은 것을 함께 올 것입니다. 참고로 1:n().
BrodieG 2014

나는 당신이 옳다고 생각하지만, 그룹화가없는 기본 행동은 행동과 같아야한다고 생각합니다 group_by(1:n()). 아무도 아침에 다른 아이디어가 없다면 나는 당신의 아이디어를 틱할 것입니다;)
Stephen Henderson

또한 n이것은 "이 기능은 각 데이터 소스에 대해 특별하게 구현되었으며 요약 내에서만 사용할 수 있습니다."에 대한 문서에 다소 위배 됩니다. 작동하는 것처럼 보이지만.
BrodieG 2014

Sepal.Length와 Petal.Length를 인덱스 번호로 어떤 식 으로든 참조 할 수 있습니까? 많은 변수가 있다면 편리 할 것입니다. ... Max.len = max ([c (1,3)])?
라스무스 라슨

19

업데이트 2017-08-03

이 글을 쓴 후 Hadley는 몇 가지 사항을 다시 변경했습니다. purrr에 있던 함수는 이제 purrrlyr 라는 새로운 혼합 패키지에 있습니다.

purrrlyr에는 purrr과 dplyr의 교차점에있는 일부 함수가 포함되어 있습니다. 패키지를 더 가볍게 만들고 tidyverse의 다른 솔루션으로 대체 되었기 때문에 purrr에서 제거되었습니다.

따라서 아래 코드가 작동하도록하려면 해당 패키지를 설치하고로드해야합니다.

원본 게시물

Hadley는 우리가 무엇을 사용해야하는지에 대해 자주 생각을 바꾸지 만, 행별 기능을 얻으려면 purrr 의 기능으로 전환해야한다고 생각 합니다. 적어도 그들은 동일한 기능을 제공하고 plyradply 에서 와 거의 동일한 인터페이스 를 가지고 있습니다.

두 가지 관련 기능은, by_row하고 invoke_rows. 내 이해는 by_row행을 반복하고 결과를 data.frame에 추가하려는 경우 사용한다는 것 입니다. invoke_rowsdata.frame의 행을 반복하고 각 열을 함수에 인수로 전달할 때 사용됩니다. 첫 번째 만 사용합니다.

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

대신를 반환하면 s data.frame가있는 목록을 얻습니다 data.frame.

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

1 개의 행이있는 data.frame을 출력하는 경우 사용하는 것은 약간만 중요합니다.

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있고 첫 번째는 호출 되지 않는 것을 제외하고 .

마지막으로, 출력이 a vector또는 a data.frame행 으로 길이 1보다 길면 다음을 위해 행 또는 열을 사용하는지 여부가 중요합니다 .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.


2
by_row더 이상 사용되지 않습니다. "다음 조합 사용 : tidyr :: nest (); dplyr :: mutate (); purrr :: map ()" github.com/hadley/purrrlyr/blob/…
momeara

그것은 많은 r입니다.
qwr

14

BrodieG의 답변 확장,

함수가 반환 한 행한다면, 대신 mutate(), do()사용되어야한다. 그런 다음 다시 결합 rbind_all()하려면 dplyr패키지 에서 사용 하십시오 .

dplyr버전 에서는 절 에서 dplyr_0.1.2사용 1:n()하는 group_by()것이 작동하지 않습니다. Hadley가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버전이 가장 빠르다 는 것을 보여줍니다


1

이 같은?

iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)

1
예, 그것은 매우 구체적인 대답입니다. 그러나 내 예와 질문은 dplyr스칼라 함수에 대한 일반적인 해결책 이 있는지 알아 보려고 합니다.
Stephen Henderson

그것은 엉뚱한 기능이있는 경우, 당신은 작성할 수 있습니다 - 일반적으로, 기능은 벡터화해야 wacky.function <- function(col.1, col.2){...}하고 iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length).
colcarroll

종종 그들은 나는 생각한다,하지만 난 당신이 뭔가를 사용할 때 생각 dplyr하거나 plyr또는 말을 data.table당신은 당신의 코드 스타일의 주 믹스에 어려운이되지 않도록 자신의 관용구를 사용하려고합니다. 따라서 질문입니다.
Stephen Henderson

plyr문서 의 첫 번째 줄 은 "plyr은 일반적인 문제 집합을 해결하는 도구 집합입니다. 큰 문제를 관리 가능한 부분으로 나누고 각 부분에 대해 작업 한 다음 모든 부분을 다시 합쳐야합니다." 이것은 기본 열 연산이 가장 좋은 도구 인 매우 다른 문제처럼 보입니다. 이것은 또한 이것을하기위한 "자연스러운" plyr/ dplyr명령이 없는 이유를 설명 할 수 있습니다 .
colcarroll

5
유명한 견적을 도살 : " 당신은 모두가 당신이 너무 망치와 드라이버를 위해 그것을 사용하게됩니다 plyr 경우 "
thelatemail
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.