คำชี้แจงกรณีเทียบเท่าใน R


92

ฉันมีตัวแปรในดาต้าเฟรมโดยที่หนึ่งในฟิลด์มักจะมี 7-8 ค่า ฉันต้องการจัดเรียงหมวดหมู่ใหม่ 3 หรือ 4 หมวดภายในตัวแปรใหม่ภายในดาต้าเฟรม แนวทางที่ดีที่สุดคืออะไร?

ฉันจะใช้คำสั่ง CASE ถ้าฉันอยู่ในเครื่องมือคล้าย SQL แต่ไม่แน่ใจว่าจะโจมตีสิ่งนี้ใน R ได้อย่างไร

ความช่วยเหลือใด ๆ ที่คุณสามารถให้ได้จะได้รับการชื่นชมมาก!


ก) เป็นจำนวนเต็มตัวเลขหมวดหมู่หรือสตริง? กรุณาโพสต์ตัวอย่างข้อมูลโดยใช้dput()b) คุณต้องการโซลูชันในฐาน R, dplyr, data.table, tidyverse ... ?
smci

คำตอบ:


39

case_when()ซึ่งเพิ่มลงใน dplyr ในเดือนพฤษภาคม 2559 แก้ปัญหานี้ในลักษณะที่คล้ายกับmemisc::cases().

ตัวอย่างเช่น:

library(dplyr)
mtcars %>% 
  mutate(category = case_when(
    .$cyl == 4 & .$disp < median(.$disp) ~ "4 cylinders, small displacement",
    .$cyl == 8 & .$disp > median(.$disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

ณ dplyr 0.7.0

mtcars %>% 
  mutate(category = case_when(
    cyl == 4 & disp < median(disp) ~ "4 cylinders, small displacement",
    cyl == 8 & disp > median(disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

4
คุณไม่จำเป็นต้อง.$อยู่ด้านหน้าของแต่ละคอลัมน์
เคท

1
ใช่ ณ วันที่ dplyr 0.7.0 (เผยแพร่เมื่อ 9 มิถุนายน 2017) .$ไม่จำเป็นอีกต่อไป ในขณะที่คำตอบนี้ถูกเขียนขึ้นในตอนแรกมันคือ
Evan Cortens

ทางออกที่ดี ถ้าข้อความทั้งสองเป็นจริง อันที่สองเขียนทับอันแรกหรือเปล่า
JdP

1
@JdP มันทำงานเหมือนกับ CASE WHEN ใน SQL ดังนั้นคำสั่งจึงได้รับการประเมินตามลำดับและผลลัพธ์คือคำสั่ง TRUE แรก (ในตัวอย่างด้านบนฉันได้ใส่ TRUE ไว้ท้ายซึ่งทำหน้าที่เป็นค่าเริ่มต้น)
Evan Cortens

ฉันชอบคำตอบนี้เพราะไม่เหมือนswitchตรงที่ช่วยให้คุณสร้างลำดับของนิพจน์แทนคีย์สำหรับเคสได้
Dannid

27

ดูcasesฟังก์ชั่นจากmemiscแพ็คเกจ ใช้ฟังก์ชันเคสด้วยสองวิธีที่แตกต่างกันในการใช้งาน จากตัวอย่างในแพ็คเกจ:

z1=cases(
    "Condition 1"=x<0,
    "Condition 2"=y<0,# only applies if x >= 0
    "Condition 3"=TRUE
    )

ที่ไหนxและyเป็นเวกเตอร์สองตัว

อ้างอิง: แพคเกจ memisc , กรณีตัวอย่าง


24

หากคุณได้รับfactorคุณสามารถเปลี่ยนระดับโดยวิธีมาตรฐาน:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
             stringsAsFactors = FALSE)
df$type <- factor(df$name) # First step: copy vector and make it factor
# Change levels:
levels(df$type) <- list(
    animal = c("cow", "pig"),
    bird = c("eagle", "pigeon")
)
df
#     name   type
# 1    cow animal
# 2    pig animal
# 3  eagle   bird
# 4 pigeon   bird

คุณสามารถเขียนฟังก์ชันง่ายๆเป็นกระดาษห่อหุ้ม:

changelevels <- function(f, ...) {
    f <- as.factor(f)
    levels(f) <- list(...)
    f
}

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = TRUE)

df$type <- changelevels(df$name, animal=c("cow", "pig"), bird=c("eagle", "pigeon"))

2
คำตอบที่ดี ฉันลืมไปว่าคุณสามารถใช้รายการเป็นอาร์กิวเมนต์ในระดับที่มีชื่อเก่าและชื่อใหม่เช่นนั้น วิธีแก้ปัญหาของฉันขึ้นอยู่กับการรักษาลำดับของระดับให้ตรงดังนั้นวิธีนี้จะดีกว่า
Aaron ออกจาก Stack Overflow

นอกจากนี้xในบรรทัดสุดท้ายควรเป็นchangelevelsอย่างไร
Aaron ออกจาก Stack Overflow

22

นี่คือวิธีการใช้switchคำสั่ง:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = FALSE)
df$type <- sapply(df$name, switch, 
                  cow = 'animal', 
                  pig = 'animal', 
                  eagle = 'bird', 
                  pigeon = 'bird')

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

ข้อเสียอย่างหนึ่งคือคุณต้องเขียนชื่อหมวดหมู่ ( animalฯลฯ ) สำหรับแต่ละรายการ มันสะดวกกว่าในเชิงไวยากรณ์ที่จะสามารถกำหนดหมวดหมู่ของเราได้ดังต่อไปนี้ (ดูคำถามที่คล้ายกันมากจะเพิ่มคอลัมน์ในกรอบข้อมูลใน R ได้อย่างไร )

myMap <- list(animal = c('cow', 'pig'), bird = c('eagle', 'pigeon'))

และเราต้องการ "กลับด้าน" การแมปนี้ ฉันเขียนฟังก์ชัน invMap ของตัวเอง:

invMap <- function(map) {
  items <- as.character( unlist(map) )
  nams <- unlist(Map(rep, names(map), sapply(map, length)))
  names(nams) <- items
  nams
}

จากนั้นกลับแผนที่ด้านบนดังนี้:

> invMap(myMap)
     cow      pig    eagle   pigeon 
"animal" "animal"   "bird"   "bird" 

จากนั้นใช้สิ่งนี้เพื่อเพิ่มtypeคอลัมน์ใน data-frame:

df <- transform(df, type = invMap(myMap)[name])

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

19

ฉันไม่เห็นข้อเสนอสำหรับ "สวิตช์" ตัวอย่างรหัส (เรียกใช้):

x <- "three"
y <- 0
switch(x,
       one = {y <- 5},
       two = {y <- 12},
       three = {y <- 432})
y

15

Imho รหัสที่ตรงไปตรงมาและเป็นสากลที่สุด:

dft=data.frame(x = sample(letters[1:8], 20, replace=TRUE))
dft=within(dft,{
    y=NA
    y[x %in% c('a','b','c')]='abc'
    y[x %in% c('d','e','f')]='def'
    y[x %in% 'g']='g'
    y[x %in% 'h']='h'
})

ฉันชอบวิธีนี้ อย่างไรก็ตามมีการใช้งาน 'อื่น ๆ ' หรือไม่เนื่องจากในบางสถานการณ์สิ่งนี้จะขาดไม่ได้
T.Fung

2
@ T.Fung y = 'else'คุณสามารถเปลี่ยนสายแรกที่จะ องค์ประกอบที่ไม่เป็นไปตามเงื่อนไขเพิ่มเติมจะไม่เปลี่ยนแปลง
Gregory Demin

7

มีswitchคำสั่งหนึ่งแต่ดูเหมือนว่าฉันไม่สามารถทำให้มันได้ผลอย่างที่คิด เนื่องจากคุณไม่ได้ให้ตัวอย่างฉันจะสร้างโดยใช้ตัวแปรปัจจัย:

 dft <-data.frame(x = sample(letters[1:8], 20, replace=TRUE))
 levels(dft$x)
[1] "a" "b" "c" "d" "e" "f" "g" "h"

หากคุณระบุหมวดหมู่ที่คุณต้องการตามลำดับที่เหมาะสมกับการกำหนดใหม่คุณสามารถใช้ปัจจัยหรือตัวแปรตัวเลขเป็นดัชนีได้:

c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x]
 [1] "def" "h"   "g"   "def" "def" "abc" "h"   "h"   "def" "abc" "abc" "abc" "h"   "h"   "abc"
[16] "def" "abc" "abc" "def" "def"

dft$y <- c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x] str(dft)
'data.frame':   20 obs. of  2 variables:
 $ x: Factor w/ 8 levels "a","b","c","d",..: 4 8 7 4 6 1 8 8 5 2 ...
 $ y: chr  "def" "h" "g" "def" ...

ฉันได้เรียนรู้ในภายหลังว่ามีฟังก์ชันสวิตช์สองแบบที่แตกต่างกัน ไม่ใช่ฟังก์ชั่นทั่วไป แต่คุณควรคิดว่ามันเป็นswitch.numericหรือswitch.character. หากอาร์กิวเมนต์แรกของคุณเป็น R 'ปัจจัย' คุณจะได้รับswitch.numericพฤติกรรมซึ่งมีแนวโน้มที่จะก่อให้เกิดปัญหาเนื่องจากคนส่วนใหญ่เห็นปัจจัยที่แสดงเป็นตัวอักษรและตั้งสมมติฐานที่ไม่ถูกต้องว่าฟังก์ชันทั้งหมดจะประมวลผลในลักษณะดังกล่าว


6

คุณสามารถใช้รหัสใหม่จากแพ็คเกจรถยนต์:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]

11
ฉันไม่สามารถรองรับฟังก์ชันที่แยกวิเคราะห์พารามิเตอร์จากข้อความได้
hadley

ใช่ แต่คุณรู้หรือไม่ว่ามีใครเขียนเวอร์ชันที่ดีกว่านี้หรือไม่? sos::findFn("recode")พบdoBy::recodeVar, epicalc::recode, memisc::recodeแต่ฉันไม่ได้มองไปที่พวกเขาในรายละเอียด ...
เบน Bolker

5

ฉันไม่ชอบสิ่งเหล่านี้พวกเขาไม่ชัดเจนสำหรับผู้อ่านหรือผู้ใช้ที่มีศักยภาพ ฉันแค่ใช้ฟังก์ชันที่ไม่ระบุตัวตนไวยากรณ์ไม่เนียนเท่าประโยคบอกเล่า แต่การประเมินจะคล้ายกับประโยคบอกเล่าและไม่เจ็บปวดขนาดนั้น นอกจากนี้ยังถือว่าการประเมินของคุณอยู่ในจุดที่กำหนดตัวแปรของคุณ

result <- ( function() { if (x==10 | y< 5) return('foo') 
                         if (x==11 & y== 5) return('bar')
                        })()

สิ่งเหล่านี้ () ทั้งหมดมีความจำเป็นในการใส่และประเมินฟังก์ชันนิรนาม


6
1) ส่วนของฟังก์ชันไม่จำเป็น result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' )คุณก็สามารถทำ 2) ใช้งานได้ก็ต่อเมื่อxและyเป็นสเกลาร์ สำหรับเวกเตอร์เช่นเดียวกับในคำถามเดิมifelseคำสั่งซ้อนจะเป็นสิ่งที่จำเป็น
Aaron ออกจาก Stack Overflow

4

switch()ฉันใช้ในกรณีที่คุณหมาย ดูเหมือนคำสั่งควบคุม แต่จริงๆแล้วมันเป็นฟังก์ชัน นิพจน์ได้รับการประเมินและอิงตามค่านี้รายการที่เกี่ยวข้องในรายการจะถูกส่งกลับ

สวิตช์ทำงานในสองวิธีที่แตกต่างกันขึ้นอยู่กับว่าอาร์กิวเมนต์แรกประเมินเป็นสตริงอักขระหรือตัวเลข

สิ่งต่อไปนี้เป็นตัวอย่างสตริงง่ายๆที่ช่วยแก้ปัญหาของคุณเพื่อยุบหมวดหมู่เก่าเป็นหมวดหมู่ใหม่

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

newCat <- switch(EXPR = category,
       cat1   = catX,
       cat2   = catX,
       cat3   = catY,
       cat4   = catY,
       cat5   = catZ,
       cat6   = catZ,
       "not available")

3

หากคุณต้องการมีไวยากรณ์เหมือน sql คุณสามารถใช้ประโยชน์จากsqldfแพ็คเกจได้ ฟังก์ชันที่จะใช้ยังเป็นชื่อsqldfและไวยากรณ์เป็นดังนี้

sqldf(<your query in quotation marks>)

2

คำชี้แจงกรณีอาจไม่ใช่แนวทางที่ถูกต้องที่นี่ หากนี่เป็นปัจจัยที่มีแนวโน้มเพียงแค่ตั้งระดับของปัจจัยให้เหมาะสม

สมมติว่าคุณมีตัวประกอบที่มีตัวอักษร A ถึง E แบบนี้

> a <- factor(rep(LETTERS[1:5],2))
> a
 [1] A B C D E A B C D E
Levels: A B C D E

ในการเข้าร่วมระดับ B และ C และตั้งชื่อว่า BC เพียงแค่เปลี่ยนชื่อของระดับเหล่านั้นเป็น BC

> levels(a) <- c("A","BC","BC","D","E")
> a
 [1] A  BC BC D  E  A  BC BC D  E 
Levels: A BC D E

ผลลัพธ์เป็นไปตามที่ต้องการ


2

การผสมplyr::mutate และการ dplyr::case_whenทำงานสำหรับฉันและสามารถอ่านได้

iris %>%
plyr::mutate(coolness =
     dplyr::case_when(Species  == "setosa"     ~ "not cool",
                      Species  == "versicolor" ~ "not cool",
                      Species  == "virginica"  ~ "super awesome",
                      TRUE                     ~ "undetermined"
       )) -> testIris
head(testIris)
levels(testIris$coolness)  ## NULL
testIris$coolness <- as.factor(testIris$coolness)
levels(testIris$coolness)  ## ok now
testIris[97:103,4:6]

คะแนนโบนัสหากคอลัมน์สามารถกลายพันธุ์เป็นตัวประกอบแทนถ่าน! บรรทัดสุดท้ายของคำสั่ง case_when ซึ่งจับแถวที่ไม่ตรงกันทั้งหมดมีความสำคัญมาก

     Petal.Width    Species      coolness
 97         1.3  versicolor      not cool
 98         1.3  versicolor      not cool  
 99         1.1  versicolor      not cool
100         1.3  versicolor      not cool
101         2.5  virginica     super awesome
102         1.9  virginica     super awesome
103         2.1  virginica     super awesome

2

คุณสามารถใช้baseฟังก์ชันmergeนี้สำหรับงานการรีแมปในลักษณะเคส:

df <- data.frame(name = c('cow','pig','eagle','pigeon','cow','eagle'), 
                 stringsAsFactors = FALSE)

mapping <- data.frame(
  name=c('cow','pig','eagle','pigeon'),
  category=c('mammal','mammal','bird','bird')
)

merge(df,mapping)
# name category
# 1    cow   mammal
# 2    cow   mammal
# 3  eagle     bird
# 4  eagle     bird
# 5    pig   mammal
# 6 pigeon     bird

1

ในขณะที่ data.table v1.13.0คุณสามารถใช้ฟังก์ชันfcase()(fast-case) เพื่อดำเนินการแบบ SQL CASE(เช่นเดียวกับdplyr::case_when()):

require(data.table)

dt <- data.table(name = c('cow','pig','eagle','pigeon','cow','eagle'))
dt[ , category := fcase(name %in% c('cow', 'pig'), 'mammal',
                        name %in% c('eagle', 'pigeon'), 'bird') ]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.