基于数据框中的两个旧列创建新列

时间:2017-09-18 18:40:33

标签: r

foo   bar complete
0     0   0
1     0   1
0     1   2

嗨,这里我的数据框有两列foo和bar。我想基于foo和bar数据创建一个新列Complete。

  • 如果foo和bar为零,那么complete应为0.
  • 如果foo为1且bar为0,则完成应为1。
  • 如果bar为1且foo为0,则完成应为2。

例如。

foo==1

修改

如果bar==1NA<th>

4 个答案:

答案 0 :(得分:3)

接下来,当两列都是1时使用NA。从行总和开始。如果其中任何一个为2(列数),请将其替换为NA。然后将其乘以max.col()值。

rs <- rowSums(data)
cbind(data, complete = max.col(data) * replace(rs, rs == 2, NA))
#    foo bar complete
# 1    0   1        2
# 2    1   0        1
# 3    0   0        0
# 4    0   0        0
# 5    1   1       NA
# 6    0   0        0
# 7    0   1        2
# 8    0   0        0
# 9    1   0        1
# 10   1   1       NA
# 11   1   0        1

如果您不想分配新对象,可以使用本地环境或将其包装到函数中:

local({
    rs <- rowSums(data)
    max.col(data) * replace(rs, rs == 2, NA)
})
# [1]  2  1  0  0 NA  0  2  0  1 NA  1

答案 1 :(得分:3)

如果寻求代数方法,我们可以尝试下面的一行:

with(data, 2L * bar + foo + 0L * NA^(bar & foo))
with(data, 2L * bar + foo + NA^(bar & foo) - 1L)
with(data, (2L * bar + foo) * NA^(bar & foo))

全部返回

[1]  2  1  0  0 NA  0  2  0  1 NA  1

解释

表达式2L * bar + foobarfoo视为二进制数字的数字。困难是在NA的情况下返回foo == 1 & bar == 1。为此,barfoo被视为逻辑值。如果两者都是1,即TRUE,则NA^(bar & foo)会返回NA,否则会1

如果表达式的一个操作数是NA,那么整个表达式也是如此。因此,将NA^(bar & foo)2L * bar + foo结合起来有几种可能性。我想知道哪个是最快的。

基准

到目前为止,

已发布了7种不同的方法

OP已将其样本数据提供为double类型。正如我在其他场合看到integerdouble值的显着不同时序,将针对每种类型重复基准运行,以研究数据类型对不同方法的影响。

基准数据

基准数据将包含100万行:

n_row <- 1e6L
set.seed(1234L)
data_int <- data.frame(foo = sample(0:1, n_row, replace = TRUE),
                       bar = sample(0:1, n_row, replace = TRUE))
with(data_int, table(foo, bar))
   bar
foo      0      1
  0 249978 250330
  1 249892 249800
data_dbl <- data.frame(foo = as.double(data_int$foo),
                       bar = as.double(data_int$bar))

基准代码

对于基准测试,使用microbenchmark包。

# define check function to compare results
check <- function(values) {
  all(sapply(values[-1], function(x) all.equal(values[[1]], x)))
}

library(dplyr)
data <- data_dbl
microbenchmark::microbenchmark(
  d.b = {
    vect = c("0 0" = 0, "1 0" = 1, "0 1" = 2)
    unname(vect[match(with(data, paste(foo, bar)), names(vect))])
  },
  Balter = with(data,ifelse(foo == 0 & bar == 0, 0,
                            ifelse(foo == 1 & bar == 0, 1,
                                   ifelse(foo == 0 & bar == 1, 2, NA)))),
  PoGibas = with(data, case_when(foo == 0 & bar == 0 ~ 0,
                                   foo == 1 & bar == 0 ~ 1,
                                   foo == 0 & bar == 1 ~ 2)),
  Rich = local({rs = rowSums(data);  max.col(data) * replace(rs, rs == 2, NA)}),
  Frank = with(data, ifelse(xor(foo, bar), max.col(data), 0*NA^foo)),
  user20650 = with(data, c(0, 1, 2, NA)[c(2*bar + foo + 1)]),
  uwe1i = with(data, 2L * bar + foo + 0L * NA^(bar & foo)),
  uwe1d = with(data, 2  * bar + foo + 0  * NA^(bar & foo)),
  uwe2i = with(data, 2L * bar + foo + NA^(bar & foo) - 1L),
  uwe2d = with(data, 2  * bar + foo + NA^(bar & foo) - 1),
  uwe3i = with(data, (2L * bar + foo) * NA^(bar & foo)),
  uwe3d = with(data, (2  * bar + foo) * NA^(bar & foo)),
  times = 11L,
  check = check)

请注意,只有创建结果向量,而data中创建新列。相应地修改了PoGibas的方法。

如上所述,使用integerdouble值可能存在速度差异。因此,我想测试使用整数常量(例如0L, 1L)与双常数0, 1的效果。

基准测试结果

首先,对于double类型的输入数据:

Unit: milliseconds
      expr        min         lq       mean     median         uq        max neval   cld
       d.b 1687.05063 1700.52197 1707.72896 1706.48511 1715.46814 1730.62160    11     e
    Balter  287.89649  377.42284  412.59764  452.75668  458.21178  472.92971    11    d 
   PoGibas  152.90900  154.82164  176.09522  158.23214  165.73524  333.48223    11   c  
      Rich   67.43862   68.68331   76.42759   77.10620   82.42179   89.90016    11  b   
     Frank  170.78293  174.66258  192.85203  179.69422  184.55237  333.74578    11   c
 user20650   20.11790   20.29744   22.32541   20.81453   21.11509   34.45654    11 a    
     uwe1i   24.86296   25.13935   28.38634   25.60604   28.79395   45.53514    11 a    
     uwe1d   24.90034   25.05439   28.62943   25.41460   29.47379   41.08459    11 a    
     uwe2i   25.21222   25.59754   30.15579   26.29135   33.00361   47.13382    11 a    
     uwe2d   24.38305   25.09385   29.46715   25.41951   29.11112   45.05486    11 a    
     uwe3i   23.27334   23.95714   27.12474   24.28073   25.86336   44.40467    11 a    
     uwe3d   23.23332   23.65073   27.60330   23.96620   29.53911   40.41175    11 a    

现在,对于integer类型的输入数据:

Unit: milliseconds
      expr       min        lq      mean    median        uq       max neval   cld
       d.b 591.71859 596.31904 607.51452 601.24232 617.13886 636.51405    11     e
    Balter 284.08896 297.06170 374.42691 303.14888 465.27859 488.19606    11    d 
   PoGibas 151.75851 155.28304 174.31369 159.18364 163.50864 329.00412    11   c  
      Rich  67.79770  71.22311  78.38562  77.46642  84.56777  96.55540    11  b   
     Frank 166.60802 170.34078 192.19833 180.09257 182.43584 350.86681    11   c
 user20650  19.79204  20.06220  21.95963  20.18624  20.42393  30.13135    11 a    
     uwe1i  27.54680  27.83169  32.36917  28.08939  37.82286  45.21722    11 ab   
     uwe1d  22.60162  22.89350  25.94329  23.10419  23.74173  47.39435    11 a    
     uwe2i  27.05104  27.57607  27.80843  27.68122  28.02048  28.88193    11 a    
     uwe2d  22.83384  22.93522  23.22148  23.12231  23.41210  24.18633    11 a    
     uwe3i  25.17371  26.44427  29.34889  26.68290  27.08276  47.71379    11 a    
     uwe3d  21.68712  21.83060  26.16276  22.37659  28.40750  43.33989    11 a    

对于integerdouble输入值, user20650 的方法最快。接下来是我的代数方法。第三个是Rich的解决方案,但比第二个慢三倍。

输入数据的类型对d.b的解决方案影响最大,对 Balter 的影响程度较小。其他解决方案似乎相当不变。

有趣的是,在我的代数解决方案中使用integerdouble常数似乎没有显着差异。

答案 2 :(得分:1)

您可以使用vect

创建命名向量(在此示例中为match)并使用该向量查找值
vect = c("0 0" = 0, "1 0" = 1, "0 1" = 2)
unname(vect[match(with(data, paste(foo, bar)), names(vect))])
# [1]  2  1  0  0 NA  0  2  0  1 NA  1

答案 3 :(得分:0)

有很多方法可以做到这一点,根据你拥有多少条件,一些更有效。但一个基本的方法是:

data$New_Column <- with(data,ifelse(foo == 0 & bar == 0, 0,
                         ifelse(foo == 1 & bar == 0, 1,
                         ifelse(foo == 0 & bar == 1, 2, NA))))

#   foo bar New_Column
#1    0   1          2
#2    1   0          1
#3    0   0          0
#4    0   0          0
#5    1   1         NA
#6    0   0          0
#7    0   1          2
#8    0   0          0
#9    1   0          1
#10   1   1         NA
#11   1   0          1