合并多个大型 DataFrame 的有效方法

Efficient way to merge multiple large DataFrames

假设我有 4 个小数据帧

df1df2df3df4

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

df1 = pd.DataFrame([['a', 1, 10], ['a', 2, 20], ['b', 1, 4], ['c', 1, 2], ['e', 2, 10]])
df2 = pd.DataFrame([['a', 1, 15], ['a', 2, 20], ['c', 1, 2]])
df3 = pd.DataFrame([['d', 1, 10], ['e', 2, 20], ['f', 1, 1]])  
df4 = pd.DataFrame([['d', 1, 10], ['e', 2, 20], ['f', 1, 15]])   


df1.columns = ['name', 'id', 'price']
df2.columns = ['name', 'id', 'price']
df3.columns = ['name', 'id', 'price']    
df4.columns = ['name', 'id', 'price']   

df1 = df1.rename(columns={'price':'pricepart1'})
df2 = df2.rename(columns={'price':'pricepart2'})
df3 = df3.rename(columns={'price':'pricepart3'})
df4 = df4.rename(columns={'price':'pricepart4'})

上面创建的是4个DataFrame,我想要的在下面的代码中。

# Merge dataframes
df = pd.merge(df1, df2, left_on=['name', 'id'], right_on=['name', 'id'], how='outer')
df = pd.merge(df , df3, left_on=['name', 'id'], right_on=['name', 'id'], how='outer')
df = pd.merge(df , df4, left_on=['name', 'id'], right_on=['name', 'id'], how='outer')

# Fill na values with 'missing'
df = df.fillna('missing')

所以我已经为没有很多行和列的 4 个 DataFrame 实现了这个。

基本上,我想将上述外部合并解决方案扩展到多个 (48) 个大小为 62245 X 3 的数据帧:

因此,我通过使用 lambda reduce 的另一个 Whosebug 答案构建了这个解决方案:

from functools import reduce
import pandas as pd
import numpy as np
dfList = []

#To create the 48 DataFrames of size 62245 X 3
for i in range(0, 49):

    dfList.append(pd.DataFrame(np.random.randint(0,100,size=(62245, 3)), columns=['name',  'id',  'pricepart' + str(i + 1)]))


#The solution I came up with to extend the solution to more than 3 DataFrames
df_merged = reduce(lambda  left, right: pd.merge(left, right, left_on=['name', 'id'], right_on=['name', 'id'], how='outer'), dfList).fillna('missing')

这导致 MemoryError

我不知道该怎么做才能阻止内核死掉。我已经坚持了两天了。我执行的一些 EXACT 合并操作代码不会导致 MemoryError 或能给您相同结果的东西,我们将不胜感激。

此外,主 DataFrame 中的 3 列(不是示例中可重现的 48 DataFrame)的类型为 int64int64float64,我更喜欢它们由于它代表的整数和浮点数而保持这种状态。

编辑:

我没有反复尝试 运行 合并操作或使用 reduce lambda 函数,而是以 2 人一组的方式完成!另外,我更改了一些列的数据类型,有些不需要是 float64。所以我把它降低到 float16。它走得很远,但最终还是抛出了 MemoryError.

intermediatedfList = dfList    

tempdfList = []    

#Until I merge all the 48 frames two at a time, till it becomes size 2
while(len(intermediatedfList) != 2):

    #If there are even number of DataFrames
    if len(intermediatedfList)%2 == 0:

        #Go in steps of two
        for i in range(0, len(intermediatedfList), 2):

            #Merge DataFrame in index i, i + 1
            df1 = pd.merge(intermediatedfList[i], intermediatedfList[i + 1], left_on=['name',  'id'], right_on=['name',  'id'], how='outer')
            print(df1.info(memory_usage='deep'))

            #Append it to this list
            tempdfList.append(df1)

        #After DataFrames in intermediatedfList merging it two at a time using an auxillary list tempdfList, 
        #Set intermediatedfList to be equal to tempdfList, so it can continue the while loop. 
        intermediatedfList = tempdfList 

    else:

        #If there are odd number of DataFrames, keep the first DataFrame out

        tempdfList = [intermediatedfList[0]]

        #Go in steps of two starting from 1 instead of 0
        for i in range(1, len(intermediatedfList), 2):

            #Merge DataFrame in index i, i + 1
            df1 = pd.merge(intermediatedfList[i], intermediatedfList[i + 1], left_on=['name',  'id'], right_on=['name',  'id'], how='outer')
            print(df1.info(memory_usage='deep'))
            tempdfList.append(df1)

        #After DataFrames in intermediatedfList merging it two at a time using an auxillary list tempdfList, 
        #Set intermediatedfList to be equal to tempdfList, so it can continue the while loop. 
        intermediatedfList = tempdfList 

有什么方法可以优化我的代码以避免 MemoryError,我什至使用了 AWS 192GB RAM(我现在欠他们 7 美元,我本可以给你们一个),那得到比我得到的更远,在将 28 个 DataFrame 的列表减少到 4..

之后它仍然抛出 MemoryError

您可以尝试一个简单的 for 循环。我应用的唯一内存优化是通过 pd.to_numeric.

向下转换为最佳 int 类型

我也在使用字典来存储数据帧。这是保存可变数量变量的好习惯。

import pandas as pd

dfs = {}
dfs[1] = pd.DataFrame([['a', 1, 10], ['a', 2, 20], ['b', 1, 4], ['c', 1, 2], ['e', 2, 10]])
dfs[2] = pd.DataFrame([['a', 1, 15], ['a', 2, 20], ['c', 1, 2]])
dfs[3] = pd.DataFrame([['d', 1, 10], ['e', 2, 20], ['f', 1, 1]])  
dfs[4] = pd.DataFrame([['d', 1, 10], ['e', 2, 20], ['f', 1, 15]])   

df = dfs[1].copy()

for i in range(2, max(dfs)+1):
    df = pd.merge(df, dfs[i].rename(columns={2: i+1}),
                  left_on=[0, 1], right_on=[0, 1], how='outer').fillna(-1)
    df.iloc[:, 2:] = df.iloc[:, 2:].apply(pd.to_numeric, downcast='integer')

print(df)

   0  1   2   3   4   5
0  a  1  10  15  -1  -1
1  a  2  20  20  -1  -1
2  b  1   4  -1  -1  -1
3  c  1   2   2  -1  -1
4  e  2  10  -1  20  20
5  d  1  -1  -1  10  10
6  f  1  -1  -1   1  15

通常,您不应将 "missing" 等字符串与数字类型组合,因为这会将您的整个系列变成 object 类型系列。这里我们使用 -1,但您可能希望使用 NaNfloat dtype。

您可能会从使用 pd.concat 执行索引对齐串联中获得一些好处。这应该比外部合并更快,内存效率更高。

df_list = [df1, df2, ...]
for df in df_list:
    df.set_index(['name', 'id'], inplace=True)

df = pd.concat(df_list, axis=1) # join='inner'
df.reset_index(inplace=True)

或者,您可以将 concat(第二步)替换为迭代 join:

from functools import reduce
df = reduce(lambda x, y: x.join(y), df_list)

这可能比 merge 更好,也可能不更好。

因此,您有 48 个 df,每个 df 有 3 列 - 每个 df 的名称、ID 和不同的列。

您不必使用合并....

相反,如果您连接所有 dfs

df = pd.concat([df1,df2,df3,df4])

您将收到:

Out[3]: 
   id name  pricepart1  pricepart2  pricepart3  pricepart4
0   1    a        10.0         NaN         NaN         NaN
1   2    a        20.0         NaN         NaN         NaN
2   1    b         4.0         NaN         NaN         NaN
3   1    c         2.0         NaN         NaN         NaN
4   2    e        10.0         NaN         NaN         NaN
0   1    a         NaN        15.0         NaN         NaN
1   2    a         NaN        20.0         NaN         NaN
2   1    c         NaN         2.0         NaN         NaN
0   1    d         NaN         NaN        10.0         NaN
1   2    e         NaN         NaN        20.0         NaN
2   1    f         NaN         NaN         1.0         NaN
0   1    d         NaN         NaN         NaN        10.0
1   2    e         NaN         NaN         NaN        20.0
2   1    f         NaN         NaN         NaN        15.0

现在您可以按名称和 ID 分组并求和:

df.groupby(['name','id']).sum().fillna('missing').reset_index()

如果您尝试使用 48 dfs,您会发现它解决了 MemoryError:

dfList = []
#To create the 48 DataFrames of size 62245 X 3
for i in range(0, 49):
    dfList.append(pd.DataFrame(np.random.randint(0,100,size=(62245, 3)), columns=['name',  'id',  'pricepart' + str(i + 1)]))

df = pd.concat(dfList)
df.groupby(['name','id']).sum().fillna('missing').reset_index()

似乎是 dask 数据帧的设计目的的一部分(数据帧的内存不足操作)。看 Best way to join two large datasets in Pandas 例如代码。抱歉,我没有复制和粘贴,但不想让我看起来像是在试图从链接条目中的回答者那里获取信用。