R中嵌套ifelse语句的替代方法

Kat*_*ney 26 loops if-statement r nested-loops

假设我们有以下数据.行代表一个国家/地区,而columns(in05:in09)表示该国家/地区是否存在于给定年份(2005:2009)中感兴趣的数据库中.

id <- c("a", "b", "c", "d")
in05 <- c(1, 0, 0, 1)
in06 <- c(0, 0, 0, 1)
in07 <- c(1, 1, 0, 1)
in08 <- c(0, 1, 1, 1)
in09 <- c(0, 0, 0, 1)
df <- data.frame(id, in05, in06, in07, in08, in09)
Run Code Online (Sandbox Code Playgroud)

我想创建一个变量firstyear,指示该国家在数据库中出现的第一年.现在我做以下事情:

df$firstyear <- ifelse(df$in05==1,2005,
    ifelse(df$in06==1,2006,
        ifelse(df$in07==1, 2007,
            ifelse(df$in08==1, 2008,
                ifelse(df$in09==1, 2009,
                    0)))))
Run Code Online (Sandbox Code Playgroud)

上面的代码已经不是很好了,我的数据集包含了很多年.是否有替代方法,使用*apply函数,循环或其他东西来创建此firstyear变量?

Dav*_*urg 24

你可以使用矢量化 max.col

indx <- names(df)[max.col(df[-1], ties.method = "first") + 1L]
df$firstyear <- as.numeric(sub("in", "20", indx))
df
#   id in05 in06 in07 in08 in09 firstyear
# 1  a    1    0    1    0    0      2005
# 2  b    0    0    1    1    0      2007
# 3  c    0    0    0    1    0      2008
# 4  d    1    1    1    1    1      2005
Run Code Online (Sandbox Code Playgroud)

  • 好老``max.col` - 总是来救援.虽然在处理关系时默认为"随机"是非常烦人的,但考虑到`which.max` /`which.min`等总是取得他们击中的第一个结果. (8认同)

Pie*_*une 21

df$FirstYear <- gsub('in', '20', names(df))[apply(df, 1, match, x=1)]
df
  id in05 in06 in07 in08 in09 FirstYear
1  a    1    0    1    0    0      2005
2  b    0    0    1    1    0      2007
3  c    0    0    0    1    0      2008
4  d    1    1    1    1    1      2005
Run Code Online (Sandbox Code Playgroud)

有很多方法可以做到这一点.我用过,match因为它会找到指定值的第一个实例.代码的其他部分用于演示.首先逐行,apply并用列名命名年份names.分配<-df$FirstYear是将结果添加到数据帧的方式.

增加信用@David Arenburg有一个很酷的想法,即在专栏中加入infor .20FirstYear

  • 我认为这也是一个聪明的伎俩.. @akrun会自豪 (4认同)

ale*_*laz 8

关于效率的一些注释的另一个答案(虽然这个QA与速度无关).

首先,避免将"列表"-y结构转换为"矩阵"可能会更好; 有时值得转换为"矩阵"并使用一个有效处理'带有"暗"属性'的向量的函数(即"矩阵"/"数组") - 有时则不是.双方max.colapply转换成"矩阵".

其次,在这样的情况下,我们不需要在获得解决方案时检查所有数据,我们可以从一个带有循环的解决方案中受益,该循环控制下一次迭代的内容.在这里,我们知道当我们找到第一个"1"时我们可以停下来.两个max.col(和which.max)必须循环一次,实际上,找到最大值; 我们知道"max == 1"没有被利用的事实.

第三,match当我们在另一个值向量中只寻找一个值时,可能会更慢,因为它match的设置相当复杂和昂贵:

x = 5; set.seed(199); tab = sample(1e6)
identical(match(x, tab), which.max(x == tab))
#[1] TRUE
microbenchmark::microbenchmark(match(x, tab), which.max(x == tab), times = 25)
#Unit: milliseconds
#                expr       min        lq    median        uq       max neval
#       match(x, tab) 142.22327 142.50103 142.79737 143.19547 145.37669    25
# which.max(x == tab)  18.91427  18.93728  18.96225  19.58932  38.34253    25
Run Code Online (Sandbox Code Playgroud)

总而言之,一种处理"data.frame"的"list"结构并在找到"1"时停止计算的方法可能是如下的循环:

ff = function(x)
{
    x = as.list(x)
    ans = as.integer(x[[1]])
    for(i in 2:length(x)) {
        inds = ans == 0L
        if(!any(inds)) return(ans)
        ans[inds] = i * (x[[i]][inds] == 1)
    }
    return(ans)
}
Run Code Online (Sandbox Code Playgroud)

其他答案中的解决方案(忽略输出的额外步骤):

david = function(x) max.col(x, "first")
plafort = function(x) apply(x, 1, match, x = 1)

ff(df[-1])
#[1] 1 3 4 1
david(df[-1])
#[1] 1 3 4 1
plafort(df[-1])
#[1] 1 3 4 1
Run Code Online (Sandbox Code Playgroud)

还有一些基准:

set.seed(007)
DF = data.frame(id = seq_len(1e6),
                "colnames<-"(matrix(sample(0:1, 1e7, T, c(0.25, 0.75)), 1e6), 
                             paste("in", 11:20, sep = "")))
identical(ff(DF[-1]), david(DF[-1]))
#[1] TRUE
identical(ff(DF[-1]), plafort(DF[-1]))
#[1] TRUE
microbenchmark::microbenchmark(ff(DF[-1]), david(DF[-1]), as.matrix(DF[-1]), times = 30)
#Unit: milliseconds
#              expr       min        lq    median        uq       max neval
#        ff(DF[-1])  64.83577  65.45432  67.87486  70.32073  86.72838    30
#     david(DF[-1]) 112.74108 115.12361 120.16118 132.04803 145.45819    30
# as.matrix(DF[-1])  20.87947  22.01819  27.52460  32.60509  45.84561    30

system.time(plafort(DF[-1]))
#   user  system elapsed 
#  4.117   0.000   4.125 
Run Code Online (Sandbox Code Playgroud)

这不是真正的大灾难,但值得一提的是,根据问题,简单,直接的算法方法可以证明同样好或甚至更好.显然,(大多数)其他时间在R中循环可能很费力.

  • 辉煌.一如既往...很久以前我在列表上写了非常有效的循环,我的代码速度非常快,但是因为"反循环"哲学毁了我:) (4认同)
  • @DavidArenburg:循环是一种生活方式 - 你可以隐藏它,'矢量化'但你无法避免它...... :-) (3认同)

sea*_*ith 5

您可以按照此推文中介绍的方法使用dplyr::case_wheninside 。dplyr::mutate()

# Using version 0.5.0.
# Dev version may work without `with()`.    
df %>%
      mutate(., firstyear = with(., case_when(
        in05 == 1 ~ 2005,
        in06 == 1 ~ 2006,
        in07 == 1 ~ 2007,
        in08 == 1 ~ 2008,
        in09 == 1 ~ 2009,
        TRUE ~ 0
)))
Run Code Online (Sandbox Code Playgroud)