在 Pandas 中合并数据框花费了惊人的时间

数据挖掘 Python 熊猫 大数据 数据清理
2021-09-17 16:44:38

我正在尝试使用 Pandas 合并时间序列数据帧列表(可能超过 100 个)。最大的文件大小为50 MB。行数和列数各不相同(例如,一个文件可能有 45,000 行和 20 列,另一个有 100 行和 900 列),但它们都有共同的“SubjectID”和“Date”列,我正在使用合并数据框。我从 CSV 文件中将数据帧读入列表,我知道每列的数据类型。

但是,当我尝试合并 10 个数据帧时,大约需要 7 个小时,而且对于所有 100 个数据帧,我的内核都崩溃了。读取所有文件大约需要一分钟。这些不是我认为是“大数据”的文件,甚至不是大文件,比如这里这里这里的帖子,所以我很惊讶将它们全部合并需要这么长时间。对于所有 100 个数据帧,我预计最终数据帧大小约为 1.5 GB,但即使如此,我认为 Pandas 也可以处理。我合并数据框的代码是

merged_df = reduce(lambda l,r: l.merge(r, on=['SubjectID', 'Date'], how='outer', suffixes=['_COPYL', '_COPYR']), df_list)

因为如果 subjectID 和 date 尚不存在列,我希望在 at 上添加列(我稍后会处理重复项)。

编辑

一些示例数据帧由以下代码生成

import numpy as np
import pandas as pd
from functools import reduce

df1 = pd.DataFrame({'SubjectID': ['A', 'A', 'A', 'B', 'B', 'C', 'A'], 'Date': ['2010-03-14', '2010-03-15', '2010-03-16', '2010-03-14','2010-05-15', '2010-03-14', '2010-03-14'], 'Var1': [np.nan, 1, 4, 7, 90, np.nan, 9], 'Var2': [np.nan, 0, 1, 1, 0, np.nan, 1]})
df2 = pd.DataFrame({'SubjectID': ['A', 'A', 'B', 'B', 'C', 'A'], 'Date': ['2010-03-14', '2010-03-15', '2010-03-14', '2010-05-15', '2010-03-14', '2010-03-14'], 'Var2': [ np.nan, 0, 1, 1, np.nan, 1], 'Var3': [0, 0, 1, np.nan, 0, 1]})

df3 = pd.DataFrame({'SubjectID': ['A', 'A', 'A', 'B', 'B', 'C'], 'Date':['2010-03-14', '2010-03-15', '2010-03-16', '2010-03-14', '2010-05-15', '2010-03-14'], 'Var3': [np.nan, 1, 0, np.nan, 0, 1]})

df1['Date'] = pd.to_datetime(df1['Date'])
df2['Date'] = pd.to_datetime(df2['Date'])
df3['Date'] = pd.to_datetime(df3['Date'])

我合并数据框的代码是

df_list = [df1, df2, df3]
merged_df = reduce(lambda l,r: l.merge(r, on=['SubjectID', 'Date'], how='outer', suffixes=['_COPYL', '_COPYR']), df_list)
merged_df = merged_df.groupby([x.split('_COPY')[0] for x in merged_df.columns], 1).apply(lambda x: x.mode(1)[0])
merged_df['Date'] = pd.to_datetime(merged_df['Date'])

我的预期输出是

            Date    SubjectID   Var1    Var2    Var3
0     2010-03-14           A    NaN      NaN    0.0
1     2010-03-14           A    NaN      1.0    1.0
2     2010-03-14           A    9.0      1.0    0.0
3     2010-03-14           A    9.0      1.0    1.0
4     2010-03-15           A    1.0      0.0    0.0
5     2010-03-16           A    4.0      1.0    0.0
6     2010-03-14           B    7.0      1.0    1.0
7     2010-05-15           B    90.0     0.0    0.0
8     2010-03-14           C    NaN      NaN    0.0

到目前为止,我已经尝试通过向下转换、在 Pandas 和 CSV 文件中合并的不同方法、转换为稀疏数据帧(我的数据确实有相当数量的 NaN)、使用 Dask 来减少每个列数据类型的内存,并且我我正在考虑在 Python 中使用 sqllite 创建关系数据库。我的版本是 python-64 3.7.0、pandas 0.23.4、Ipython 6.5.0,使用具有 32 GB 内存的 Mac OSX Darwin。我认为我的计算机这样做应该没有问题,但确实如此。真的没有更好的方法还是我做错了我错过的事情?

2个回答

有几点我可以提供帮助。

首先,Pandas 通常不擅长合并多个大型数据帧,因为每次您将新数据帧合并到旧数据帧时,它都会复制两者以制作第三个数据帧 - 这显然开始花费大量时间作为您的数据帧每一步都在成长。

其次(未经测试),您可以尝试通过尝试一些愚蠢的方法来克服合并步骤中的任何内存瓶颈,例如合并两个或三个文件,然后将其写入磁盘。重复此操作,直到您有 30 个更大的 CSV 文件,然后再做一次,这样您就有了,比如说 10 个文件,依此类推。我不确定 python 垃圾收集会在什么时候启动你的单个reduce函数,所以你甚至可能开始使用 SWAP 内存,这会让事情变得非常慢!该解决方案至少可以防止这种情况发生。您应该在新的 Python 会话中执行每个重复。

另一种想法,当您从 CSV 文件中读取数据时:您可以使用终端工具将所有 CSV 文件简单地粘贴到一个文件中,然后将其读入 pandas一次然后执行所需的清理操作。清理可能会很痛苦(根据您的merge on要求,但您可能会克服熊猫制作副本的漫长等待时间。如果您的文件都在一个文件夹中,您可以在终端中运行以下命令将它们全部放在一个文件中

cat *.csv > merged.csv

这里还有其他方法,以防您需要省略标题行等。

最后,我建议看一下data.table包,它现在可以在 Python 中使用——它始于 R 世界。它有一个更强大的内存管理系统(基于数据库的想法),并且能够在单个“DataTable”中存储大量数据,以防 Pandas 崩溃。这是一个很好的包来填补 Pandas 和 Dask 之间的空白(都是很棒的包!)。

这是一个视频,展示了 Python data.table 的工作原理,还展示了一些基准。

我最终使用本机 csv 库逐行读取文件并将每一行存储到嵌套的列表字典中。我还编写了一个函数来检查 1) 字典中是否已经存在该行,如果存在,则转到下一行,2) 如果该行不存在,看看它是否可以合并到带有现有行并尽可能合并,否则,将其作为新行添加到字典中。这种方法的工作速度要快得多,因为不是一次读取和存储整个文件,而且字典使用的内存比 Pandas 数据帧少。

n1k31t4 的第一点是为什么它需要这么长时间并且第一次崩溃。我也可以使用 SQLite 或其他关系数据库包,但我没有使用,因为我没有服务器来真正存储所有内容。我认为那将是理想的。希望这可以帮助某人。