[英]Match/subset one dataframe based on conditional values in another dataframe in R
[英]conditional match and return values in r
我有兩個名為 A 和 B 的表。 B 中有 30 萬行。 a 中只有一行。 我想根據每一行的 B 中是否有匹配值來生成表 C。 如果是,返回1; 如果不是,返回0; 最后得到一個矩陣C,其中包括(0, 1)與B同行。我在excel中使用了Match函數,但是我的數據太大了。 可以在R中實現嗎?
A:
A01B A01C A01D A01E A01F A01G
乙:
id1 a A01C NA NA NA
id2 b A01C A01D NA NA
id3 c B01C B03D NA NA
id4 d A01F A01F A01F NA
...
C:
A01B A01C A01D A01E A01F A01G
0 1 0 0 0 0
0 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 1 0
有很多方法可以做到這一點; 這是我能想到的。 (可能有一些非常巧妙和高效的東西,但我認為對於 300k 行,這可以)。
首先將您的代碼轉換為可重現的示例。
這里A
是 R 中的一個向量(根據需要讀取你的向量並強制轉換為向量)
A <- c("A01B", "A01C", "A01D", "A01E", "A01F", "A01G")
我在這里使用data.table
包是因為我喜歡它的語法。 你需要讓你的 B 成為一個 data.table 而不僅僅是一個 data.frame
library(data.table)
# I used dput(B) to get this command to create a reproducible example
B <- data.table(structure(list(col1 = c("id1", "id2", "id3", "id4"), col2 = c("a",
"b", "c", "d"), col3 = c("A01C", "A01C", "B01C", "A01F"), col4 = c(NA,
"A01D", "B03D", "A01F"), col5 = c(NA, NA, NA, "A01F"), col6 = c(NA_character_,
NA_character_, NA_character_, NA_character_)), class = "data.frame", row.names = c(NA,
-4L)))
# col1 col2 col3 col4 col5 col6
# <char> <char> <char> <char> <char> <char>
# 1: id1 a A01C <NA> <NA> <NA>
# 2: id2 b A01C A01D <NA> <NA>
# 3: id3 c B01C B03D <NA> <NA>
# 4: id4 d A01F A01F A01F <NA>
現在解決你的問題。 先回答再解釋。 回答:
> col_names <- tail(names(B), -2)
> B[,
sapply(
A,
function (code) { pmin(1, rowSums(.SD == code, na.rm=T)) },
simplify=F, USE.NAMES=T
),
.SDcols=col_names
]
A01B A01C A01D A01E A01F A01G
<num> <num> <num> <num> <num> <num>
1: 0 1 0 0 0 0
2: 0 1 1 0 0 0
3: 0 0 0 0 0 0
4: 0 0 0 0 1 0
編輯:剛剛意識到,如果您放棄數據框並只使用除 B 的前兩列之外的所有矩陣,它會更容易閱讀! 您的結果也將是一個矩陣而不是一個數據框。
# B[, ..col_names] if using a data.table
# B[, col_names] if using a data.frame
sapply(A, function (code) { pmin(1, rowSums(B[, ..col_names] == code, na.rm=T)) })
A01B A01C A01D A01E A01F A01G
[1,] 0 1 0 0 0 0
[2,] 0 1 1 0 0 0
[3,] 0 0 0 0 0 0
[4,] 0 0 0 0 1 0
說明:首先假設我只有一個代碼“A01C”並且我只是想生成 A01C 列。
首先制作一個我們要檢查的列名的向量(除前兩個之外的所有內容)
col_names <- tail(names(B), -2)
然后檢查這些列中是否有任何列是 A01C( .SDcols=col_names
只選擇列 3 到 6)
# this is TRUE if the column has A01C in it.
> B[, .SD == 'A01C', .SDcols=col_names]
col3 col4 col5 col6
[1,] TRUE NA NA NA
[2,] TRUE FALSE NA NA
[3,] FALSE FALSE NA NA
[4,] FALSE FALSE FALSE NA
但是我們希望將這些組合為每行一個值。 我們可以通過在每行中添加 TRUE 來實現,這將返回匹配的數量。 rowSums
將執行此操作。 我添加na.rm=T
將 NA 視為 0。 .(A01C=rowSums(...))
語法只是說“使輸出成為名為 A01C 的列”。
# But we wnat to condense this to one value per row.
> B[, .(A01C=rowSums(.SD == 'A01C', na.rm=T) > 0), .SDcols=col_names]
A01C
<num>
1: 1
2: 1
3: 0
4: 0
太好了,所以現在我們只需要遍歷A
每個代碼並為每個代碼執行此操作。
> B[,
sapply(
A,
function (code) { rowSums(.SD == code, na.rm=T) },
simplify=F, USE.NAMES=T
),
.SDcols=col_names
]
A01B A01C A01D A01E A01F A01G
<num> <num> <num> <num> <num> <num>
1: 0 1 0 0 0 0
2: 0 1 1 0 0 0
3: 0 0 0 0 0 0
4: 0 0 0 0 3 0
除了注意這會返回匹配的數量(例如,A01F 列第 4 行有“3”而不是“1”,因為該行中有 3 個 A01F)。 你似乎只想要一個 1 或 0,所以我們可以取每個數字和 1 中的最小值(或者我們可以做一個 > 0 檢查並轉換為數字,沒關系)。 為此,我們將rowSums(...)
更改為pmin(1, rowSums(...))
並獲得上面已發布的所需結果。
您可以使用tidyr::unite
將B
的列值合並為一列,然后使用cSplit_e
的splitstackshape
將它們擴展為 1/0 值
result <- B %>%
tidyr::unite(tmp, V3:V4, na.rm = TRUE) %>%
splitstackshape::cSplit_e('tmp', sep = '_', type = 'character', fill = 0)
result
# V1 V2 tmp tmp_A01C tmp_A01D tmp_A01F tmp_B01C tmp_B03D
#1 id1 a A01C 1 0 0 0 0
#2 id2 b A01C_A01D 1 1 0 0 0
#3 id3 c B01C_B03D 0 0 0 1 1
#4 id4 d A01F_A01F 0 0 1 0 0
如果有某些價值A
中不存在的B
在所有我們可以使用setdiff
在創建這些列result
。
result[setdiff(unlist(A), names(result))] <- 0
您可以將%in%
與apply
一起apply
:
C <- +t(apply(B, 1, "%in%", x=A))
colnames(C) <- A
C
# A01B A01C A01D A01E A01F A01G
#a 0 1 0 0 0 0
#b 0 1 1 0 0 0
#c 0 0 0 0 0 0
#d 0 0 0 0 1 0
數據:
A <- c("A01B", "A01C", "A01D", "A01E", "A01F", "A01G")
B <- read.table(row.names=2, text="
id1 a A01C NA NA NA
id2 b A01C A01D NA NA
id3 c B01C B03D NA NA
id4 d A01F A01F A01F NA")[-1]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.