读取特定的大块熊猫/不读取熊猫中的所有块

时间:2019-05-23 09:24:59

标签: python pandas for-loop

我正在尝试相应地用于this question and answer逐块读取大型csv文件并进行处理。由于我不是python的本地人,因此遇到了优化问题,并在此处寻求更好的解决方案。

我的代码做什么:

我用

读取了csv的行数
with open(file) as f:
    row_count = sum(1 for line in f)

之后,我将我的数据“切片”为30个相等大小的块,并使用for循环和pd.read_csv(file, chunksize)将其相应地处理为链接的答案。由于在一张图中绘制30张图是非常不清楚的,因此我每5步以模(可能会变化)绘制它。为此,我使用一个外部计数器。

chunksize = row_count // 30
counter = 0
for chunk in pd.read_csv(file, chunksize=chunksize):
    df = chunk
    print(counter)
    if ((counter % 5) == 0 | (counter == 0):
        plt.plot(df["Variable"])
    counter = counter +1
plt.show()

现在我的问题:

似乎,此循环在处理循环之前先读取块大小,这是合理的。我可以看到,因为print(counter)步骤也相当慢。由于我读取了几百万个csv行,因此每个步骤都需要花费一些时间。有没有办法在读入之前跳过for循环中不需要的块?我正在尝试类似的东西:

wanted_plts <- [1,5,10,15,20,25,30]
for i in wanted_plts:
   for chunk[i] in pd.read_csv(file, chunksize=chunksize):
   .
   .

我认为我已经了解了如何处理for循环范围的这种语法的问题。应该有一个优雅的方法来解决这个问题。

也:我发现了.get_chunk(x)的熊猫,但这似乎只产生了一个大小为x的块。

我的另一种尝试是像pd.read_csv一样将pd.read_csv()[0,1,2]的读者对象子集化,但这似乎也是不可能的。


修正:我知道在matplotlib中绘制大量数据确实很慢。我对它进行了预处理,但是为了使这段代码可读,我删除了所有不必要的部分。

此外,这里的答案可能微不足道。也许我想得太多。

3 个答案:

答案 0 :(得分:1)

不使用CSV将CSV解析为DataFrame时,您会浪费大量资源。为避免这种情况,您可以在第一遍创建线索引:

fp = open(file_name)
row_count = 0
pos = {0: 0}
line = fp.readline()
while line:
    row_count += 1
    pos[row_count] = fp.tell()
    line = fp.readline()

不要处置文件句柄!由于read_csv()接受流,因此您可以根据需要移动文件指针:

chunksize = row_count // 30
wanted_plts = [1,5,10,15,20,25,30]
for i in wanted_plts: 
    fp.seek(pos[i*chunksize])  # this will bring you to the first line of the desired chunk
    obj = pd.read_csv(fp, chunksize=chunksize)  # read your chunk lazily
    df = obj.get_chunk()  # convert to DataFrame object
    plt.plot(df["Variable"]) # do something  

fp.close()  # Don't forget to close the file when finished.

最后一个警告:以这种方式读取CSV时,您将丢失列名。因此进行调整:

 obj = pd.read_csv(fp, chunksize=chunksize, names=[!!<column names you have>!!])

P.S。 file是保留字,请避免使用它来防止不良的副作用。您可以改用file_file_name

答案 1 :(得分:0)

我弄弄了您的设置,试图找到一种跳过块的方法,使用诸如pyqtgraph之类的另一个渲染库或使用matplotlib.pyplot子例程而不是plot(),所有方法均无济于事

因此,我唯一可以给您的合理建议是通过传递read_csv参数,将usecols的范围限制为仅对您感兴趣的数据。 / p>

代替:

for chunk in pd.read_csv(file, chunksize=chunksize):
    plt.plot(chunk['Variable'])

使用:

for chunk in pd.read_csv(file, usecols=['Variable'], chunksize=chunksize):
    plt.plot(chunk)

而且,如果还没有的话,请确定通过可能的最大chunksize来限制迭代次数(因此,对于您来说,最低的row_count分隔线)。

我还没有量化它们各自的权重,但是您将同时受益于csv_read()plot()方法的开销,即使由于您当前的数据块已经相当大这一事实也是如此。

使用我的测试数据,将chunksize翻三倍可以将处理时间减少一半:

chunksize=1000 => 在12.7秒内执行
chunksize=2000 =>在9.06秒内执行
chunksize=3000 =>在7.68秒内执行
chunksize=4000 =>在6.94秒内执行

在读取时指定usecols还将处理时间减少一半:

chunksize=1000 + usecols=['Variable'] =>在8.33秒内执行
chunksize=2000 + usecols=['Variable'] =>在5.27秒内执行
chunksize=3000 + usecols=['Variable'] =>在4.39秒内执行
chunksize=4000 + usecols=['Variable'] => 在3.54秒内执行

答案 2 :(得分:0)

据我所知,pandas不提供任何跳过文件块的支持。至少我在文档中什么都没找到。

通常,很难从文件中跳过行(根本不读取它们),除非您事先知道要跳过多少行以及每行中有多少个字符。在这种情况下,您可以尝试使用IOseek来将流位置移动到您需要下一次迭代的确切位置。
但这似乎不是您的情况。

我认为提高效率的最好办法是使用标准IO读取线条,并将仅需要/想要绘制的线条转换为数据框。

例如,考虑以下自定义迭代器。
实例化时,它保存标题(第一行)。每次迭代都从文件中读取一行代码,然后跳过以下n*chunksize行。它将返回标头行,然后是读取行,并包装在io.StringIO对象中(因此它是一个流,可以直接馈送到pandas.read_csv中)。

import io
from itertools import islice

class DfReaderChunks:
    def __init__(self, filename, chunksize, n):
        self.fo = open(filename)
        self.chs = chunksize
        self.skiplines = self.chs * n
        self.header = next(self.fo)

    def getchunk(self):
        ll = list(islice(self.fo, self.chs))
        if len(ll) == 0:
            raise StopIteration
        dd = list(islice(self.fo, self.skiplines))
        return self.header + ''.join(ll)

    def __iter__(self):
        return self

    def __next__(self):
        return io.StringIO(self.getchunk())

    def close(self):
        self.fo.close()

    def __del__(self):
        self.fo.close()

使用此类,您可以从文件中读取内容:

reader = DfReaderChunks(file, chunksize, 4)
for dfst in reader:
    df = pd.read_csv(dfst)
    print(df) #here I print to stdout, you can plot
reader.close()

与您的设置“等效”:

for chunk in pd.read_csv(file, chunksize=chunksize):
    df = chunk
    if (counter % 5 == 0):
        print(df) #again I print, you can plot
    counter += 1

我使用39 Mb(100000行或随机数)的数据帧测试了以上两个片段所用的时间。

在我的计算机上,前者需要0.458秒,而后者需要0.821秒。

唯一的缺点是以前的代码段无法跟踪行索引(每次都是新的数据帧,因此索引始终从0开始),但是打印的块是相同的。