如何使用pandas读取较大的csv文件?


194

我试图在熊猫中读取较大的csv文件(大约6 GB),但出现内存错误:

MemoryError                               Traceback (most recent call last)
<ipython-input-58-67a72687871b> in <module>()
----> 1 data=pd.read_csv('aphro.csv',sep=';')

...

MemoryError: 

有什么帮助吗?


3
奇怪的是,在此问题提出之前将近一年,人们提出了一个非常类似的问题 ……
DarkCygnus 17-6-26


这回答了你的问题了吗?使用熊猫的“大数据”工作流程
AMC

Answers:


260

该错误表明机器没有足够的内存来一次将整个CSV读入DataFrame。假设您一次也不需要整个内存中的整个数据集,一种避免问题的方法是分批处理CSV(通过指定chunksize参数):

chunksize = 10 ** 6
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

chunksize参数指定每个块的行数。(当然,最后一块可能少于chunksize行。)


17
您通常需要2倍的最终内存才能读取某些内容(从csv中读取,尽管其他格式更适合具有较低的内存要求)。仅供参考,这是一次尝试几乎所有操作的真实情况。更好地对其进行分块(具有恒定的内存使用量)。
杰夫

24
@altabq:这里的问题是我们没有足够的内存来构建一个包含所有数据的DataFrame。上面的解决方案试图通过一次减少一个块(例如,通过聚合或仅提取所需信息)减少一个块来解决这种情况,从而节省了内存。无论您做什么,都不要DF.append(chunk)在循环内调用。那将使用O(N^2)复制操作。这是更好地聚集的数据添加到列表,然后生成列表中的数据框与一个调用pd.DataFramepd.concat(根据汇总的数据的类型)。
unutbu

12
@altabq:DF.append(chunk)在循环中调用需要O(N^2)复制操作,其中N块的大小为,因为每次调用都会DF.append返回一个新的DataFrame。在循环外调用pd.DataFramepd.concat 一次,可减少复制到的数量O(N)
unutbu

5
@Pyderman:是的,该chunksize参数指的是每个块的行数。当然,最后一块的chunksize行数可能少于行。
unutbu

7
@Pyderman:是的;循环后调用pd.concat([list_of_dfs]) 一次比循环中调用pd.concatdf.append多次调用快得多。当然,您需要大量内存才能将整个6GB的csv作为一个DataFrame保存。
unutbu '16

85

分块不一定总是解决此问题的第一站。

  1. 文件是否由于重复的非数字数据或不需要的列而变大?

    如果是这样,您有时可以通过读取列作为类别并通过pd.read_csv usecols参数选择所需的列来节省大量内存。

  2. 您的工作流程是否需要切片,操作,导出?

    如果是这样,则可以使用dask.dataframe进行切片,执行计算并迭代导出。打包由dask静默执行,它也支持pandas API的子集。

  3. 如果所有其他方法均失败,请通过块逐行读取。

    作为最后手段,可以通过熊猫csv库进行分块。


3
我不知道达斯克。+100!
noamtm

34

我这样进行:

chunks=pd.read_table('aphro.csv',chunksize=1000000,sep=';',\
       names=['lat','long','rf','date','slno'],index_col='slno',\
       header=None,parse_dates=['date'])

df=pd.DataFrame()
%time df=pd.concat(chunk.groupby(['lat','long',chunk['date'].map(lambda x: x.year)])['rf'].agg(['sum']) for chunk in chunks)

22
您从切换read_csv到的原因read_table吗?
皮德曼

33

对于大数据,我建议您使用库“ dask”,
例如:

# Dataframes implement the Pandas API
import dask.dataframe as dd
df = dd.read_csv('s3://.../2018-*-*.csv')

您可以从此处阅读更多文档。

另一个很好的选择是使用modin,因为所有功能都与pandas相同,但它利用了dask等分布式数据框架库。


11
比熊猫有任何好处,不胜感激,添加了更多指针
PirateApp '18

2
我没有使用Dask很长时间了,但是在我的用例中,主要的优点是Dask可以在多台机器上并行运行,也可以将数据作为切片放入内存中。
Simbarashe Timothy Motsi '18

2
谢谢!是熊猫的替代品还是在熊猫的顶层工作
PirateApp '18

3
欢迎使用,它可以作为Numpy,Pandas和Scikit-Learn的包装。
Simbarashe Timothy Motsi '18

1
我试图面对Dask的几个问题,并且总是为所有问题抛出一个错误。即使有块,它也会引发内存错误。见stackoverflow.com/questions/59865572/...
Genarito

10

上面的答案已经满足了这个主题。无论如何,如果您需要内存中的所有数据,请查看bcolz。它压缩内存中的数据。我有非常好的经验。但是它缺少许多熊猫功能

编辑:我得到的压缩率大约是我认为的1/10或原始大小,这当然取决于数据类型。缺少的重要功能是聚合。


2
请通过告诉我们a)您获得什么压缩率和b)缺少的熊猫的主要特征来改善这个答案。它可以处理NA吗?琴弦?分类的?日期?
smci 2016年

??它可以处理NA吗?琴弦?分类的?日期?这些都是使熊猫Csv阅读缓慢而松弛的原因。NA和诸如字符串(甚至是短字符串)之类的对象是杀手er。顺便说一句,您博客中引用的.ipynb已关闭。
smci 2016年

1
@smci我正在读你的笔记。但我建议您看看文档。我需要自己阅读。
PlagTag '16

2
好的,因此它不能处理NA,字符串或日期。我怀疑它是否也可以处理浮点数。
smci 2016年

1
我想您可以使用上述chunks方法对熊猫进行预处理,如果需要内存中的所有数据进行分析,则可以使用bcolz。只是一个想法。
JakeCowton

6

您可以将数据读取为大块,并将每个大块另存为泡菜。

import pandas as pd 
import pickle

in_path = "" #Path where the large file is
out_path = "" #Path to save the pickle files to
chunk_size = 400000 #size of chunks relies on your available memory
separator = "~"

reader = pd.read_csv(in_path,sep=separator,chunksize=chunk_size, 
                    low_memory=False)    


for i, chunk in enumerate(reader):
    out_file = out_path + "/data_{}.pkl".format(i+1)
    with open(out_file, "wb") as f:
        pickle.dump(chunk,f,pickle.HIGHEST_PROTOCOL)

在下一步中,您将读取泡菜并将每个泡菜附加到所需的数据框中。

import glob
pickle_path = "" #Same Path as out_path i.e. where the pickle files are

data_p_files=[]
for name in glob.glob(pickle_path + "/data_*.pkl"):
   data_p_files.append(name)


df = pd.DataFrame([])
for i in range(len(data_p_files)):
    df = df.append(pd.read_pickle(data_p_files[i]),ignore_index=True)

3
如果您的最终结果df完全适合内存(隐含)并且包含的​​数据量与输入的数据量相同,那么您肯定不需要分块吗?
jpp

例如,如果您的文件非常宽(例如大于100列且包含许多字符串列),则在这种情况下您将需要分块。这会增加将df保留在内存中所需的内存。即使是这样的4GB文件,在具有64 GB RAM的盒子上最终可能会使用20到30 GB RAM。
cdabel

4

函数read_csv和read_table几乎相同。但是,在程序中使用函数read_table时,必须分配定界符“,”。

def get_from_action_data(fname, chunk_size=100000):
    reader = pd.read_csv(fname, header=0, iterator=True)
    chunks = []
    loop = True
    while loop:
        try:
            chunk = reader.get_chunk(chunk_size)[["user_id", "type"]]
            chunks.append(chunk)
        except StopIteration:
            loop = False
            print("Iteration is stopped")

    df_ac = pd.concat(chunks, ignore_index=True)

如果在这篇文章中说明您的问题,这将有所帮助。就像“ read_csv和read_table有什么区别?” 或“为什么读取表需要定界符?”
nate_weldon '17

1
这取决于文件的外观。某些文件具有常见的分隔符,例如“,”或“ |” 或“ \ t”,但您可能会看到带有分隔符的其他文件,例如0x01、0x02(使之成为一个)等。因此,read_table更适合于不常见的分隔符,但read_csv可以完成相同的工作。
诺法

3

解决方案1:

使用大数据的熊猫

解决方案2:

TextFileReader = pd.read_csv(path, chunksize=1000)  # the number of rows per chunk

dfList = []
for df in TextFileReader:
    dfList.append(df)

df = pd.concat(dfList,sort=False)

3
在这里,我们再次将6 GB的文件完全加载到内存中,是否有任何选项,我们可以处理当前块,然后读取下一个块
debaonline4u

6
只是不做dfList.append,只需分别处理每个块(df
gokul_uf

3

下面是一个示例:

chunkTemp = []
queryTemp = []
query = pd.DataFrame()

for chunk in pd.read_csv(file, header=0, chunksize=<your_chunksize>, iterator=True, low_memory=False):

    #REPLACING BLANK SPACES AT COLUMNS' NAMES FOR SQL OPTIMIZATION
    chunk = chunk.rename(columns = {c: c.replace(' ', '') for c in chunk.columns})

    #YOU CAN EITHER: 
    #1)BUFFER THE CHUNKS IN ORDER TO LOAD YOUR WHOLE DATASET 
    chunkTemp.append(chunk)

    #2)DO YOUR PROCESSING OVER A CHUNK AND STORE THE RESULT OF IT
    query = chunk[chunk[<column_name>].str.startswith(<some_pattern>)]   
    #BUFFERING PROCESSED DATA
    queryTemp.append(query)

#!  NEVER DO pd.concat OR pd.DataFrame() INSIDE A LOOP
print("Database: CONCATENATING CHUNKS INTO A SINGLE DATAFRAME")
chunk = pd.concat(chunkTemp)
print("Database: LOADED")

#CONCATENATING PROCESSED DATA
query = pd.concat(queryTemp)
print(query)


2

如果您使用熊猫将大文件读入块中,然后逐行产生,这就是我所做的

import pandas as pd

def chunck_generator(filename, header=False,chunk_size = 10 ** 5):
   for chunk in pd.read_csv(filename,delimiter=',', iterator=True, chunksize=chunk_size, parse_dates=[1] ): 
        yield (chunk)

def _generator( filename, header=False,chunk_size = 10 ** 5):
    chunk = chunck_generator(filename, header=False,chunk_size = 10 ** 5)
    for row in chunk:
        yield row

if __name__ == "__main__":
filename = r'file.csv'
        generator = generator(filename=filename)
        while True:
           print(next(generator))

1

我想根据已经提供的大多数潜在解决方案做出更全面的回答。我还想指出另一种可能有助于阅读过程的潜在帮助。

选项1:dtypes

“ dtypes”是一个非常强大的参数,可用于减少read方法的内存压力。看到这个这个答案。熊猫默认情况下会尝试推断数据的dtypes。

参照数据结构,存储的每个数据都会进行内存分配。在基本级别上,请参考以下值(下表说明了C编程语言的值):

The maximum value of UNSIGNED CHAR = 255                                    
The minimum value of SHORT INT = -32768                                     
The maximum value of SHORT INT = 32767                                      
The minimum value of INT = -2147483648                                      
The maximum value of INT = 2147483647                                       
The minimum value of CHAR = -128                                            
The maximum value of CHAR = 127                                             
The minimum value of LONG = -9223372036854775808                            
The maximum value of LONG = 9223372036854775807

请参阅页面以查看NumPy和C类型之间的匹配。

假设您有一个由数字组成的整数数组。您可以在理论上和实践上都进行分配,比如说16位整数类型的数组,但是您分配的内存将比实际存储该数组所需的更多。为防止这种情况,您可以dtype在上设置选项read_csv。您不希望将数组项存储为长整数,而实际上可以使用8位整数(np.int8np.uint8)来使它们适合。

观察以下dtype映射。

资料来源:https : //pbpython.com/pandas_dtypes.html

您可以像在{column:type}一样将dtype参数作为参数传递给pandas方法read

import numpy as np
import pandas as pd

df_dtype = {
        "column_1": int,
        "column_2": str,
        "column_3": np.int16,
        "column_4": np.uint8,
        ...
        "column_n": np.float32
}

df = pd.read_csv('path/to/file', dtype=df_dtype)

选项2:大块读取

逐块读取数据使您可以访问内存中的部分数据,并且可以对数据进行预处理,并保留处理后的数据而不是原始数据。如果将此选项与第一个dtypes结合使用会更好。

我想指出该过程的“熊猫食谱”部分,您可以在这里找到它。注意那两个部分;

选项3:达斯

Dask是在Dask网站上定义为的框架:

Dask为分析提供高级并行性,从而为您喜欢的工具提供大规模性能

它的诞生是为了覆盖熊猫无法到达的必要部分。Dask是一个功能强大的框架,通过以分布式方式处理它,可以使您访问更多数据。

您可以使用dask预处理整个数据,Dask负责分块部分,因此与熊猫不同,您可以定义处理步骤并让Dask完成工作。Dask不会在compute和和/或显式推送计算之前应用这些计算persist(有关差异,请参见此处的答案)。

其他援助(想法)

  • 为数据设计的ETL流。仅保留原始数据中需要的内容。
    • 首先,使用Dask或PySpark之类的框架将ETL应用于整个数据,然后导出处理后的数据。
    • 然后查看处理后的数据是否可以整体容纳在内存中。
  • 考虑增加RAM。
  • 考虑在云平台上使用该数据。

0

除了上述答案之外,对于那些想要处理CSV然后导出到csv,镶木地板或SQL的用户来说,d6tstack是另一个不错的选择。您可以加载多个文件,并且它处理数据架构更改(添加/删除的列)。已经内置了核心支持之外的其他功能。

def apply(dfg):
    # do stuff
    return dfg

c = d6tstack.combine_csv.CombinerCSV([bigfile.csv], apply_after_read=apply, sep=',', chunksize=1e6)

# or
c = d6tstack.combine_csv.CombinerCSV(glob.glob('*.csv'), apply_after_read=apply, chunksize=1e6)

# output to various formats, automatically chunked to reduce memory consumption
c.to_csv_combine(filename='out.csv')
c.to_parquet_combine(filename='out.pq')
c.to_psql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # fast for postgres
c.to_mysql_combine('mysql+mysqlconnector://usr:pwd@localhost/db', 'tablename') # fast for mysql
c.to_sql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # slow but flexible

0

如果有人仍在寻找这样的东西,我发现这个叫做modin的新库可以提供帮助。它使用可以帮助读取的分布式计算。这是一篇很好的文章,比较了它与熊猫的功能。它基本上使用与熊猫相同的功能。

import modin.pandas as pd
pd.read_csv(CSV_FILE_NAME)

您能否评论一下这个新模块modin与完善的模块之间的比较dask.dataframe?例如,请参阅从熊猫到dask,以利用所有本地cpu核心
jpp

0

在使用chunksize选项之前,如果要确定要在@unutbu所提到的分块for循环中编写的过程函数,可以简单地使用nrows选项。

small_df = pd.read_csv(filename, nrows=100)

一旦确定过程块已准备就绪,就可以将其放入整个数据帧的块循环中。

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.