我已经有一个包含 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/