首页 文章

如何使用r计算数据帧中一组行的相似程度

提问于
浏览
2

我有以下数据集:

name    date         cat1    cat2    cat3    cat4    cat5
joe     15/09/2013   A       D       C       D       NA
joe     14/09/2013   D       A       C       NA      NA
joe     13/09/2013   A       C       NA      NA      NA
jack    15/09/2013   G       I       K       D       O
jack    14/09/2013   H       G       O       M       K

对于每个人,我想要计算每个类别相互比较的相似程度的百分比 . 例如

name    percentage
joe     88.9%      
jack    60%

请注意,NA被忽略,类别出现的次数是无关紧要的 .

为了引导你完成我的逻辑(这可能是错误的,或者可能有更好的方法来做到这一点,如果是这样,请说):让我们以乔为例,

row1(15/09/2013)与row2(14/09/2013)匹配100%row1(15/09/2013)与row3(13/09/2013)匹配66%row2(14/09/2013) )与row3(13/09/2013)匹配66%row2(14/09/2013)与row1(15/09/2013)匹配100%row3(13/09/2013)与row1(15/09)相比/ 2013)匹配100%row3(13/09/2013)与row2(14/09/2013)匹配100%

所以平均得分是88.9%

对于千斤顶,只有类别'G','K','O'出现在两行中,因此平均得分为60%

我已经查看了R中的ddply函数,但我不确定是否可以使用它来创建上面的数据帧(名称,百分比) . 我认为我应该避免的其他选项,因为我确信在R中必须有一种更有效的方法,就是创建一个带有嵌套for循环的R脚本!

一些可能是最好的方法(因为这个数据框会非常大)的最终选择是使用Python,因此如果有人知道如何做到这一点就是Python(猜测我们将使用Pandas)我会很高兴一些救命 .

所以要明确两个问题:

  • 如果有可能使用ddply可以有人请告诉我如何,否则有没有人有任何其他想法如何我可以解决这个问题?

  • 使用上面的小数据框有人可以举例说明他们如何在Python中解决这个问题吗?

3 回答

  • 2

    使用 ddply ,我发现它类似于@Roland的想法:

    • 按名称分组

    • 为每个组使用 combn 找到2行的组合 . combn 真的很慢也许更好用 expand.grid

    • 对于2行的每个组合,删除缺失值并仅保留唯一值 . 最好在分组之前为所有数据执行此操作 .

    • 计算2得分,因为关系不对称 .

    这是我的代码:

    library(plyr)
    id <- grep("cat*",names(dat))
    
    
    compare.row <- function(x,y){
      xx <- x[id]
      xx <- unique(xx[!is.na(xx)])
      yy <- y[id]
      yy <- unique(yy[!is.na(yy)])
      v = c(length(intersect(xx,yy))/length(yy),
            length(intersect(xx,yy))/length(xx))
    }
    
    
    ddply(dat,.(name),function(x){
      ll <-  combn(seq(nrow(x)),2,FUN=function(i)
                     compare.row(x[i[1],],x[i[2],]))
      mean(unlist(ll))
    })
    
     name        V1
    1 jack 0.6000000
    2  joe 0.8888889
    

    EDIT 添加一些bencmarking:

    有了这些小数据,data.table解决方案就是赢家;

    library(microbenchmark)
    microbenchmark(ag(),ro(),jb(),times=5)
    Unit: milliseconds
     expr       min        lq    median        uq       max neval
     ag()  8.410804  8.790441  9.389289  9.684352 13.981724     5
     ro()  4.351227  4.765756  4.787374  5.414287  7.320817     5
     jb() 11.077366 11.413388 11.888599 11.923870 12.119946     5
    
  • 1

    还有一个选择:

    d <- read.table(
      text='name    date         cat1    cat2    cat3    cat4    cat5
    joe     15/09/2013   A       D       C       D       NA
    joe     14/09/2013   D       A       C       NA      NA
    joe     13/09/2013   A       C       NA      NA      NA
    jack    15/09/2013   G       I       K       D       O
    jack    14/09/2013   H       G       O       M       K', 
      header=T, stringsAsFactors=FALSE)
    
    library(plyr)
    ddply(d, 'name', function(x) {
      combns <- expand.grid(seq_len(nrow(x)), seq_len(nrow(x)))
      combns <- combns[!combns[, 1] == combns[, 2], ]
      mean(sapply(seq_len(nrow(combns)), function(i) {
        n <- sum(!is.na(unique(unlist(x[combns[i, 1], -(1:2)]))))
        sum(!is.na(match(unique(unlist(x[combns[i, 1], -(1:2)])), 
                         unique(unlist(x[combns[i, 2], -(1:2)])), 
                         incomparables=NA))) / n
      }))
    })
    
  • 1

    我不明白为什么row2 vs row3给出66%,但row1 vs row3为100% . 我没有看到那里的逻辑 .

    以下是我理解的逻辑实现:

    fun <- function(df) {
      M <- as.matrix(df)
      res1 <- combn(seq_len(nrow(M)), 2, function(ind) {
        i <- na.omit(intersect(M[ind[1],], M[ind[2],]))
        l <- length(unique(na.omit(M[ind[2],])))
        length(i)/l
      })
      res2 <- combn(rev(seq_len(nrow(M))), 2, function(ind) {
        i <- na.omit(intersect(M[ind[1],], M[ind[2],]))
        l <- length(unique(na.omit(M[ind[2],])))
        length(i)/l
      })
      c(res1,res2)
    }
    
    fun(DF[1:3,3:7])
    #[1] 1.0000000 1.0000000 1.0000000 0.6666667 0.6666667 1.0000000
    

    然后我尝试使用 ddply 这个函数,但是懒惰评估或范围确定存在问题 . 所以,我转向data.table:

    library(data.table)
    DT <- data.table(DF)
    DT[, mean(fun(.SD)), .SDcols=3:7, by=name]
    #   name        V1
    #1:  joe 0.8888889
    #2: jack 0.6000000
    

    我不知道这对你的数据是否足够有效 .

相关问题