【问题标题】:Convert long-form dataframe of pairwise distances to distance matrix in python将成对距离的长格式数据帧转换为python中的距离矩阵
【发布时间】:2021-09-12 03:00:13
【问题描述】:

我有一个成对距离的熊猫数据框,格式为:

    SampleA   SampleB  Num_Differences
0  sample_1  sample_2                1
1  sample_1  sample_3                4
2  sample_2  sample_3                8

请注意,没有自我比较(例如,不会表示 sample_1 与 sample_1)。我想将此表转换为方形距离矩阵,如下所示:

            sample_1      sample_2  sample_3
sample_1                       1              4
sample_2         1                            8
sample_3         4             8    

谁能给我一些关于如何在 python 中进行这种转换的指示?该问题类似于R中的上一个问题(Converting pairwise distances into a distance matrix in R),但我不知道要使用的相应python函数。这个问题似乎也与这个问题相反(Convert a distance matrix to a list of pairwise distances in Python)。

以我正在使用的形式重现数据帧的一些代码:

df = pd.DataFrame([['sample_1', 'sample_2', 1],
                   ['sample_1', 'sample_3', 4],
                   ['sample_2', 'sample_3', 8]],
                  columns=['SampleA', 'SampleB', 'Num_Differences'])

谢谢!

【问题讨论】:

  • 我有点不清楚。 R 问题的链接似乎只是重塑了数据,但您似乎正在执行一些反向计算以在1 4 8 的输出中获得 2 和 6。您怎么知道要使用什么结果,因为可能存在无限的减法运算,可能会产生1 4 8 的距离。
  • 对缺乏明确性表示歉意。我实际上并不想进行计算,只是将数据从“熔融”/长格式重塑为矩阵形式。也许我没有使用正确的术语。我还编辑了问题以修正数字 - 它们是我最初使用的一个更复杂的示例遗留下来的 - 哎呀。

标签: python pandas matrix pairwise


【解决方案1】:

您可以将形状重塑为正方形,然后通过添加转置值使其对称:

# make unique, sorted, common index
idx = sorted(set(df['SampleA']).union(df['SampleB']))

# reshape
(df.pivot(index='SampleA', columns='SampleB', values='Num_Differences')
   .reindex(index=idx, columns=idx)
   .fillna(0, downcast='infer')
   .pipe(lambda x: x+x.values.T)
 )

或者,您可以使用有序分类索引并在使用pivot_table 进行整形期间保留 NA。然后将转置后的值相加,使对称:

cat = sorted(set(df['SampleA']).union(df['SampleB']))

(df.assign(SampleA=pd.Categorical(df['SampleA'],
                                  categories=cat,
                                  ordered=True),
           SampleB=pd.Categorical(df['SampleB'],
                                  categories=cat,
                                  ordered=True),
           )
    .pivot_table(index='SampleA',
                 columns='SampleB',
                 values='Num_Differences',
                 dropna=False, fill_value=0)
    .pipe(lambda x: x+x.values.T)
)

输出:

SampleB   sample_1  sample_2  sample_3
SampleA                               
sample_1         0         1         4
sample_2         1         0         8
sample_3         4         8         0

【讨论】:

  • 谢谢 - 正是我需要的,不需要额外的库。
【解决方案2】:
 pd.pivot_table(df, values='Num_Differences', index='Sample_A',
                columns='SampleB', aggfunc=max, fill_value=0)

请注意,如果同一对 Sample_A、Sample_B 的实例不超过一个,则使用什么 aggfunc 并不重要;您可以使用 sum、max、min、mode、mean 等。如果可能有多个,您可能需要考虑希望 Pandas 如何处理。

【讨论】:

  • 感谢您的回答。虽然这显示了样本之间的差异,但我更喜欢将 sample_1、sample_2 和 sample_3 作为列名和行名。此方法将 sample_1 和 sample_2 显示为行名,将 sample_2 和 sample_3 显示为列名。
【解决方案3】:

我们似乎正在将加权边缘列表转换为邻接矩阵。我们可以使用networkx 函数将from_pandas_edgelist 转换为adjacency_matrix

import networkx as nx
import pandas as pd

# Create Graph
G = nx.from_pandas_edgelist(
    df,
    source='SampleA',
    target='SampleB',
    edge_attr='Num_Differences'
)

# Build adjacency matrix
adjacency_df = pd.DataFrame(
    nx.adjacency_matrix(G, weight='Num_Differences').todense(),
    index=G.nodes,
    columns=G.nodes
)

adjacency_df:

          sample_1  sample_2  sample_3
sample_1         0         1         4
sample_2         1         0         8
sample_3         4         8         0

如果想要 NaN 而不是 0,我们也可以用 numpy.fill_diagonal 填充对角线:

import networkx as nx
import numpy as np
import pandas as pd


G = nx.from_pandas_edgelist(
    df,
    source='SampleA',
    target='SampleB',
    edge_attr='Num_Differences'
)

adjacency_df = pd.DataFrame(
    nx.adjacency_matrix(G, weight='Num_Differences').todense(),
    index=G.nodes,
    columns=G.nodes,
    dtype=float  # Compatible dtype with NaN is needed
)
# Overwrite the values on the diagonal
np.fill_diagonal(adjacency_df.values, np.NaN)

adjacency_df:

          sample_1  sample_2  sample_3
sample_1       NaN       1.0       4.0
sample_2       1.0       NaN       8.0
sample_3       4.0       8.0       NaN

【讨论】:

    【解决方案4】:
    1. 预计算原始成对距离中的唯一标签数组:
    idx = pd.concat([df['SampleA'], df['SampleB']]).unique()
    idx.sort() 
    idx
    
    array(['sample_1', 'sample_2', 'sample_3'], dtype=object)
    
    1. 旋转,然后重新索引索引和列,以在生成的中间 DataFrame 中引入零值:
    res = (df.pivot('SampleA', 'SampleB', 'Num_Differences')
             .reindex(index=idx, columns=idx)
             .fillna(0)
             .astype(int))
    res
    
    SampleB   sample_1  sample_2  sample_3
    SampleA                               
    sample_1         0         1         4
    sample_2         0         0         8
    sample_3         0         0         0
    
    1. 将中间 DataFrame 添加到它自己的转置中以生成对称的成对距离矩阵:
    res += res.T
    res
    
    SampleB   sample_1  sample_2  sample_3
    SampleA                               
    sample_1         0         1         4
    sample_2         1         0         8
    sample_3         4         8         0
    

    【讨论】:

    • 谢谢!看起来挺好的。有没有办法让得到的矩阵是对称的? (而不是下半部分的零)
    • 好点;我已经编辑了我的答案以产生一个对称的距离矩阵。看起来我最终得到了类似于@mozway 的出色答案!
    猜你喜欢
    • 2021-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-11
    • 2011-08-08
    • 2011-08-14
    • 2021-04-24
    相关资源
    最近更新 更多