首页 > 解决方案 > 提高类数据框结构的性能

问题描述

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

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

最后,预期的结构类似于

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

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

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

        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']

标签: pythonperformancenumpydata-structures

解决方案


推荐阅读