数据框列中的字符串列表行之间的成对距离

Posted

技术标签:

【中文标题】数据框列中的字符串列表行之间的成对距离【英文标题】:pairwise distances between list of strings rows within a dataframe column 【发布时间】:2021-09-29 09:59:53 【问题描述】:

我有一个数据框,其中有一列字符串 id 列表。 (见下文)。 我想在所有行之间的所有成对“距离”之间创建一个距离矩阵 (例如,如果 10 行,那么它是一个 10x 10 矩阵)。 这些行是 id 列表,所以我不确定如何使用 pdist 之类的东西。

这些值是字符串 ID。就像字符串名称一样


ids
0   [58545-19, 462423-43, 277581-25]
1   [0]
2   [454950-82, 433701-46, 228790-63, 266250-52, 458759-98, 152986-78, 222217-39, 433515-16, 265589-83, 439403-23, 277892-38, 223497-19, 224072-83, 461887-57, 436147-12, 227479-78, 228893-32, 279415-18, 439426-27, 437742-46, 438156-73, 438458-68, 277898-05, 438675-76, 454658-95, 431222-77, 462579-94, 434939-86, 222211-09, 178215-13, 459566-11, 463200-04, 439278-94, 459505-18, 399139-66, 455735-62, 327382-03, 439040-62, 233779-51, 431387-38, 438589-72, 437892-49, 458178-76]
3   [431380-63]
4   [442539-01, 434388-16, 454950-82, 463197-61, 228893-32, 464322-07, 462579-94, 438781-51, 437273-11, 265395-79, 463560-76, 462525-31, 439426-27, 438458-68, 464300-38, 442676-80]
5   [234729-10, 435926-98, 416670-04, 179514-28]
6   [0]
7   [0]
8   [267726-25, 235217-71, 227314-72, 185293-18, 434447-56, 170271-19, 454661-20]
9   [0]

【问题讨论】:

计算距离的数值在哪里?? “58545-19”只是0索引中第一个列表的第一个元素中的一个ID吗?或距离的数值(58545-19=58526)??如果你使用 to_dict 方法,那么我们可以更容易地制作样本数据.. "58545-19" = 只是一个字符串 id。可以像人名一样被视为字符串。 那么您的数据是否具有与每个值对应的数值(即 58545-19)? OR 对应于列表的值(即 [58545-19, 462423-43, 277581-25])? 它们是字符串 id 的列表,它们恰好是数字,它们可以是任何 id 字符串,例如名称。所以他们可以重复(例如,相同的名字可以出现在其他集合中......因此目标是找到相似的集合。谢谢你! 在这种情况下,您如何定义“距离”?如果值只是 id,那么d: string x string -> number 是什么?另外,请添加预期输出的示例。 【参考方案1】:

这是一个使用scipy.spatial.distance.pdist 函数计算成对距离的解决方案(请参阅最后的完整代码)。

一步一步

自定义jaccard函数

虽然scipy.spatial.distance 有一个jaccard 方法,但这是为布尔数组制作的。我们需要定义一个自定义函数(使用jaccard distance的这个定义:1-intersection/union):

def jaccard(u, v):
    u,v = set(u[0]), set(v[0]) # pdist will pass 2D data [[a,b,c]], so we need to slice
    return 1-len(u.intersection(v))/len(u.union(v))

然后我们将它应用到我们的数据框列。

警告:pdist 需要一个多维数组作为输入(Series 不起作用),因此我们需要将列切片为 DataFrame (df[['ids']])。此外,直接将函数作为metric 传递会导致错误,因为函数未矢量化(请参阅下面关于该点的注释),因此我们需要将其包装在 lambda 中。

pdist(df[['ids']], metric=lambda u,v: jaccard(u,v))

如上所述,也可以改为传递矢量化函数。为此,我们可以使用numpy.vectorize请注意,该功能与以前略有不同。这里我们不对传递值的第一个元素进行切片,因为它已经是 1D。

def jaccard(u, v):
    u,v = set(u), set(v)
    return 1-len(u.intersection(v))/len(u.union(v))

pdist(df[['ids']], metric=np.vectorize(jaccard))

注意。对所提供数据集的快速测试表明,矢量化方法实际上比 lambda 慢。

输出为 2D

最后,我们使用scipy.spatial.distance.squareformpandas.DataFrame 构造函数将输出转换回矩阵:

pd.DataFrame(squareform(pdist(df[['ids']], metric=lambda u,v: jaccard(u,v))))

示例(完整代码)

让我们从这个输入开始:

df = pd.DataFrame([[['58545-19', '462423-43', '277581-25']],
                   [['0']],
                   [['454950-82', '433701-46', '228790-63', '266250-52', '458759-98', '152986-78', '222217-39', '433515-16', '265589-83', '439403-23', '277892-38', '223497-19', '224072-83', '461887-57', '436147-12', '227479-78', '228893-32', '279415-18', '439426-27', '437742-46', '438156-73', '438458-68', '277898-05', '438675-76', '454658-95', '431222-77', '462579-94', '434939-86', '222211-09', '178215-13', '459566-11', '463200-04', '439278-94', '459505-18', '399139-66', '455735-62', '327382-03', '439040-62', '233779-51', '431387-38', '438589-72', '437892-49', '458178-76']],
                   [['431380-63']],
                   [['442539-01', '434388-16', '454950-82', '463197-61', '228893-32', '464322-07', '462579-94', '438781-51', '437273-11', '265395-79', '463560-76', '462525-31', '439426-27', '438458-68', '464300-38', '442676-80']],
                   [['234729-10', '435926-98', '416670-04', '179514-28']],
                   [['0']],
                   [['0']],
                   [['267726-25', '235217-71', '227314-72', '185293-18', '434447-56', '170271-19', '454661-20']],
                   [['0']],
                  ], columns=['ids'])
from scipy.spatial.distance import pdist, squareform

def jaccard(u, v):
    u,v = set(u[0]), set(v[0])
    return 1-len(u.intersection(v))/len(u.union(v))

pd.DataFrame(squareform(pdist(df[['ids']], metric=lambda u,v: jaccard(u,v))))

输出:

     0    1         2    3         4    5    6    7    8    9
0  0.0  1.0  1.000000  1.0  1.000000  1.0  1.0  1.0  1.0  1.0
1  1.0  0.0  1.000000  1.0  1.000000  1.0  0.0  0.0  1.0  0.0
2  1.0  1.0  0.000000  1.0  0.907407  1.0  1.0  1.0  1.0  1.0
3  1.0  1.0  1.000000  0.0  1.000000  1.0  1.0  1.0  1.0  1.0
4  1.0  1.0  0.907407  1.0  0.000000  1.0  1.0  1.0  1.0  1.0
5  1.0  1.0  1.000000  1.0  1.000000  0.0  1.0  1.0  1.0  1.0
6  1.0  0.0  1.000000  1.0  1.000000  1.0  0.0  0.0  1.0  0.0
7  1.0  0.0  1.000000  1.0  1.000000  1.0  0.0  0.0  1.0  0.0
8  1.0  1.0  1.000000  1.0  1.000000  1.0  1.0  1.0  0.0  1.0
9  1.0  0.0  1.000000  1.0  1.000000  1.0  0.0  0.0  1.0  0.0

这是所提供数据集距离的图形表示(白色 = 更远):

【讨论】:

试图给你赏金。它不让我给管理员发消息。非常感谢你。 我认为您已经将赏金授予另一个答案;)【参考方案2】:

如果您想计算列表之间的 Jaccard 距离,因此基于共同项目的数量,您可以遍历行,计算相异度,然后构造您的 distances DataFrame。此外,由于生成的 DataFrame 将是对称的,为了优化计算,您可以只构建上三角形,然后将其复制到下三角形中以创建完整的 DataFrame。

从包含 ID 的 Dataframe df 开始,您可以通过以下方式执行此操作:

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

distances = pd.DataFrame(columns=range(df.shape[0]))

for i in range(0, len(df)):
    for j in range(i, len(df)):
        distances.loc[i, j] = jaccard(df['ids'].iloc[i],df['ids'].iloc[j])

distances[distances.isnull()] = distances.transpose()

【讨论】:

以上是关于数据框列中的字符串列表行之间的成对距离的主要内容,如果未能解决你的问题,请参考以下文章

将成对距离表转换为仅两列中个人的距离列表

根据每个句子的第一个单词将 pandas 数据框列中的字符串列表分解为新列

处理 NaN 的成对距离

计算大量 GPS 坐标之间的成对路由距离

如何从 pyspark 数据框列中的列表中删除特定字符串

以字符串形式存储在 Pandas 数据框列中的解析列表