合并多个大型数据框的高效方法

9
假设我有4个小的DataFrame: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个数据框,我希望在以下代码中实现。
# 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')

我已经为不太大的四个数据框实现了这一点。

基本上,我希望将以上外部合并解决方案扩展到大小为62245 X 3的48个数据框:

所以我通过另一个使用lambda reduce的StackOverflow答案进行构建,得出了这个解决方案:

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

我不知道该怎么做才能防止内核崩溃..我被卡在这里已经两天了..如果有一些确切的合并操作的代码,它不会引起MemoryError,或者是一些可以给你相同结果的东西,那将非常感谢。

另外,主要数据框中的3列(示例中不可再现的48个数据框除外)的类型分别为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个数据框的列表减少到4个后,它仍然会抛出 MemoryError


@coldspeed 如果我没错的话,你回答中的concat应该正确地执行了外部合并,正如你的答案输出所显示的那样 - 在Abhishek的示例中,你得到了与他相同的答案,而不是使用“inner”会得到空的DataFrame。 - Marco Spinaci
@MarcoSpinaci 是这样吗?... 哦,有趣,感谢澄清! - cs95
4个回答

15

使用 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更好,也可能不是。


concat肯定比append或merge快得多。 - MrE
有什么想法可以克服这个问题吗?https://stackoverflow.com/questions/58077949/dataframe-merge-gives-process-finished-with-exit-code-137-interrupted-by-signa? - Ratha

5

2

您可以尝试使用简单的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,但您可能希望改用float数据类型的NaN


0

所以,您有48个包含3列的dfs - 名称、ID和每个df的不同列。

您不必使用合并....

相反,如果您将所有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()

这与我的答案完全相同,但更糟糕,因为它复制了名称和ID列。-1 - cs95
此外,在这里并没有提到需要使用groupby。 - cs95
也许我没有表达清楚。我编辑了答案。你尝试使用48个dfs运行它了吗?这可以解决MemoryError问题... - theletz
仅仅因为它对你有效并不意味着它对OP也有效。而且那不是我上一条评论的重点,我已经在你之前提到了 concat 解决方案。因此,如果你的答案修复了内存错误,那么我的答案也会修复。 - cs95

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接