按列变量确定的块大小加载pandas数据框架

11

如果我有一个太大无法使用pandas(在这种情况下是35GB)将其加载到内存中的csv文件,我知道可以通过使用chunksize对文件进行分块处理。

但是我想知道是否可以根据一列中的值更改chunksize。

我有一个ID列,然后每个ID都有几行信息,就像这样:

ID,   Time,  x, y
sasd, 10:12, 1, 3
sasd, 10:14, 1, 4
sasd, 10:32, 1, 2
cgfb, 10:02, 1, 6
cgfb, 10:13, 1, 3
aenr, 11:54, 2, 5
tory, 10:27, 1, 3
tory, 10:48, 3, 5
ect...

我不想将ID分成不同的块。例如,大小为4的块将被处理:

ID,   Time,  x, y
sasd, 10:12, 1, 3
sasd, 10:14, 1, 4
sasd, 10:32, 1, 2
cgfb, 10:02, 1, 6
cgfb, 10:13, 1, 3 <--this extra line is included in the 4 chunk

ID,   Time,  x, y
aenr, 11:54, 2, 5
tory, 10:27, 1, 3
tory, 10:48, 3, 5
...

这可行吗?

如果不行,也许可以使用csv库,并使用类似以下代码的for循环遍历:

for line in file:
    x += 1
    if x > 1000000 and curid != line[0]:
        break
    curid = line[0]
    #code to append line to a dataframe
尽管我知道这只会创建一个块,并且for循环需要很长时间来处理。

你的文件存储在哪里?一种方法是事先按ID进行过滤(如果您正在提取数据库,则可以使用SELECT * FROM ... WHERE ID == BLAH),为每个唯一的ID值创建不同的文件。 - blacksite
很遗憾,这是不可能的,我无法访问数据库。 - Josh Kidd
可能会很麻烦,但我认为可以这样做:现在尝试使用chunksize,流式传输整个35GB文件,并为每个唯一的ID值(set(df['ID']))创建一个单独的CSV文件。然后,对于您较大文件中的每一行,将该行写入(即追加)到对应于该行ID的现有ID文件中?虽然我对它可能需要多长时间持怀疑态度,但编码不会太难......只是一个想法!否则,恐怕我无法提供更多帮助了。 - blacksite
2个回答

8

如果您逐行遍历csv文件,可以使用生成器依赖于任何列来yield块。

工作示例:

import pandas as pd

def iter_chunk_by_id(file):
    csv_reader = pd.read_csv(file, iterator=True, chunksize=1, header=None)
    first_chunk = csv_reader.get_chunk()
    id = first_chunk.iloc[0,0]
    chunk = pd.DataFrame(first_chunk)
    for l in csv_reader:
        if id == l.iloc[0,0]:
            id = l.iloc[0,0]
            chunk = chunk.append(l)
            continue
        id = l.iloc[0,0]
        yield chunk
        chunk = pd.DataFrame(l)
    yield chunk

## data.csv ##
# 1, foo, bla
# 1, off, aff
# 2, roo, laa
# 3, asd, fds
# 3, qwe, tre
# 3, tre, yxc   

chunk_iter = iter_chunk_by_id("data.csv")

for chunk in chunk_iter:
    print(chunk)
    print("_____")

输出:

   0     1     2
0  1   foo   bla
1  1   off   aff
_____
   0     1     2
2  2   roo   laa
3  2   jkl   xds
_____
   0     1     2
4  3   asd   fds
5  3   qwe   tre
6  3   tre   yxc
_____

这样会为每个id创建一个chunk,请问如何改为创建大小为1000000的更大块,然后将相同id的剩余行追加到同一块中,以确保相同id的行不分开成块? - Josh Kidd
@JoshuaKidd,你理解了iter_chunk_by_id()函数吗?你可以轻松修改它以符合你的新要求:在行if id == l.iloc[0,0]中添加一个or条件,以便在块的长度小于1000000时继续添加行(顺便问一下:为什么你取消了我的答案作为被接受的答案?) - elcombato

1

我在@elcombato提供的答案基础上进行了扩展,以适应任何块大小。实际上,我有一个类似的用例,逐行处理每一行使我的程序变得难以忍受地慢。

def iter_chunk_by_id(file_name, chunk_size=10000):
"""generator to read the csv in chunks of user_id records. Each next call of generator will give a df for a user"""

csv_reader = pd.read_csv(file_name, compression='gzip', iterator=True, chunksize=chunk_size, header=0, error_bad_lines=False)
chunk = pd.DataFrame()
for l in csv_reader:
    l[['id', 'everything_else']] = l[
        'col_name'].str.split('|', 1, expand=True)
    hits = l['id'].astype(float).diff().dropna().nonzero()[0]
    if not len(hits):
        # if all ids are same
        chunk = chunk.append(l[['col_name']])
    else:
        start = 0
        for i in range(len(hits)):
            new_id = hits[i]+1
            chunk = chunk.append(l[['col_name']].iloc[start:new_id, :])
            yield chunk
            chunk = pd.DataFrame()
            start = new_id
        chunk = l[['col_name']].iloc[start:, :]

yield chunk

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