pandas日期列的多个日期范围

时间:2018-05-16 21:08:52

标签: python pandas date datetime

当前df:

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

我有ID和日期的df。 ID在原始df中是唯一的。 我想基于日期创建一个新的df。每个ID都有一个最大日期,我想使用该日期并返回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")

4 个答案:

答案 0 :(得分:4)

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 :(得分:4)

另一种方法是使用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 :(得分:2)

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)

答案 3 :(得分:2)

您可以使用groupby

尝试date_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