สามารถใช้แพ็คเกจ 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 (และใช่ฉันรู้ว่านี่ไม่ใช่รหัสที่ควรใช้ แต่ฉันคิดว่ามันทำให้วัตถุประสงค์ชัดเจน) สำหรับการสร้างคอลัมน์ใหม่ g:

 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? เฟรมข้อมูลนี้เป็นเพียงตัวอย่างเฟรมข้อมูลที่ฉันจัดการมีขนาดใหญ่กว่ามาก เพราะความเร็วฉันพยายามใช้ dplyr แต่อาจมีวิธีอื่นที่ดีกว่าในการจัดการปัญหานี้


2
ใช่ แต่dplyr::case_when()ชัดเจนกว่าifelseมาก
smci

คำตอบ:


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(ขาทั้งสองข้างของเงื่อนไขจะต้องมีชนิดเดียวกัน) ดังนั้นในกรณีที่จะต้องถูกแทนที่ด้วยNANA_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 ด้วย NA

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ฉันต้องการแถวที่ไม่ตรงตามเงื่อนไขที่จะอยู่เหมือนกัน?
Nazer

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

11
case_when สวยมากและฉันใช้เวลานานมากในการคิดว่ามันอยู่ที่นั่นจริง ๆ ฉันคิดว่าสิ่งนี้ควรเป็นแบบฝึกหัด dplyr ที่ง่ายที่สุดมันเป็นเรื่องธรรมดามากที่จะมีความต้องการในการคำนวณสิ่งต่าง ๆ สำหรับชุดย่อยของข้อมูล แต่ยังคงต้องการทำให้ข้อมูลสมบูรณ์
Javier Fajardo

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
โค้ดที่ดี! คำตอบของ G. Grotendieck ทำงานได้และสั้นมากดังนั้นฉันจึงเลือกอันนั้นมาเป็นคำตอบสำหรับคำถามของฉัน แต่ฉันขอบคุณสำหรับการแก้ปัญหาของคุณ ฉันแน่ใจว่าจะลองแบบนี้เช่นกัน
rdatasculptor

เนื่องจากDT_funมีการปรับเปลี่ยนอินพุตเข้าแทนที่มาตรฐานอาจไม่ยุติธรรม - นอกจากจะไม่ได้รับอินพุตเดียวกันจากการทำซ้ำครั้งที่ 2 ไปข้างหน้า (ซึ่งอาจส่งผลกระทบต่อเวลาตั้งแต่DT$gจัดสรรแล้ว?) ผลลัพธ์จึงแพร่กระจายกลับไปans1และอาจ หากเครื่องมือเพิ่มประสิทธิภาพของ R เห็นว่าจำเป็นหรือไม่ไม่แน่ใจในเรื่องนี้ ... ) หลีกเลี่ยงสำเนาอื่นDPLYR_funและBASE_funจำเป็นต้องทำหรือไม่
Ken Williams

เพียงเพื่อให้ชัดเจนว่าฉันคิดว่าdata.tableวิธีนี้ดีมากและฉันใช้data.tableทุกที่ที่ฉันต้องการความเร็วในการทำงานบนโต๊ะและฉันไม่ต้องการไปจนถึง C ++ มันไม่จำเป็นต้องมีความระมัดระวังเกี่ยวกับการปรับเปลี่ยนในสถานที่ แต่!
Ken Williams

ฉันกำลังพยายามทำความคุ้นเคยกับข้อมูลที่เป็นระเบียบมากขึ้นจาก data.table และนี่เป็นหนึ่งในตัวอย่างของกรณีการใช้งานทั่วไปที่ data.table นั้นง่ายต่อการอ่านและมีประสิทธิภาพมากขึ้น เหตุผลหลักของฉันที่ต้องการพัฒนาความเป็นระเบียบเรียบร้อยมากขึ้นในคำศัพท์ของฉันคือการอ่านสำหรับฉันและคนอื่น ๆ แต่ในกรณีนี้ดูเหมือนว่า data.table จะชนะ
Paul McMurdie

38

dplyr ตอนนี้มีฟังก์ชั่นcase_whenที่ให้ vectorised ถ้า ไวยากรณ์นั้นแปลกเล็กน้อยเมื่อเทียบกับmosaic:::derivedFactorที่คุณไม่สามารถเข้าถึงตัวแปรในแบบ dplyr มาตรฐานและจำเป็นต้องประกาศโหมดของ NA แต่มันเร็วกว่า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_)))
G. Grothendieck

3
เกณฑ์มาตรฐานนี้เป็นไมโครวินาที / มิลลิวินาที / วันเป็นอย่างไร มาตรฐานนี้ไม่มีความหมายหากไม่มีหน่วยการวัดที่ให้ไว้ นอกจากนี้การทำเครื่องหมายบนม้านั่งบนชุดข้อมูลที่มีขนาดเล็กกว่า 1e6 ก็ไม่มีความหมายเช่นกันเนื่องจากไม่ได้ปรับขนาด
David Arenburg

3
โปรดแก้ไขคำตอบของคุณคุณไม่ต้องการ.$อีกต่อไปในเวอร์ชั่นใหม่ของ dplyr
Amit Kohli

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ฟังก์ชัน(คล้ายกัน) ในแพ็คเกจเดียวกัน
Jake Fisher

ดูเหมือนว่าrecodeจาก dplyr 0.5 จะทำเช่นนี้ ฉันยังไม่ได้ตรวจสอบเลย ดูblog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher

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

ใช้ dplyr 0.7.4

คู่มือ: http://dplyr.tidyverse.org/reference/case_when.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.