用 pandas 打开一个 20GB 的文件进行分析

数据挖掘 Python 大数据 熊猫 蟒蛇
2021-10-03 20:43:08

我目前正在尝试使用 pandas 和 python 打开一个文件以用于机器学习目的,将它们全部放在 DataFrame 中对我来说是理想的。现在该文件有 18GB 大,我的 RAM 是 32GB,但我不断收到内存错误。

根据您的经验,有可能吗?如果不是,您是否知道解决此问题的更好方法?(蜂巢表?将我的 RAM 大小增加到 64?创建一个数据库并从 python 访问它)

4个回答

如果它是一个 csv 文件,并且您在训练算法时不需要一次访问所有数据,您可以分块读取它。pandas.read_csv方法允许您像这样以块的形式读取文件:

import pandas as pd
for chunk in pd.read_csv(<filepath>, chunksize=<your_chunksize_here>)
    do_processing()
    train_algorithm()

这是该方法的文档

有两种可能性:要么您需要将所有数据保存在内存中进行处理(例如,您的机器学习算法希望一次使用所有数据),要么您可以不使用它(例如,您的算法只需要行样本或列)。

在第一种情况下,您需要解决内存问题增加你的内存大小,租一台高内存的云机器,使用就地操作,提供你正在读取的数据类型的信息,确保删除所有未使用的变量并收集垃圾等。

很可能 32GB 的 RAM 不足以让 Pandas 处理您的数据。请注意,整数“1”在存储为文本时仅为 1 个字节,但在表示为时为 8 个字节int64(这是 Pandas 从文本中读取它时的默认值)。您可以使用浮点数“1.0”制作相同的示例,float64默认情况下它从 3 字节字符串扩展为 8 字节。您可以通过让 Pandas 准确知道每列使用哪些类型并强制使用尽可能小的表示来赢得一些空间,但我们甚至没有在这里开始谈论 Python 的数据结构开销,这可能会在这里或那里轻松添加一两个额外的指针, 并且在 64 位机器上每个指针是 8 个字节。

总结一下:不,32GB RAM 可能不足以让 Pandas 处理 20GB 文件。

在第二种情况下(更现实,可能适用于您),您需要解决数据管理问题事实上,当您真的只需要部分数据进行处理时,必须加载所有数据,这可能是数据管理不善的标志。这里有多种选择:

  1. 使用 SQL 数据库。如果可以的话,它几乎总是首选和相当舒适的解决方案。20GB 听起来像是大多数 SQL 数据库可以很好地处理的大小,即使在(高端)笔记本电脑上也不需要分发。您将能够索引列,通过 SQL 进行基本聚合,并使用简单的pd.read_sql. 将数据移动到数据库还可以让您有机会考虑列的实际数据类型和大小。

  2. 如果您的数据主要是数字(即数组或张量),您可以考虑将其保存为 HDF5 格式(请参阅PyTables),这样您就可以方便地从磁盘中读取大型数组的必要切片。基本的numpy.save 和 numpy.load也通过内存映射磁盘上的数组来实现相同的效果。对于 GIS 和相关的栅格数据,有专门的 数据库,它可能不像 SQL 那样直接连接到 pandas,但也应该可以让您合理方便地进行切片和查询。

  3. 据我所知,Pandas 不支持 HDF5 或 numpy 数组的这种“部分”内存映射。如果您仍然想要一种“纯熊猫”解决方案,您可以尝试通过“分片”来解决:或者单独存储大表的(例如,在单独的文件中或在单个 HDF5 的单独“表”中)文件)并且仅按需加载必要的文件,或单独存储行块。但是,您随后需要实现加载必要块的逻辑,从而重新发明已经在大多数 SQL 数据库中实现的自行车,因此选项 1 在这里可能仍然更容易。但是,如果您的数据以 CSV 格式出现,您可以通过将chunksize参数指定为pd.read_csv.

我前几天刚遇到这个问题!不确定这是否对您的具体情况有帮助,因为您没有提供太多详细信息,但我的情况是在“大型”数据集上离线工作。数据是从能量计中以 20GB 压缩 CSV 文件的形式获得的,时间序列数据以几秒为间隔。

文件 IO:

data_root = r"/media/usr/USB STICK"
fname = r"meters001-050-timestamps.csv.gz"
this_file = os.path.join(data_root,fname)
assert os.path.exists(this_file), this_file
this_file

直接在 gzip 文件上创建一个块迭代器(不要解压缩!)

cols_to_keep = [0,1,2,3,7]
column_names = ['METERID','TSTAMP','ENERGY','POWER_ALL','ENERGY_OUT',]
parse_dates = ['TSTAMP']
dtype={'METERID': np.int32, 
       'ENERGY': np.int32,
       'POWER_ALL': np.int32,
       'ENERGY_OUT': np.int32,
      }
df_iterator = pd.read_csv(this_file, 
                        skiprows=0, 
                        compression='gzip',
                        chunksize=1000000, 
                        usecols=cols_to_keep,
                        delimiter=";",
                        header=None,
                        names = column_names,
                      dtype=dtype,
                     parse_dates=parse_dates,
                     index_col=1,
                     )

迭代块

new_df = pd.DataFrame()
count = 0
for df in df_iterator:
    chunk_df_15min = df.resample('15T').first()
    #chunk_df_30min = df.resample('30T').first()
    #chunk_df_hourly = df.resample('H').first()
    this_df = chunk_df_15min
    this_df = this_df.pipe(lambda x: x[x.METERID == 1])
    #print("chunk",i)
    new_df = pd.concat([new_df,chunk_df_15min])
    print("chunk",count, len(chunk_df_15min), 'rows added')
    #print("chunk",i, len(temp_df),'rows added')
    #break
    count += 1

在块循环内部,我正在按时进行一些过滤和重新采样。为此,我将 HDF5 的大小从 20GB 减小到几百 MB,以便进一步进行离线数据探索。

read_csv()根据我的经验,在读取大文件时,使用参数进行初始化low_memory=False往往会有所帮助。我认为您没有提到您正在阅读的文件类型,所以我不确定这对您的情况有多适用。