将一份成对距离的长格式数据框在Python中转换为距离矩阵。

8
我有一个Pandas数据帧,其中包含成对距离,格式如下:
    SampleA   SampleB  Num_Differences
0  sample_1  sample_2                1
1  sample_1  sample_3                4
2  sample_2  sample_3                8

请注意,没有自我比较(例如,样本_1与样本_1不会被表示)。我想将此表格转换为方阵距离矩阵,如下所示:
            sample_1      sample_2  sample_3
sample_1                       1              4
sample_2         1                            8
sample_3         4             8    

有没有人能够指导我如何使用Python进行此类转换?这个问题类似于以前在R中提出的一个问题(在R中将成对距离转换为距离矩阵),但我不知道要使用哪些相应的Python函数。此问题似乎也与这个问题相反(在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”的距离,您如何知道要使用哪些结果? - Henry Ecker
很抱歉表述不够清晰。我实际上并不想进行计算,只是将数据从“熔解(molten)”/长格式转换为矩阵形式。也许我没有使用正确的术语。我还编辑了问题,修复了数字 - 它们是从我最初使用的更复杂的示例中留下来的 - 糟糕。 - frustrated_bioinformatician
4个回答

4

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

# 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 重塑时保留 NAs。然后添加转置值以使其对称:

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

3
  1. 预先计算原始配对距离中的唯一标签数组:
idx = pd.concat([df['SampleA'], df['SampleB']]).unique()
idx.sort() 
idx

array(['sample_1', 'sample_2', 'sample_3'], dtype=object)

将索引和列进行透视,并重新索引,使结果中间的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

将中间的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

谢谢!看起来不错。有没有办法让结果矩阵对称?(而不是在下半部分填零) - frustrated_bioinformatician
好的观点;我已经编辑了我的答案,以生成对称距离矩阵。看起来我得到了类似于@mozway优秀答案的东西! - Peter Leimbigler

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

我们也可以使用 numpy.fill_diagonal 来填充对角线,如果想要 NaN 替代 0:
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

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显示为列名称。 - frustrated_bioinformatician

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