2017-05-30 69 views
1

我在Python中有一組記錄,包含一個id,至少一個屬性和一組日期範圍。我希望獲取每個ID的代碼,並將所有屬性匹配的記錄組合在一起,並且在日期範圍內沒有空白。在熊貓數據框中組合日期範圍

由於日期範圍沒有空隙,我的意思是一個記錄的結束日期大於或等於該id的下一個記錄。

例如,具有ID「10」,開始日期「2016-01-01」和結束日期「2017-01-01」的記錄可以與具有該ID的另一記錄合併,開始日期「2017 -01-01「,結束日期爲」2018-01-01「,但不能與」2017-01-10「開始的記錄合併,因爲與2017-01-01之間存在差距-01至2017-01-09。

下面是一些例子 -

有:

FruitID,FruitType,StartDate,EndDate 
1,Apple,2015-01-01,2016-01-01 
1,Apple,2016-01-01,2017-01-01 
1,Apple,2017-01-01,2018-01-01 
2,Orange,2015-01-01,2016-01-01 
2,Orange,2016-05-31,2017-01-01 
2,Orange,2017-01-01,2018-01-01 
3,Banana,2015-01-01,2016-01-01 
3,Banana,2016-01-01,2017-01-01 
3,Blueberry,2017-01-01,2018-01-01 
4,Mango,2015-01-01,2016-01-01 
4,Kiwi,2016-09-15,2017-01-01 
4,Mango,2017-01-01,2018-01-01 

旺旺:

FruitID,FruitType,NewStartDate,NewEndDate 
1,Apple,2015-01-01,2018-01-01 
2,Orange,2015-01-01,2016-01-01 
2,Orange,2016-05-31,2018-01-01 
3,Banana,2015-01-01,2017-01-01 
3,Blueberry,2017-01-01,2018-01-01 
4,Mango,2015-01-01,2016-01-01 
4,Kiwi,2016-09-15,2017-01-01 
4,Mango,2017-01-01,2018-01-01 

我目前的解決方案如下。它提供了我正在尋找的結果,但對於大型數據集,性能似乎並不好。此外,我的印象是,您通常希望避免在可能的情況下迭代數據幀的各個行。非常感謝您提供的任何幫助!

import pandas as pd 
from dateutil.parser import parse 

have = pd.DataFrame.from_items([('FruitID', [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]), 
           ('FruitType', ['Apple', 'Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Banana', 'Banana', 'Blueberry', 'Mango', 'Kiwi', 'Mango']), 
           ('StartDate', [parse(x) for x in ['2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', '2016-05-31', 
                    '2017-01-01', '2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', '2016-09-15', '2017-01-01']]), 
           ('EndDate', [parse(x) for x in ['2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01', 
                   '2018-01-01', '2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01', '2018-01-01']]) 
           ]) 

have.sort_values(['FruitID', 'StartDate']) 

rowlist = [] 
fruit_cur_row = None 

for row in have.itertuples(): 
    if fruit_cur_row is None: 
     fruit_cur_row = row._asdict() 
     fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate) 

    elif not(fruit_cur_row.get('FruitType') == row.FruitType): 
     rowlist.append(fruit_cur_row) 

     fruit_cur_row = row._asdict() 
     fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate) 

    elif (row.StartDate <= fruit_cur_row.get('NewEndDate')): 
     fruit_cur_row['NewEndDate'] = max(fruit_cur_row['NewEndDate'], row.EndDate) 
    else: 
     rowlist.append(fruit_cur_row) 
     fruit_cur_row = row._asdict() 
     fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate) 

rowlist.append(fruit_cur_row) 
have_mrg = pd.DataFrame.from_dict(rowlist) 
print(have_mrg[['FruitID', 'FruitType', 'NewStartDate', 'NewEndDate']]) 
+0

你能不能解釋一下什麼樣的手段「日期範圍內沒有差距」?我無法理解這個問題。謝謝。 –

+0

我已更新我的帖子,以包含有關「無間隙」的更多詳細信息,以嘗試澄清此問題。 – Netbrian

回答

1

使用嵌套groupby方法:

def merge_dates(grp): 
    # Find contiguous date groups, and get the first/last start/end date for each group. 
    dt_groups = (grp['StartDate'] != grp['EndDate'].shift()).cumsum() 
    return grp.groupby(dt_groups).agg({'StartDate': 'first', 'EndDate': 'last'}) 

# Perform a groupby and apply the merge_dates function, followed by formatting. 
df = df.groupby(['FruitID', 'FruitType']).apply(merge_dates) 
df = df.reset_index().drop('level_2', axis=1) 

注意,這種方法假定您的日期已經排序。如果沒有,您需要首先在您的DataFrame上使用sort_values。如果您有嵌套的日期跨度,此方法可能不起作用。

輸出結果:

FruitID FruitType StartDate  EndDate 
0  1  Apple 2015-01-01 2018-01-01 
1  2  Orange 2015-01-01 2016-01-01 
2  2  Orange 2016-05-31 2018-01-01 
3  3  Banana 2015-01-01 2017-01-01 
4  3 Blueberry 2017-01-01 2018-01-01 
5  4  Kiwi 2016-09-15 2017-01-01 
6  4  Mango 2015-01-01 2016-01-01 
7  4  Mango 2017-01-01 2018-01-01 
+0

這種方法似乎是解決問題的最簡潔的方法。非常感謝你! – Netbrian

0

這裏是我想出了...

df = pd.melt(data, id_vars=['FruitID', 'FruitType'], var_name='WhichDate', value_name='Date') 
df['Date'] = pd.to_datetime(df['Date']) 
df = df.sort_values(['FruitType', 'Date']).drop_duplicates(['FruitType', 'Date']) 
df = df.assign(Counter = np.nan) 
StartDf = df[df['WhichDate']=='StartDate'] 
StartDf = StartDf.assign(Counter=np.arange(len(StartDf))) 
df[df['WhichDate']=='StartDate'] = StartDf 
df.fillna(method='ffill', inplace=True) 
s = df.groupby(['Counter', 'FruitID', 'FruitType']).agg({'Date': [min, max]}).rename(columns={'min': 'NewStartDate', 'max': 'NewEndDate'}) 
s.columns = s.columns.droplevel() 
s = s.reset_index() 
del s['Counter'] 
s = s.sort_values(['FruitID', 'FruitType']).reset_index(drop=True) 

,輸出...

FruitID FruitType NewStartDate NewEndDate 
0  1  Apple 2015-01-01 2018-01-01 
1  2  Orange 2015-01-01 2016-01-01 
2  2  Orange 2016-05-31 2018-01-01 
3  3  Banana 2015-01-01 2017-01-01 
4  3 Blueberry 2017-01-01 2018-01-01 
5  4  Kiwi 2016-09-15 2017-01-01 
6  4  Mango 2015-01-01 2016-01-01 
7  4  Mango 2017-01-01 2018-01-01 

說明

首先,我重新創建你的數據幀。

data = pd.DataFrame({'FruitID' : [1,1,1,2,2,2,3,3,3,4,4,4], 
        'FruitType': ['Apple', 'Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Banana', 'Banana', 
            'Blueberry', 'Mango', 'Kiwi', 
            'Mango'], 
      'StartDate': ['2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', '2016-05-31', 
          '2017-01-01', '2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', 
          '2016-09-15', '2017-01-01'], 
      'EndDate' : ['2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01', 
         '2018-01-01', '2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01', 
         '2018-01-01']}) 

接下來,我用熊貓melt函數將數據重塑到長格式。

df = pd.melt(data, id_vars=['FruitID', 'FruitType'], var_name='WhichDate', value_name='Date') 

然後,我按日期排序爲每個果型和重複的日期

df['Date'] = pd.to_datetime(df['Date']) 
df = df.sort_values(['FruitType', 'Date']).drop_duplicates(['FruitType', 'Date']) 

創建用來標記與起始日期每行一個輔助列中刪除任何行。在做groupby之前,我們需要這樣做。然後使用fillna來幫助分組。

df = df.assign(Counter = np.nan) 
StartDf = df[df['WhichDate']=='StartDate'] 
StartDf = StartDf.assign(Counter=np.arange(len(StartDf))) 
df[df['WhichDate']=='StartDate'] = StartDf 
df.fillna(method='ffill', inplace=True) 

最後,我們使用groupbyagg獲得每個分區的minmax日期。

s = df.groupby(['Counter', 'FruitID', 'FruitType']).agg({'Date': [min, max]}).rename(columns={'min': 'NewStartDate', 'max': 'NewEndDate'}) 
s.columns = s.columns.droplevel() 
s = s.reset_index() 
del s['Counter'] 
s = s.sort_values(['FruitID', 'FruitType']).reset_index(drop=True) 
+0

這似乎非常接近,但輸出結果似乎比我習慣的結構不同。當我運行s = s.sort_values(['FruitID','FruitType']) print(s.info()))時,NewStartDate和NewEndDate字段似乎在不同的級別上(我不太熟悉)作爲ID? – Netbrian

+0

請參閱我的代碼的更新。我添加了一條從多級索引索引中刪除'Date'的行 –