【问题标题】:Convert dataframe rows to Python set将数据框行转换为 Python 集
【发布时间】:2017-11-29 13:24:37
【问题描述】:

我有这个数据集:

import pandas as pd
import itertools

A = ['A','B','C']
M = ['1','2','3']
F = ['plus','minus','square']

df = pd.DataFrame(list(itertools.product(A,M,F)), columns=['A','M','F'])
print(df)

示例输出如下:

   A  M       F
0   A  1    plus
1   A  1   minus
2   A  1  square
3   A  2    plus
4   A  2   minus
5   A  2  square

我想对这个数据框中的每一行进行成对比较(jaccard相似度),例如比较

A 1 plusA 2 square 并获得两者之间的相似度值。

我写了一个jaccard函数:

def jaccard(a, b):
    c = a.intersection(b)
    return float(len(c)) / (len(a) + len(b) - len(c))

这只是在现场工作,因为我使用了intersection

我想要这样的输出(这个预期的结果值只是随机数):

    0     1     2     3     45
0  1.00  0.43  0.61  0.55  0.46
1  0.43  1.00  0.52  0.56  0.49
2  0.61  0.52  1.00  0.48  0.53
3  0.55  0.56  0.48  1.00  0.49
45  0.46  0.49  0.53  0.49  1.00

获得成对指标结果的最佳方法是什么?

谢谢你,

【问题讨论】:

  • “它不起作用”是什么意思?使用您给定的dfset(df.loc[0]) 按预期计算为{'1', 'A', 'plus'}
  • 在我的笔记本中,结果是{'A','M','F'}
  • 那是因为你有[[0]] 而不是[0],这将返回一个数据框对象而不是一个系列对象。由于它是一个数据框,set 将返回列值。
  • 好的,谢谢@Sebastian 我才意识到我使用双括号
  • 如何计算第 0 行和第 1 行的 0.43?三项中有两项相交,所以不应该是 2 / (3 + 3 - 2) = 0.5?

标签: python pandas dataframe set similarity


【解决方案1】:

您想要的完整实现可以在这里找到:

series_set = df.apply(frozenset, axis=1)
new_df = series_set.apply(lambda a: series_set.apply(lambda b: jaccard(a,b)))

【讨论】:

  • 谢谢塞巴斯蒂安,我已经尝试了一个多小时得到结果
  • 老实说,我没想到会这么简单,我刚刚开始使用apply 来获得“足够好”的解决方案。
  • 嗨@Sebastian,是否可以对角删除一半的结果?因为它重复,对吧?
  • 嵌套应用?哇,这将在大量输入时遭受
  • 当然可以,但是有没有更简单的方法来获取两个系列的笛卡尔积并将其转换为具有自定义功能的 DataFrame?还是有更好的方法来解决这个问题?就像我说的,这是我“足够好”的方法,但我希望看到更精致的答案。
【解决方案2】:

你可以通过向量化你的函数来摆脱嵌套的应用。首先,获取所有成对组合并将其传递给函数的矢量化版本 -

def jaccard_similarity_score(a, b):
    c = a.intersection(b)
    return float(len(c)) / (len(a) + len(b) - len(c))

i = df.apply(frozenset, 1).to_frame()
j = i.assign(foo=1)
k = j.merge(j, on='foo').drop('foo', 1)
k.columns = ['A', 'B']

fnc = np.vectorize(jaccard_similarity_score)
y = fnc(k['A'], k['B']).reshape(len(df), -1)
y
array([[ 1. ,  0.5,  0.5,  0.5,  0.2,  0.2],
       [ 0.5,  1. ,  0.5,  0.2,  0.5,  0.2],
       [ 0.5,  0.5,  1. ,  0.2,  0.2,  0.5],
       [ 0.5,  0.2,  0.2,  1. ,  0.5,  0.5],
       [ 0.2,  0.5,  0.2,  0.5,  1. ,  0.5],
       [ 0.2,  0.2,  0.5,  0.5,  0.5,  1. ]])

这已经更快了,但让我们看看我们是否可以甚至更快。


使用senderle的快速cartesian_product -

def cartesian_product(*arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)  


i = df.apply(frozenset, 1).values
j = cartesian_product(i, i)
y = fnc(j[:, 0], j[:, 1]).reshape(-1, len(df))

y

array([[ 1. ,  0.5,  0.5,  0.5,  0.2,  0.2],
       [ 0.5,  1. ,  0.5,  0.2,  0.5,  0.2],
       [ 0.5,  0.5,  1. ,  0.2,  0.2,  0.5],
       [ 0.5,  0.2,  0.2,  1. ,  0.5,  0.5],
       [ 0.2,  0.5,  0.2,  0.5,  1. ,  0.5],
       [ 0.2,  0.2,  0.5,  0.5,  0.5,  1. ]])

【讨论】:

  • 绝对是一个更好的解决方案,+1。不过,您不能否认嵌套 applys 的简单性,它应该只是一个较慢的常数因素。另外,我编辑了我的答案以使用frozenset,完全忘记了这一点。
  • @Sebastian 诚然是的,但我敢打赌,常数因子相当大,你应该看到中等大小输入的差异(是的,对于大输入,由于问题的组合性质)。
  • 另外,我不确定这其中计算成本高昂的部分是什么,但假设它正在应用 fnc,您可以通过仅将其应用于上三角形将时间减少大约两倍.
  • @Sebastian 那,而且函数本身本身就很慢。更重要的是,使用 freezesets 列在性能方面提供了 0 好处,因为它们是对象。这是 OP 输入的性质。是的,只计算上三角应该提供更多的速度增益。
猜你喜欢
  • 1970-01-01
  • 2021-10-29
  • 2020-10-22
  • 2013-09-21
  • 2018-12-08
  • 2017-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多