【问题标题】:Filtering rows in a dataset by columns按列过滤数据集中的行
【发布时间】:2018-06-28 10:14:39
【问题描述】:

我有下表:

FN LN LN1 LN2 LN3 LN4 LN5
a   b   b   x   x   x   x
a   c   b   d   e   NA  NA
a   d   c   a   b   x   x
a   e   b   c   d   x   e

我正在过滤 LN1 到 LN5 中存在 LN 的记录。

我使用的代码:

testFilter = filter(test, LN %in% c(LN1, LN2, LN3, LN4, LN5)) 

结果不是我所期望的:

ï..FN LN LN1 LN2 LN3  LN4  LN5
1     a  b   b   x   x    x    x
2     a  c   b   d   e <NA> <NA>
3     a  d   c   a   b    x    x
4     a  e   b   c   d    x    e

我知道c(LN1, LN2, LN3, LN4, LN5) 给出:"b" "b" "c" "b" "x" "d" "a" "c" "x" "e" "b" "d" "x" NA "x" "x" "x" NA "x" "e" 并且知道这就是错误所在。

理想情况下,我只想返回第一条和第四条记录。

FN LN LN1 LN2 LN3 LN4 LN5
a   b   b   x   x   x   x
a   e   b   c   d   x   e

我只想使用列名过滤它们。这只是 540 万条记录的一个子集。

【问题讨论】:

  • 我猜你的意思是 subset 不是 filter (用于时间序列)。 LN 是一个列名,如 LN1LN5 所以你的“条件”不能工作。你到底想做什么?
  • 我想显示在对应的 LN1 或 LN2 或 LN3 或 LN4 或 LN5 中存在 LN 值的记录。感谢您及时的回复!在第一条记录中,'b' 出现在 LN1 中。因此,显示第一条记录。在第二条记录中,'c' 不在 LN1/LN2/3/4/5 中。所以,不要显示它。在第三条记录中,'d' 不在 LN1/LN2/3/4/5 中。所以,不要显示它。在第 4 条记录中,“e”出现在 LN5 中。所以,展示它。

标签: r dataframe subset


【解决方案1】:

使用应用

# data
df1 <- read.table(text = "
FN LN LN1 LN2 LN3 LN4 LN5
a   b   b   x   x   x   x
a   c   b   d   e   NA  NA
a   d   c   a   b   x   x
a   e   b   c   d   x   e", header = TRUE, stringsAsFactors = FALSE)


df1[ apply(df1, 1, function(i) i[2] %in% i[3:7]), ]
#   FN LN LN1 LN2 LN3 LN4 LN5
# 1  a  b   b   x   x   x   x
# 4  a  e   b   c   d   x   e

注意:考虑将以下其他解决方案用于大型数据集,这可能比此应用解决方案快 60 倍。

【讨论】:

  • 哈哈,为什么不用最快的方案呢?
  • @EricFail 嗯,为什么“哈哈”?我在获得很多支持后添加了注释,并想为其他 data.table 解决方案突出显示。
  • 我一直在自嘲,因为我的代码有点笨拙,却是最快的解决方案
【解决方案2】:

还有一种使用data.tableReduce() 的替代方法:

library(data.table)
cols <- paste0("LN", 1:5)
setDT(test)[test[, .I[Reduce(`|`, lapply(.SD, function(x) !is.na(x) & LN == x))], 
                 .SDcols = cols]]
   FN LN LN1 LN2 LN3 LN4 LN5
1:  a  b   b   x   x   x   x
2:  a  e   b   c   d   x   e

数据

library(data.table)
test <- fread(
"FN LN LN1 LN2 LN3 LN4 LN5
  a   b   b   x   x   x   x
  a   c   b   d   e   NA  NA
  a   d   c   a   b   x   x
  a   e   b   c   d   x   e")

基准测试

library(data.table)
library(dplyr)
n_row <- 1e6L
set.seed(123L)
DT <- data.table(
  FN = "a",
  LN = sample(letters, n_row, TRUE))
cols <- paste0("LN", 1:5)
DT[, (cols) := lapply(1:5, function(x) sample(c(letters, NA), n_row, TRUE))]
DT
df1 <- as.data.frame(DT)

bm <- microbenchmark::microbenchmark(
  zx8754 = {
    df1[ apply(df1, 1, function(i) i[2] %in% i[3:7]), ]
  },
  eric = {
    df1[ which(df1$LN == df1$LN1 |
                 df1$LN == df1$LN2 |
                 df1$LN == df1$LN3 |
                 df1$LN == df1$LN4 |
                 df1$LN == df1$LN5), ]
  },
  uwe = {
    DT[DT[, .I[Reduce(`|`, lapply(.SD, function(x) !is.na(x) & LN == x))], 
          .SDcols = cols]]
  },
  axe = { 
    filter_at(df1, vars(num_range("LN", 1:5)), any_vars(. == LN))
  },
  jaap = {df1[!!rowSums(df1$LN == df1[, 3:7], na.rm = TRUE),]},
  times = 50L
)
print(bm, "ms")
Unit: milliseconds
   expr        min         lq       mean     median         uq       max neval cld
 zx8754 3120.68925 3330.12289 3508.03001 3460.83459 3589.10255 4552.9070    50   c
   eric   69.74435   79.11995  101.80188   83.78996   98.24054  309.3864    50 a  
    uwe   93.26621  115.30266  130.91483  121.64281  131.75704  292.8094    50 a  
    axe   69.82137   79.54149   96.70102   81.98631   95.77107  315.3111    50 a  
   jaap  362.39318  489.86989  543.39510  544.13079  570.10874 1110.1317    50  b

对于 1 M 行,硬编码子集最快,其次是 data.table/Reduce()dplyr/filter_at 方法。使用 apply() 会慢 60 倍。

ggplot(bm, aes(expr, time)) + geom_violin() + scale_y_log10() + stat_summary(fun.data = mean_cl_boot)

【讨论】:

  • 真正有教育意义的答案!比较不同答案的基准也很棒。
【解决方案3】:

一个快速且非常简单的dplyr 解决方案:

filter_at(df1, vars(num_range("LN", 1:5)), any_vars(. == LN))

这在性能上与@EricFail 的硬编码答案非常相似,因为这只是在内部将调用扩展到:

filter(df1, (LN1 == LN) | (LN2 == LN) | (LN3 == LN) | (LN4 == LN) | (LN5 == LN))

可以在vars 中使用任何其他select 助手而不是num_range,以便根据它们的名称轻松选择许多变量。或者可以直接给列位置。

【讨论】:

    【解决方案4】:

    不是最简单的代码,而是

    df1[ which(df1$LN == df1$LN1 |
               df1$LN == df1$LN2 |
               df1$LN == df1$LN3 |
               df1$LN == df1$LN4 |
               df1$LN == df1$LN5), ]
    #>   FN LN LN1 LN2 LN3 LN4 LN5
    #> 1  a  b   b   x   x   x   x
    #> 4  a  e   b   c   d   x   e
    

    【讨论】:

    • 我更喜欢这个解决方案,但它不能很好地扩展,假设我们有 100 列。
    • 如果它们以某种合乎逻辑的方式命名,则可能会被重写。
    【解决方案5】:

    你也可以使用rowSums:

    df1[!!rowSums(df1$LN == df1[, 3:7], na.rm = TRUE),]
    

    给出:

      FN LN LN1 LN2 LN3 LN4 LN5
    1  a  b   b   x   x   x   x
    4  a  e   b   c   d   x   e
    

    有关基准,请参阅answer of @Uwe

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多