提高类似数据框结构的性能

时间:2020-05-13 15:54:10

标签: python performance numpy data-structures

我正面临有关我的代码中过程的数据结构挑战,在该过程中,我需要在正例和负例中计算字符串的频率。 这是一个很大的瓶颈,我似乎无法找到更好的解决方案。

我必须遍历数据集中的每个长字符串,并提取子字符串,我需要计算其子频率。在一个完美的世界中,以下形状的熊猫数据框将是完美的:

最后,预期的结构类似于

string | frequency positive | frequency negative
________________________________________________
str1   |        5           |         7 
str2   |        2           |         4 
...

但是,对于明显的性能限制,这是不可接受的。 我的解决方案是使用字典来跟踪行,并使用Nx2 numpy矩阵来跟踪频率。之所以这样做,是因为在此之后,无论如何我都需要将频率放在Nx2 numpy矩阵中。

当前,我的解决方案是这样的:

        str_freq = np.zeros((N, 2), dtype=np.uint32)
        str_dict = {}
        str_dict_counter = 0

        for i, string in enumerate(dataset):
            substrings = extract(string) # substrings is a List[str]

            for substring in substrings:
                row = str_dict.get(substring, None)

                if row is None:
                    str_dict[substring] = str_dict_counter
                    row = str_dict_counter
                    str_dict_counter += 1

                str_freq[row, target[i]] += 1 # target[i] is equal to 1 or 0

但是,这确实是我的代码的瓶颈,我想加快速度。

关于此代码的某些内容是不可压缩的,例如extract(string),因此必须保留该循环。但是,如果可能的话,使用并行处理没有问题。

我特别想知道的是,是否有一种方法可以改善内循环。众所周知,Python的循环不好,但是这个循环似乎毫无意义,但是由于(据我所知)我们无法像使用numpy数组那样对字典进行多次获取和设置,所以我不知道该怎么做改善它。

您建议做什么?是用某些较低级别的语言重写的唯一解决方案吗?

虽然我也打算使用SQL-lite,但是我不知道这是否值得。 记录下来,这大约需要10MB的数据,目前大约需要45秒,但是每次都需要用新数据重复进行。

编辑:添加示例以测试自己

import random
import string
import re
import numpy as np
import pandas as pd

def get_random_alphaNumeric_string(stringLength=8):
    return bytes(bytearray(np.random.randint(0,256,stringLength,dtype=np.uint8)))


def generate_dataset(n=10000):
    d = []
    for i in range(n):
        rnd_text = get_random_alphaNumeric_string(stringLength=1000)
        d.append(rnd_text)

    return d


def test_dict(dataset):
    pattern = re.compile(b"(q.{3})")
    target = np.random.randint(0,2,len(dataset))

    str_freq = np.zeros((len(dataset)*len(dataset[0]), 2), dtype=np.uint32)
    str_dict = {}
    str_dict_counter = 0



    for i, string in enumerate(dataset):
        substrings = pattern.findall(string) # substrings is a List[str]

        for substring in substrings:
            row = str_dict.get(substring, None)

            if row is None:
                str_dict[substring] = str_dict_counter
                row = str_dict_counter
                str_dict_counter += 1

            str_freq[row, target[i]] += 1 # target[i] is equal to 1 or 0

    return str_dict, str_freq[:str_dict_counter,:]

def test_df(dataset):
    pattern = re.compile(b"(q.{3})")
    target = np.random.randint(0,2,len(dataset))

    df = pd.DataFrame(columns=["str","pos","neg"])
    df.astype(dtype={"str":bytes, "pos":int, "neg":int}, copy=False)
    df = df.set_index("str")

    for i, string in enumerate(dataset):
        substrings = pattern.findall(string) # substrings is a List[str]

        for substring in substrings:
            check = substring in df.index

            if not check:
                row = [0,0]
                row[target[i]] = 1
                df.loc[substring] = row 
            else:
                df.loc[substring][target[i]] += 1

    return df


dataset = generate_dataset(1000000)

d,f = test_dict(dataset) # takes ~10 seconds on my laptop

# to get the value of some key, say b'q123'
f[d[b'q123'],:]

d = test_df(dataset) # takes several minutes (hasn't finished yet)
# the same but with a dataframe
d.loc[b'q123']

0 个答案:

没有答案