うーん、できたら同じ商品を連続して短期間で注文した場合などに注文と到着がの順番が一致していない場合問題になるので本当は元から注文ID的な値は保持しておく・・・のが無難なのですが、過去のデータで既にそういったIDが存在しないけれども扱わないといけない・・・というケースを想定して話を進めます!
たとえばかぼちゃをA商店とB商店に順番に連日注文してB商店の方が到着が早かった・・・みたいなケースがあると正確なマージができないのでご注意ください!
色々な書き方がありますが、サンプルとして以下のようなコードにしてみました!(各所のコードの詳細は後述しています)
また、注文済みで未到着の商品があると想定して2021-08-15のにんじんの行をchudf
側に追加してあります!
from collections import deque
from typing import Dict, List
import pandas as pd
import numpy as np
chudf: pd.DataFrame = pd.DataFrame(
data=[{
'注文日': '2021-08-01',
'商品': 'かぼちゃ',
'入荷先': 'A商店',
}, {
'注文日': '2021-08-03',
'商品': 'だいこん',
'入荷先': 'A商店',
}, {
'注文日': '2021-08-05',
'商品': 'にんじん',
'入荷先': 'B商店',
}, {
'注文日': '2021-08-10',
'商品': 'かぼちゃ',
'入荷先': 'B商店',
}, {
'注文日': '2021-08-11',
'商品': 'にんじん',
'入荷先': 'A商店',
}, {
'注文日': '2021-08-15',
'商品': 'にんじん',
'入荷先': 'B商店',
}]
)
itemdf: pd.DataFrame = pd.DataFrame(
data=[{
'入荷日': '2021-08-01',
'商品': 'かぼちゃ',
'商品評価': '良',
'備考': '甘い',
}, {
'入荷日': '2021-08-04',
'商品': 'だいこん',
'商品評価': '普通',
'備考': '',
}, {
'入荷日': '2021-08-05',
'商品': 'にんじん',
'商品評価': '良',
'備考': '色良し',
}, {
'入荷日': '2021-08-13',
'商品': 'かぼちゃ',
'商品評価': '交換',
'備考': '傷',
}, {
'入荷日': '2021-08-11',
'商品': 'にんじん',
'商品評価': '良',
'備考': '色良し',
}])
# 今回のサンプルデータでは注文データは元から日付で昇順になっていますが、
# 実データでは昇順になっていないことを加味して一応日付の昇順でソートしています。
chudf.sort_values(by='注文日', inplace=True)
itemdf.sort_values(by='入荷日', inplace=True)
# 仮の注文IDとして連番を割り振っています。
chudf['注文ID'] = np.arange(1, len(chudf) + 1)
# 商品名をキーとした各商品の注文IDのキューの辞書作っています。
name: str
grouped_df: pd.DataFrame
name_key_deques: Dict[str, deque] = {}
for name, grouped_df in chudf.groupby('商品'):
name_key_deques[name] = deque(grouped_df['注文ID'].tolist())
# 商品名ごとにループを回して、キューからマージ用の対象の注文IDを
# 取得し、itemdfに設定しています。
order_ids: List[int] = []
for item_name in itemdf['商品']:
order_ids.append(name_key_deques[item_name].popleft())
itemdf['注文ID'] = order_ids
# 連結時に商品名のカラムが被っていて不要なので片側を消しています。
del itemdf['商品']
# 連結処理をしています。
merged_df: pd.DataFrame = chudf.merge(
right=itemdf, on='注文ID', how='left')
print(merged_df)
連結結果ですが、コードを実行すると以下のようになっています!
|
注文日 |
商品 |
入荷先 |
注文ID |
入荷日 |
商品評価 |
備考 |
0 |
2021-08-01 |
かぼちゃ |
A商店 |
1 |
2021-08-01 |
良 |
甘い |
1 |
2021-08-03 |
だいこん |
A商店 |
2 |
2021-08-04 |
普通 |
|
2 |
2021-08-05 |
にんじん |
B商店 |
3 |
2021-08-05 |
良 |
色良し |
3 |
2021-08-10 |
かぼちゃ |
B商店 |
4 |
2021-08-13 |
交換 |
傷 |
4 |
2021-08-11 |
にんじん |
A商店 |
5 |
2021-08-11 |
良 |
色良し |
5 |
2021-08-15 |
にんじん |
B商店 |
6 |
NaN |
NaN |
NaN |
以下コードの詳細を書いておきます!
# 今回のサンプルデータでは注文データは元から日付で昇順になっていますが、
# 実データでは昇順になっていないことを加味して一応日付の昇順でソートしています。
chudf.sort_values(by='注文日', inplace=True)
itemdf.sort_values(by='入荷日', inplace=True)
まず上記の部分ですが、日付の昇順で処理を行いためsort_values
メソッドで日付で昇順ソートを設定しています!
# 仮の注文IDとして連番を割り振っています。
chudf['注文ID'] = np.arange(1, len(chudf) + 1)
続いて上記のコードで、chudf
の各行にそれぞれ1, 2, 3, 4...と順番に注文IDとしてIDを割り振っています!この注文IDのカラムは後でマージ時に使っています!
# 商品名をキーとした各商品の注文IDのキューを作っています。
name: str
grouped_df: pd.DataFrame
name_key_deques: defaultdict = defaultdict(deque)
for name, grouped_df in chudf.groupby('商品'):
name_key_deques[name] = deque(grouped_df['注文ID'].tolist())
deque
はキュー(先頭と末尾のデータを扱う場合にリストよりも高速に動作するデータの型)となります!この辺はリンクを貼っておきます。
deque:
chudf.groupby('商品')
の部分で、商品名ごとにグループ化したデータフレームが取れます(grouped_df
)。ループで回すと商品名(name
)も一緒に得られます。
name_key_deques[name] = deque(grouped_df['注文ID'].tolist())
部分で、商品名をキーとした各商品のIDのキューを格納した辞書を作っています。以下のようなデータになるイメージです!
{
'かぼちゃ': [1, 4],
'だいこん': [1],
'にんじん': [3, 5, 6],
}
# 商品名ごとにループを回して、キューからマージ用の対象の注文IDを
# 取得し、itemdfに設定しています。
order_ids: List[int] = []
for item_name in itemdf['商品']:
order_ids.append(name_key_deques[item_name].popleft())
itemdf['注文ID'] = order_ids
上記の部分でitemdf
の商品名でループを行い、注文データの方から作ったキューから注文IDを取得しitemdf
に設定しています!キューのpopleft
ではキューの中から先頭の値(今回は注文ID)が取り出され、且つ取り出された値はキューから無くなります。
キューは商品名ごとに分けてあるので、たとえば1回目のにんじんに対するpopleft
では取り出される注文IDは3、次のpopleftでは5...となります!
これで双方のデータフレームに注文IDのカラムが設定されたので最後にmerge
メソッドでマージして完了です!(到着していない想定の2021-08-15のにんじんは連結できないのでNaNとなっています)