Dplyr paketi koşullu mutasyon için kullanılabilir mi?


178

Mutasyon, mutasyon koşullu olduğunda kullanılabilir (belirli sütun değerlerinin değerlerine bağlı olarak)?

Bu örnek ne demek istediğimi göstermeye yardımcı oluyor.

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame")

  a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2

Yeni bir sütun g oluşturmak için dplyr paketini kullanarak benim sorunum için bir çözüm bulmak umuyordum (ve evet bu kod çalışmasını biliyorum, ama sanırım amacı açık yapar):

 library(dplyr)
 df <- mutate(df,
         if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
         if (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4) {g = 3})

Aradığım kodun sonucu bu özel örnekte bu sonucu olmalıdır:

  a b c d e f  g
1 1 1 6 6 1 2  3
2 3 3 3 2 2 3  3
3 4 4 6 4 4 4  3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7  2
7 5 2 5 2 6 5  2
8 1 6 3 6 3 2  3

Herkes bunu dplyr içinde nasıl yapılacağı hakkında bir fikri var mı? Bu veri çerçevesi sadece bir örnektir, ele aldığım veri çerçeveleri çok daha büyük. Hızı nedeniyle dplyr kullanmaya çalıştım, ama belki de bu sorunu ele almanın daha iyi yolları var mı?


2
Evet ama dplyr::case_when()bir ifelse,
smci

Yanıtlar:


216

kullanım ifelse

df %>%
  mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               ifelse(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA)))

Eklenen - if_else: Not dplyr 0.5 bir olduğu if_elsealternatif yerine olacaktır şekilde tanımlanan fonksiyon ifelseile if_else; ancak, if_elsedaha katı olduğundan ifelse(durumun her iki bacağının da aynı tipte olması gerekir) NA, bu durumda bu durumda değiştirilmesi gerekir NA_real_.

df %>%
  mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               if_else(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA_real_)))

Eklendi - case_when Bu soru gönderildiği için dplyr ekledi, case_whenbu yüzden başka bir alternatif olurdu:

df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
                            a == 0 | a == 1 | a == 4 | a == 3 |  c == 4 ~ 3,
                            TRUE ~ NA_real_))

Eklendi - aritmetik / na_if Değerler sayısalsa ve koşullar (sondaki NA'nın varsayılan değeri hariç), söz konusu durumda olduğu gibi, birbirini dışlarsa, her terim çarpılacak şekilde aritmetik bir ifade kullanabiliriz na_if0'ı NA ile değiştirmek için sonunda kullanarak istenen sonuç .

df %>%
  mutate(g = 2 * (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)) +
             3 * (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
         g = na_if(g, 0))

3
Bunun yerine mantık nedir, bunun yerine NAkoşulları karşılamayan satırların aynı kalmasını istiyorum?
Nazer

10
mutate(g = ifelse(condition1, 2, ifelse(condition2, 3, g))
G. Grothendieck

11
case_when sooooo güzel olduğunda ve aslında orada olduğunu anlamak çok uzun sürdü. Bu en basit dplyr öğreticilerde olması gerektiğini düşünüyorum, verilerin alt kümeleri için şeyler hesaplama ihtiyacına sahip olmak çok yaygın, ama yine de veri tam tutmak isteyen.
Javier Fajardo

55

Sorunu ele almak için başka daha iyi yollar istediğinizden, aşağıdakileri kullanmanın başka bir yolu data.table:

require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]

Koşullu ifadelerin sırasının gdoğru bir şekilde elde edilmesi için tersine çevrildiğini unutmayın . gİkinci ödev sırasında bile yapılmış bir kopya yok - yerinde değiştirildi .

Daha büyük verilerde , hem 'evet' hem de 'hayır' durumlarını değerlendirebildiğinden ve iç içe geçmenin IMHO'yu okuması / sürdürmesi zorlaşabileceğinden iç içe kullanmaktan daha iyi bir performansa sahip olacaktır .if-else


İşte nispeten daha büyük verilerle ilgili bir karşılaştırma:

# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT) 
# [1] 10000000        6
DF <- as.data.frame(DT)

DT_fun <- function(DT) {
    DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
    DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
    mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

BASE_fun <- function(DF) { # R v3.1.0
    transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

system.time(ans1 <- DT_fun(DT))
#   user  system elapsed 
#  2.659   0.420   3.107 

system.time(ans2 <- DPLYR_fun(DF))
#   user  system elapsed 
# 11.822   1.075  12.976 

system.time(ans3 <- BASE_fun(DF))
#   user  system elapsed 
# 11.676   1.530  13.319 

identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE

identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE

Bunun isteyebileceğiniz bir alternatif olup olmadığından emin değilim, ama umarım yardımcı olur.


4
Güzel kod parçası! G. Grotendieck'in cevabı işe yarıyor ve kısa, bu yüzden soruma cevap olarak bir tane seçtim, ama çözümünüz için teşekkür ederim. Ben de bu şekilde deneyeceğim.
rdatasculptor

Yana DT_funkendi giriş INPLACE değiştiriyorsa, kriter oldukça adil olmayabilir - ileri 2 yineleme aynı girişini almıyor ek olarak (çünkü zamanlama etki edebilecek bir DT$gzaten tahsis edilir?), Sonuç da geri yayar ans1ve bu nedenle olabilir ( R'ın iyileştirici Deems gerekli olur? emin değilim bu konuda ...) önlemek başka kopyasını oDPLYR_fun ve BASE_funihtiyaç yapmak için?
Ken Williams

Sadece açık olmak gerekirse, bu data.tableçözüm harika olduğunu düşünüyorum ve data.tabletablolarda işlemler için gerçekten hıza ihtiyaç duyduğum her yerde kullanıyorum ve C ++ 'a kadar gitmek istemiyorum. Yine de, yerinde değişiklikler konusunda gerçekten dikkatli olmanızı gerektirir!
Ken Williams

Ben data.table daha düzenli şeyler için alışmaya çalışıyorum, ve bu oldukça yaygın bir kullanım örneği bu data.table hem okunması kolay hem de daha verimli örneklerden biridir. Kelime dağarcığımda daha düzenli gelişmek istememin temel nedeni kendim ve diğerleri için okunabilirliktir, ancak bu durumda data.table kazançları gibi görünüyor.
Paul McMurdie

38

dplyr şimdi bir vectorized sunan bir işleve case_whensahiptir. Sözdizimi, mosaic:::derivedFactorstandart dplyr yolunda değişkenlere erişemediğinizden ve NA modunu bildirmeniz gerektiğinden biraz gariptir , ancak bundan daha hızlıdır mosaic:::derivedFactor.

df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                     a %in% c(0,1,3,4) | c == 4 ~ 3L, 
                     TRUE~as.integer(NA)))

DÜZENLEME:dplyr::case_when() Paketin 0.7.0 sürümünden önceki bir sürümü kullanıyorsanız , değişken adlarından önce ' .$' (örn . .$a == 1İçine yazma case_when) koymanız gerekir .

Kıyaslama : Kıyaslama için (Arun'un gönderisindeki işlevleri yeniden kullanma) ve örnek boyutunu küçültme için:

require(data.table) 
require(mosaic) 
require(dplyr)
require(microbenchmark)

set.seed(42) # To recreate the dataframe
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)

DPLYR_case_when <- function(DF) {
  DF %>%
  mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                       a %in% c(0,1,3,4) | c==4 ~ 3L, 
                       TRUE~as.integer(NA)))
}

DT_fun <- function(DT) {
  DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
  DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
  mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
                    ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

mosa_fun <- function(DF) {
  mutate(DF, g = derivedFactor(
    "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
    "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
    .method = "first",
    .default = NA
  ))
}

perf_results <- microbenchmark(
  dt_fun <- DT_fun(copy(DT)),
  dplyr_ifelse <- DPLYR_fun(copy(DF)),
  dplyr_case_when <- DPLYR_case_when(copy(DF)),
  mosa <- mosa_fun(copy(DF)),
  times = 100L
)

Bu şunları verir:

print(perf_results)
Unit: milliseconds
           expr        min         lq       mean     median         uq        max neval
         dt_fun   1.391402    1.560751   1.658337   1.651201   1.716851   2.383801   100
   dplyr_ifelse   1.172601    1.230351   1.331538   1.294851   1.390351   1.995701   100
dplyr_case_when   1.648201    1.768002   1.860968   1.844101   1.958801   2.207001   100
           mosa 255.591301  281.158350 291.391586 286.549802 292.101601 545.880702   100

case_whenAyrıca şu şekilde de yazılabilir:df %>% mutate(g = with(., case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, a %in% c(0,1,3,4) | c==4 ~ 3L, TRUE ~ NA_integer_)))
G. Grothendieck

3
Mikrosaniye / milisaniye / gün cinsinden bu kriter nedir? Bu kıyaslama, sağlanan ölçüm birimi olmadan anlamsızdır. Ayrıca, 1e6'dan daha küçük bir veri kümesinde karşılaştırmalı işaretleme, ölçeklenmediğinden de anlamsızdır.
David Arenburg

3
Pls cevabınızı değiştirir .$, dplyr'in yeni versiyonunda artık ihtiyacınız yok
Amit Kohli

14

derivedFactorDan fonksiyon mosaicpaketine bu işlemek için tasarlanmış gibi görünüyor. Bu örneği kullanmak şöyle görünecektir:

library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
     "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
     "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
     .method = "first",
     .default = NA
     ))

(Sonucunun bir faktör yerine sayısal olmasını istiyorsanız, derivedFactorbir as.numericaramayı tamamlayabilirsiniz .)

derivedFactor isteğe bağlı sayıda koşul için de kullanılabilir.


4
@hadley bunu dplyr için varsayılan sözdizimi yapmalıdır. Yuvalanmış "ifelse" ifadelerine ihtiyaç duyulması, paketin en kötü yanıdır, bu da diğer işlevlerin çok iyi olması nedeniyle böyledir
rsoren

Ayrıca .asFactor = Fseçeneği kullanarak veya derivedVariableaynı paketteki (benzer) işlevini kullanarak sonucun bir faktör olmasını engelleyebilirsiniz .
Jake Fisher

Görünüşe göre recodedplyr 0.5 bunu yapacak. Henüz araştırmadım. Bkz. Blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher

12

case_when şu anda SQL tarzı davanın oldukça temiz bir uygulaması:

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame") -> df


df %>% 
    mutate( g = case_when(
                a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 )     ~   2,
                a == 0 | a == 1 | a == 4 |  a == 3 | c == 4       ~   3
))

Dplyr 0.7.4 kullanma

Kılavuz: http://dplyr.tidyverse.org/reference/case_when.html

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.