部分自我加入

时间:2015-11-17 06:25:18

标签: r data.table

对于协作过滤应用程序,我需要将data.table中的每个观察值与其组中每个其他观察值(不包括其自身)的加权平均值进行比较。例如:

library('data.table')
ex <- function(n){ # example data
  set.seed(123)
  data.table(id = 1:n,
             grp = sample(LETTERS[1:3], n, replace = TRUE),
             wt = sample.int(10, n, replace = TRUE),
             x = sample.int(100, n, replace = TRUE) )[order(grp),]
}
(d <- ex(10))
#     id grp wt   x
#  1:  1   A 10  89
#  2:  6   A  9  71
#  3:  3   B  7  65
#  4:  7   B  3  55
#  5:  9   B  4  29
#  6: 10   B 10  15
#  7:  2   C  5  70
#  8:  4   C  6 100
#  9:  5   C  2  66
# 10:  8   C  1  60

我想有一种算术方法可以让我按组进行加权平均,然后&#34;退出&#34;个人观察的平均值。但是,我想知道是否有一种聪明的data.table方法可以将其作为自我加入方式对待grp具有不同id的成员的加权平均值。

我在使用dplyr

full_join()中找到了解决方法
library('dplyr') 
d <- ex(10)
unique(
  subset(data.table(full_join(d, d, by='grp')), 
         id.x != id.y)[, .(grp, x = x.x, wt=wt.x, 
                           rest_of_grp_wtd_avg = sum(wt.y * x.y) / sum(wt.y)),
                       by=.(id = id.x)][order(grp, id),]
) # produces desired result
#    id grp   x wt rest_of_grp_wtd_avg
# 1:  1   A  89 10            71.00000
# 2:  6   A  71  9            89.00000
# 3:  3   B  65  7            25.35294
# 4:  7   B  55  3            34.33333
# 5:  9   B  29  4            38.50000
# 6: 10   B  15 10            52.57143
# 7:  2   C  70  5            88.00000
# 8:  4   C 100  6            67.75000
# 9:  5   C  66  2            84.16667
#10:  8   C  60  1            83.23077

但是,由于full_join返回普通data.frame,并且因为我无法在没有unique()的情况下使其工作,我想它不会那么高效在规模上是一个很好的data.table解决方案。

另外,sqldf (编辑:现在)有效:

library('sqldf')
sqldf('select a.*, 
  sum(b.wt * b.x) / sum(b.wt) as rest_of_grp_wtd_avg
  from d as a
  left outer join d as b on a.grp = b.grp and a.id <> b.id
  group by a.id') # returns the desired solution

我确实得到了一个纯粹的data.table解决方案,但即使按data.table标准也是如此:

setkey(d,id)
merge(d[CJ(d$id, id2 = d$id),][id != id2, ],
      d, by.x = c('id2','grp'), by.y=c('id','grp')
      )[order(grp, id), .(rest_of_grp_wtd_avg = sum(wt.y * x.y) / sum(wt.y)), 
        by=.(id, grp, wt=wt.x, x=x.x)] # returns desired result

此计算的最优雅语法是什么?

2 个答案:

答案 0 :(得分:3)

我认为你过分复杂了。使用您的公式rest_of_grp_wtd_avg = (sum(wt*x)-wt*x) / (sum(wt)-wt)可以很好地添加一个新变量以及每组其他观察值的平均值。您只需要通过d运算符引用将其添加到:=。对于纯`data.table解决方案,您可以将代码缩短为:

d[, rest_of_grp_wtd_avg := (sum(wt*x)-wt*x) / (sum(wt)-wt), grp]

给出:

> d
    id grp wt   x rest_of_grp_wtd_avg
 1:  1   A 10  89            71.00000
 2:  6   A  9  71            89.00000
 3:  3   B  7  65            25.35294
 4:  7   B  3  55            34.33333
 5:  9   B  4  29            38.50000
 6: 10   B 10  15            52.57143
 7:  2   C  5  70            88.00000
 8:  4   C  6 100            67.75000
 9:  5   C  2  66            84.16667
10:  8   C  1  60            83.23077

这与您的结果相同:

> all.equal(d, res)
[1] TRUE

res由{

}构建的地方

setkey(d,id)
res <- merge(d[CJ(d$id, id2 = d$id),][id != id2, ],
             d, by.x = c('id2','grp'), by.y=c('id','grp'))[order(grp, id), .(rest_of_grp_wtd_avg = sum(wt.y * x.y) / sum(wt.y)), 
                                                           by=.(id, grp, wt=wt.x, x=x.x)]

您想要排除某些行的示例:

d[id < 9, rest_of_grp_wtd_avg := (sum(wt*x)-wt*x) / (sum(wt)-wt), grp]

给出:

> d
    id grp wt   x rest_of_grp_wtd_avg
 1:  1   A 10  89            71.00000
 2:  6   A  9  71            89.00000
 3:  3   B  7  65            55.00000
 4:  7   B  3  55            65.00000
 5:  9   B  4  29                  NA
 6: 10   B 10  15                  NA
 7:  2   C  5  70            88.00000
 8:  4   C  6 100            67.75000
 9:  5   C  2  66            84.16667
10:  8   C  1  60            83.23077

答案 1 :(得分:1)

不需要自我加入。使用dplyr的window functions功能,您可以非常轻松地计算每组的度量:

ex(10) %>%
    group_by(grp) %>%
    mutate(rest_of_grp_wtd_avg = (sum(wt*x)-wt*x) / (sum(wt)-wt))