Панды Python агрегируют прошлые строки, когда выполняется условие для временных рядов

#python #pandas

Вопрос:

У меня проблема с временными рядами, и я хочу объединить некоторые данные на основе значений, которые отображаются в определенном столбце. для иллюстрации рассмотрим следующую таблицу

Дата кола Кольб Кольк
2019-01-01 1 -10 Нулевой
2019-01-02 2 -5 Нулевой
2019-01-03 3 0 101
2019-01-04 4 5 101
2019-01-05 5 10 101
2019-01-06 6 15 Нулевой
2019-01-07 7 20 101

Я хочу выполнить следующее:

  1. в тот момент, когда значение ColC не равно null, агрегируйте значения до этой строки и получите дельту для столбца даты
  2. если элемент X в ColC не равен нулю, но элемент (X-1) также не равен нулю, просто проигнорируйте строку X.

Для предыдущей таблицы результатом будет

agg(ColC) авг(колА) avg(ColB) дельта(Дата) [в днях]
101 2 -5 2
101 6.5 17.5 1

До сих пор я не мог найти никакого способа добиться этого

Ответ №1:

Попробуйте с groupby :

 #convert Date column to datetime if needed
df["Date"] = pd.to_datetime(df["Date"])

#keep only rows where there aren't consecutive non-null values
df2 = df[~(df["colC"].notnull()amp;df["colC"].shift().notnull())]

#groupby consecutive null values and aggregate
output = df2.groupby(df2["colC"].notnull().shift().cumsum().fillna(0)) 
            .agg({"colA": "mean", 
                  "colB": "mean", 
                  "colC": "first", 
                  "Date": lambda x: (x.max()-x.min()).days}) 
            .rename_axis(None) 
            .rename(columns={"Date": "Delta"})

>>> output
     colA  colB   colC  Delta
0.0   2.0  -5.0  101.0      2
1.0   6.5  17.5  101.0      1
 

Ответ №2:

Вы можете установить группы colC с cumsum() , а затем сгруппировать по группам по .groupby() следующим образом:

(При условии, что нулевые значения равны NaN или отсутствуют):

 # Convert date to datetime format
df['Date'] = pd.to_datetime(df['Date'])

# disregard rows if the element X of colC is not null but the element (X-1) is also not null
df2 = df.loc[df['colC'].isna() | df['colC'].shift().isna()]

# set grouping for `colC`
group = (df2['colC'].shift(1).notna() amp; df2['colC'].isna()).cumsum()

df_out = (df2.groupby(group, as_index=False)
             .agg(**{'agg(colC)':('colC', 'last'), 
                     'avg(colA)':('colA', 'mean'),
                     'avg(colB)':('colB', 'mean'), 
                     'delta(Date)':('Date', lambda x: (x.iloc[-1] - x.iloc[0]).days)})
         )
 

Ввод данных:

 data = {'Date': ['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07'],
 'colA': [1, 2, 3, 4, 5, 6, 7],
 'colB': [-10, -5, 0, 5, 10, 15, 20],
 'colC': [np.nan, np.nan, 101.0, 101.0, 101.0, np.nan, 101.0]}
df = pd.DataFrame(data)

         Date  colA  colB   colC
0  2019-01-01     1   -10    NaN
1  2019-01-02     2    -5    NaN
2  2019-01-03     3     0  101.0
3  2019-01-04     4     5  101.0
4  2019-01-05     5    10  101.0
5  2019-01-06     6    15    NaN
6  2019-01-07     7    20  101.0
 

Результат:

 print(df_out)

   agg(colC)  avg(colA)  avg(colB)  delta(Date)
0      101.0        2.0       -5.0            2
1      101.0        6.5       17.5            1
 

Объяснение / Иллюстрация

Давайте добавим больше данных для лучшей иллюстрации:

 data = {'Date': ['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08', '2019-01-09', '2019-01-10', '2019-01-11', '2019-01-12', '2019-01-13', '2019-01-14', '2019-01-15'],
 'colA': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 
 'colB': [-10, -5, 0, 5, 10, 15, 15, 15, 20, 20, 15, 15, 15, 20, 20],
 'colC': [np.nan, np.nan, 101, 101, 101, np.nan, np.nan, 102, 103, 104, np.nan, np.nan, np.nan, 112, 113]}

df = pd.DataFrame(data)


          Date  colA  colB   colC
0   2019-01-01     1   -10    NaN
1   2019-01-02     2    -5    NaN
2   2019-01-03     3     0  101.0
3   2019-01-04     4     5  101.0
4   2019-01-05     5    10  101.0
5   2019-01-06     6    15    NaN
6   2019-01-07     7    15    NaN
7   2019-01-08     8    15  102.0
8   2019-01-09     9    20  103.0
9   2019-01-10    10    20  104.0
10  2019-01-11    11    15    NaN
11  2019-01-12    12    15    NaN
12  2019-01-13    13    15    NaN
13  2019-01-14    14    20  112.0
14  2019-01-15    15    20  113.0
 

После преобразования формата даты мы удаляем ненужные строки, сохраняя только строки, которые либо NaN включены colC , либо даже если текущая строка не NaN включена, но ее предыдущая строка NaN включена colC .

 # Convert date to datetime format
df['Date'] = pd.to_datetime(df['Date'])

df2 = df.loc[df['colC'].isna() | df['colC'].shift().isna()]
 

Результат:

 print(df2)

         Date  colA  colB   colC
0  2019-01-01     1   -10    NaN
1  2019-01-02     2    -5    NaN
2  2019-01-03     3     0  101.0
5  2019-01-06     6    15    NaN
6  2019-01-07     7    15    NaN
7  2019-01-08     8    15  102.0
10 2019-01-11    11    15    NaN
11 2019-01-12    12    15    NaN
12 2019-01-13    13    15    NaN
13 2019-01-14    14    20  112.0
 

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

 df2['Group_indicator'] = df2['colC'].shift(1).notna() amp; df2['colC'].isna()
 

Мы устанавливаем логическую маску так, чтобы для первой записи группы было установлено True значение, а для других записей False (в первой строке есть исключение, но нет проблем, когда мы генерируем серийные номера для групп)

Результат:

          Date  colA  colB   colC  Group_indicator
0  2019-01-01     1   -10    NaN            False
1  2019-01-02     2    -5    NaN            False
2  2019-01-03     3     0  101.0            False
5  2019-01-06     6    15    NaN             True
6  2019-01-07     7    15    NaN            False
7  2019-01-08     8    15  102.0            False
10 2019-01-11    11    15    NaN             True
11 2019-01-12    12    15    NaN            False
12 2019-01-13    13    15    NaN            False
13 2019-01-14    14    20  112.0            False
 

Затем мы генерируем номера групп:

 df2['Group'] = df2['Group_indicator'].cumsum()
 

Результат:

          Date  colA  colB   colC  Group_indicator  Group
0  2019-01-01     1   -10    NaN            False      0
1  2019-01-02     2    -5    NaN            False      0
2  2019-01-03     3     0  101.0            False      0
5  2019-01-06     6    15    NaN             True      1
6  2019-01-07     7    15    NaN            False      1
7  2019-01-08     8    15  102.0            False      1
10 2019-01-11    11    15    NaN             True      2
11 2019-01-12    12    15    NaN            False      2
12 2019-01-13    13    15    NaN            False      2
13 2019-01-14    14    20  112.0            False      2
 

Вы можете видеть, что серийные номера групп генерируются для каждой группы с одинаковыми номерами групп для всех записей одной и той же группы.

После этого мы объединяемся в группу с этим номером группы и объединяем строки с кодом:

 df_out = (df2.groupby('Group', as_index=False)
             .agg(**{'agg(colC)':('colC', 'last'), 
                     'avg(colA)':('colA', 'mean'),
                     'avg(colB)':('colB', 'mean'), 
                     'delta(Date)':('Date', lambda x: (x.iloc[-1] - x.iloc[0]).days)})
         )
 

Здесь, поскольку наша заинтересованная запись colC является последней записью в группе, мы объединяемся с 'last' on colC .

Date Чтобы получить агрегацию delta(Date) , мы берем последнюю/первую запись Date с группой по x.iloc[-1] / x.iloc[0] (т. Е. последнюю и первую записи серии Pandas для Date внутри группы) и вычисляем их разницу путем вычитания. Поскольку это временной ряд, последние/первые записи должны соответствовать записям max/min Date внутри группы.

Следовательно, результат:

    Group  agg(colC)  avg(colA)  avg(colB)  delta(Date)
0      0      101.0        2.0      -5.00            2
1      1      102.0        7.0      15.00            2
2      2      112.0       12.5      16.25            3
 

Здесь результат содержит столбец Group , в то время как наша исходная версия кода без него. Это связано с тем, что мы определили отдельную серию Панд group для группировки, а не определили вспомогательный столбец Group на этом рисунке.