计算两个列表的相似度

30

我有两个列表:

例如, a = [1,8,3,9,4,9,3,8,1,2,3] 和 b = [1,8,1,3,9,4,9,3,8,1,2,3]

它们都包含整数。这些整数没有实际意义(例如,1与3的关系不比与8的关系更近)。

我正在尝试设计一种算法来计算两个有序列表之间的相似度。有序是此处的关键字(因此我不能只取两个列表的集合并计算它们的set_difference百分比)。有时数字会重复出现(如上面的3、8和9),我不能忽略这些重复项。

在上面的示例中,我要调用的函数将告诉我,例如a和b大约相似90%。我该怎么做呢?编辑距离是我想到的一种方法。我知道如何在字符串中使用它,但我不确定如何在整数列表中使用它。谢谢!


考虑将字符串简单地视为字符列表,计算字符串的编辑距离和计算整数列表的编辑距离之间似乎存在着相当简单的映射关系。 - Chowlett
也许你正在寻找海明距离 - Pat B
@Pat B:汉明距离要求序列长度相同,无法处理删除/插入。看一下OP的例子(ab)。 - NPE
1
@aix:说得好。我猜你可以使用izip_longest来压缩这两个列表以解决这个问题。 - Pat B
7个回答

33

你可以使用difflib模块。

ratio()
返回一个浮点数,表示序列之间的相似度,范围在[0,1]之间。

这将返回:

 >>> s1=[1,8,3,9,4,9,3,8,1,2,3]
 >>> s2=[1,8,1,3,9,4,9,3,8,1,2,3]
 >>> sm=difflib.SequenceMatcher(None,s1,s2)
 >>> sm.ratio()
 0.9565217391304348

1
唯一的问题在于这些空格也会计入百分比差异。 - aerain
更多的原因说明你不想使用这种方法:它会对双位数的整数进行更严厉的惩罚,而有时也会混淆单个和双位(或更多)数字。 - aerain
实际上没有关于空格的问题,因为SequenceMatcher足够智能,可以将空格视为垃圾。 - kraymer
这非常有用,我正在尝试查找单词数组之间的相似性,它像魔法一样运行。 - lesolorzanov
谢谢,但它并不适用于所有情况。例如,对于s1=[1,2,3,4,5,6,7,8,9,10]和s2=[2,1,3,4,5,6,7,8,9,9],它返回0.8而不是0.7(有3个元素不同)。 - Tommaso Di Noto
显示剩余2条评论

12

听起来编辑距离(或Levenshtein距离)恰好是这项工作的正确工具。

以下是一个可用于整数列表的Python实现:http://hetland.org/coding/python/levenshtein.py

使用该代码,levenshtein([1,8,3,9,4,9,3,8,1,2,3], [1,8,1,3,9,4,9,3,8,1,2,3])返回1,它是编辑距离。

给定编辑距离和两个数组的长度,计算“相似度百分比”指标应该非常简单。


1
很好,Yup运行得很棒。谢谢!如果要将编辑距离转化为百分比,您应该除以什么?不确定要使用哪个列表。 - aerain
实际上,我建议使用difflib模块的方法。我不知道它可以用来比较序列相似性。 - aerain
要获得比率,您可以通过序列的长度进行除法运算! - Nico Coallier

5

应对这个问题的一种方法是利用直方图。以下是一个示例(使用numpy进行演示):

In []: a= array([1,8,3,9,4,9,3,8,1,2,3])
In []: b= array([1,8,1,3,9,4,9,3,8,1,2,3])

In []: a_c, _= histogram(a, arange(9)+ 1)
In []: a_c
Out[]: array([2, 1, 3, 1, 0, 0, 0, 4])

In []: b_c, _= histogram(b, arange(9)+ 1)
In []: b_c
Out[]: array([3, 1, 3, 1, 0, 0, 0, 4])

In []: (a_c- b_c).sum()
Out[]: -1

现在有很多方法可以利用a_cb_c,其中(表面上)最简单的相似度测量方式是:

In []: 1- abs(-1/ 9.)
Out[]: 0.8888888888888888

接下来:

In []: norm(a_c)/ norm(b_c)
Out[]: 0.92796072713833688

并且:

In []: a_n= (a_c/ norm(a_c))[:, None]
In []: 1- norm(b_c- dot(dot(a_n, a_n.T), b_c))/ norm(b_c)
Out[]: 0.84445724579043624

因此,您需要更加具体地找出最适合您目的的相似度度量方法。

我知道已经过了很长时间,但你能提供你给出的相似度量的参考吗? - Makaroni

3

如果这些值没有特定的含义,那么可以使用相同的算法计算字符串的编辑距离。


1

我很久以前为类似的任务实现了一些东西。现在,我只有一个博客条目。它很简单:你需要计算两个序列的概率密度函数,然后找到由概率密度函数图形表示的公共区域。

链接上的图片无法显示,我当时使用的外部服务器已经失效了。

现在,针对您的问题,代码转换为:

def overlap(pdf1, pdf2):
    s = 0
    for k in pdf1:
        if pdf2.has_key(k):
            s += min(pdf1[k], pdf2[k])
    return s

def pdf(l):
    d = {}
    s = 0.0
    for i in l:
        s += i
        if d.has_key(i):
            d[i] += 1
        else:
            d[i] = 1
    for k in d:
        d[k] /= s
    return d

def solve():
    a = [1, 8, 3, 9, 4, 9, 3, 8, 1, 2, 3]
    b = [1, 8, 1, 3, 9, 4, 9, 3, 8, 1, 2, 3]
    pdf_a = pdf(a)
    pdf_b = pdf(b)
    print pdf_a
    print pdf_b
    print overlap(pdf_a, pdf_b)
    print overlap(pdf_b, pdf_a)

if __name__ == '__main__':
    solve()

不幸的是,它给出了一个意外的答案,只有0.212292609351


1
@kraymer提出的解决方案在以下情况下不起作用
s1=[1,2,3,4,5,6,7,8,9,10]
s2=[2,1,3,4,5,6,7,8,9,9]  

尽管有3个不同的元素而不是2个,它仍然返回0.8。

一个解决方法可能是:

def find_percentage_agreement(s1, s2):
    assert len(s1)==len(s2), "Lists must have the same shape"
    nb_agreements = 0  # initialize counter to 0
    for idx, value in enumerate(s1):
        if s2[idx] == value:
            nb_agreements += 1

    percentage_agreement = nb_agreements/len(s1)

    return percentage_agreement

哪个会返回预期结果:

>>> s1=[1,2,3,4,5,6,7,8,9,10]
>>> s2=[2,1,3,4,5,6,7,8,9,9]
>>> find_percentage_agreement(s1, s2)
0.7

0

除非我误解了重点。

from __future__ import division

def similar(x,y):
    si = 0
    for a,b in zip(x, y):
        if a == b:
            si += 1
    return (si/len(x)) * 100


if __name__ in '__main__':
    a = [1,8,3,9,4,9,3,8,1,2,3] 
    b = [1,8,1,3,9,4,9,3,8,1,2,3]
    result = similar(a,b)
    if result is not None:
        print "%s%s Similar!" % (result,'%')

1
我认为主要问题在于它无法处理删除/插入操作(它将此 OP 示例中的两个序列视为相似度为18%,而他期望的相似度约为90%)。 - NPE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接