python - Cython 解析数字字符串行

标签 python pandas cython

我已经有一个包含 300000 多行的 Python 字符串表,如下所示:

  123    1  2.263E-04  2.024E+00  8.943E+03  9.030E+02  2.692E+03  5.448E+03  3.816E-01  1.232E-01  0.000E+00  4.389E+02  1.950E+02

如果有帮助的话,该表是使用以下 Fortran FORMAT 语句生成的:

FORMAT (2I5,1P11E11.3)

我想看看是否可以比 pandas.read_csv(..., delim_whitespace=True) 更快地加载它,这对我来说需要 540 毫秒。

text = r'''  372    1  0.000E+00  0.000E+00  0.000E+00  9.150E+02  3.236E+03  0.000E+00  0.000E+00  0.000E+00  0.000E+00  0.000E+00  3.623E+02\n'''*300000
%timeit df = pd.read_csv(StringIO(text), delim_whitespace=True, header=None)

产量:

549 ms ± 3.42 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

我认为知道行长和列宽会让 read_fwf 更快,但显然优化程度较低:

widths = [5]*2 + [11]*11
%timeit df = pd.read_fwf(StringIO(text), widths=widths, header=None)

产量:

2.95 s ± 29 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

使用 Cython 可以做得更快吗?我对 C 或 Cython 的经验很少,所以不幸的是我不知道从哪里开始使用初始示例。我也对 f2py 之类的东西持开放态度,但前提是它值得为 Cython 带来麻烦。我的依赖项中已经有一些 numba 和 Cython 的东西,所以我对 Cython 解决方案更加开放。我意识到 numba 不处理文本,所以它对此没有帮助。

感谢任何可以提供帮助的人!

最佳答案

我找到了一个可以满足我需求的 Cython 解决方案。这是使用 Jupyter Notebook 的 Cython cell magic 来处理编译。我选择 2000000 进行数组初始化,因为这对于我的数据来说是一个合理的上限。该函数仅返回实际填充的 numpy 数组的行。然后将该 numpy 数组传递给 pandas 数据框相当便宜。

我不确定还可以进行多少优化,因为我实际上还抛出了一些垃圾行,我认为这些垃圾行排除了内存映射。我有可能像 an answer to another question I had 那样使用指针,但是如果我移动指针而不是迭代行,那么在文件中查找数据并检测坏行会很棘手(有关读取数据页的更大问题的更多信息,请参阅下文)。

%%cython
import numpy as np
cimport numpy as np
np.import_array()
from libc.stdlib cimport atof
from cpython cimport bool

def read_with_cython(filename):    

    cdef float[:, ::1]  data = np.zeros((2000000, 13), np.float32)
    cdef int i = 0
    with open(filename, 'rb') as f:
        for line in f:
            if len(line) == 133:
                data[i, 0] = atof(line[0:5])
                data[i, 1] = atof(line[5:10])
                data[i, 2] = atof(line[12:21])
                data[i, 3] = atof(line[23:32])
                data[i, 4] = atof(line[34:43])
                data[i, 5] = atof(line[45:54])
                data[i, 6] = atof(line[56:65])
                data[i, 7] = atof(line[67:76])
                data[i, 8] = atof(line[78:87])
                data[i, 9] = atof(line[89:98])
                data[i, 10] = atof(line[100:109])
                data[i, 11] = atof(line[111:120])
                data[i, 12] = atof(line[122:131])

            i += 1

    return data.base[:i]

有了这个,我就可以运行以下命令:

text = '''  372    1  0.000E+00  0.000E+00  0.000E+00  9.150E+02  3.236E+03  0.000E+00  0.000E+00  0.000E+00  0.000E+00  0.000E+00  3.623E+02\n'''*300000

with open('demo_file.txt', 'w') as f:
    f.write(text)

%timeit result = read_with_cython('demo_file.txt')

并得到这个结果:

473 ms ± 6.63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

为了比较和完整性,我还编写了一个快速的纯Python版本:

def read_python(text):
    data = np.zeros((300000, 13), dtype=np.float)
    for i, line in enumerate(text.splitlines()):
        data[i, 0] = float(line[:5])
        data[i, 1] = float(line[5:10])
        for j in range(11):
            a = 10+j*11
            b = a + 11
            data[i, j+2] = float(line[a:b])

    return data

运行时间为 1.15 秒:

1.15 s ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

然后我尝试将其改编为一个运行时间为 717 毫秒的非常简单的 Cython 示例:

%%cython
def read_python_cy(text):
    text.replace('\r\n', '')
    i = 0

    while True:
        float(line[i:i+5])
        float(line[i+5:i+10])
        for j in range(11):
            a = i+10+j*11
            b = i+a + 11
            float(line[a:b])
        i += 131

    return 0

717 ms ± 5.66 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

然后我崩溃了,想出了上面更优化的 Cython 版本。

就在那时,我意识到 Cython 可以更有效地解决这个问题和缓慢的正则表达式问题。我使用正则表达式来查找并捕获大约 5000 页的数据,然后将这些数据连接到我试图在此处读取的表中。下面显示了更接近我的实际 Cython 功能的内容。它处理查找数据页、捕获页级详细信息(时间),然后读取实际数据行,直到检测到停止标志(以 0 或 1 开头的行)。我的正则表达式花费了超过 1 秒的时间来提取我想要的数据,因此这总体上为我节省了大量时间。

%%cython
import numpy as np
cimport numpy as np
np.import_array()
from libc.stdlib cimport atof
import cython
from cpython cimport bool

def read_pages_cython(filename):    

    cdef int n_pages = 0
    cdef bool reading_page = False
    cdef float[:, ::1]  data = np.zeros((2000000, 14), np.float32)
    cdef int i = 0
    cdef float time
    with open(filename, 'rb') as f:
        for line in f:
            if not reading_page:
                if b'SUMMARY' in line:
                    time = atof(line[73:80])
                    reading_page = True
            else:
                if len(line) == 133:
                    data[i, 0] = atof(line[0:5])
                    # data[i, 1] = atof(line[5:10])
                    data[i, 2] = atof(line[12:21])
                    data[i, 3] = atof(line[23:32])
                    data[i, 4] = atof(line[34:43])
                    data[i, 5] = atof(line[45:54])
                    data[i, 6] = atof(line[56:65])
                    data[i, 7] = atof(line[67:76])
                    data[i, 8] = atof(line[78:87])
                    data[i, 9] = atof(line[89:98])
                    data[i, 10] = atof(line[100:109])
                    data[i, 11] = atof(line[111:120])
                    data[i, 12] = atof(line[122:131])
                    data[i, 13] = time

                if len(line) > 6:
                    if line[:1] == b'1':
                        if b'SUMMARY' in line:
                            time = atof(line[73:80])
                            reading_page = True
                        else:
                            reading_page = False
                            i += 1
                            continue

                    elif line[:1] == b'0':
                        reading_page = False
                        i += 1
                        continue

            i += 1

    return data.base[:i]

关于python - Cython 解析数字字符串行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52599491/

相关文章:

python - 查询嵌套的 python 对象

python - 不带语法糖的带有参数的修饰符的等效含义是什么?

python - 无法安装python图像库

python-3.x - 使用日期值 reshape /透视数据

python - 不要将 nan 值写入 Excel 工作表

python - 无法从 Cython 扩展覆盖类的 __init__

python - 使用列表根据多列中的值有条件地填充新列

Python Pandas 在单个单元格中进行计算

python - Cython:将 C 缓冲区内存 View 返回给 Python

python - 尝试在 Windows 8.1 上安装 kivy