确定一个值是否在一行列中出现一次,但第二个值根本没有出现
Determining if one value occurs once in a row of columns, but a second value doesn't occur at all
可能是个糟糕的标题,但我有 table 个存储为“1”、“2”和“3”的限定词。我想做的是查看每一行(大约 300,000 行,但可变。)并确定单个“3”出现的位置,(如果它出现不止一次,我对它不感兴趣)和其余的该行中的列有一个“1”,并且 return 是一个列表。 (列数和列名根据输入文件而变化。)
本能地,我想通过嵌套的 for 循环来尝试这样做,循环索引行数,然后是列数,然后是一些查找一个“3”而不查找“2”的函数。 -- 这可能意味着首选方法是某些应用函数是否正确?
另一个方法是计算列数总和,加 2,然后对行求和,同时有一个限定符,即行中不能有 2。但这似乎很复杂。
df1
seq loc Ball Cat Square Water
1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1
2 AAAAAACCAGTCTCAGTTCGGATTG b 1 1 3 3
3 AAAAAACCAGTCTCAGTTCGGATTG t 1 3 2 1
4 AAAAAACCGGTCACAGTTCAGATTG b 1 1 1 2
5 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1
Expected Ouput:
seq loc Group
1 AAAAAACCAGTCCCAGTTCGGATTG t Ball
2 AAAAAACCGGTCACAGTTCAGATTG t Square
dput of df1:
structure(list(seq = structure(c(1L, 2L, 2L, 3L, 3L), .Label =
c("AAAAAACCAGTCCCAGTTCGGATTG",
"AAAAAACCAGTCTCAGTTCGGATTG", "AAAAAACCGGTCACAGTTCAGATTG"), class =
"factor"),
loc = structure(c(2L, 1L, 2L, 1L, 2L), .Label = c("b",
"t"), class = "factor"), Ball = c("3", "1", "1", "1", "1"
), Cat = c("1", "1", "3", "1", "1"), Square = c("1", "3",
"2", "1", "3"), Water = c("1", "3", "1", "2", "1")), row.names = c(NA,
-5L), class = c("tbl_df", "tbl", "data.frame"))
这是一个没有 tidyverse 甚至 *apply 函数的解决方案。首先,让我们将这四列转换为整数:
cols <- 3:6
df1[cols] <- lapply(df1[cols], as.integer)
然后
df <- df1[rowSums(df1[cols]) == (3 + length(cols) - 1) & rowSums(df1[cols] == 3) == 1, ]
df$Group <- names(df)[cols][which(t(df[cols]) == 3, arr.ind = TRUE)[, 1]]
df
# A tibble: 2 x 7
# seq loc Ball Cat Square Water Group
# <fct> <fct> <int> <int> <int> <int> <chr>
# 1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1 Ball
# 2 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1 Square
在第一行中,我 select 右边的行有两个条件:在那些 cols
列(rowSums(df1[cols] == 3) == 1
)中必须只有一个元素等于 3,并且总数行的总和必须是 3 + length(cols) - 1
。然后在第二行中,我检查哪些列具有 3
并选择 df
的相应名称作为 Group
.
的值
我在进行逐行计算时经常使用基本的 apply
。如果你想要一个 tidyverse 解决方案,你可以用实际的 dplyr::rowwise
做一些事情。这里只是使用基数 R:
filter_on = apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){sum(x == 3) == 1 & sum(x == 1) == 3})
df1 = df1[filter_on,]
columns = colnames(df1)[3:6]
df1$Group = unlist(apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){columns[x == 3]}))
正在添加一个额外的版本。这仅涵盖行选择。
#create vector of wanted column names
cols <- c("Ball", "Cat", "Square", "Water")
#make values numeric
df1[, cols] <- df1[, cols] %>% mutate_if(is.character, as.numeric)
#filter rows
df1[which((rowSums(df1[, cols]) == (length(cols)+2) ) & (rowSums(df1[, cols] == 2) == 0)),]
seq loc Ball Cat Square Water
1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1
5 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1
看起来 apply
版本是前三个帖子中最快的,但并不快。
microbenchmark::microbenchmark(
which = df1[which((rowSums(df1[, cols]) == (length(cols)+2) ) & (rowSums(df1[, cols] == 2) == 0)),],
filter = df1[rowSums(df1[cols]) == (3 + length(cols) - 1) & rowSums(df1[cols] == 3) == 1, ],
apply = df1[apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){sum(x == 3) == 1 & sum(x == 1) == 3}),]
)
Unit: microseconds
expr min lq mean median uq max neval cld
which 429.043 436.4665 446.2817 445.811 451.3140 493.553 100 a
filter 429.555 435.5715 447.8151 440.307 449.2670 724.202 100 a
apply 339.958 346.9975 435.0437 351.222 362.2295 8141.819 100 a
只是为了展示我们使用长格式而不是按行处理数据的替代方法。这里,使用 data.table
函数:
library(data.table)
d <- melt(setDT(df1), id.vars = c("seq", "loc"))
d[d[ , .I[sum(value == 3) == 1 & !any(value == 2)], by = .(seq, loc)]$V1][value == 3]
# seq loc variable value
# 1: AAAAAACCAGTCCCAGTTCGGATTG t Ball 3
# 2: AAAAAACCGGTCACAGTTCAGATTG t Square 3
使用 'sec' 和 'loc' 作为 id 变量将 melt
数据转换为长格式。如果 'sec' 和 'loc' 的组合不是行的唯一标识符,则创建唯一的行索引(例如 ri := 1:.N
)。
对于每个 'sec' 和 'loc'(by = .(seq, loc)
;即对于原始数据中的每一行),为所需条件创建一个逻辑向量:每行一个 3,没有 2 (sum(value == 3) == 1 & !any(value == 2)
)。获取相应的行索引 (.I
)。索引自动命名为 'V1',然后用于子集 'd'.
最后,select 行,其中 'value' 等于 3 ([value == 3]
)。
我的解决方案是从@Julius Vainora 那里取而代之。我的解决方案更复杂,但我使用了 match()
并添加了一个索引列。
DF$index <- seq.int(nrow(DF))
col_names <- names(DF)[3:ncol(DF)]
DF$Group <- col_names[which(DF[cols] == 3, arr.ind = TRUE)[,2][
DF$index[match(
DF$index, which(
DF[cols] == 3, arr.ind = TRUE[,1])]]]
可能是个糟糕的标题,但我有 table 个存储为“1”、“2”和“3”的限定词。我想做的是查看每一行(大约 300,000 行,但可变。)并确定单个“3”出现的位置,(如果它出现不止一次,我对它不感兴趣)和其余的该行中的列有一个“1”,并且 return 是一个列表。 (列数和列名根据输入文件而变化。)
本能地,我想通过嵌套的 for 循环来尝试这样做,循环索引行数,然后是列数,然后是一些查找一个“3”而不查找“2”的函数。 -- 这可能意味着首选方法是某些应用函数是否正确?
另一个方法是计算列数总和,加 2,然后对行求和,同时有一个限定符,即行中不能有 2。但这似乎很复杂。
df1
seq loc Ball Cat Square Water
1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1
2 AAAAAACCAGTCTCAGTTCGGATTG b 1 1 3 3
3 AAAAAACCAGTCTCAGTTCGGATTG t 1 3 2 1
4 AAAAAACCGGTCACAGTTCAGATTG b 1 1 1 2
5 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1
Expected Ouput:
seq loc Group
1 AAAAAACCAGTCCCAGTTCGGATTG t Ball
2 AAAAAACCGGTCACAGTTCAGATTG t Square
dput of df1:
structure(list(seq = structure(c(1L, 2L, 2L, 3L, 3L), .Label =
c("AAAAAACCAGTCCCAGTTCGGATTG",
"AAAAAACCAGTCTCAGTTCGGATTG", "AAAAAACCGGTCACAGTTCAGATTG"), class =
"factor"),
loc = structure(c(2L, 1L, 2L, 1L, 2L), .Label = c("b",
"t"), class = "factor"), Ball = c("3", "1", "1", "1", "1"
), Cat = c("1", "1", "3", "1", "1"), Square = c("1", "3",
"2", "1", "3"), Water = c("1", "3", "1", "2", "1")), row.names = c(NA,
-5L), class = c("tbl_df", "tbl", "data.frame"))
这是一个没有 tidyverse 甚至 *apply 函数的解决方案。首先,让我们将这四列转换为整数:
cols <- 3:6
df1[cols] <- lapply(df1[cols], as.integer)
然后
df <- df1[rowSums(df1[cols]) == (3 + length(cols) - 1) & rowSums(df1[cols] == 3) == 1, ]
df$Group <- names(df)[cols][which(t(df[cols]) == 3, arr.ind = TRUE)[, 1]]
df
# A tibble: 2 x 7
# seq loc Ball Cat Square Water Group
# <fct> <fct> <int> <int> <int> <int> <chr>
# 1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1 Ball
# 2 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1 Square
在第一行中,我 select 右边的行有两个条件:在那些 cols
列(rowSums(df1[cols] == 3) == 1
)中必须只有一个元素等于 3,并且总数行的总和必须是 3 + length(cols) - 1
。然后在第二行中,我检查哪些列具有 3
并选择 df
的相应名称作为 Group
.
我在进行逐行计算时经常使用基本的 apply
。如果你想要一个 tidyverse 解决方案,你可以用实际的 dplyr::rowwise
做一些事情。这里只是使用基数 R:
filter_on = apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){sum(x == 3) == 1 & sum(x == 1) == 3})
df1 = df1[filter_on,]
columns = colnames(df1)[3:6]
df1$Group = unlist(apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){columns[x == 3]}))
正在添加一个额外的版本。这仅涵盖行选择。
#create vector of wanted column names
cols <- c("Ball", "Cat", "Square", "Water")
#make values numeric
df1[, cols] <- df1[, cols] %>% mutate_if(is.character, as.numeric)
#filter rows
df1[which((rowSums(df1[, cols]) == (length(cols)+2) ) & (rowSums(df1[, cols] == 2) == 0)),]
seq loc Ball Cat Square Water
1 AAAAAACCAGTCCCAGTTCGGATTG t 3 1 1 1
5 AAAAAACCGGTCACAGTTCAGATTG t 1 1 3 1
看起来 apply
版本是前三个帖子中最快的,但并不快。
microbenchmark::microbenchmark(
which = df1[which((rowSums(df1[, cols]) == (length(cols)+2) ) & (rowSums(df1[, cols] == 2) == 0)),],
filter = df1[rowSums(df1[cols]) == (3 + length(cols) - 1) & rowSums(df1[cols] == 3) == 1, ],
apply = df1[apply(X = df1[3:6],
MARGIN = 1,
FUN = function(x){sum(x == 3) == 1 & sum(x == 1) == 3}),]
)
Unit: microseconds
expr min lq mean median uq max neval cld
which 429.043 436.4665 446.2817 445.811 451.3140 493.553 100 a
filter 429.555 435.5715 447.8151 440.307 449.2670 724.202 100 a
apply 339.958 346.9975 435.0437 351.222 362.2295 8141.819 100 a
只是为了展示我们使用长格式而不是按行处理数据的替代方法。这里,使用 data.table
函数:
library(data.table)
d <- melt(setDT(df1), id.vars = c("seq", "loc"))
d[d[ , .I[sum(value == 3) == 1 & !any(value == 2)], by = .(seq, loc)]$V1][value == 3]
# seq loc variable value
# 1: AAAAAACCAGTCCCAGTTCGGATTG t Ball 3
# 2: AAAAAACCGGTCACAGTTCAGATTG t Square 3
使用 'sec' 和 'loc' 作为 id 变量将
melt
数据转换为长格式。如果 'sec' 和 'loc' 的组合不是行的唯一标识符,则创建唯一的行索引(例如 ri := 1:.N
)。
对于每个 'sec' 和 'loc'(by = .(seq, loc)
;即对于原始数据中的每一行),为所需条件创建一个逻辑向量:每行一个 3,没有 2 (sum(value == 3) == 1 & !any(value == 2)
)。获取相应的行索引 (.I
)。索引自动命名为 'V1',然后用于子集 'd'.
最后,select 行,其中 'value' 等于 3 ([value == 3]
)。
我的解决方案是从@Julius Vainora 那里取而代之。我的解决方案更复杂,但我使用了 match()
并添加了一个索引列。
DF$index <- seq.int(nrow(DF))
col_names <- names(DF)[3:ncol(DF)]
DF$Group <- col_names[which(DF[cols] == 3, arr.ind = TRUE)[,2][
DF$index[match(
DF$index, which(
DF[cols] == 3, arr.ind = TRUE[,1])]]]