簡體   English   中英

基於數據框中的兩個舊列創建新列

[英]Creating a new column based on two old columns in a data frame

data <- data.frame(foo = c(0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1),
                   bar = c(1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0))

嗨,這里我有一個包含兩列foo和bar的數據框。 我想基於foo和bar數據創建一個新列Complete。

  • 如果foo和bar為零,則complete應為0。
  • 如果foo是1而bar是0那么完成應該是1。
  • 如果bar是1而foo是0那么完成應該是2。

例如。

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

編輯:

如果foo==1bar==1NA

接下來,當兩列都是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

如果尋求代數方法,我們可以嘗試下面的一行:

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視為二進制數的數字。 難度是在foo == 1 & bar == 1情況下返回NA 為此, barfoo被視為邏輯值。 如果兩者都是1 ,即TRUENA^(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 db 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 db 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的解決方案,但比第二個慢三倍。

輸入數據的類型對db的解決方案影響最大,而對Balter的解決方案影響較小。 其他解決方案似乎相當不變。

有趣的是,在我的代數解決方案中使用integerdouble常數似乎沒有顯着差異。

您可以創建命名向量(在此示例中為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

有很多方法可以做到這一點,一些更有效,取決於你有多少條件。 但一個基本的方法是:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM