首先我们来回答一下问题的标题
1- 如何有效读取包含浮点数的 csv 的 15M 行
我建议你使用modin https://github.com/modin-project/modin:
生成样本数据:
import modin.pandas as mpd
import pandas as pd
import numpy as np
frame_data = np.random.randint(0, 10_000_000, size=(15_000_000, 2))
pd.DataFrame(frame_data*0.0001).to_csv('15mil.csv', header=False)
!wc 15mil*.csv ; du -h 15mil*.csv
15000000 15000000 480696661 15mil.csv
459M 15mil.csv
现在来看看基准测试:
%%timeit -r 3 -n 1 -t
global df1
df1 = pd.read_csv('15mil.csv', header=None)
9.7 s ± 95.1 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
%%timeit -r 3 -n 1 -t
global df2
df2 = mpd.read_csv('15mil.csv', header=None)
3.07 s ± 685 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
(df2.values == df1.values).all()
True
所以我们可以看到 modin 大约是快 3 倍在我的设置上。
现在回答您的具体问题
2-清理包含非数字字符的csv文件,然后读取它
正如人们所指出的,您的瓶颈可能是转换器。您正在调用这些 lambda 3000 万次。在这种规模下,甚至函数调用开销也变得不平凡。
让我们来解决这个问题。
生成脏数据集:
!sed 's/.\{4\}/&)/g' 15mil.csv > 15mil_dirty.csv
方法
首先,我尝试将 modin 与转换器参数一起使用。然后,我尝试了一种不同的方法,减少调用正则表达式的次数:
首先,我将创建一个类似文件的对象,通过正则表达式过滤所有内容:
class FilterFile():
def __init__(self, file):
self.file = file
def read(self, n):
return re.sub(r"[^\d.,\n]", "", self.file.read(n))
def write(self, *a): return self.file.write(*a) # needed to trick pandas
def __iter__(self, *a): return self.file.__iter__(*a) # needed
然后我们将它作为 read_csv 中的第一个参数传递给 pandas:
with open('15mil_dirty.csv') as file:
df2 = pd.read_csv(FilterFile(file))
基准:
%%timeit -r 1 -n 1 -t
global df1
df1 = pd.read_csv('15mil_dirty.csv', header=None,
converters={0: lambda x: np.float32(re.sub(r"[^\d.]", "", x)),
1: lambda x: np.float32(re.sub(r"[^\d.]", "", x))}
)
2min 28s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1 -t
global df2
df2 = mpd.read_csv('15mil_dirty.csv', header=None,
converters={0: lambda x: np.float32(re.sub(r"[^\d.]", "", x)),
1: lambda x: np.float32(re.sub(r"[^\d.]", "", x))}
)
38.8 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1 -t
global df3
df3 = pd.read_csv(FilterFile(open('15mil_dirty.csv')), header=None,)
1min ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
看来莫丁又赢了!
不幸的是 modin 还没有实现从缓冲区读取,所以我设计了最终的方法。
最终方法:
%%timeit -r 1 -n 1 -t
with open('15mil_dirty.csv') as f, open('/dev/shm/tmp_file', 'w') as tmp:
tmp.write(f.read().translate({ord(i):None for i in '()'}))
df4 = mpd.read_csv('/dev/shm/tmp_file', header=None)
5.68 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
这使用translate
这比re.sub
,并且还使用/dev/shm
这是 ubuntu(和其他 Linux)通常提供的内存文件系统。写入其中的任何文件都不会写入磁盘,因此速度很快。
最后,它使用 modin 读取文件,解决 modin 的缓冲区限制。
这种方法是关于快 30 倍比你的方法要好,而且也很简单。