如何在Python Pandas DataFrame中找到正则匹配的起始和结束位置?

3 投票
1 回答
1829 浏览
提问于 2025-04-30 16:04

我从数据库中获取DNA或蛋白质序列。这些序列是对齐的,所以虽然我总是知道一个输入序列,但它通常是被截断的,并且包含一些用“-”字符表示的空隙。我首先想在查询字符串中找到一个区域。在这种情况下,使用正则表达式搜索是非常合适的。接下来,我想从其他对齐的字符串中提取相应的区域(我在这里把它们称为“markup”和“hit”)。因为这些序列是对齐的,所以我想要的区域在所有字符串中都有相同的起始和结束位置。有没有简单的方法可以在pandas数据框中获取正则匹配的起始和结束位置呢?

import pandas as pd
import re
q1,q2,q3 = 'MPIMGSSVYITVELAIAVLAILG','MPIMGSSVYITVELAIAVLAILG','MPI-MGSSVYITVELAIAVLAIL'
m1,m2,m3 = '|| ||  ||||||||||||||||','||   | ||| :|| || |:: |','||:    ::|: :||||| |:: '
h1,h2,h3 = 'MPTMGFWVYITVELAIAVLAILG','MP-NSSLVYIGLELVIACLSVAG','MPLETQDALYVALELAIAALSVA' 
#create a pandas dataframe to hold the aligned sequences
df = pd.DataFrame({'query':[q1,q2,q3],'markup':[m1,m2,m3],'hit':[h1,h2,h3]})
#create a regex search string to find the appropriate subset in the query sequence, 
desired_region_from_query = 'PIMGSS'
regex_desired_region_from_query = '(P-*I-*M-*G-*S-*S-*)'

Pandas有一个很不错的提取功能,可以从查询中切出匹配的序列:

df['query'].str.extract(regex_desired_region_from_query)

但是我需要匹配的起始和结束位置,以便从markup和hit列中提取相应的区域。对于单个字符串,可以这样做:

match = re.search(regex_desired_region_from_query, df.loc[2,'query'])
sliced_hit = df.loc[2,'hit'][match.start():match.end()]
sliced_hit
Out[3]:'PLETQDA'

我目前的解决方法如下。(编辑后包含了nhahtdh的建议,因此避免了重复搜索。)

#define function to obtain regex output (start, stop, etc) as a tuple
def get_regex_output(x):
    m = re.search(regex_desired_region_from_query, x)
    return (m.start(), m.end())
#apply function
df['regex_output_tuple'] = df['query'].apply(get_regex_output)
#convert the tuple into two separate columns
columns_from_regex_output = ['start','end']      
for n, col in enumerate(columns_from_regex_output):
    df[col] = df['regex_output_tuple'].apply(lambda x: x[n])
#delete the unnecessary column
df = df.drop('regex_output_tuple', axis=1)

现在我想用得到的起始和结束整数来切割字符串。这个代码会很好:
df.sliced = df.string[df.start:df.end]
但我觉得目前并不存在这个功能。相反,我又一次使用了lambda函数:

#create slice functions
fn_slice_hit = lambda x : x['hit'][x['start']:x['end']]
fn_slice_markup = lambda x : x['markup'][x['start']:x['end']]

#apply the slice functions
df['sliced_markup'] = df.apply(fn_slice_markup, axis = 1)
df['sliced_hit'] = df.apply(fn_slice_hit, axis = 1)
print(df)

                       hit                   markup                    query   start  end sliced_markup sliced_hit
0  MPTMGFWVYITVELAIAVLAILG  || ||  ||||||||||||||||  MPIMGSSVYITVELAIAVLAILG       1    7        | ||       PTMGFW
1  MP-NSSLVYIGLELVIACLSVAG  ||   | ||| :|| || |:: |  MPIMGSSVYITVELAIAVLAILG       1    7        |   |      P-NSSL
2  MPLETQDALYVALELAIAALSVA  ||:    ::|: :||||| |::   MPI-MGSSVYITVELAIAVLAIL       1    8       |:    :    PLETQDA

pandas的.match、.extract、.findall函数有没有类似于.start()或.end()的属性呢?
有没有更优雅的切割方法?
任何帮助都将不胜感激!

暂无标签

1 个回答

1

我觉得在pandas里可能没有这个功能,但如果有的话会很不错。你可以去https://github.com/pydata/pandas/issues,提交一个新的问题。说明一下你希望看到的这个功能改进。

关于.start()和.end()这两个方法,可能把它们作为extract()方法的参数会更合适。如果用str.extract(pat, start_index=True),那么它应该返回一个包含起始索引的Series或DataFrame,而不是捕获组的值。end_index=True也是一样。这两个参数可能需要互斥。

我也喜欢你提到的这个建议:

df.sliced = df.string[df.start:df.end]

Pandas已经有一个str.slice方法了。

df.sliced = df.string.str.slice(1, -1)

不过这个方法的参数必须是整数。你可以在Github上再提一个问题,建议str.slice方法可以接受系列对象,并逐个元素应用。

很抱歉没有比你的lambda小技巧更好的解决方案,但正是像这样的使用场景,推动着Pandas变得更好。

撰写回答