Регистрация и прямая и/или обратная заливка на основе пользовательских критериев

#python-3.x #pandas #algorithm #dataframe #datetime

Вопрос:

Все date (ы) в df присутствуют в ref_date ref_df , а не наоборот. Соответствующий каждому date входу df , мне нужно получить ref_date из ref_df , основываясь на следующей логике:

  1. Если a date повторяется более одного раза и отсутствуют предыдущие или следующие ref_date (ы), то из ребер повторения выделите ближайший отсутствующий date предыдущий или следующий ref_date (ы).
  2. Если a date повторяется более одного раза, но отсутствует предыдущее/следующее ref_date , то это то ref_date же самое, что date .
  3. Могут отсутствовать ref_date (и), не включенные в df . Это происходит, когда date (ы) не повторяются вокруг данных ref_date (ов) для заполнения.

Пример:

 >>> import pandas as pd
>>> from datetime import datetime as dt
>>> df = pd.DataFrame({'date':[dt(2020,1,20), dt(2020,1,20), dt(2020,1,20), dt(2020,2,25), dt(2020,3,18), dt(2020,3,18), dt(2020,4,9), dt(2020,4,12), dt(2020,4,12), dt(2020,4,12), dt(2020,4,12), dt(2020,4,12), dt(2020,5,28), dt(2020,6,1), dt(2020,6,1), dt(2020,6,1), dt(2020,6,28), dt(2020,6,28)], 'qty':range(18)})
>>> ref_df = pd.DataFrame({'ref_date':[dt(2019,12,8), dt(2020,1,20), dt(2020,2,25), dt(2020,3,18), dt(2020,4,9), dt(2020,4,10), dt(2020,4,12), dt(2020,4,13), dt(2020,4,14), dt(2020,5,28), dt(2020,5,29), dt(2020,5,30), dt(2020,6,1), dt(2020,6,2), dt(2020,6,3), dt(2020,6,28), dt(2020,6,29), dt(2020,7,7)]})
>>> df
         date  qty
0  2020-01-20    0
1  2020-01-20    1
2  2020-01-20    2
3  2020-02-25    3
4  2020-03-18    4
5  2020-03-18    5
6  2020-04-09    6
7  2020-04-12    7
8  2020-04-12    8
9  2020-04-12    9
10 2020-04-12   10
11 2020-04-12   11
12 2020-05-28   12
13 2020-06-01   13
14 2020-06-01   14
15 2020-06-01   15
16 2020-06-28   16
17 2020-06-28   17
>>> ref_df
     ref_date
0  2019-12-08
1  2020-01-20
2  2020-02-25
3  2020-03-18
4  2020-04-09
5  2020-04-10
6  2020-04-12
7  2020-04-13
8  2020-04-14
9  2020-05-28
10 2020-05-29
11 2020-05-30
12 2020-06-01
13 2020-06-02
14 2020-06-03
15 2020-06-28
16 2020-06-29
17 2020-07-07
 

Ожидаемый вывод:

 >>> df
         date  qty    ref_date
0  2020-01-20    0  2019-12-08
1  2020-01-20    1  2020-01-20  # Note: repeated as no gap
2  2020-01-20    2  2020-01-20
3  2020-02-25    3  2020-02-25
4  2020-03-18    4  2020-03-18
5  2020-03-18    5  2020-03-18  # Note: repeated as no gap
6  2020-04-09    6  2020-04-09
7  2020-04-12    7  2020-04-10  # Note: Filling from the edges
8  2020-04-12    8  2020-04-12
9  2020-04-12    9  2020-04-12  # Note: repeated as not enough gap
10 2020-04-12   10  2020-04-13
11 2020-04-12   11  2020-04-14
12 2020-05-28   12  2020-05-28
13 2020-06-01   13  2020-05-30  # Filling nearest previous
14 2020-06-01   14  2020-06-01  # First filling previous
15 2020-06-01   15  2020-06-02  # Filling nearest next
16 2020-06-28   16  2020-06-28  
17 2020-06-28   17  2020-06-29
 

Ответ №1:

Я могу получить ответ, но это не выглядит как самый эффективный способ сделать это. Может ли кто-нибудь предложить оптимальный способ сделать это:

 ref_df['date'] = ref_df['ref_date']
df = pd.merge_asof(df, ref_df, on='date', direction='nearest')
df = df.rename(columns={'ref_date':'nearest_ref_date'})
nrd_cnt = df.groupby('nearest_ref_date')['date'].count().reset_index().rename(columns={'date':'nrd_count'})
nrd_cnt['lc'] = nrd_cnt['nearest_ref_date'].shift(1)
nrd_cnt['uc'] = nrd_cnt['nearest_ref_date'].shift(-1)
df = df.merge(nrd_cnt, how='left', on='nearest_ref_date')
# TODO: Review it. Looping it to finite number 100 to avoid infite loop (in case of edge cases)
for _ in range(100):
    df2 = df.copy()
    df2['days'] = np.abs((df2['nearest_ref_date'] - df2['date']).dt.days)
    df2['repeat_rank'] = df2.groupby('nearest_ref_date')['days'].rank(method='first')
    reduced_ref_df = ref_df[~ref_df['ref_date'].isin(df2['nearest_ref_date'].unique())]
    df2 = pd.merge_asof(df2, reduced_ref_df, on='date', direction='nearest')
    df2 = df2.rename(columns={'ref_date':'new_nrd'})
    df2.loc[(df2['new_nrd']<=df2['lc']) | (df2['new_nrd']>=df2['uc']), 'new_nrd'] = pd.to_datetime(np.nan)
    df2.loc[(~pd.isna(df2['new_nrd'])) amp; (df2['repeat_rank'] > 1), 'nearest_ref_date'] = df2['new_nrd']
    df2 = df2[['date', 'qty', 'nearest_ref_date', 'lc', 'uc']]
    if df.equals(df2):
        break
    df = df2
df = df[['date', 'qty', 'nearest_ref_date']]
df.loc[:, 'repeat_rank'] = df.groupby('nearest_ref_date')['nearest_ref_date'].rank(method='first')
df = pd.merge_asof(df, ref_df, on='date', direction='nearest')
# Repeated nearest_ref_date set to nearest ref_date
df.loc[df['repeat_rank'] > 1, 'nearest_ref_date'] = df['ref_date']
# Sorting nearest_ref_date within the ref_date group (without changing order of rest of cols).
df.loc[:, 'nearest_ref_date'] = df[['ref_date', 'nearest_ref_date']].sort_values(['ref_date', 'nearest_ref_date']).reset_index().drop('index',axis=1)['nearest_ref_date']
df = df[['date', 'qty', 'nearest_ref_date']]
df
         date  qty    ref_date
0  2020-01-20    0  2019-12-08
1  2020-01-20    1  2020-01-20
2  2020-01-20    2  2020-01-20
3  2020-02-25    3  2020-02-25
4  2020-03-18    4  2020-03-18
5  2020-03-18    5  2020-03-18
6  2020-04-09    6  2020-04-09
7  2020-04-12    7  2020-04-10
8  2020-04-12    8  2020-04-12
9  2020-04-12    9  2020-04-12
10 2020-04-12   10  2020-04-13
11 2020-04-12   11  2020-04-14
12 2020-05-28   12  2020-05-28
13 2020-06-01   13  2020-05-30
14 2020-06-01   14  2020-06-01
15 2020-06-01   15  2020-06-02
16 2020-06-28   16  2020-06-28  
17 2020-06-28   17  2020-06-29