在Pandas数据框中,找出每两行字符串之间的差异。

6

我是Python的新手,这个问题困扰了我很长时间。 我有一个文件看起来像这样:

    name   seq
1   a1     bbb
2   a2     bbc
3   b1     fff
4   b2     fff
5   c1     aaa
6   c2     acg

其中name是字符串的名称,seq是字符串本身。我想要一个新的列或者一个新的数据框来表示每两行之间不重叠的差异数。例如,我想知道名称[a1-a2]、[b1-b2]和[c1-c2]之间序列的差异数。

因此,我需要像这样的东西:

    name   seq   diff  
1   a1     bbb    NA   
2   a2     bbc    1
3   b1     fff    NA
4   b2     fff    0
5   c1     aaa    NA
6   c2     acg    2

非常感谢您的帮助


你尝试了什么来解决它?这两行总是以相同的字符开头吗? - Björn
是的,它们以相同的字母开头,它们之间唯一的区别是结尾的数字。 - LDT
每个字母总是有两个序列吗?@LDT - help-ukraine-now
是的,它们按照示例中指定的顺序进行排序。 - LDT
嘿@LDT,我认为你有三个适当的解决方案来解决你的问题。我倾向于建议你接受yatu或anky的版本,因为它们更简洁/更高效,并且具有更大的灵活性(比我的)。 - Björn
4个回答

6

看起来你想要计算一组字符串的Jaccard距离。以下是使用groupbyscipy.spatial.distance.jaccard的一种方法:

from scipy.spatial.distance import jaccard
g = df.groupby(df.name.str[0])

df['diff'] = [sim for _, seqs in g.seq for sim in 
              [float('nan'), jaccard(*map(list,seqs))]]

print(df)

  name  seq  diff
1   a1  bbb   NaN
2   a2  bbc   1.0
3   b1  fff   NaN
4   b2  fff   0.0
5   c1  aaa   NaN
6   c2  acg   2.0

1
不错,今天学到了jaccard,一看到这个就立刻想到了Levenshtein。 :) - anky
1
啊,太好了 :) 嗯,听起来也是一个选项,但我不记得相似度测量具体是什么了 @anky - yatu
2
我已经为你的两个答案点赞了,因为我对你在字符串距离度量方面的知识和代码片段的效率深感印象深刻。 - Björn
非常感谢您,yatu。我想问一下,如果我的数据框中的“name”是这样的[1,1],[2,2],[3,3]而不是[a1,a2],[b1,b2],[c1,c2],那么在您的代码中我需要改变什么? - LDT
请使用以下代码:df.groupby(df.index//2) @LDT - yatu

5

Levenshtein距离的替代方案:

import Levenshtein
s = df['name'].str[0]
out = df.assign(Diff=s.drop_duplicates(keep='last').map(df.groupby(s)['seq']
                    .apply(lambda x: Levenshtein.distance(x.iloc[0],x.iloc[-1]))))

  name  seq  Diff
1   a1  bbb   NaN
2   a2  bbc   1.0
3   b1  fff   NaN
4   b2  fff   0.0
5   c1  aaa   NaN
6   c2  acg   2.0

非常感谢你,anky。我想问一下,如果我的数据框中的“name”像这样[1,1],[2,2],[3,3]而不是[a1,a2],[b1,b2],[c1,c2],那么在你的代码中我需要改变什么? - LDT
@LDT 然后把s改成s = df['name'],而不是s = df['name'].str[0] - anky
1
非常感谢你,@anky,你救了我。节日快乐。也感谢你回答我的问题。谢谢。 - LDT

2

作为第一步,我重新创建了您的数据:

#!/usr/bin/env python3
import pandas as pd

# Setup
data = {'name': {1: 'a1', 2: 'a2', 3: 'b1', 4: 'b2', 5: 'c1', 6: 'c2'}, 'seq': {1: 'bbb', 2: 'bbc', 3: 'fff', 4: 'fff', 5: 'aaa', 6: 'acg'}}
df = pd.DataFrame(data)

解决方案 您可以尝试遍历数据框并比较最后一次迭代的seq值与当前值。对于两个字符串(存储在数据框的seq列中)的比较,您可以应用类似于此函数中的简单列表推导式:

def diff_letters(a,b):
    return sum ( a[i] != b[i] for i in range(len(a)) )

遍历数据框中的行

diff = ['NA']

row_iterator = df.iterrows()
_, last = next(row_iterator)

# Iterate over the df get populate a list with result of the comparison
for i, row in row_iterator:
    if i % 2 == 0:
        diff.append(diff_letters(last['seq'],row['seq']))
    else:
        # for odd row numbers append NA value
        diff.append("NA")
    last = row
df['diff'] = diff

结果看起来像这样

  name  seq diff
1   a1  bbb   NA
2   a2  bbc    1
3   b1  fff   NA
4   b2  fff    0
5   c1  aaa   NA
6   c2  acg    2

1
检查这个

import pandas as pd

data = {'name':  ['a1', 'a2','b1','b2','c1','c2'],
    'seq': ['bbb', 'bbc','fff','fff','aaa','acg']
    }

df = pd.DataFrame (data, columns = ['name','seq'])
diffCntr=0
df['diff'] = np.nan
i=0
while i < len(df)-1:
    diffCntr=np.nan
    item=df.at[i,'seq']
    df.at[i,'diff']=diffCntr
    diffCntr=0
    for j in df.at[i+1,'seq']:
        if item.find(j) < 0:
            diffCntr +=1
    df.at[i+1,'diff']=diffCntr
    i +=2    
df  

结果是这样的:
    name seq    diff
0   a1   bbb    NaN
1   a2   bbc    1.0
2   b1   fff    NaN
3   b2   fff    0.0
4   c1   aaa    NaN
5   c2   acg    2.0

非常感谢你,Rola。我想问一下,如果我的数据框中的“name”是这样的[1,1],[2,2],[3,3]而不是[a1,a2],[b1,b2],[c1,c2],那么在你的代码中我需要改变什么? - LDT
@LDT 代码中没有需要更改的部分。你只需要将你的数据框中的a1、a2、b1、b2、c1和c2更改为1、1、2、2、3、3即可。我的代码会跟踪数据框记录并比较每两行连续的“seq”值,而不考虑“name”列中插入的值。 - Rola

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