Python在处理大型CSV文件(使用numpy)时出现内存不足的问题。

35

我有一个3GB的CSV文件,想用Python读取,并需要按列计算中位数。

from numpy import * 
def data():
    return genfromtxt('All.csv',delimiter=',')

data = data() # This is where it fails already.

med = zeros(len(data[0]))
data = data.T
for i in xrange(len(data)):
    m = median(data[i])
    med[i] = 1.0/float(m)
print med

我得到的错误是这样的:
Python(1545) malloc: *** mmap(size=16777216) failed (error code=12)

*** error: can't allocate region

*** set a breakpoint in malloc_error_break to debug

Traceback (most recent call last):

  File "Normalize.py", line 40, in <module>

  data = data()

  File "Normalize.py", line 39, in data

  return genfromtxt('All.csv',delimiter=',')

File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-
packages/numpy/lib/npyio.py", line 1495, in genfromtxt

for (i, line) in enumerate(itertools.chain([first_line, ], fhd)):

MemoryError

我认为这只是一个内存不足的错误。我正在运行64位的MacOSX,具有4GB的内存,并且numpy和Python都以64位模式编译。

我该如何解决这个问题?我应该尝试分布式处理来管理内存吗?

谢谢。

编辑:我也尝试了这个方法,但没有成功...

genfromtxt('All.csv',delimiter=',', dtype=float16)

1
使用pandas.read_csv函数,它的速度明显更快。 - Andy Hayden
3个回答

73

如其他人所提到的,对于非常大的文件,最好的方法是进行迭代处理。

但是,出于各种原因,您通常希望整个文件在内存中。

genfromtxtloadtxt效率低得多(虽然它可以处理缺失数据,而loadtxt则更加“精简”,这就是为什么两个函数共存的原因)。

如果您的数据非常规则(例如只是相同类型的简单分隔行),您还可以通过使用numpy.fromiter来改进。

如果您有足够的RAM,请考虑使用np.loadtxt('yourfile.txt', delimiter=',')(如果文件有标题,则您可能还需要指定skiprows)。

快速比较一下,使用 loadtxt 加载约500MB文本文件时,峰值使用量为~900MB的RAM,而使用 genfromtxt 加载相同的文件时,使用的RAM为~2.5GB。

Loadtxt 通过numpy.loadtxt加载约500MB ascii文件时的内存和CPU使用情况


Genfromtxt 通过numpy.genfromtxt加载约500MB ascii文件时的内存和CPU使用情况


或者考虑类似以下的方法。它只适用于非常简单、规则的数据,但速度非常快。(loadtxtgenfromtxt做了很多猜测和错误检查,如果您的数据非常简单和规则,可以大大改进它们。)

import numpy as np

def generate_text_file(length=1e6, ncols=20):
    data = np.random.random((length, ncols))
    np.savetxt('large_text_file.csv', data, delimiter=',')

def iter_loadtxt(filename, delimiter=',', skiprows=0, dtype=float):
    def iter_func():
        with open(filename, 'r') as infile:
            for _ in range(skiprows):
                next(infile)
            for line in infile:
                line = line.rstrip().split(delimiter)
                for item in line:
                    yield dtype(item)
        iter_loadtxt.rowlength = len(line)

    data = np.fromiter(iter_func(), dtype=dtype)
    data = data.reshape((-1, iter_loadtxt.rowlength))
    return data

#generate_text_file()
data = iter_loadtxt('large_text_file.csv')

从迭代器创建数组

使用 fromiter 加载相同的 ~500MB 数据文件


6
基本上就是暴力破解 :) 如果您感兴趣,这是我的Shell脚本链接:https://gist.github.com/2447356 它并不太优雅,但足以胜任。 - Joe Kington
啊,不错!(虽然我承认我希望有 import memoryprofile 或者其他什么的,唉!) - huon
好的,有heapy(guppy的一部分:http://guppy-pe.sourceforge.net/),但是不幸的是它对于numpy数组效果不佳。虽然很遗憾,但是“import memoryprofile”会非常好用! - Joe Kington
1
亲爱的@JoeKington,你能否使用单一刻度来表示图表的Y轴,以便比较视觉上更加相似? - Boris Gorelik
在我看来,你最好将内存使用量与输出数组大小进行比较,而不是文件大小。例如,如果你想要加载一个8192x8192的双精度矩阵,那么一个最优秀的函数只需要512MB(8 * 8192 * 8192字节)来加载它,无论文本文件有多大。 - rudimeier

4

使用genfromtxt()的问题在于它试图将整个文件加载到内存中,即numpy数组中。这对于小文件很好,但对于像你这样的3GB输入来说很不好。由于你只是计算列中位数,因此没有必要读取整个文件。一种简单但不是最有效的方法是逐行多次读取整个文件并迭代列。


好的,但是有没有更可持续的解决方案呢?比如在Java程序中,你可以选择启动它时分配5GB的内存。Python中有类似的功能吗?我的意思是,下一次我可能只有一个4GB的CSV文件。 - Ihmahr
1
Python不限制您可以分配多少内存。如果在64位Python中出现“MemoryError”,则确实已经用尽了内存。 - Baffe Boyois
1
不幸的是,并非所有的Python模块都支持64位架构。 - cjohnson318

2

为什么不使用Python的csv模块呢?

>> import csv
>> reader = csv.reader(open('All.csv'))
>>> for row in reader:
...     print row

因为我的整个程序都使用了numpy和基本的线性代数,而且在读取器中我无法完成所有这些操作。 - Ihmahr
结合 kz26 的答案,这实际上提供了一个可行的解决方法。还有趣的是:经过一次迭代后,文件被缓存,处理器的使用率从 60% 跳到了 99%。 - Ihmahr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接