R中的等效案例声明


87

我在一个数据帧中有一个变量,其中一个字段通常具有7-8个值。我想在数据框内的新变量中合并3个或4个新类别。最好的方法是什么?

如果我使用的是类似SQL的工具,但不知道如何在R中进行攻击,我将使用CASE语句。

您能提供的任何帮助将不胜感激!


a)它们是整数,数字,分类还是字符串?请使用dput()b)发布示例数据片段,是否要在R,dplyr,data.table,tidyverse ...中找到解决方案?
smci

Answers:


38

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
您不需要.$每列的前面。
kath

1
是的,从dplyr 0.7.0(2017年6月9日发布)开始,.$不再需要。这个答案最初是写的。
Evan Cortens

很好的解决方案。如果两个陈述都是正确的。第二个覆盖第一个吗?
JdP '18年

1
@JdP它的工作方式与SQL中的CASE WHEN一样,因此按顺序对语句进行求值,结果是第一个TRUE语句。(因此,在上面的示例中,我在最后输入了TRUE,它是默认值。)
Evan Cortens

我喜欢这个答案,因为与不同switch,它允许您创建一个表达式序列而不是案例的键。
丹妮德

27

看一下包装中的cases功能memisc。它通过两种不同的使用方式来实现案例功能。从包中的示例中:

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

其中xy是两个向量。

参考:memisc软件包案例示例


23

如果可以,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"))

1
好答案。我忘记了您可以使用列表作为带有这样的旧名称和新名称的级别的参数。我的解决方案取决于保持水平顺序顺畅的方法,因此这样做更好。
亚伦(Aaron)

另外,x最后一行的应该是changelevels吗?
亚伦(Aaron)

20

这是使用该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

16

我看不到“切换”的建议。代码示例(运行它):

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

14

恕我直言,最简单,通用的代码:

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.numericswitch.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 2011年

是的,但是您知道有人写过更好的版本吗?sos::findFn("recode")认定doBy::recodeVarepicalc::recodememisc::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语句将是必需的。
亚伦(Aaron)

4

在您指的是这些情况下,我正在使用switch()。它看起来像一个控制语句,但实际上,它是一个函数。对表达式进行求值,并基于该值返回列表中的相应项目。

开关以两种不同的方式工作,具体取决于第一个参数是字符串还是数字。

以下是一个简单的字符串示例,该示例解决了将旧类别折叠为新类别的问题。

对于字符串形式,在命名值之后有一个未命名的参数作为默认值。

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

3

如果您想使用类似sql的语法,则可以使用sqldfpackage。所使用的函数也是名称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::mutatedplyr::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]

如果该列可以从mut而不是char变为因素,则可获得加分!捕获所有未匹配的行的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()(快速使用)执行CASE类似SQL的操作(也类似于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') ]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.