两个数据帧的最小Gower距离

时间:2013-09-26 15:47:17

标签: r algorithm distance

我正在寻找一种实现方法,用于确定一个(例如,test)数据帧中所有记录与第二个(例如training)数据帧中任何记录的Gower距离的最小值。结果是一个向量,test中的每一行都有一个元素。

数据是带有无序分类属性的分类,可以生成,例如,如下所示:

set.seed(20130926L)
DIMS <- 12
CATS <- 2

create.data <- function(SPARSITY) {
  sparse.data <- rbinom(CATS ** DIMS, 1, SPARSITY)
  sparse.array <- array(sparse.data, dim=rep(CATS, DIMS))
  sparse.table <- as.table(sparse.array)
  sparse.df <- as.data.frame(sparse.table)
  sparse.df <- subset(sparse.df, Freq > 0, select=-Freq)
  sparse.df
}

data.train <- create.data(0.001)
data.test <- create.data(0.01)

head(data.train, 3)

##      Var1 Var2 Var3 Var4 Var5 Var6 Var7 Var8 Var9 Var10 Var11 Var12
## 745     A    A    A    B    A    B    B    B    A     B     A     A
## 1156    B    B    A    A    A    A    A    B    A     A     B     A
## 1574    B    A    B    A    A    B    A    A    A     B     B     A

summary(data.test)

##  Var1   Var2   Var3   Var4   Var5   Var6   Var7   Var8   Var9   Var10 
##  A:24   A:31   A:23   A:20   A:30   A:27   A:22   A:20   A:26   A:23  
##  B:24   B:17   B:25   B:28   B:18   B:21   B:26   B:28   B:22   B:25  
##  Var11  Var12 
##  A:24   A:22  
##  B:24   B:26

对于data.test中的所有行,我如何找到Gower距离最小的data.training中的行(或者至少距离该特定行的距离)?下面的代码可以使用,但是对于20个属性或超过2个类别需要太多内存:

nrow(data.test)

## [1] 48

library(StatMatch, quietly=T, warn.conflicts=F)
apply(gower.dist(data.train, data.test), 2, min)

##  [1] 0.3333 0.4167 0.2500 0.5000 0.3333 0.4167 0.2500 0.3333 0.2500 0.4167
## [11] 0.5000 0.3333 0.3333 0.3333 0.4167 0.4167 0.2500 0.4167 0.1667 0.3333
## [21] 0.4167 0.3333 0.4167 0.5000 0.3333 0.5000 0.5000 0.4167 0.3333 0.3333
## [31] 0.2500 0.4167 0.5000 0.4167 0.3333 0.5000 0.3333 0.4167 0.3333 0.3333
## [41] 0.5000 0.5833 0.5000 0.2500 0.3333 0.4167 0.3333 0.5000

函数cluster::daisy()也返回距离矩阵。

类似:How to calculate Euclidean distance (and save only summaries) for large data frames。在那里,建议对data.train的子集多次调用距离函数。我可以这样做,但计算时间仍然过高。

毕竟,Gower距离的定义允许更有效的算法,也许是一种递归的分治方法,它按属性操作属性并在子集上调用自身。回想一下,Gower's distance是属性方向距离的(加权)总和,定义为

  • 表示分类属性:如果相等则为0,否则为1
  • 对于有序属性:如果相等则为0,否则与秩距离成比例
  • 表示连续属性(此处不需要):与距离比例和属性范围成比例

以下是Gower (A, A)之间距离的简单演示 并计算AB的所有组合。一个属性上不同的行的距离为0.5,两个属性上不同的行的最大距离为1.0:

(ex.train <- expand.grid(Var1=LETTERS[1:2], Var2=LETTERS[1:2]))

##   Var1 Var2
## 1    A    A
## 2    B    A
## 3    A    B
## 4    B    B

ex.test <- ex.train[1, ]
gower.dist(ex.train, ex.test)

##      [,1]
## [1,]  0.0
## [2,]  0.5
## [3,]  0.5
## [4,]  1.0

如果同时分析train.datatest.data,则可能的实现可能如下所示:

  1. 对于第一列的所有值级别v
    1. 选择test.data的子集,其中第一列的值为v
    2. 选择train.data的子集,其中第一列的值为v
    3. 递归调用过程以获得最小
    4. 的上限
    5. 选择train.data的子集,其中第一列的值为<> v
    6. 使用先前获得的早期截止上限来递归调用过程
  2. 是否真的没有实现,或者可能是描述这种算法的论文?

2 个答案:

答案 0 :(得分:4)

我不熟悉高尔的距离,但根据你的描述,似乎对于无序的分类属性,高尔的距离相当于汉明距离除以向量的长度。换句话说,向量xy之间的Gower距离只是mean(x!=y)。在这种情况下,您可以通过避免计算整个距离矩阵来节省大量的计算时间,而是使用colSums。这是一个示例,包含三个级别和10000个训练行:

> set.seed(123)
> train.rows<-10000
> test.rows<-100
> cols<-20
> levels<-c("a","b","c")
> train.set<-sample(levels,train.rows*cols,T)
> dim(train.set)<-c(train.rows,cols)
> test.set<-sample(levels,test.rows*cols,T)
> dim(test.set)<-c(test.rows,cols)
> system.time(gdist<-apply(gower.dist(train.set,test.set),2,min))   
   user  system elapsed 
 13.396   0.324  13.745 
> system.time(hdist<-apply(test.set,1,function(x) min(colSums(x!=t(train.set))/cols)))
   user  system elapsed 
  0.492   0.008   0.504 
> identical(hdist,gdist)
[1] TRUE

如果数据不是离散的和无序的,那么Gower距离的公式是不同的,但我怀疑有一种类似的方法可以更有效地计算它,而无需通过gower.dist计算整个距离矩阵。

更新:使用@ Frank的建议可以提高效率,并且可以预先生成t(train.set)而不是在函数内:

require(microbenchmark)
ttrain.set<-t(train.set)
microbenchmark(
  a=apply(test.set,1,function(x) min(colSums(x!=t(train.set))/cols)),
  b=apply(test.set,1,function(x) min(colSums(x!=ttrain.set)/cols)))

## Unit: milliseconds
##  expr      min       lq   median       uq      max neval
##     a 523.3781 533.2950 589.0048 620.4411 725.0183   100
##     b 367.5428 371.6004 396.7590 408.9804 496.4001   100

答案 1 :(得分:0)

我将此作为评论的一部分,但除非我错过了问题,否则它确实是一个候选人:不应该只是:

 ddat <- gower.dist(data.train, data.test)
 which(ddat==min(ddat), arr.ind=TRUE)

#     row col
#[1,]   3  19

? (它已经设计为自己进行“应用”操作。)

如果目标是将min dist放到'data.test'中的特定行,那么它将更快并占用更少的空间。我仍然没有弄清楚为什么这会导致记忆困难。目标是找到最小距离或找出每个data.test行的最小距离。