การกลายพันธุ์หลายคอลัมน์แบบไดนามิกในขณะที่การปรับอากาศในแถวที่เฉพาะเจาะจง


11

ฉันรู้ว่ามีคำถามที่คล้ายกันอยู่หลายแห่งที่นี่ แต่ดูเหมือนจะไม่มีใครตอบคำถามที่แม่นยำที่ฉันมี

set.seed(4)
df = data.frame(
  Key = c("A", "B", "A", "D", "A"),
  Val1 = rnorm(5),
  Val2 = runif(5),
  Val3 = 1:5
)

ฉันต้องการ zeroise ค่าของคอลัมน์ค่าสำหรับแถวที่ Key == "A" ชื่อคอลัมน์ถูกอ้างอิงผ่าน a grep:

cols = grep("Val", names(df), value = TRUE)

โดยปกติแล้วเพื่อให้ได้สิ่งที่ฉันต้องการในกรณีนี้ฉันจะใช้data.tableสิ่งนี้:

library(data.table)
df = as.data.table(df)
df[Key == "A", (cols) := 0]

และผลลัพธ์ที่ต้องการคือ:

  Key      Val1       Val2 Val3
1   A  0.000000 0.00000000    0
2   B -1.383814 0.55925762    2
3   A  0.000000 0.00000000    0
4   D  1.437151 0.05632773    4
5   A  0.000000 0.00000000    0

อย่างไรก็ตามในครั้งนี้ฉันต้องใช้dplyrเพราะฉันกำลังทำงานในโครงการของทีมที่ทุกคนใช้งาน ข้อมูลที่ฉันให้ไว้เป็นตัวอย่างและข้อมูลจริงของฉันคือ> 5m แถวที่มีคอลัมน์ค่า 16 คอลัมน์ที่จะอัปเดต ทางออกเดียวที่ฉันสามารถทำได้คือใช้mutate_atสิ่งนี้:

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

อย่างไรก็ตามดูเหมือนว่าข้อมูลจริงของฉันจะช้ามาก ฉันหวังว่าจะหาทางออกที่ดีกว่าและที่สำคัญกว่าคือเร็วกว่า

ฉันได้ลองใช้หลาย ๆ ชุดโดยmapไม่ใช้การ!!ใช้getและ:=(ซึ่งน่ารำคาญสามารถถูกหลอกลวงโดย:=ใน data.table) ฯลฯ แต่ฉันคิดว่าฉันเข้าใจว่างานเหล่านี้ไม่ลึกพอที่จะสร้างโซลูชันที่ถูกต้อง


6
ใช้เวลานานแค่ไหน? DF [DF $ คีย์ == "A", cols] <- 0 ฉันจะเห็นว่ามันช้าเพราะคุณกำลังเรียก ifelse และวนรอบคอลัมน์และแถว
StupidWolf

StupidWolf อันนี้เร็วมากกับข้อมูลของฉันในขณะที่กะทัดรัดและสวยงามมาก ขอบคุณ รู้สึกอิสระที่จะเพิ่มเป็นคำตอบหากคุณต้องการ
LiviusI

ตกลงฉันสามารถแสดงวิธีการแก้ปัญหาอื่นให้คุณได้รับรอบ ..
23826

คำตอบ:


9

ด้วยคำสั่ง dplyr นี้

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

คุณกำลังประเมินข้อความจริง df $ Key == "A", n คูณโดยที่ n = จำนวนคอลัมน์ที่คุณมี

วิธีแก้ไขอย่างหนึ่งคือการกำหนดแถวที่คุณต้องการเปลี่ยนแปลงล่วงหน้า:

idx = which(DF$Key=="A")
DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})

วิธีที่สะอาดกว่าและดีกว่าโดยชี้ให้เห็นอย่างถูกต้องโดย @IceCreamToucan (ดูความคิดเห็นด้านล่าง) คือการใช้ฟังก์ชั่นแทนที่ในขณะที่ส่งผ่านพารามิเตอร์เพิ่มเติม:

DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0)

เราสามารถทดสอบวิธีเหล่านี้ทั้งหมดและฉันคิดว่า dplyr และ data.table เปรียบเทียบกัน

#simulate data
set.seed(100)
Key = sample(LETTERS[1:3],1000000,replace=TRUE)
DF = as.data.frame(data.frame(Key,matrix(runif(1000000*10),nrow=1000000,ncol=10)))
DT = as.data.table(DF)

cols = grep("[35789]", names(DF), value = TRUE)

#long method
system.time(DF %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(DF$Key == "A", 0, x)))
user  system elapsed 
  0.121   0.035   0.156 

#old base R way
system.time(DF[idx,cols] <- 0)
   user  system elapsed 
  0.085   0.021   0.106 

#dplyr
# define function
func = function(){
       idx = which(DF$Key=="A")
       DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})
}
system.time(func())
user  system elapsed 
  0.020   0.006   0.026

#data.table
system.time(DT[Key=="A", (cols) := 0])
   user  system elapsed 
  0.012   0.001   0.013 
#replace with dplyr
system.time(DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0))
user  system elapsed 
  0.007   0.001   0.008

4
อาร์กิวเมนต์พิเศษในการกลายพันธุ์จะถูกประเมินหนึ่งครั้งและส่งผ่านเป็นพารามิเตอร์ของฟังก์ชันที่ให้ไว้ (คล้ายกับเช่น lapply) ดังนั้นคุณสามารถทำได้โดยไม่ต้องสร้างตัวแปร temp idx อย่างชัดเจนในขณะที่df %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

ขอบคุณที่ชี้นำ @IceCreamToucan ฉันไม่รู้ ใช่ฟังก์ชั่นแทนที่ดียิ่งขึ้นและซุ่มซ่ามน้อยกว่าฉัน ฉันจะรวมไว้ในคำตอบถ้าคุณไม่รังเกียจ? (เครดิตให้คุณแน่นอน)
StupidWolf

หลังจากทดสอบบนเครื่องของฉันดูเหมือนว่าreplaceวิธีการจะช้ากว่าidxวิธีดั้งเดิมของคุณเล็กน้อย
IceCreamToucan

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