Индекс даты Pandas последнего ненулевого значения при группировании даты

#python #pandas #date #group-by #rolling-computation

#python #pandas #Дата #группировка по #свертка-вычисление

Вопрос:

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

Вот воспроизводимый пример:

 import pandas as pd
from datetime import datetime as dt
import numpy as np

df = pd.DataFrame({})

df["date"] = [dt(2020, 10, i 1) for i in range(10)]
df["group"] = ["a" if int(i/3) == (i/3) else "b" for i in range(10)]
df["value"] = [i if int(i/2) == (i/2) else np.nan for i in range(10)]
 

фрейм данных

         date group  value
0 2020-10-01     a    0.0
1 2020-10-02     b    NaN
2 2020-10-03     b    2.0
3 2020-10-04     a    NaN
4 2020-10-05     b    4.0
5 2020-10-06     b    NaN
6 2020-10-07     a    6.0
7 2020-10-08     b    NaN
8 2020-10-09     b    8.0
9 2020-10-10     a    NaN
 

Целевой результат:

         date group  value  output
0 2020-10-01     a    0.0  2020-10-01
1 2020-10-02     b    NaN  NaT
2 2020-10-03     b    2.0  2020-10-03
3 2020-10-04     a    NaN  2020-10-01
4 2020-10-05     b    4.0  2020-10-05
5 2020-10-06     b    NaN  2020-10-05
6 2020-10-07     a    6.0  2020-10-07
7 2020-10-08     b    NaN  2020-10-05
8 2020-10-09     b    8.0  2020-10-09
9 2020-10-10     a    NaN  2020-10-07
 

Моя попытка:

 df = df.set_index("date").sort_index(ascending = True)

def latest_non_null_value_index(x):
        y = x[np.isnan(x) == False]
        print(y.index)
        if len(y) > 0:
            return y.index[-1]
        else:
            return np.nan

latest_index = df
        .groupby(["group"])
        .rolling("35D")
        ["value"]
        .apply(lambda x: latest_non_null_value_index(x).timestamp())
        .reset_index()
  
def to_datetime_from_timestamp(x):
  if pd.isnull(x) == False:
      return dt.fromtimestamp(x)
  else:
      return pd.NaT
           
latest_index["value"] = latest_index["value"]
    .apply(to_datetime_from_timestamp)
 

Что я получаю:

   group       date               value
0     a 2020-10-01 2020-10-01 02:00:00
1     a 2020-10-04 2020-10-01 02:00:00
2     a 2020-10-07 2020-10-03 02:00:00
3     a 2020-10-10 2020-10-03 02:00:00
4     b 2020-10-02                 NaT
5     b 2020-10-03 2020-10-06 02:00:00
6     b 2020-10-05 2020-10-07 02:00:00
7     b 2020-10-06 2020-10-07 02:00:00
8     b 2020-10-08 2020-10-07 02:00:00
9     b 2020-10-09 2020-10-10 02:00:00
 

Есть идеи, что я здесь пропустил?

РЕДАКТИРОВАТЬ: также кажется, что у меня нет этой проблемы при получении последнего значения… Это действительно связано с индексом.

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

Ответ №1:

Вы могли бы использовать pd.fillna «ffill» для пересылки заполнения недостающих значений

 import pandas as pd
from datetime import datetime as dt
import numpy as np

df = pd.DataFrame({})

df["date"] = [dt(2020, 10, i 1) for i in range(10)]
df["group"] = ["a" if int(i/3) == (i/3) else "b" for i in range(10)]
df["value"] = [i if int(i/2) == (i/2) else np.nan for i in range(10)]

df = df.sort_values("date")  # Just make sure that row are properly ordered

date = df["date"].copy()
date[df.value.isna()] = pd.NaT
latest_index = date.groupby(df.group).fillna(method="ffill")
 

Это не учитывает ваши временные рамки, но вы можете удалить значения, которые находятся за пределами временного окна, например:

 latest_index[(df.date - latest_index).dt.days > 35] = pd.NaT
 

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

 df = df.set_index("date", drop=False)
df = df.sort_index()

date = pd.to_numeric(df["date"].copy())  # it wasn't letting me aggregate dates so we have to convert to float then back to dates
date[df.value.isna()] = None
latest_index = date.groupby(df.group).rolling("35D").max()
latest_index = pd.to_datetime(latest_index)
 

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

1. Это будет работать с бесконечным временным окном, но не с переходящим окном. Позвольте мне попытаться привести лучший пример для иллюстрации. Все еще очень полезно.

2. Извините, я пропустил переходную часть. Итак, в вашем примере просто кажется, что вы не хотите смотреть более 35 дней назад на день с не пропущенным значением. Это правильно? Если это так, вы можете просто проверить, составляет ли разница между заполненным значением и текущей датой более 35 дней. «latest_index[(df[«date»] — latest_index).dt.days> 35] = pd.NaT»

3. Да, это хорошо работает таким образом 🙂 Мне все еще любопытно узнать, что происходит с этим индексом…

4. По умолчанию pandas перемещает группу по столбцу в индекс. Обычно вы можете просто установить as_index=False , хотите ли вы, чтобы это произошло, но я не думаю, что это сработает, поскольку вы также используете rolling . Самый простой способ исправить это — запустить latest_index.reset_index() .