Masa-Kawa
@Masa-Kawa (Masa Kawaguchi)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[Pandas] 日付のずれている2つの表をマージしたい。

解決したいこと

次のような2つの表があったとします。
これを一つの表にしたいと思っています。
IDがありませんので注文日と入荷日、商品名をキーにしようと考えています。

注文表

注文日 商品 入荷先
2021-08-01 かぼちゃ A商店
2021-08-03 だいこん A商店
2021-08-05 にんじん B商店
2021-08-10 かぼちゃ B商店
2021-08-11 にんじん A商店

データフレーム名: chudf

商品表 

入荷日  商品  商品評価  備考 
2021-08-01 かぼちゃ 甘い
2021-08-04 だいこん 普通
2021-08-05 にんじん 色良し
2021-08-13 かぼちゃ 交換
2021-08-11 にんじん 色良し

データフレーム名: itemdf

発生している問題

もともとは当日入荷していることも多く、支払日をキーとしてマージすればよいと考えたのですが、配送の関係でときどき入荷日が遅れることがわかりました。

このような表をまとめるいい方法はありますか。

自分で試したこと

日付をキーにするとこのようになります。

# datetime を文字列に変えて日付を抜き出し、共通の列'kyoutsu'を作る
chudf.loc[:,'kyoutsu']=chudf['注文日'].astype(str).str[:10]
itemdf.loc[:,'kyoutsu']=itemdf['入荷日'].astype(str).str[:10]

# マージする
pd.merge(chudf, itemdf, on=kyoutsu, how = 'left')

timedeltaやoffsetを使って1日ずらすならできそうな気はしたのですが数日ずれてしまうとわからなくなってしまいました。

お知恵を拝借できればうれしいです。

背景情報

Python, Pandasの初心者です。独学でデータ管理に挑戦しています。
Python 3.8.3
pandas 1.2.1

0

1Answer

うーん、できたら同じ商品を連続して短期間で注文した場合などに注文と到着がの順番が一致していない場合問題になるので本当は元から注文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となっています)

1Like

Comments

  1. @Masa-Kawa

    Questioner

    助かりました。IDなどは当然はじめから作っておくべきだとは思いますが、今回はすでにあるデータを分析したかったので無理な注文をしました。
    理解するには時間がかかりそうですが、解読してみます。
    ご回答ありがとうございました。
  2. @Masa-Kawa

    Questioner

    わかりやすい解説をつけていただいてありがとうございました。
    プログラムを拝見して勉強させていただきました。

    groupbyは使いこなせればよさそうですが、まだ良くわかっていません。
    なお、dequeという方法も教えていただきました。
    スタックを扱うのに高速で効率がよいとのことで、ぜひ活用したいと思います。
    ありがとうございました。
  3. お役に立てたようで良かったです!

Your answer might help someone💌