这是一个想法,涉及到下降到numpy。
有几个步骤,但由于底层工作是通过numpy 数字数组完成的,这可能更有效。
import pandas as pd, numpy as np
data = {"id":["seq1", "seq2", "seq3"], "sequence":["ATCTGC", "AACTGC", "AACTCC"]}
df = pd.DataFrame(data)
a = np.array(list(map(list, df['sequence'])))
values = np.unique(a, return_inverse=True)[1].reshape(a.shape)
n = len(a)
d = {(i, j): np.sum(a[i]!=a[j]) for i in range(n) for j in range(n) if j > i}
res = np.zeros((n, n))
keys = list(zip(*d.keys()))
res[keys[0], keys[1]] = list(d.values())
res += res.T
df_res = pd.DataFrame(res, columns=data['id'], index=data['id'], dtype=int)
# seq1 seq2 seq3
# seq1 0 1 2
# seq2 1 0 1
# seq3 2 1 0
说明
- 将您的序列转换为
numpy 数组,其中每个元素都是一个字母。
- 使用
np.unique 分解您的数组(即将每个字母与一个数字相关联)。
- 使用
np.sum 查找因式分解数组中各行之间的字母差异,并将结果添加到字典中。由于您的结果是三角形的,因此只执行一半的计算。
- 从字典中创建新的
numpy 数组并添加逆以使三角数组充满。
- 将您的字典转换为数据框。
性能基准测试
我看到了大约 7 倍的性能提升。
%timeit original(df) # 3.32s
%timeit jp(df) # 461ms
import pandas as pd, numpy as np
data = {"id":["seq1", "seq2", "seq3"], "sequence":["ATCTGC", "AACTGC", "AACTCC"]}
df = pd.DataFrame(data)
df = pd.concat([df]*100)
def original(df):
def count_differences( seq, df ):
return df.apply(lambda x: sum(1 for i, j in zip(x["sequence"], seq) if i != j), axis=1)
df2 = df.apply(lambda x: count_differences( x["sequence"], df), axis=1)
return df2
def jp(df):
a = np.array(list(map(list, df['sequence'])))
values = np.unique(a, return_inverse=True)[1].reshape(a.shape)
n = len(a)
d = {(i, j): np.sum(a[i]!=a[j]) for i in range(n) for j in range(n) if j > i}
res = np.zeros((n, n))
keys = list(zip(*d.keys()))
res[keys[0], keys[1]] = list(d.values())
res += res.T
df_res = pd.DataFrame(res, columns=range(len(df['id'])), index=range(len(df['id'])), dtype=int)
return df_res