【问题标题】:Calculating the similarity of two lists计算两个列表的相似度
【发布时间】:2011-10-06 06:50:42
【问题描述】:

我有两个列表:

例如。 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 的距离更近)。

我正在尝试设计一种算法来计算两个 ORDERED 列表之间的相似性。 Ordered 是这里的关键字(所以我不能只取两个列表的集合并计算它们的 set_difference 百分比)。有时数字会重复(例如上面的 3、8 和 9,我不能忽略重复)。

在上面的示例中,我调用的函数会告诉我 a 和 b 相似度约为 90%。我怎样才能做到这一点?我想到了编辑距离。我知道如何将它与字符串一起使用,但我不确定如何将它与整数列表一起使用。谢谢!

【问题讨论】:

  • 考虑到一个字符串只是一个字符列表,在计算字符串的编辑距离和计算整数列表的编辑距离之间似乎有一个非常简单的映射。
  • 也许您正在寻找hamming distance
  • @Pat B:汉明距离要求序列长度相同,它不能处理删除/插入。看看 OP 的例子(ab)。
  • @aix:好点。我猜你可以用 izip_longest 压缩这两个列表来解决这个问题。

标签: python algorithm


【解决方案1】:

您可以使用difflib 模块

比率()
将序列相似度的度量返回为 [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

【讨论】:

  • 这里唯一的问题是这些空格最终也会计入百分比差异
  • 您不想使用这种方法的更多原因:这比单个数字更惩罚两位数,有时会混淆一位数和双(或更多)位数字。
  • 事实上没有(关于空格),因为 SequenceMatcher 足够聪明,可以将空格检测为垃圾。
  • @aerain 检查文档后,SequenceMatcher 接受任何类型的序列,所以我编辑了处理 int 列表的答案
  • 谢谢!现在忽略我以前在这个线程中的 cmets。我其实最喜欢这种方法!
【解决方案2】:

听起来编辑(或 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,即编辑距离。

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

【讨论】:

  • 是的,效果很好。谢谢!您将编辑距离除以多少以获得百分比?不确定要使用哪一个列表
  • 实际上我推荐使用 difflib 模块方法。我不知道它可以用来比较序列相似性。
  • 要得到一个比率,你可以除以序列的长度!
【解决方案3】:

解决此问题的一种方法是使用histogram。举个例子(用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

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

【讨论】:

  • 我知道它已经很久了,但是你能参考你给出的相似性度量吗?
【解决方案4】:

如果值没有任何特定含义,只需使用相同的算法来计算字符串的编辑距离。

【讨论】:

    【解决方案5】:

    很久以前我已经为类似的任务实现了一些东西。现在,我只有a blog entry for that。这很简单:您必须计算两个序列的 pdf,然后它会找到 pdf 的图形表示所覆盖的公共区域。

    抱歉链接上的图片损坏了,我当时使用的外部服务器现在已经死了。

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

    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

    【讨论】:

      【解决方案6】:

      @kraymer 提出的解决方案在以下情况下不起作用

      s1=[1,2,3,4,5,6,7,8,9,10]
      s2=[2,1,3,4,5,6,7,8,9,9]  
      

      因为它返回 0.8,即使有 3 个不同的元素而不是 2 个。

      解决方法可能是:

      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
      

      【讨论】:

        【解决方案7】:

        除非我没有抓住重点。

        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,'%')
        

        【讨论】:

        • 我认为主要问题是它无法处理删除/插入(它认为此 OP 示例中的两个序列相似 18%,而他预计约 90%)。
        猜你喜欢
        • 2012-03-11
        • 2018-08-09
        • 1970-01-01
        • 1970-01-01
        • 2015-05-03
        • 1970-01-01
        • 1970-01-01
        • 2017-08-22
        相关资源
        最近更新 更多