Dplyr kullanarak bir tablonun her satırına bir işlev mi uygulamak?


121

İle çalışırken, plyrsık sık adplyher satıra uygulamak zorunda olduğum skaler işlevler için kullanmayı yararlı buldum .

Örneğin

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

Şimdi dplyrdaha fazlasını kullanıyorum , merak ediyorum bunu yapmanın düzenli / doğal bir yolu var mı? İstediğim bu DEĞİLDİR :

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

Geçenlerde mdplydplyr'de bir eşdeğeri olup olmadığını sordum ve hadley buna dayanarak bir şeyler üretebileceklerini önerdi do. Sanırım burada da işe yarayacak.
baptiste

4
Sonunda dplyr, rowwise()her satıra göre
gruplanacak

@hadley thx, adplyyine de bir gruplama kullanmadığınız zamanki gibi davranması gerekmez mi? yakından entegre işlevi group_byDEĞİL olarak adlandırıldığı içinsplit_by
Stephen Henderson

@StephenHenderson hayır, çünkü bir bütün olarak masa üzerinde işlem yapmanın da bir yolunu bulmanız gerekiyor.
hadley

1
@HowYaDoing Evet ama bu yöntem genelleme yapmıyor. Örneğin psum, pmean veya pmedian yok.
Stephen Henderson

Yanıtlar:


202

Dplyr 0.2'den itibaren (sanırım) rowwise()uygulanıyor, bu yüzden bu sorunun cevabı şöyle oluyor:

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

Sigara rowwisealternatif

Beş yıl (!) Sonra bu yanıt hala çok fazla trafik alıyor. Verildiği için, rowwisebirçok insan sezgisel bulmasına rağmen, giderek daha fazla tavsiye edilmiyor. Kendinize bir iyilik yapın ve bu konuyu iyi bir şekilde ele almak için Jenny Bryan'ın R'deki Satır odaklı iş akışlarından düzenli ters malzeme ile geçin.

Bulduğum en basit yol, Hadley'in örneklerinden birine dayanıyor pmap:

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

Bu yaklaşımı kullanarak, .fiçindeki işleve ( ) rastgele sayıda argüman verebilirsiniz pmap.

pmap iyi bir kavramsal yaklaşımdır çünkü satır bazlı işlemler yaptığınızda aslında bir vektör listesinden (bir veri çerçevesindeki sütunlar) tuple'lar ile çalıştığınız gerçeğini yansıtır.


Bunu (yukarıdan) ideal cevaba değiştirdim çünkü amaçlanan kullanım bu.
Stephen Henderson

1
Dinamik olarak oluşturulmuş bir veri çerçevesinin değerlerini eklemek mümkün müdür? Dolayısıyla bu veri çerçevesinde sütun isimleri bilinmemektedir. Sütun isimleri biliniyorsa ekleyebilirim.
Arun Raja

stackoverflow.com/questions/28807266/… cevabı buldum. Bunda toplam yerine korelasyon kullanıyorlar. Ama aynı kavram.
Arun Raja

13
Eğer işe yaramazsa, gerçekten dplyr :: mutate kullandığınızdan emin olun plyr :: mutate - beni deli etti
jan-

Teşekkürler YAK, bu da beni ısırdı. Her ikisini de plyrve dplyrpaketler, neredeyse kesinlikle yanlış kullanıyorsunuz mutateaçıkça kapsam sağlar sürece dplyr::mutate.
Chris Warth

22

Deyimsel yaklaşım, uygun şekilde vektörleştirilmiş bir fonksiyon yaratmak olacaktır.

Rtemin pmaxancak aynı zamanda içerir, burada uygun olan Vectorizebir sargı olarak mapplybir keyfi bir fonksiyonun bir vektörlenmiş rasgele versiyonunu yaratmak için izin vermek.

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 ++ 'da vektörleştirmeyi uygulamanın daha hızlı olacağını, ancak magicPonyişlevi sizin için yazacak bir paket olmadığını unutmayın.


thx bu, büyük bir cevaptır dediğin gibi mükemmel genel R tarzı -idiomatic, ama onun gerçekten bir olup olmadığını sorumu ele sanmıyorum dplyro dplyr örn olmadan basit olurdu ... yolu with(df, Coalesce(a,b))bir olduğuna göre belki yine de bir tür cevap - bunun için kullanmayın dplyr?
Stephen Henderson

4
Çift bir olmadığını kontrol itiraf etmeliyim magicPonypaketi. Çok kötü
rsoren

21

Satırlara göre gruplamanız gerekir:

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

Bu ne 1yaptı adply.


Görünüşe göre daha basit veya "daha güzel" bir sözdizimi olmalı.
Stephen Henderson

@StephenHenderson, olabilir, ben dplyruzman değilim . Umarım başka biri daha iyi bir şeyle gelir. Not ile biraz temizledim 1:n().
BrodieG

Haklı olduğunuzdan şüpheleniyorum, ancak gruplama olmadan varsayılan davranışın davranış gibi olması gerektiğini düşünüyorum group_by(1:n()). Sabah kimsenin başka bir fikri yoksa sizinkini işaretlerim;)
Stephen Henderson

Ayrıca, bunun aşağıdaki dokümantasyona bir şekilde aykırı olduğunu unutmayın n: "Bu işlev her veri kaynağı için özel olarak uygulanmaktadır ve yalnızca özetleme içinden kullanılabilir.", Ancak işe yarıyor gibi görünmektedir.
BrodieG

Sepal.Length ve Petal.Length'e bir şekilde indeks numaralarına göre başvurabilir misiniz? Çok fazla değişkeniniz varsa, kullanışlı olurdu. Mesela ... Max.len = max ([c (1,3)])?
Rasmus Larsen

19

Güncelleme 2017-08-03

Bunu yazdıktan sonra Hadley bazı şeyleri tekrar değiştirdi. Purrr'da kullanılan işlevler şimdi purrrlyr adı verilen yeni bir karma pakette şu şekilde tanımlanmaktadır:

purrrlyr, purrr ve dplyr'in kesişme noktasında bulunan bazı işlevleri içerir. Ambalajı daha hafif hale getirmek için ve tidyverse'teki diğer çözümlerle değiştirildikleri için purrr'dan çıkarıldılar.

Dolayısıyla, aşağıdaki kodun çalışması için bu paketi kurmanız + yüklemeniz gerekir.

Orijinal gönderi

Hadley sık sık ne kullanmamız gerektiği konusunda fikrini değiştirir, ancak satır başına işlevselliği elde etmek için purrr'deki işlevlere geçmemiz gerektiğini düşünüyorum . En azından, aynı işlevsellik sunan ve hemen hemen aynı arayüze sahip adplygelen plyr .

İlgili iki işlev vardır by_rowve invoke_rows. Anladığım kadarıyla, by_rowsatırlar üzerinden döngü yapmak ve sonuçları data.frame'e eklemek istediğinizde kullanıyorsunuz . invoke_rowsdata.frame satırları üzerinde döngü oluşturduğunuzda ve her sütunu bir işleve argüman olarak ilettiğinizde kullanılır. Biz sadece ilkini kullanacağız.

Örnekler

library(tidyverse)

iris %>% 
  by_row(..f = function(this_row) {
    browser()
  })

Bu, iç kısımları görmemizi sağlar (böylece ne yaptığımızı görebiliriz), bu da onunla yapmakla aynı şeydir 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

Varsayılan by_rowolarak, çıktıya göre bir liste sütunu ekler:

iris %>% 
  by_row(..f = function(this_row) {
      this_row[1:4] %>% unlist %>% mean
  })

verir:

# 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

bunun yerine a döndürürsek data.frame, data.frames içeren bir liste alırız :

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
    )
  })

verir:

# 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

Fonksiyonun çıktısını nasıl eklediğimiz .collateparam tarafından kontrol edilir . Üç seçenek vardır: liste, satırlar, sütunlar. Çıktımızın uzunluğu 1 olduğunda, satırlar veya sütunlar kullanmamız önemli değildir.

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
  })

ikisi de üretir:

# 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 satırlı bir data.frame çıkarırsak, kullandığımız çok az önemlidir:

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
    )
  })

ikisi de verir:

# 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

ikincinin sütuna sahip olması ve birinci sütunun .rowolmaması dışında.

Son olarak, çıktımız 1 vectorveya data.framesatırlar halinde uzunluk 1'den daha uzunsa , aşağıdakiler için satır veya sütun kullanıp kullanmamamız önemlidir .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")

sırasıyla üretir:

# 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

Yani, sonuç olarak. İsterseniz adply(.margins = 1, ...)işlevselliğini kullanabilirsiniz by_row.


2
by_rowkullanımdan kaldırıldı, "tidyr :: nest (); dplyr :: mutate (); purrr :: map () kombinasyonunu
kullan

Bu çok fazla r.
qwr

14

BrodieG'nin cevabını genişletmek,

İşlevini verir birden fazla satırda, o zaman yerine mutate(), do()kullanılmalıdır. Sonra tekrar birleştirmek rbind_all()için dplyrambalajından kullanın .

In dplyrsürümü dplyr_0.1.2kullanılarak 1:n()içinde group_by()maddesi benim için çalışmaz. Umarım Hadleyrowwise() yakında uygulamaya koyar.

iris %>%
    group_by(1:nrow(iris)) %>%
    do(do_fn) %>%
    rbind_all()

Performansı test etmek,

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)

aşağıdaki sonuçlara sahiptir:

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

Bu, yeni purrrsürümün en hızlı


1

Bunun gibi bir şey mi?

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

1
Evet, teşekkürler, bu çok özel bir cevap. Ancak benim örneğim ve sorum, dplyrherhangi bir skaler fonksiyon için genel bir çözüm olup olmadığını anlamaya çalışıyor .
Stephen Henderson

Genel olarak, işlevler vektörleştirilmelidir - tuhaf bir işlevse, yazabilir wacky.function <- function(col.1, col.2){...}ve sonra yazabilirsiniz iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length).
colcarroll

Çoğu zaman tahmin etmeliler, ama bence dplyrya plyrda gibi bir şey kullanırken ya da ya da gibi bir şey data.tablekullanırken onların deyimlerini kullanmaya çalışmalısın, böylece kodun stillerin karışımını paylaşmak zor olmaz. Dolayısıyla soru.
Stephen Henderson

plyrDokümantasyonun ilk satırı "plyr, ortak bir dizi sorunu çözen bir araç setidir: büyük bir sorunu yönetilebilir parçalara ayırmanız, her bir parça üzerinde işlem yapmanız ve ardından tüm parçaları tekrar bir araya getirmeniz gerekir." Bu, temel sütun işlemlerinin en iyi araç olduğu çok farklı bir problem gibi görünüyor. Bu aynı zamanda bunu yapmak için neden "doğal" plyr/ dplyrkomut olmadığını da açıklayabilir .
colcarroll

5
Ünlü bir alıntıyı kesmek için : "Sahip olduğunuz tek şey bir katsa, onu bir çekiç ve tornavida için de
kullanacaksınız
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.