将一个变量的值分配给另外两个变量中的一个的条件赋值。

27

我想创建一个新变量,它的值根据其它变量的取值而定,其中的条件是基于其他仍然存在的变量。这里有一个使用虚假数据的玩具示例。

每一行数据框都代表一个学生。每个学生最多可以修读两门学科(subj1subj2),并且可以在每门学科中追求学位(“BA”)或专业(“MN”)。我的真实数据包括成千上万的学生,几种类型的学位,大约50门学科,以及每个学生最多可以有5个主修/副修。

df <- data.frame(
  ID = 1:20,
  subj1 = factor(c(
    "SCI", NA, "BUS", "ENG", "ENG", "SCI", "ENG", "BUS", "ENG",
    "ENG", "BUS", "ENG", "BUS", "BUS", "BUS", "SCI", "SCI", "BUS",
    "ENG", "BUS"
  )),
  degree1 = factor(rep(c("MN", NA, "BA", "MN", "BA"), c(1L, 1L, 3L, 2L, 13L))),
  subj2 = factor(c(
    "BUS", "ENG", NA, NA, "BUS", NA, "SCI", "ENG", NA, "ENG", "ENG",
    "BUS", "SCI", NA, "ENG", "BUS", "BUS", NA, "ENG", "ENG"
  )),
  degree2 = factor(c(
    "MN", "MN", NA, NA, "MN", NA, "BA", "MN", NA, "MN", "BA", "BA",
    "MN", NA, "BA", "MN", "MN", NA, "BA", "MN"
  ))
)
df
#>    ID subj1 degree1 subj2 degree2
#> 1   1   SCI      MN   BUS      MN
#> 2   2  <NA>    <NA>   ENG      MN
#> 3   3   BUS      BA  <NA>    <NA>
#> 4   4   ENG      BA  <NA>    <NA>
#> 5   5   ENG      BA   BUS      MN
#> 6   6   SCI      MN  <NA>    <NA>
#> 7   7   ENG      MN   SCI      BA
#> 8   8   BUS      BA   ENG      MN
#> 9   9   ENG      BA  <NA>    <NA>
#> 10 10   ENG      BA   ENG      MN
#> 11 11   BUS      BA   ENG      BA
#> 12 12   ENG      BA   BUS      BA
#> 13 13   BUS      BA   SCI      MN
#> 14 14   BUS      BA  <NA>    <NA>
#> 15 15   BUS      BA   ENG      BA
#> 16 16   SCI      BA   BUS      MN
#> 17 17   SCI      BA   BUS      MN
#> 18 18   BUS      BA  <NA>    <NA>
#> 19 19   ENG      BA   ENG      BA
#> 20 20   BUS      BA   ENG      MN

现在我想创建第六个变量df$major,如果subj1是学生的主修科目,则它等于subj1的值,或者如果subj2是主修科目,则等于subj2的值。主修科目是第一个学位为“BA”的科目。我尝试了以下代码:
df$major[df$degree1 == "BA"] = df$subj1
df$major[df$degree1 != "BA" & df$degree2 == "BA"] = df$subj2

很不幸,我收到了一个错误信息:

> df$major[df$degree1 == "BA"] = df$subj1
Error in df$major[df$degree1 == "BA"] = df$subj1 : 
  NAs are not allowed in subscripted assignments

我认为这意味着,如果至少有一行的赋值结果为NA,则无法使用向量化赋值。

我觉得我可能漏掉了一些基本的东西,但是上面的代码似乎是显而易见的事情,我也想不出其他的替代方案。


有些行的degree1和degree2都是BA。在这种情况下,major列应该是什么? - David Robinson
如果deg1="BA",那么subj1是主修专业。如果deg1!="BA" & deg2=="BA",那么subj2是主修专业。就我遇到的问题而言,选择主修专业的具体条件并不重要,更重要的是为什么这种分配方法会失败。 - eipi10
2个回答

36

您原来的赋值方法至少有两个问题。

1)使用下标赋值df$major[df$degree1 == "BA"] <-可能会出现问题。使用==可能会产生NA,这就是引发错误的原因。从?"[<-"中可知:“当替换(即在赋值的左侧使用索引)NA时,不会选择任何要替换的元素。由于不确定是否应该使用rhs的元素,只有当rhs值的长度为一时才允许这样做(因此两个解释将具有相同的结果)。 ”有很多方法可以解决这个问题,但我更喜欢使用which

df$major[which(df$degree1 == "BA")] <-

它们的区别在于 == 返回 TRUEFALSENA,而 which 返回一个对象中为 TRUE 的索引。

> df$degree1 == "BA"
 [1] FALSE    NA  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE

> which(df$degree1 == "BA")
 [1]  3  4  5  8  9 10 11 12 13 14 15 16 17 18 19 20

2) 当你进行下标赋值时,右侧需要合理地适应左侧(这是我理解的方式)。这可能意味着左右两侧长度相等,这似乎是你的例子所暗示的。因此,您还需要对赋值的右侧进行子集处理:

df$major[which(df$degree1 == "BA")] <- df$subj1[which(df$degree1 == "BA")]
我希望这能澄清为什么你最初的尝试会导致错误。
像 @DavidRobinson 建议的那样使用 ifelse,是这种类型的赋值的一个好方法。我对此的看法:
df$major2 <- ifelse(df$degree1 == "BA", df$subj1, ifelse(df$degree2 == "BA",
  df$subj2,NA))

这相当于

df$major[which(df$degree1 == "BA")] <- df$subj1[which(df$degree1 == "BA")]
df$major[which(df$degree1 != "BA" & df$degree2 == "BA")] <- 
  df$subj2[which(df$degree1 != "BA" & df$degree2 == "BA")]

根据嵌套的ifelse语句深度,另一种方法可能更适合您的实际数据。


编辑:

我原本想写第三个导致原始代码失败的原因(即尚未分配df$major),但我在没有这样做的情况下就能使它运行。尽管如此,我记得过去曾遇到过这个问题。您正在使用哪个版本的R?(对于我来说是2.15.0)。如果使用ifelse()方法,则不需要此步骤。在使用[时,您的解决方案是可行的,不过我会选择

df$major <- NA

要获取主题的字符值,而不是因子级别索引,请使用as.character()(对于因子来说等同于并调用levels(x)[x]):

df$major[which(df$degree1 == "BA")] <- as.character(df$subj1)[which(df$degree1 == "BA")]
df$major[which(df$degree1 != "BA" & df$degree2 == "BA")] <- 
  as.character(df$subj2)[which(df$degree1 != "BA" & df$degree2 == "BA")]

ifelse()方式同样适用:

df$major2 <- ifelse(df$degree1 == "BA", as.character(df$subj1),
  ifelse(df$degree2 == "BA", as.character(df$subj2), NA))

1
其次,当我尝试使用“which”方法时,我得到了一个我没有预期的结果。 df$major不是设置为相应的df$subj1值,而是被分配了一个数字,即1、2或3。起初我认为这些对应于df$subj1中的因子,但在df$major中哪些数字与df$subj1的相应值相匹配没有任何规律可言。这是我运行的代码(针对我的问题中的数据):df1$major[which(df1$degree1 =="BA")] <- df1$subj1[which(df1$degree1=="BA")]。 - eipi10
我应该提到,在运行上述代码之前,我键入了df $ major =“None”来初始化df $ major,否则对df $ major的分配会导致错误。更正我的先前评论:df $ major中的数字确实对应于df $ subj1中的因子值。所以,我的问题是:如何获取df $ major中的实际因子值,而不是对应于因子值的数字? - eipi10
我正在运行2.14.1版本。感谢您添加编辑和提供出色的答案! - eipi10

8

通常情况下,对于这些情况,ifelse函数是正确的选择,类似于:

df$major = ifelse((!is.na(df$degree1) & df$degree1 == "BA") & (is.na(df$degree2) | df$degree1 != "BA"), df$subj1, df$subj2)

然而,它的精确使用取决于您在 df$degree1df$degree2 都为 "BA" 时所做的操作。


David,在我的实际数据集中,学生如果还没有正式被录取到专业,也可以有“预备专业”。如果一个学生只有预备专业,我会将其分配为他们的主要专业。但是如果他们既有预备专业又有专业,我会将专业分配为他们的主要专业。ifelse语句是否可以嵌套处理这样的复杂情况? - eipi10
是的:一个典型的嵌套示例看起来会像这样 ifelse(A, B, ifelse(C, D)) - David Robinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接