web-dev-qa-db-ja.com

複数の大きなDataFrameをマージする効率的な方法

4つの小さなデータフレームがあるとします

df1df2df3およびdf4

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つのデータフレームでこれを達成しました。

基本的に、上記の外部マージソリューションをサイズ62245 X 3:のMULTIPLE(48)DataFramesに拡張します

そこで、ラムダリデュースを使用した別の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が発生します。

カーネルが停止するのを防ぐために何をすべきかわかりません。2日間このままでいました。実行したEXACTマージ操作のコードのうち、MemoryErrorまたは同じ結果が得られるものをいただければ幸いです。

また、メインのDataFrameの3つの列(この例では再現可能な48のDataFramesではありません)のタイプはint64int64およびfloat64です。それが表す整数と浮動小数点数の。

編集:

マージ操作を繰り返し実行したり、ラムダ削減関数を使用したりする代わりに、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 $を借りているので、 yall)、それは私が得たものよりも遠くなり、28のデータフレームのリストを4に減らした後でもMemoryErrorをスローします。

5

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(2番目のステップ)を反復joinに置き換えることができます。

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

これはmergeよりも優れている場合とそうでない場合があります。

5
cs95

Daskデータフレームが設計された機能の一部のようです(データフレームを使用したメモリ操作が不足しています)。コード例については、 パンダで2つの大きなデータセットを結合する最良の方法 を参照してください。コピーと貼り付けではなく申し訳ありませんが、リンクされたエントリの回答者からクレジットを取得しようとしているように思われたくありません。

1
user85779

単純な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

系列全体をobject型系列に変換するため、原則として、「欠落」などの文字列を数値型と組み合わせないでください。ここでは-1を使用していますが、代わりにNaNfloat dtypeとともに使用することもできます。

1
jpp

したがって、48個のdfがあり、それぞれに3つの列があります。名前、ID、および各dfごとに異なる列です。

マージを使用する必要はありません...

代わりに、すべてのdfを連結すると

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 dfで試してみると、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()
0
theletz