R에서 동등한 Case 문


92

필드 중 하나에 일반적으로 7-8 개의 값이있는 데이터 프레임에 변수가 있습니다. 데이터 프레임 내의 새 변수 내에서 3 개 또는 4 개의 새 범주를 축소하고 싶습니다. 가장 좋은 방법은 무엇입니까?

SQL과 같은 도구를 사용했지만 R에서 이것을 공격하는 방법을 잘 모르겠다면 CASE 문을 사용합니다.

당신이 제공 할 수있는 어떤 도움이라도 대단히 감사 할 것입니다!


a) 정수, 숫자, 범주 또는 문자열입니까? dput()b) base R, dplyr, data.table, tidyverse ...를 사용하여 예제 데이터 스 니펫을 게시하십시오 .
smci

답변:


39

case_when()2016 년 5 월에 dplyr에 추가 된은 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 (2017 년 6 월 9 일 릴리스)부터는 .$더 이상 필요하지 않습니다. 이 답변이 원래 작성되었을 때 그랬습니다.
Evan Cortens 17.12.07

훌륭한 솔루션. 두 진술이 모두 사실이라면. 두 번째가 첫 번째를 덮어 쓰나요?
JdP

1
@JdP SQL의 CASE WHEN과 똑같이 작동하므로 명령문이 순서대로 평가되고 결과가 첫 번째 TRUE 문입니다. (그래서 위의 예에서, 나는 기본값 역할을하는 말에 TRUE에 넣었습니다.)
에반 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
좋은 대답입니다. 예전 이름과 새 이름을 가진 레벨에 대한 인수로 목록을 사용할 수 있다는 것을 잊었습니다. 내 솔루션은 레벨 순서를 똑바로 유지하는 것에 달려 있으므로 이것이 더 좋습니다.
아론은 스택 오버플로를 왼쪽

또한 x마지막 줄에 changelevels있는가?
아론은 스택 오버플로를 왼쪽

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하여 데이터 프레임에 열 을 추가하는 것은 쉽습니다 .

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

나는이 방법을 좋아한다. 그러나 어떤 상황에서는 이것이 필수 불가결 한 '다른'구현이
있습니까?

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
난 그냥 텍스트에서의 매개 변수를 구문 분석하는 기능 지원하지 않습니다
해들리

예,하지만 더 나은 버전을 작성한 사람이 있는지 알고 있습니까? sos::findFn("recode")발견은 doBy::recodeVar, epicalc::recode, memisc::recode,하지만 난 ... 자세히 그들을 못 봤어
벤 Bolker

5

나는 이것들 중 어느 것도 좋아하지 않으며 독자 또는 잠재적 인 사용자에게 명확하지 않습니다. 저는 익명의 함수를 사용합니다. 구문은 case 문처럼 매끄럽지 않지만 평가는 case 문과 비슷하며 그렇게 고통스럽지 않습니다. 이것은 또한 변수가 정의 된 위치 내에서 평가한다고 가정합니다.

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)에만 작동 xy스칼라는; 벡터의 경우 원래 질문에서와 같이 중첩 된 ifelse문이 필요합니다.
아론은 스택 오버플로를 왼쪽

4

나는 당신이 언급하는 경우에 사용하고 있습니다 switch(). 제어문처럼 보이지만 실제로는 함수입니다. 식이 평가되고이 값에 따라 목록의 해당 항목이 반환됩니다.

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]

열이 char 대신 요인으로 mutate에서 나올 수 있다면 보너스 포인트! 일치하지 않는 모든 행을 포착하는 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()(빠른 경우)를 사용하여 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.