pandas无法从大StringIO对象读取

10 投票
1 回答
7753 浏览
提问于 2025-04-18 12:03

我正在使用pandas来管理一个很大的8字节整数数组。这些整数作为一个列中的空格分隔元素,包含在一个用逗号分隔的CSV文件里,数组的大小大约是10000x10000。

pandas可以快速读取前几列的逗号分隔数据,并将空格分隔的字符串轻松存储到另一个DataFrame中。但是,当我尝试将这个表从一个包含空格分隔字符串的单列转换为8位整数的DataFrame时,就遇到了麻烦。

我尝试了以下方法:

intdata = pd.DataFrame(strdata.columnname.str.split().tolist(), dtype='uint8')

但是内存使用量太大了——10MB的整数却消耗了2GB的内存。有人告诉我这是语言的限制,我在这种情况下无能为力。

作为一个可能的解决办法,有人建议我将字符串数据保存到一个CSV文件中,然后再将这个CSV文件重新加载为一个包含空格分隔整数的DataFrame。这种方法效果很好,但为了避免写入磁盘时的速度慢,我尝试将数据写入一个StringIO对象。

这里有一个简单的但不工作的例子:

import numpy as np
import pandas as pd
from cStringIO import StringIO

a = np.random.randint(0,256,(10000,10000)).astype('uint8')
b = pd.DataFrame(a)
c = StringIO()
b.to_csv(c, delimiter=' ', header=False, index=False)
d = pd.io.parsers.read_csv(c, delimiter=' ', header=None, dtype='uint8')

这导致了以下错误信息:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.7/site-packages/pandas/io/parsers.py", line 443, in parser_f
    return _read(filepath_or_buffer, kwds)
  File "/usr/lib64/python2.7/site-packages/pandas/io/parsers.py", line 228, in _read
    parser = TextFileReader(filepath_or_buffer, **kwds)
  File "/usr/lib64/python2.7/site-packages/pandas/io/parsers.py", line 533, in __init__
    self._make_engine(self.engine)
  File "/usr/lib64/python2.7/site-packages/pandas/io/parsers.py", line 670, in _make_engine
    self._engine = CParserWrapper(self.f, **self.options)
  File "/usr/lib64/python2.7/site-packages/pandas/io/parsers.py", line 1032, in __init__
    self._reader = _parser.TextReader(src, **kwds)
  File "parser.pyx", line 486, in pandas.parser.TextReader.__cinit__ (pandas/parser.c:4494)
ValueError: No columns to parse from file

这让我很困惑,因为如果我用 'c.csv' 代替 c 来运行完全相同的代码,代码就能正常工作。而且,如果我使用以下代码片段:

file = open('c.csv', 'w')
file.write(c.getvalue())

CSV文件保存得没有任何问题,所以写入StringIO对象并不是问题所在。

可能我需要在read_csv那一行把 c 替换成 c.getvalue(),但这样做时,解释器会试图在终端打印 c 的内容!肯定有办法解决这个问题。

提前感谢你的帮助。

1 个回答

16

这里有两个问题,一个是基础问题,另一个是你可能还没遇到过的。:^)

首先,在你写入c之后,你的位置是在(虚拟)文件的末尾。你需要用seek把位置移动回开头。我们用一个更小的网格作为例子:

>>> a = np.random.randint(0,256,(10,10)).astype('uint8')
>>> b = pd.DataFrame(a)
>>> c = StringIO()
>>> b.to_csv(c, delimiter=' ', header=False, index=False)
>>> next(c)
Traceback (most recent call last):
  File "<ipython-input-57-73b012f9653f>", line 1, in <module>
    next(c)
StopIteration

这会导致出现“没有列”的错误。不过,如果我们先用seek的话:

>>> c.seek(0)
>>> next(c)
'103,3,171,239,150,35,224,190,225,57\n'

但现在你会注意到第二个问题——逗号?我以为我们请求的是空格作为分隔符?但是to_csv只接受sep,而不是delimiter。我觉得它要么应该接受这个参数,要么就应该提示我们不支持,但默默忽略掉这点感觉像是个bug。无论如何,如果我们使用sep(或者delim_whitespace=True):

>>> a = np.random.randint(0,256,(10,10)).astype('uint8')
>>> b = pd.DataFrame(a)
>>> c = StringIO()
>>> b.to_csv(c, sep=' ', header=False, index=False)
>>> c.seek(0)
>>> d = pd.read_csv(c, sep=' ', header=None, dtype='uint8')
>>> d
     0    1    2    3    4    5    6    7    8    9
0  209   65  218  242  178  213  187   63  137  145
1  161  222   50   92  157   31   49   62  218   30
2  182  255  146  249  115   91  160   53  200  252
3  192  116   87   85  164   46  192  228  104  113
4   89  137  142  188  183  199  106  128  110    1
5  208  140  116   50   66  208  116   72  158  169
6   50  221   82  235   16   31  222    9   95  111
7   88   36  204   96  186  205  210  223   22  235
8  136  221   98  191   31  174   83  208  226  150
9   62   93  168  181   26  128  116   92   68  153

撰写回答