读取巨大的.csv文件


107

我目前正在尝试从Python 2.7中的.csv文件读取数据,该文件最多包含100万行和200列(文件范围从100mb到1.6gb)。对于少于300,000行的文件,我可以(非常缓慢地)执行此操作,但是一旦超过该行,就会出现内存错误。我的代码如下所示:

def getdata(filename, criteria):
    data=[]
    for criterion in criteria:
        data.append(getstuff(filename, criteron))
    return data

def getstuff(filename, criterion):
    import csv
    data=[]
    with open(filename, "rb") as csvfile:
        datareader=csv.reader(csvfile)
        for row in datareader: 
            if row[3]=="column header":
                data.append(row)
            elif len(data)<2 and row[3]!=criterion:
                pass
            elif row[3]==criterion:
                data.append(row)
            else:
                return data

在getstuff函数中使用else子句的原因是,所有符合条件的元素都将一起列在csv文件中,因此当我经过它们时,为了节省时间,我离开了循环。

我的问题是:

  1. 我如何设法使其与较大的文件一起使用?

  2. 有什么办法可以使它更快?

我的计算机具有8gb RAM,运行64位Windows 7,处理器为3.40 GHz(不确定您需要什么信息)。


1
我知道有几个类似的看似问题,但是似乎没有一个问题足以解决我的问题。对不起,如果我错过了一个。
查尔斯·狄龙

2
您应该将读取的数据存储在数据库(例如Sqlite)中,而不是将其保留在内存中。然后,您可以运行进一步的处理,例如对数据库进行过滤
Michael Butscher

Answers:


158

您正在将所有行读入列表,然后处理该列表。不要那样做

在生成行时对其进行处理。如果需要先过滤数据,请使用生成器函数:

import csv

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        count = 0
        for row in datareader:
            if row[3] == criterion:
                yield row
                count += 1
            elif count:
                # done when having read a consecutive series of rows 
                return

我还简化了您的过滤器测试;逻辑相同,但更为简洁。

因为只匹配与条件匹配的单个行序列,所以还可以使用:

import csv
from itertools import dropwhile, takewhile

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        # first row, plus any subsequent rows that match, then stop
        # reading altogether
        # Python 2: use `for row in takewhile(...): yield row` instead
        # instead of `yield from takewhile(...)`.
        yield from takewhile(
            lambda r: r[3] == criterion,
            dropwhile(lambda r: r[3] != criterion, datareader))
        return

您现在可以getstuff()直接循环。在getdata()

def getdata(filename, criteria):
    for criterion in criteria:
        for row in getstuff(filename, criterion):
            yield row

现在直接getdata()在您的代码中循环:

for row in getdata(somefilename, sequence_of_criteria):
    # process row

现在,您仅在内存中保留一行,而不是每个条件存储数千行。

yield使函数成为生成器函数,这意味着直到开始循环它之前,它不会做任何工作。


当将此技术用于时,您获得相同的内存效率csv.DictReader吗?因为我对2.5GB .csv文件的测试表明,使用而不是这样尝试逐行进行迭代,而不是csv.reader导致Python进程增长到完整的2.5GB内存使用率。
user5359531

@ user5359531表示您将对字典对象的引用保留在某处。DictReader本身不保留引用,因此问题出在其他地方。
马丁·彼得斯

39

尽管Martijin的答案是最好的。这是为初学者处理大型csv文件的更直观的方法。这使您可以一次处理一组行或块。

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

9
为什么使用熊猫使其更直观?
wwii

25
对于像我这样的新手来说,4行代码总是更好。
mmann1123

3
常规的Python代码也很短,可以让您按行处理。生成器功能仅用于过滤内容。您将如何在Pandas中进行相同的过滤?
马丁·彼得斯

1
这太棒了!解决了我使用pandas加载和处理大型csv文件的问题。谢谢!
Elsa Li

1
即使某些行的内容跨越多行,它也可以很好地工作!
Dielson Sales

19

我进行了大量的振动分析,并研究了大型数据集(数以亿计的点)。我的测试显示pandas.read_csv()函数比numpy.genfromtxt()快20倍。genfromtxt()函数比numpy.loadtxt()快3倍。似乎您需要大数据集的熊猫。

我在博客上讨论了用于测试的代码和数据集,该博客讨论了MATLAB vs Python进行振动分析


3
OP的主要问题不是速度问题之一,而是内存耗尽问题之一。使用不同的功能来处理文件本身并不会消除将其读入列表而不是使用流处理器的弊端。
pydsigner

6

对我有用的是而且超快速的是

import pandas as pd
import dask.dataframe as dd
import time
t=time.clock()
df_train = dd.read_csv('../data/train.csv', usecols=[col1, col2])
df_train=df_train.compute()
print("load train: " , time.clock()-t)

另一个可行的解决方案是:

import pandas as pd 
from tqdm import tqdm

PATH = '../data/train.csv'
chunksize = 500000 
traintypes = {
'col1':'category',
'col2':'str'}

cols = list(traintypes.keys())

df_list = [] # list to hold the batch dataframe

for df_chunk in tqdm(pd.read_csv(PATH, usecols=cols, dtype=traintypes, chunksize=chunksize)):
    # Can process each chunk of dataframe here
    # clean_data(), feature_engineer(),fit()

    # Alternatively, append the chunk to list and merge all
    df_list.append(df_chunk) 

# Merge all dataframes into one dataframe
X = pd.concat(df_list)

# Delete the dataframe list to release memory
del df_list
del df_chunk

df_train=df_train.compute()您第一个解决方案中的行是否会将整个数据集加载到内存中...这是他试图不做的事情?
Sam Dillard

3

对于着陆这个问题的人。将熊猫与' chunksize '和' usecols ' 一起使用,比其他建议的选项更快地读取了一个巨大的zip文件。

import pandas as pd

sample_cols_to_keep =['col_1', 'col_2', 'col_3', 'col_4','col_5']

# First setup dataframe iterator, ‘usecols’ parameter filters the columns, and 'chunksize' sets the number of rows per chunk in the csv. (you can change these parameters as you wish)
df_iter = pd.read_csv('../data/huge_csv_file.csv.gz', compression='gzip', chunksize=20000, usecols=sample_cols_to_keep) 

# this list will store the filtered dataframes for later concatenation 
df_lst = [] 

# Iterate over the file based on the criteria and append to the list
for df_ in df_iter: 
        tmp_df = (df_.rename(columns={col: col.lower() for col in df_.columns}) # filter eg. rows where 'col_1' value grater than one
                                  .pipe(lambda x:  x[x.col_1 > 0] ))
        df_lst += [tmp_df.copy()] 

# And finally combine filtered df_lst into the final lareger output say 'df_final' dataframe 
df_final = pd.concat(df_lst)

1

这是Python3的另一个解决方案:

import csv
with open(filename, "r") as csvfile:
    datareader = csv.reader(csvfile)
    count = 0
    for row in datareader:
        if row[3] in ("column header", criterion):
            doSomething(row)
            count += 1
        elif count > 2:
            break

datareader是一个生成器函数。


因此,这与使用yield运算符的解决方案一样有效。:对不起,事实并非如此。回调函数调用会增加更多开销,尤其是因为您必须显式且单独地处理状态。
马丁·皮特斯

@MartijnPieters谢谢。更新了答案。
Rishabh Agrahari '18

0

如果您使用的是熊猫并且有很多RAM(足以将整个文件读入内存),请尝试使用pd.read_csvwith low_memory=False,例如:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.