【问题标题】:flagging redundant rows with NA用 NA 标记冗余行
【发布时间】:2020-09-10 07:51:09
【问题描述】:

我有一个包含完整行和不完整行的 data.frame,例如:

dat <- data.frame(
  "one" = c(1, 1, 1, 3, NA),
  "two" = c(2, 2, NA, 4, 4),
  "three" = c(1, 1, NA, 5, 5),
  "four"  = c(2, NA, 2, 6, 6)
)

我想将包含 NA 的行标记为 TRUE,其中它们的值等于同一列中的值,在 NA 较少的行上,所以

dat$redundant <- c(FALSE, TRUE, TRUE, FALSE, TRUE)

基本上我正在寻找duplicated(dat, MARGIN = 1),但NA 算作任何当前值的匹配项。这可能吗?

【问题讨论】:

  • 如何让最后一行为真
  • 如果您的示例 dat 有一行包含 NA 但所需的 redundant 值为 FALSE,这会更清楚,因为该行的一列中有一个唯一值。我认为dat &lt;- data.frame( "one" = c(1, 1, 1, 3, NA, NA), "two" = c(2, 2, NA, 4, 4, 9), "three" = c(1, 1, NA, 5, 5, 5), "four" = c(2, NA, 2, 6, 6,6) ) ..可以这样做吗?
  • 该示例可以通过不同的方式给出预期的输出。最好能把例子弄得有点独特
  • 你是对的@PeterEllis,你的例子更好——data.frame 中的最后一行应该返回FALSE。
  • @akrun 如果最后一行的 NA 是 3,它将匹配上面的行

标签: r


【解决方案1】:

我们可以使用

library(dplyr)
library(tidyr)
dat %>% 
     fill(everything()) %>% 
     duplicated
 #[1] FALSE  TRUE  TRUE FALSE  TRUE

【讨论】:

  • 这非常接近,但它在某种程度上取决于行的排序顺序。有时fill() 会放置一个返回错误结果的值。
  • @obrl_soil 那么,你希望如何填满 NA
  • 我实际上根本不确定这是正确的方法 - 我的目标几乎是部分 grep,例如将行值连接到一个字符串,'12.45' 应该对 '12345' 是多余的。每一行都需要与任何其他具有较少 NA 的行进行比较。似乎这样很快就会变得低效......
  • @obrl_soil 好的,您还有其他解决方案。如果有帮助,请检查那些
【解决方案2】:

您可以sweep 每行的差异,构建rowSums 忽略NA,忽略上面的tri 和diag。从这里rowSums 是&gt;0 应该表示忽略NA 的重复行。

tt <- sapply(seq_len(nrow(dat)),
 function(i) rowSums(sweep(dat, 2, unlist(dat[i,])), TRUE)==0)
tt[upper.tri(tt, TRUE)] <- FALSE
rowSums(tt) > 0
#[1] FALSE  TRUE  TRUE FALSE  TRUE

将第 2 行添加到 dat 并重试:

dat2 <- rbind(dat, dat[2,])
tt <- sapply(seq_len(nrow(dat2)),
 function(i) rowSums(sweep(dat2, 2, unlist(dat2[i,])), TRUE)==0)
tt[upper.tri(tt, TRUE)] <- FALSE
dat2$redundant <- rowSums(tt) > 0
dat2
#   one two three four redundant
#1    1   2     1    2     FALSE
#2    1   2     1   NA      TRUE
#3    1  NA    NA    2      TRUE
#4    3   4     5    6     FALSE
#5   NA   4     5    6      TRUE
#21   1   2     1   NA      TRUE

还有来自@Peter-Ellis 的数据(谢谢!):

dat <- data.frame("one" = c(1, 1, 1, 3, NA, NA), "two" = c(2, 2, NA, 4, 4, 9),   
  "three" = c(1, 1, NA, 5, 5, 5), "four"  = c(2, NA, 2, 6, 6,6) )
tt <- sapply(seq_len(nrow(dat)),
 function(i) rowSums(sweep(dat, 2, unlist(dat[i,])), TRUE)==0)
tt[upper.tri(tt, TRUE)] <- FALSE
rowSums(tt) > 0
#[1] FALSE  TRUE  TRUE FALSE  TRUE FALSE

【讨论】:

  • 这在某一列中有 NA 但该行中的其他列不是其余列的重复的情况下不起作用(如果我理解“它们的值等于值 int eh 同一列,在 NAS 较少的行上“OP 的一部分)。
  • @PeterEllis 感谢您的澄清!希望更新现在能按预期工作。
  • 也许在dat &lt;- data.frame( "one" = c(1, 1, 1, 3, NA, NA), "two" = c(2, 2, NA, 4, 4, 9), "three" = c(1, 1, NA, 5, 5, 5), "four" = c(2, NA, 2, 6, 6,6) ) 上尝试您的解决方案,它应该返回 FALSE、TRUE、TRUE、FALSE、TRUE、FALSE
  • 嗯,不幸的是,这似乎只是在完整的情况下设置了 FALSE
  • @obrl_soil 所以您希望与NA 的比较仅在一个方向上有效?喜欢1 == NA 但NA != 1?
【解决方案3】:

好的,在我看来这真的很棘手,我可能误解了这个问题。这至少是答案的开始。根据我的 cmets,这是您原始示例的扩展版本

dat <- data.frame(
  "one" = c(1, 1, 1, 3, NA, NA),
  "two" = c(2, 2, NA, 4, 4, 9),
  "three" = c(1, 1, NA, 5, 5, 5),
  "four"  = c(2, NA, 2, 6, 6,6)
)

# desired behaviour is foo(dat) = c(FALSE, TRUE, TRUE, FALSE, TRUE, FALSE)

为了记录,这就是dat 的样子:

> dat
  one two three four
1   1   2     1    2
2   1   2     1   NA
3   1  NA    NA    2
4   3   4     5    6
5  NA   4     5    6
6  NA   9     5    6

当行同时包含 NA 并且在其任何列中不包含该列的唯一值时,所需的行为是 TRUE其他将被标记为 TRUE 的行——一个重要的附带条件,我可能会误解你的问题)。

这是一个非常笨拙的解决方案。它涉及一次遍历一个单元格的数据!非常不喜欢 R。

foo <- function(d){
  # we are going to approach this backwards! You are a "good" row if you 
  # are either complete, or one of your cells is unique in its column, compared
  # to the good rows
  
  
  flag1 <- complete.cases(d)
  
  flag2 <- rep(FALSE, nrow(d))
  for(i in 1:nrow(d)){
    for(j in 1:ncol(d)){
      # check if the value in this row, col is NOT IN any of the complete cases
      # of data in other rows or rows that we are keeping
      if(!is.na(d[i, j]) && !d[i, j] %in% d[-i, j][flag1 | flag2]){
        flag2[i] <- TRUE
      }
    }
  }
  
  # so flag3 is EITHER your row is complete, OR it has a unique value
  flag3 <- (flag1 | flag2)
  
  # now we return the not-good rows, so TRUE will be redundant rows
  return(!flag3)

}

它适用于原始测试用例:

> foo(dat)
[1] FALSE  TRUE  TRUE FALSE  TRUE FALSE

但是,我可以想到有问题的边缘情况。如果我们的数据的最后一行重复了怎么办?我不确定这里期望的行为是什么,但是我给你第一次在第二列中得到 9 时给出一个 FALSE,然后是 TRUE(因为这些是前一行的重复)。见:

> dat2 <- rbind(dat, c(NA, 9, 5, 6))
> dat2
  one two three four
1   1   2     1    2
2   1   2     1   NA
3   1  NA    NA    2
4   3   4     5    6
5  NA   4     5    6
6  NA   9     5    6
7  NA   9     5    6
> foo(dat2)
[1] FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE

因此,您可能需要根据您在此和相关边缘情况下的实际期望行为对其进行编辑。但希望这给了你一个开始。

【讨论】:

  • 我认为@akrun 的解决方案比我的要好得多,但我将这里留给后代。我只是没想过填写 NA,然后从那里识别重复项。
  • 我知道你打算用这个去哪里,我认为你可能是正确的,但是你的函数在我的真实数据上抛出了一个索引错误。 Error: Must subset columns with a valid subscript vector. i Logical subscripts must match the size of the indexed input. x Input has size 1 but subscript flag1 | flag2` 的大小为 61。` 切换到 ||没有帮助
  • 我认为您需要更完整的演示数据集和对实际算法的全面描述,因为我认为所描述的存在一些歧义。
【解决方案4】:

好的,最后改用基于 grep 的方法,我想这有点奇怪但有效:

  find_unique_combos <- function(dat) {

    na_count <- rowSums(is.na(dat))

    strings <- apply(dat, MARGIN = 1, function(row) {
      row[is.na(row)] <- '.'
      paste0(row, collapse ='')
    })
    
    sapply(seq_along(strings), function(i) {
      if(na_count[i] == 0) { return(TRUE) }
      test_targets <- strings[na_count <= na_count[i]]
      test_targets <- test_targets[!test_targets %in% strings[i]]
      !any(grepl(strings[i], test_targets))
    })

  }

我的输入数据集中没有重复的行,它们也不是很大的表,所以这应该没有问题。谢谢大家让我思考。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 2016-10-16
    • 2011-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多