सशर्त उत्परिवर्तन के लिए dplyr पैकेज का उपयोग किया जा सकता है?


178

क्या उत्परिवर्तन सशर्त (निश्चित स्तंभ मानों के आधार पर) होने पर इस्तेमाल किया जा सकता है?

यह उदाहरण यह दिखाने में मदद करता है कि मेरा क्या मतलब है।

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

मैं dplyr पैकेज का उपयोग करके अपनी समस्या का हल खोजने की उम्मीद कर रहा था (और हां मुझे पता है कि यह कोड नहीं है जो काम करना चाहिए, लेकिन मुझे लगता है कि यह एक नया स्तंभ जी बनाने के लिए उद्देश्य स्पष्ट करता है):

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

मैं जिस कोड की तलाश कर रहा हूं उसका परिणाम इस विशेष उदाहरण में होना चाहिए:

  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

क्या किसी को इस बारे में अंदाजा है कि यह कैसे किया जाता है? यह डेटा फ़्रेम केवल एक उदाहरण है, मैं जिस डेटा फ़्रेम से काम कर रहा हूं वह बहुत बड़ा है। इसकी गति के कारण मैंने dplyr का उपयोग करने की कोशिश की, लेकिन शायद इस समस्या को संभालने के लिए अन्य, बेहतर तरीके हैं?


2
हां, लेकिन dplyr::case_when()ifelse

जवाबों:


216

उपयोग 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)))

जोड़ा - if_else: ध्यान दें कि dplyr 0.5 में एक if_elseफ़ंक्शन परिभाषित किया गया है, इसलिए इसके ifelseसाथ बदलने के लिए एक विकल्प होगा if_else; हालाँकि, ध्यान दें कि चूंकि if_elseसख्त है ifelse(हालत के दोनों पैर एक ही प्रकार के होने चाहिए) इसलिए NAउस स्थिति में इसे बदलना होगा 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_)))

जोड़ा गया - case_when जब से यह सवाल पोस्ट किया गया था dplyr ने जोड़ा है case_whenइसलिए एक और विकल्प होगा:

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

जोड़ा - अंकगणित / na_if यदि मान संख्यात्मक हैं और स्थितियां (अंत में NA के डिफ़ॉल्ट मान को छोड़कर) परस्पर अनन्य हैं, जैसा कि सवाल में है, तो हम एक अंकगणितीय अभिव्यक्ति का उपयोग कर सकते हैं जैसे कि प्रत्येक शब्द को गुणा किया जाता है। na_ifएनए के साथ 0 को बदलने के लिए अंत में उपयोग करके वांछित परिणाम ।

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
क्या तर्क है अगर इसके बजाय NA, मैं उन पंक्तियों को चाहता हूं जो शर्तों को पूरा नहीं करते हैं बस एक ही रहने के लिए?
नज़ीर

10
mutate(g = ifelse(condition1, 2, ifelse(condition2, 3, g))
जी। ग्रोथेंडिक

11
case_when बहुत सुंदर है, और मुझे यह पता लगाने में बहुत देर लगी कि यह वास्तव में था। मुझे लगता है कि यह सबसे सरल despr ट्यूटोरियल में होना चाहिए, डेटा के सबसेट के लिए सामान की गणना करने की आवश्यकता होना बहुत आम है, लेकिन फिर भी डेटा को पूर्ण रखना चाहते हैं।
जेवियर फाजार्डो

55

जब से आप समस्या को संभालने के अन्य बेहतर तरीकों के बारे में पूछते हैं, यहाँ एक और तरीका है 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]

ध्यान दें कि सशर्त बयानों का क्रम gसही ढंग से प्राप्त करने के लिए उलटा है। gदूसरे असाइनमेंट के दौरान भी इसमें कोई कॉपी नहीं की गई है - इसे इन-प्लेस में बदल दिया गया है

बड़े डेटा पर यह नेस्टेड का उपयोग करने की तुलना में बेहतर प्रदर्शन होगा if-else, क्योंकि यह 'हां' और 'नहीं' दोनों मामलों का मूल्यांकन कर सकता है , और नेस्टिंग को IMHO पढ़ने / बनाए रखने के लिए कठिन हो सकता है।


यहाँ अपेक्षाकृत बड़े डेटा पर एक बेंचमार्क है:

# 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

यकीन नहीं होता कि यह एक ऐसा विकल्प है जो आपने माँगा है, लेकिन मुझे आशा है कि इससे मदद मिलेगी।


4
कोड का अच्छा टुकड़ा! जी। ग्रोटेंडिएक का जवाब काम करता है और कम है इसलिए मैंने उस एक को अपने प्रश्न के उत्तर के रूप में चुना, लेकिन मैं आपके समाधान के लिए धन्यवाद देता हूं। मुझे यकीन है कि यह इस तरह से भी कोशिश करेगा।
rdatasculptor

चूँकि DT_fun, इसके इनपुट की गति को संशोधित किया जा रहा है, इसलिए बेंचमार्क काफी उचित नहीं हो सकता है - इसके अलावा आगे से 2 पुनरावृत्ति से एक ही इनपुट प्राप्त नहीं करना (जो कि DT$gपहले से आवंटित होने के बाद से समय को प्रभावित कर सकता है?), परिणाम भी वापस प्रचारित ans1हो सकता है और इसलिए हो सकता है अगर आर के अनुकूलक समझे यह आवश्यक? सुनिश्चित नहीं हैं कि इस पर ...) से बचने के लिए एक और प्रतिलिपि कि DPLYR_funऔर BASE_funबनाने के लिए की जरूरत है?
केन विलियम्स

बस स्पष्ट होने के लिए, मुझे लगता है कि यह data.tableसमाधान बहुत अच्छा है, और मैं data.tableजहां भी उपयोग करता हूं मुझे तालिकाओं पर संचालन के लिए गति की आवश्यकता है और मैं C ++ के लिए सभी तरह से नहीं जाना चाहता हूं। यह जगह में संशोधन के बारे में वास्तव में सावधान रहने की आवश्यकता है, हालांकि!
केन विलियम्स

मैं data.table से अधिक tidyverse सामान के लिए उपयोग करने की कोशिश कर रहा हूं, और यह एक बहुत ही सामान्य उपयोग-मामले के उन उदाहरणों में से एक है, जो data.table दोनों पढ़ना आसान है और अधिक कुशल है। मेरी शब्दावली में अधिक स्पष्टता विकसित करने का मेरा मुख्य कारण खुद और दूसरों के लिए पठनीयता है, लेकिन इस मामले में यह डेटा की तरह लगता है। जीतता है।
पॉल मैकमुर्डी

38

dplyr में अब एक फ़ंक्शन case_whenहै जो एक वेक्टर प्रदान करता है यदि। सिंटैक्स थोड़ा अजीब है, mosaic:::derivedFactorक्योंकि आप मानक डापर तरीके से चर का उपयोग नहीं कर सकते हैं, और एनए के मोड को घोषित करने की आवश्यकता है, लेकिन यह तुलना में काफी तेज है 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)))

संपादित करें: यदि आप dplyr::case_when()पैकेज के संस्करण 0.7.0 से पहले उपयोग कर रहे हैं , तो आपको ' .$' (जैसे .$a == 1अंदर लिखें case_when) चर नामों को पूर्ववर्ती करने की आवश्यकता है ।

बेंचमार्क : बेंचमार्क के लिए (अरुण के पद से पुन: उपयोग कार्य) और नमूना आकार को कम करना:

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
)

यह देता है:

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_whenके रूप में भी लिखा जा सकता है: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_)))
जी। Grothendieck

3
क्या यह बेंचमार्क माइक्रोसेकंड / मिलीसेकंड / दिनों में है, क्या? मापक इकाई प्रदान किए बिना यह बेंचमार्क अर्थहीन है। इसके अलावा, 1e6 से छोटे डेटा सेट पर बेंच-मार्किंग भी अर्थहीन है क्योंकि यह कोई पैमाना नहीं है।
डेविड अरेनबर्ग

3
Pls अपने उत्तर को संशोधित करें, आपको .$dplyr के नए संस्करण में अब और ज़रूरत नहीं है
अमित कोहली

14

derivedFactorसे समारोह mosaicपैकेज इस संभाल करने के लिए तैयार किया जा रहा है। इस उदाहरण का उपयोग करना, ऐसा लगेगा:

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

(यदि आप चाहते हैं कि परिणाम कारक के बजाय संख्यात्मक हो, तो आप कॉल derivedFactorमें लपेट सकते as.numericहैं।)

derivedFactor सशर्त की एक मनमानी संख्या के लिए भी इस्तेमाल किया जा सकता है।


4
@hadley यह dplyr के लिए डिफ़ॉल्ट सिंटैक्स बनाना चाहिए। नेस्टेड "ifelse" बयान की आवश्यकता पैकेज का सबसे खराब हिस्सा है, जो मुख्य रूप से मामला है क्योंकि अन्य कार्य बहुत अच्छे हैं
rsoren

आप .asFactor = Fविकल्प का उपयोग करके या derivedVariableउसी पैकेज में (समान) फ़ंक्शन का उपयोग करके कारक होने से भी रोक सकते हैं ।
जेक फिशर

ऐसा लगता है कि recodedplyr से 0.5 यह करेगा। मैंने इसकी जांच अभी तक नहीं की है। ब्लॉग
जेक फिशर

12

case_when अब जब SQL- स्टाइल केस का एक बहुत अच्छा कार्यान्वयन है:

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

डॉपलर का उपयोग कर 0.7.4

मैनुअल: http://dplyr.tidyverse.org/reference/case_when.html

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.