【问题标题】:pandas multiple date ranges from column of datespandas 日期列中的多个日期范围
【发布时间】:2019-01-13 14:46:50
【问题描述】:

当前df:

ID  Date
11  3/19/2018
22  1/5/2018
33  2/12/2018
..  ..

我有带有 ID 和日期的 df。 ID 在原始 df 中是唯一的。 我想根据日期创建一个新的df。每个 ID 都有一个 Max Date,我想使用该日期并返回 4 天(每个 ID 5 行) 有数千个 ID。

期望得到:

ID  Date
11  3/15/2018
11  3/16/2018
11  3/17/2018
11  3/18/2018
11  3/19/2018
22  1/1/2018
22  1/2/2018
22  1/3/2018
22  1/4/2018
22  1/5/2018
33  2/8/2018
33  2/9/2018
33  2/10/2018
33  2/11/2018
33  2/12/2018
…   …

我尝试了以下方法,我认为使用date_range 可能是正确的方向,但我一直出错。

pd.date_range

def date_list(row):
    list = pd.date_range(row["Date"], periods=5)
    return list

df["Date_list"] = df.apply(date_list, axis = "columns")

【问题讨论】:

    标签: python pandas date datetime


    【解决方案1】:

    这是另一个使用df.assign 覆盖datepd.concat 将范围粘合在一起的方法。 cᴏʟᴅsᴘᴇᴇᴅ 的解决方案在性能上获胜,但我认为这可能是一个不错的补充,因为它很容易阅读和理解。

    df = pd.concat([df.assign(Date=df.Date - pd.Timedelta(days=i)) for i in range(5)])
    

    替代方案:

    dates = (pd.date_range(*x) for x in zip(df['Date']-pd.Timedelta(days=4), df['Date']))
    
    df = (pd.DataFrame(dict(zip(df['ID'],dates)))
            .T
            .stack()
            .reset_index(0)
            .rename(columns={'level_0': 'ID', 0: 'Date'}))
    

    完整示例:

    import pandas as pd
    
    data = '''\
    ID  Date
    11  3/19/2018
    22  1/5/2018
    33  2/12/2018'''
    
    # Recreate dataframe
    df = pd.read_csv(pd.compat.StringIO(data), sep='\s+')
    df['Date']= pd.to_datetime(df.Date)
    
    df = pd.concat([df.assign(Date=df.Date - pd.Timedelta(days=i)) for i in range(5)])
    df.sort_values(by=['ID','Date'], ascending = [True,True], inplace=True)
    print(df)
    

    返回:

       ID       Date
    0  11 2018-03-15
    0  11 2018-03-16
    0  11 2018-03-17
    0  11 2018-03-18
    0  11 2018-03-19
    1  22 2018-01-01
    1  22 2018-01-02
    1  22 2018-01-03
    1  22 2018-01-04
    1  22 2018-01-05
    2  33 2018-02-08
    2  33 2018-02-09
    2  33 2018-02-10
    2  33 2018-02-11
    2  33 2018-02-12
    

    【讨论】:

      【解决方案2】:

      reindexpd.date_range

      让我们尝试创建一个日期范围的平面列表并重新索引此 DataFrame。

      from itertools import chain
      
      v = df.assign(Date=pd.to_datetime(df.Date)).set_index('Date')
      # assuming ID is a string column
      v.reindex(chain.from_iterable(
          pd.date_range(end=i, periods=5) for i in v.index)
      ).bfill().reset_index()  
      
               Date  ID
      0  2018-03-14  11
      1  2018-03-15  11
      2  2018-03-16  11
      3  2018-03-17  11
      4  2018-03-18  11
      5  2018-03-19  11
      6  2017-12-31  22
      7  2018-01-01  22
      8  2018-01-02  22
      9  2018-01-03  22
      10 2018-01-04  22
      11 2018-01-05  22
      12 2018-02-07  33
      13 2018-02-08  33
      14 2018-02-09  33
      15 2018-02-10  33
      16 2018-02-11  33
      17 2018-02-12  33
      

      concat 基于keys 的解决方案

      只是为了好玩。我的reindex 解决方案肯定更高效且更易于阅读,所以如果您要选择一个,请使用它。

      v = df.assign(Date=pd.to_datetime(df.Date))
      v_dict = {
          j : pd.DataFrame(
                  pd.date_range(end=i, periods=5), columns=['Date']
              ) 
          for j, i in zip(v.ID, v.Date)
      }
      
      (pd.concat(v_dict, axis=0)
        .reset_index(level=1, drop=True)
        .rename_axis('ID')
        .reset_index()
      )
      
          ID       Date
      0   11 2018-03-14
      1   11 2018-03-15
      2   11 2018-03-16
      3   11 2018-03-17
      4   11 2018-03-18
      5   11 2018-03-19
      6   22 2017-12-31
      7   22 2018-01-01
      8   22 2018-01-02
      9   22 2018-01-03
      10  22 2018-01-04
      11  22 2018-01-05
      12  33 2018-02-07
      13  33 2018-02-08
      14  33 2018-02-09
      15  33 2018-02-10
      16  33 2018-02-11
      17  33 2018-02-12
      

      【讨论】:

      • 太棒了。 +1。不使用链可以吗?
      • @HarvIpan 你的意思是完全没有循环?嗯,有兴趣看看。让我试试。
      • @HarvIpan 如果您愿意通过减去 4 天来创建第二个日期列,那么 jezrael 对this question 的回答允许您通过一些堆叠和 groupby 来做到这一点
      • @AntonvBR 我很惊讶它没有!谢谢!顺便说一句,你也有一个不错的解决方案,+1 :)
      • @AntonvBR yeaaaaah,只是在尝试不同的东西......看看有什么效果。第二个解决方案是bad,但它是一个解决方案。无论如何。
      【解决方案3】:

      ID 分组,选择Date 列,然后为每个组生成一系列直到最大日期的五天。

      我没有写一个长的 lambda,而是写了一个辅助函数。

      def drange(x): 
          e = x.max()
          s = e-pd.Timedelta(days=4)
          return pd.Series(pd.date_range(s,e))
      
      res = df.groupby('ID').Date.apply(drange)
      

      然后从生成的多索引中删除无关级别,我们得到我们想要的输出

      res.reset_index(level=0).reset_index(drop=True)
      # outputs:
      
          ID       Date
      0   11 2018-03-15
      1   11 2018-03-16
      2   11 2018-03-17
      3   11 2018-03-18
      4   11 2018-03-19
      5   22 2018-01-01
      6   22 2018-01-02
      7   22 2018-01-03
      8   22 2018-01-04
      9   22 2018-01-05
      10  33 2018-02-08
      11  33 2018-02-09
      12  33 2018-02-10
      13  33 2018-02-11
      14  33 2018-02-12
      

      紧凑型替代方案

      # Help function to return Serie with daterange
      func = lambda x: pd.date_range(x.iloc[0]-pd.Timedelta(days=4), x.iloc[0]).to_series()
      
      res = df.groupby('ID').Date.apply(func).reset_index().drop('level_1',1)
      

      【讨论】:

      • 不错的解决方案。您甚至可以使用 iloc[0] 来检索第一个值(因为在这种情况下它们是唯一的)。我添加了它...但是我认为您的解决方案可能很好!
      【解决方案4】:

      你可以试试groupbydate_range

      df.groupby('ID').Date.apply(lambda x : pd.Series(pd.date_range(end=x.iloc[0],periods=5))).reset_index(level=0)
      Out[793]: 
         ID       Date
      0  11 2018-03-15
      1  11 2018-03-16
      2  11 2018-03-17
      3  11 2018-03-18
      4  11 2018-03-19
      0  22 2018-01-01
      1  22 2018-01-02
      2  22 2018-01-03
      3  22 2018-01-04
      4  22 2018-01-05
      0  33 2018-02-08
      1  33 2018-02-09
      2  33 2018-02-10
      3  33 2018-02-11
      4  33 2018-02-12
      

      【讨论】:

      • 这与我采用的方法基本相同,但我喜欢你指定结束和句点的方式
      • @HaleemurAli 是的,我只是注意到几乎相同:-)
      猜你喜欢
      • 1970-01-01
      • 2012-11-06
      • 2017-09-26
      • 1970-01-01
      • 2017-02-23
      • 1970-01-01
      • 2014-01-07
      • 2018-02-20
      • 1970-01-01
      相关资源
      最近更新 更多