【问题标题】:Merge dataframes and fill in blank values based on start/end dates合并数据框并根据开始/结束日期填写空白值
【发布时间】:2020-09-25 23:28:24
【问题描述】:

我有具有特定值(在本例中为“货币”)的开始日期和结束日期的 pandas 数据框 (df)。 我需要将它与另一个数据框(tbl)合并,并根据第一个 DF 的开始/结束日期填写空白货币行。 NULL 表示没有结束日期 - 所以一切都在前进。在这种情况下,2020 年 1 月 11 日之后的一切都是美元。来自 SQL 的数据因此为 NULL。

df = pd.DataFrame(data={
        'port': 'PortA'
        'currency': ['USD', 'CAD', 'EUR', 'USD'],
        'start_date': ['01/01/2020', '01/04/2020', '01/06/2020', '01/11/2020'],
        'end_date': ['01/04/2020', '01/06/2020', '01/11/2020', '01/15/2020']
        })
df[['start_date', 'end_date']] = df[['start_date', 'end_date']].apply(pd.to_datetime, errors='ignore')
tbl = pd.DataFrame(data={
        'port': 'PortA',
        'as_of_date': [x for x in pd.date_range(start='01/01/2020', end='01/15/2020')]
        })

This is what I need

df_merge 是我需要的最终外观。 第二个问题 - 如果我没有要合并的第二个数据框(tbl)怎么办。有没有一种简单的方法可以“取消堆叠”现有的 df,使其看起来与 df_merge 相同?

谢谢。

【问题讨论】:

    标签: python pandas dataframe merge


    【解决方案1】:
    1. 首先在您的数据框中创建一个带有pd.date_rangeas_of_date 列,该列是带有lambda x: 的每行的开始日期和结束日期之间的日期列表(删除重复项并保留最后一个)。
    2. 分解as_of_date 上的数据框,以准备合并dateport
    3. 只需合并数据框(根据您的第二个问题,您可以简单地排除此步骤)。

    第 1 步:创建日期范围列

    df['as_of_date'] = df.apply(lambda x: list(pd.date_range(x['start_date'], x['end_date'], freq='d')), axis=1)
    df
    Out[1]: 
        port currency start_date   end_date  \
    0  PortA      USD 2020-01-01 2020-01-04   
    1  PortA      CAD 2020-01-04 2020-01-06   
    2  PortA      EUR 2020-01-06 2020-01-11   
    3  PortA      USD 2020-01-11 2020-01-15   
    
                                              as_of_date  
    0  [2020-01-01 00:00:00, 2020-01-02 00:00:00, 202...  
    1  [2020-01-04 00:00:00, 2020-01-05 00:00:00, 202...  
    2  [2020-01-06 00:00:00, 2020-01-07 00:00:00, 202...  
    3  [2020-01-11 00:00:00, 2020-01-12 00:00:00, 202...  
    

    第 2 步:分解数据框并删除重复项

    df = df.explode('as_of_date').drop_duplicates('as_of_date', keep='last')
    df
    Out[2]: 
        port currency start_date   end_date as_of_date
    0  PortA      USD 2020-01-01 2020-01-04 2020-01-01
    0  PortA      USD 2020-01-01 2020-01-04 2020-01-02
    0  PortA      USD 2020-01-01 2020-01-04 2020-01-03
    1  PortA      CAD 2020-01-04 2020-01-06 2020-01-04
    1  PortA      CAD 2020-01-04 2020-01-06 2020-01-05
    2  PortA      EUR 2020-01-06 2020-01-11 2020-01-06
    2  PortA      EUR 2020-01-06 2020-01-11 2020-01-07
    2  PortA      EUR 2020-01-06 2020-01-11 2020-01-08
    2  PortA      EUR 2020-01-06 2020-01-11 2020-01-09
    2  PortA      EUR 2020-01-06 2020-01-11 2020-01-10
    3  PortA      USD 2020-01-11 2020-01-15 2020-01-11
    3  PortA      USD 2020-01-11 2020-01-15 2020-01-12
    3  PortA      USD 2020-01-11 2020-01-15 2020-01-13
    3  PortA      USD 2020-01-11 2020-01-15 2020-01-14
    3  PortA      USD 2020-01-11 2020-01-15 2020-01-15
    

    第 3 步:合并两个数据框(根据您的第二个问题 - 如果您没有 tbl 数据框,则可以忽略此步骤。而只需运行 df = df[['port', 'as_of_date', 'currency']] 以保留并重新排序您需要的列:

    df_merge = pd.merge(df[['port', 'currency', 'as_of_date']], tbl, how='left', on=['as_of_date', 'port'])
    df_merge
    Out[3]: 
         port currency as_of_date
    0   PortA      USD 2020-01-01
    1   PortA      USD 2020-01-02
    2   PortA      USD 2020-01-03
    3   PortA      CAD 2020-01-04
    4   PortA      CAD 2020-01-05
    5   PortA      EUR 2020-01-06
    6   PortA      EUR 2020-01-07
    7   PortA      EUR 2020-01-08
    8   PortA      EUR 2020-01-09
    9   PortA      EUR 2020-01-10
    10  PortA      USD 2020-01-11
    11  PortA      USD 2020-01-12
    12  PortA      USD 2020-01-13
    13  PortA      USD 2020-01-14
    14  PortA      USD 2020-01-15
    

    完整代码:

    df = pd.DataFrame(data={
            'port': ['PortA','PortA','PortA','PortA'],
            'currency': ['USD', 'CAD', 'EUR', 'USD'],
            'start_date': ['01/01/2020', '01/04/2020', '01/06/2020', '01/11/2020'],
            'end_date': ['01/04/2020', '01/06/2020', '01/11/2020', '01/15/2020']
            })
    df[['start_date', 'end_date']] = df[['start_date', 'end_date']].apply(pd.to_datetime, errors='ignore')
    tbl = pd.DataFrame(data={
            'port': 'PortA',
            'as_of_date': [x for x in pd.date_range(start='01/01/2020', end='01/15/2020')]
            })
    df['as_of_date'] = df.apply(lambda x: list(pd.date_range(x['start_date'], x['end_date'], freq='d')), axis=1)
    df = df.explode('as_of_date').drop_duplicates('as_of_date', keep='last')
    df_merge = pd.merge(df[['port', 'currency', 'as_of_date']], tbl, how='left', on=['as_of_date', 'port'])
    df_merge
    

    【讨论】:

      【解决方案2】:

      您可以使用pd.date_rangeexplodemerge 来做到这一点:

      df_dates = df.assign(dates=[pd.date_range(i, j + pd.Timedelta(days=-1), freq='D') 
                                   for i, j in zip(df['start_date'], df['end_date'])])\
                   .explode('dates')
      
      bl.merge(df_dates[['port','dates','currency']], 
               left_on=['port', 'as_of_date'], 
               right_on=['port', 'dates'])
      

      输出:

           port as_of_date      dates currency
      0   PortA 2020-01-01 2020-01-01      USD
      1   PortA 2020-01-02 2020-01-02      USD
      2   PortA 2020-01-03 2020-01-03      USD
      3   PortA 2020-01-04 2020-01-04      CAD
      4   PortA 2020-01-05 2020-01-05      CAD
      5   PortA 2020-01-06 2020-01-06      EUR
      6   PortA 2020-01-07 2020-01-07      EUR
      7   PortA 2020-01-08 2020-01-08      EUR
      8   PortA 2020-01-09 2020-01-09      EUR
      9   PortA 2020-01-10 2020-01-10      EUR
      10  PortA 2020-01-11 2020-01-11      USD
      11  PortA 2020-01-12 2020-01-12      USD
      12  PortA 2020-01-13 2020-01-13      USD
      13  PortA 2020-01-14 2020-01-14      USD
      

      注意: pd.Timedelta(days=-1) 用于处理多行上的重复日期。 End_date 与下一行的 start_date 重叠。

      更改代码以修复最后日期:

      d = pd.Timedelta(days=-1)
      l = pd.date_range #To shorten typing
      df_dates = df.assign(dates=[l(i, j + d) if j != df.iloc[-1, df.columns.get_loc('end_date')] 
                                     else l(i, j) for i, j in zip(df['start_date'], df['end_date'])])\
                   .explode('dates')
      
      print(tbl.merge(df_dates[['port','dates','currency']], left_on=['port', 'as_of_date'], right_on=['port', 'dates']))
      

      【讨论】:

      • 看起来不错 +1,只需要包含最后日期 2020-01-15(我在最初的回答中做了同样的事情 :))
      • @DavidErickson Yuck...我没注意到。
      • 谢谢!我真的很感谢你的回答!很抱歉我没有早点回复。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-28
      相关资源
      最近更新 更多