Python pandas 计算子字符串的唯一字符串源的数量

Posted

技术标签:

【中文标题】Python pandas 计算子字符串的唯一字符串源的数量【英文标题】:Python pandas counting the number of unique string sources for substrings 【发布时间】:2021-07-27 20:51:00 【问题描述】:

假设我有一个包含 5 个字符串的列表,例如:

AAAAB
BBBBA
BBBBA
ABBBB

我想找到并计算每一个可能的 4 字符子字符串,并跟踪它们来自的唯一 5 字符字符串的数量。这意味着虽然 BBBB 在三个不同的字符串源中找到,但只有两个唯一的源。

示例输出:

    substring    repeats    unique sources
0     AAAA          1              1
1     AAAB          1              1
2     BBBB          3              2
3     BBBA          2              1
4     ABBB          1              1

我已经设法仅使用 Python、一个更新的字典和两个用于比较现有子字符串和全长字符串的列表来小规模地做到这一点。但是,当将其应用于我的完整数据集(约 160 000 个全长字符串(12 个字符)产生 1.5 亿个子字符串(4 个字符))时,常量字典更新和列表比较过程太慢(我的脚本已经运行了一周) )。 在 Python 和 pandas 中,计算所有全长字符串中存在的子字符串的数量既容易又便宜。

所以我的问题是:如何有效地计算和更新 DataFrame 中子字符串的唯一全长源的计数?

【问题讨论】:

在看到列表值之前是否知道列表中值的字符集?例如 A-Z 等。 是的。每个全长字符串总是由相同的 20 个字符构成(它是 20 个主要的氨基酸单字母代码,因此字母表中的前 20 个大写字母也可以作为示例)。 前 20 个包括在内? A-T 还是 A-S? 如果我们采用字母表:ABCDEFGHIJKLMNOPQRST。或者,如果我们采用正确的氨基酸列表:ARNDCQEGHILKMFPSTWYV。 【参考方案1】:

TLDR:对于您描述的数据规模,在我的计算机上进行估计大约需要 2 小时。

import numpy as np
import pandas as pd

def substring_search(fullstrings, sublen=4):
    '''
    fullstrings: array like of strings
    sublen: length of substring to search
    '''
    # PART 1: FIND SUBSTRINGS

    # length of full strings, assumes all are same
    strsize = len(fullstrings[0])

    # get unique strings, # occurences
    strs, counts = np.unique(fullstrings, return_counts=True)
    fullstrings = pd.DataFrame('string':strs,
                                'count':counts)
    unique_n = len(fullstrings)

    # create array to hold substrings
    substrings = np.empty(unique_n * (strsize - sublen + 1), dtype=str)
    substrings = pd.Series(substrings)

    # slice to find each substring
    c = 0
    while c + sublen <= strsize:
        sliced = fullstrings['string'].str.slice(c, c+sublen)
        s = c * unique_n
        e = s + unique_n
        substrings[s: e] = sliced
        c += 1

    # take the set of substrings, save in output df
    substrings = np.unique(substrings)
    output = pd.DataFrame('substrings':substrings,
                           'repeats': 0,
                           'unique_sources': 0)

    # PART 2: CHECKING FULL STRINGS FOR SUBSTRINGS

    for i, s in enumerate(output['substrings']):
        # check which fullstrings contain each substring
        idx = fullstrings['string'].str.contains(s)
        count = fullstrings['count'][idx].sum()
        output.loc[i, 'repeats'] = count
        output.loc[i, 'unique_sources'] = idx.sum()
    print('Finished!')

    return output

应用于您的示例:

>>> example = ['AAAAB', 'BBBBA', 'BBBBA', 'ABBBB']
>>> substring_search(example)

  substrings  repeats  unique_sources
0       AAAA        1               1
1       AAAB        1               1
2       ABBB        1               1
3       BBBA        2               1
4       BBBB        3               2

说明

上面代码中的基本思想是遍历所有唯一的子字符串,并(对于每个子字符串)使用pandasstr方法检查完整字符串列表。这节省了一个 for 循环(即,您不会为每个子字符串遍历每个完整字符串)。另一个想法是只检查唯一的完整字符串(除了唯一的子字符串);您事先保存每个完整字符串的出现次数并在最后更正计数。

基本结构是:

    获取输入中的唯一字符串,并记录每个字符串出现的次数。 在输入中查找所有唯一的子字符串(我使用pandas.Series.str.slice 进行此操作) 循环遍历每个子字符串,并使用pandas.Series.str.contains(按元素)检查完整字符串。由于这些是唯一的,而且我们知道每个发生的次数,我们可以同时填写 repeatsunique_sources

测试

这是我用来创建更大输入数据的代码:

n = 100
size = 12

letters = list(string.ascii_uppercase[:20])
bigger = [''.join(np.random.choice(letters, size)) for i in range(n)]

所以biggern size-长度字符串:

['FQHMHSOIEKGO',
 'FLLNCKAHFISM',
 'LDKKRKJROIRL',
 ...
 'KDTTLOKCDMCD',
 'SKLNSAQQBQHJ',
 'TAIAGSIEQSGI']

使用打印进度的修改代码(发布在下面),我尝试使用n=150000size=12,并得到了这个初始输出:

Starting main loop...
5%, 344.59 seconds
10.0%, 685.28 seconds

所以 10 * 685 秒 / 60(秒/分钟)= ~114 分钟。所以 2 小时并不理想,但实际上比 1 周更有用。我不怀疑有一些更聪明的方法可以做到这一点,但如果没有发布其他内容,这可能会有所帮助。

如果您确实使用此代码,您可能需要通过一些较小的示例来验证结果是否正确。我不确定的一件事是你是否想计算一个子字符串是否只出现在每个完整字符串中(即contains),或者你是否想要它出现在一个完整字符串中的次数(即count)。至少希望这是一个小小的改变。

这是在进行搜索时打印进度的附加代码; #PART 2 中还有其他语句:

def substring_search_progress(fullstrings, sublen=4):
    '''
    fullstrings: array like of strings
    sublen: length of substring to search
    '''
    # PART 1: FIND SUBSTRINGS

    # length of full strings, assumes all are same
    strsize = len(fullstrings[0])

    # get unique strings, # occurences
    strs, counts = np.unique(fullstrings, return_counts=True)
    fullstrings = pd.DataFrame('string':strs,
                                'count':counts)
    unique_n = len(fullstrings)

    # create array to hold substrings
    substrings = np.empty(unique_n * (strsize - sublen + 1), dtype=str)
    substrings = pd.Series(substrings)

    # slice to find each substring
    c = 0
    while c + sublen <= strsize:
        sliced = fullstrings['string'].str.slice(c, c+sublen)
        s = c * unique_n
        e = s + unique_n
        substrings[s: e] = sliced
        c += 1

    # take the set of substrings, save in output df
    substrings = np.unique(substrings)
    output = pd.DataFrame('substrings':substrings,
                           'repeats': 0,
                           'unique_sources': 0)

    # PART 2: CHECKING FULL STRINGS FOR SUBSTRINGS

    # for marking progress
    total = len(output)
    every = 5
    progress = every

    # main loop
    print('Starting main loop...')
    start = time.time()
    for i, s in enumerate(output['substrings']):

        # progress
        if (i / total * 100) > progress:
            now = round(time.time() - start, 2)
            print(f'progress%, now seconds')
            progress = (((i / total * 100) // every) + 1) * every

        # check which fullstrings contain each substring
        idx = fullstrings['string'].str.contains(s)
        count = fullstrings['count'][idx].sum()
        output.loc[i, 'repeats'] = count
        output.loc[i, 'unique_sources'] = idx.sum()
    print('Finished!')

    return output

【讨论】:

以上是关于Python pandas 计算子字符串的唯一字符串源的数量的主要内容,如果未能解决你的问题,请参考以下文章

Postgres:计算子查询中的唯一数组条目

java例题_49 计算子串出现的次数

根据子数组的位置和数组的第一个元素计算子数组元素的总和

Python字符串方法

Pandas - 计算并获取列中字符串值的唯一出现次数

在pandas DataFrame中按列计算唯一字符串[关闭]