заполнение недостающих дат, а также сегментов одновременно

#python #pandas #jupyter-notebook

#python #pandas #jupyter-записная книжка

Вопрос:

У меня есть фрейм данных, подобный

 Start_MONTH  Bucket Count Complete Partial

 10/01/2015      0       57     91      0.66

 11/01/2015      0       678    8       0.99

 02/01/2016      0        68    12       0.12

 10/01/2015      1       78     79      0.22

 11/01/2015      1       99     56     0.67

 1/01/2016       1       789    67     0.78

 10/01/2015      3       678    178    0.780

11/01/2015       3       2880   578     0.678
  

В принципе, мне нужно заполнить каждый start_month (отсутствует 12/01/2015, 01/01/2016, …), и каждое ведро, подобное 2, отсутствует, а остальные столбцы (count, complete, partial) будут равны нулю для отсутствующего ведра и start_month.Я думал, что использование relativedelta (месяцы = 1) поможет, но не уверен, как это использовать.

 pandas as pd
data =  [['10/01/2015',0 ,57 ,91,0.66],
 ['11/01/2015',0, 678, 8,0.99],
  ['02/01/2016',0,68,12,0.12],
  ['10/01/2015' ,1, 78,79,0.22],
  ['11/01/2015' ,1 ,99,56, 0.67],
  ['1/01/2016', 1 ,789,67,0.78],
  ['10/01/2015', 3,678, 178, 0.780],
  ['11/01/2015' ,3, 2880,578,0.678]]
df = pd.DataFrame(data, columns = ['Start_Month', 'Bucket', 'Count', 
'Complete','Partial']) 
  

в принципе, я хочу, чтобы и Start_month, и группа bucket повторялись как группа с другими значениями 0, т. Е. с 10/01/2015 по 2/1/2016 (отсутствует 12/01/2015, 01/01/2016), все месяцы должны быть там, и все сегменты из 0-3 (отсутствует 2) должны быть там
введите описание изображения здесь

Я попробовал это, это частично делает то, что я ищу

 df['Start_Month'] = pd.to_datetime(df['Start_Month'])
s = df.groupby(['Bucket',pd.Grouper(key='Start_Month', freq='MS')])['Count','Complete','Partial'].sum()
df1 = (s.reset_index(level=0)
    .groupby('Bucket')['Count','Complete','Partial']
    .apply(lambda x: x.asfreq('MS'))
    .reset_index())
  

Он добавляет несколько недостающих месяцев, но не повторяется для каждого сегмента и не добавляет целые числа между сегментами

Ответ №1:

Вот франкенхак решения, пока кто-нибудь не опубликует, как это действительно должно быть сделано:

Запуск df

 data =  [
    ['10/01/2015',0 ,57 ,91,0.66],
    ['11/01/2015',0, 678, 8,0.99],
    ['02/01/2016',0,68,12,0.12],
    ['10/01/2015' ,1, 78,79,0.22],
    ['11/01/2015' ,1 ,99,56, 0.67],
    ['1/01/2016', 1 ,789,67,0.78],
    ['10/01/2015', 3,678, 178, 0.780],
    ['11/01/2015' ,3, 2880,578,0.678]
]
df = pd.DataFrame(data, columns=['Start_Month', 'Bucket', 'Count', 'Complete','Partial'])#.set_index('Start_Month')
df['Start_Month'] = pd.to_datetime(df['Start_Month'])

    Start_Month Bucket  Count   Complete    Partial
0   2015-10-01     0      57        91       0.660
1   2015-11-01     0      678        8       0.990
2   2016-02-01     0      68        12       0.120
3   2015-10-01     1      78        79       0.220
4   2015-11-01     1      99        56       0.670
5   2016-01-01     1      789       67       0.780
6   2015-10-01     3      678      178       0.780
7   2015-11-01     3      2880     578       0.678
  

Создайте отдельный df с полными датами для for bucket 0

 df0 = pd.DataFrame([pd.date_range('2015-10-01', '2016-02-01', freq='MS'), 
                    [0]*5]).T.rename(columns={0: 'Start_Month', 1:'Bucket'})

    Start_Month Bucket
0   2015-10-01  0
1   2015-11-01  0
2   2015-12-01  0
3   2016-01-01  0
4   2016-02-01  0
  

Отфильтруйте исходный df по соответствующей дате и сегменту и объедините результаты с df0

 df_filt = df[(df['Start_Month'].isin(df0['Start_Month'])) amp; (df['Bucket'] == 0)]
df0 = pd.merge(df0, df_filt, left_on='Start_Month', right_on='Start_Month', how='outer')
df0 = df0.drop('Bucket_y', axis=1).rename(columns={'Bucket_x': 'Bucket'})

    Start_Month Bucket  Count   Complete Partial
0   2015-10-01  0        57.0   91.0     0.66
1   2015-11-01  0        678.0  8.0      0.99
2   2015-12-01  0        NaN    NaN      NaN
3   2016-01-01  0        NaN    NaN      NaN
4   2016-02-01  0        68.0   12.0     0.12
  

Повторите процесс для сегментов 1, 2 и 3, создавая df1, df2, df3.

(не отображается из-за повторения… и, конечно, вы могли бы сделать это в цикле). Затем объедините все 4 df вместе и заполните na нулями.

 # Concat
df_final = pd.concat([df0, df1, df2, df3], axis=0).fillna(0)

    Start_Month Bucket       Count   Complete   Partial
0   2015-10-01       0       57.0        91.0   0.660
1   2015-11-01       0       678.0       8.0    0.990
2   2015-12-01       0       0.0         0.0    0.000
3   2016-01-01       0       0.0         0.0    0.000
4   2016-02-01       0       68.0        12.0   0.120
0   2015-10-01       1       78.0        79.0   0.220
1   2015-11-01       1       99.0        56.0   0.670
2   2015-12-01       1       0.0         0.0    0.000
3   2016-01-01       1       789.0       67.0   0.780
4   2016-02-01       1       0.0         0.0    0.000
0   2015-10-01       2       0.0         0.0    0.000
1   2015-11-01       2       0.0         0.0    0.000
2   2015-12-01       2       0.0         0.0    0.000
3   2016-01-01       2       0.0         0.0    0.000
4   2016-02-01       2       0.0         0.0    0.000
0   2015-10-01       3       678.0       178.0  0.780
1   2015-11-01       3       2880.0      578.0  0.678
2   2015-12-01       3       0.0         0.0    0.000
3   2016-01-01       3       0.0         0.0    0.000
4   2016-02-01       3       0.0         0.0    0.000
  

ОБНОВЛЕНИЕ: показ полностью зацикленного кода и ответ на ваш вопрос в комментарии.

 def get_separate_df(df, bucket_num):
    df_bucket = pd.DataFrame([pd.date_range('2015-10-01', '2016-02-01', freq='MS'), 
                        [bucket_num]*5]).T.rename(columns={0: 'Start_Month', 1:'Bucket'})
    df_filt = df[(df['Start_Month'].isin(df_bucket['Start_Month'])) amp; 
                        (df['Bucket'] == bucket_num)]
    df_bucket = pd.merge(df_bucket, df_filt, left_on='Start_Month', right_on='Start_Month', how='outer')
    df_bucket = df_bucket.drop('Bucket_y', axis=1).rename(columns={'Bucket_x': 'Bucket'})

    return df_bucket

dfs = [get_separate_df(df, i) for i in range(4)] 

# Concat
df_final = pd.concat(dfs, axis=0).fillna(0)
  

Что касается вашего вопроса в комментариях, вы можете получить пустой фрейм данных с повторяющейся последовательностью дат и сегментов, подобных этому:

 bucket_list = [ele for ele in [0,1,2,3] for i in range(5)]
dates = list(pd.date_range('2015-10-01', '2016-02-01', freq='MS'))*4
df = pd.DataFrame(data=[dates, bucket_list]).T.rename(columns={0:'Start_Month', 1:'Bucket'})

Output:
    Start_Month Bucket
0   2015-10-01  0
1   2015-11-01  0
2   2015-12-01  0
3   2016-01-01  0
4   2016-02-01  0
5   2015-10-01  1
6   2015-11-01  1
7   2015-12-01  1
8   2016-01-01  1
9   2016-02-01  1
10  2015-10-01  2
11  2015-11-01  2
12  2015-12-01  2
13  2016-01-01  2
14  2016-02-01  2
15  2015-10-01  3
16  2015-11-01  3
17  2015-12-01  3
18  2016-01-01  3
19  2016-02-01  3
  

Комментарии:

1. Возможно ли создать фиктивную таблицу с датами и сегментами вместе из диапазона значений от первого до последнего? Я думаю, что это упростит и автоматизирует

Ответ №2:

Написал похожий, но просто немного обобщил

  import pandas as pd
 import numpy as np

 # converting date string to date
 df['Start_Month'] = pd.to_datetime(df['Start_Month'])

 # finding the the date range and increasin by 1 month start
 rng = pd.date_range(df['Start_Month'].min(),df['Start_Month'].max(), freq='MS')

 # creating date dataframe
 df1 = pd.DataFrame({ 'Start_Month': rng})

 # Converting bucket field to integer
 df['Bucket'] = df['Bucket'].astype(int)

 # finding the bucket values max and min
Bucket=np.arange(df['Bucket'].min(),df['Bucket'].max() 1,1)

 # Repeating the date range for every bucket
df1=pd.concat([df1]*len(Bucket))

 # repeating bucket values to each date
df1['Bucket']=np.repeat(Bucket, len(rng))

# merging to the previous dataframe and filling it with 0
merged_left = pd.merge(left=df1, right=df, how='left', on=['Start_Month','Bucket']).fillna(0)