Фрейм данных Pandas: заполнение интерполированных значений даты для каждой категории

#python #pandas #dataframe #missing-data #linear-interpolation

Вопрос:

Я работаю в Python 3 с фреймом данных Pandas. В нем есть столбцы для категории, даты и значения. Для каждой категории я хочу добавить строки с недостающими днями, чтобы значение было линейно интерполировано.

Чтобы создать минимальный пример, я использую следующий код

 df = pd.DataFrame({
'cat':['A', 'A', 'A', 'A', 'B', 'B', 'B'],
'date': ['2021-1-1', '2021-1-4', '2021-1-5', '2021-1-7', '2021-11-1', '2021-11-2', '2021-11-5'],
'value': [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 9.0]})

df['cat'] = df['cat'].astype('category')
df['date'] = df['date'].astype('datetime64')
 

Что дает следующий фрейм данных

 cat date          value
A   2021-01-01    1.0
A   2021-01-04    2.0
A   2021-01-05    3.0
A   2021-01-07    4.0
B   2021-11-01    5.0
B   2021-11-02    6.0
B   2021-11-05    9.0
 

Я бы хотел, чтобы результат был похож на этот пример, где я использовал ‘<‘ для обозначения вновь вставленных строк

 cat date          value
A   2021-01-01    1.0
A   2021-01-02    1.333  <
A   2021-01-03    1.667  <
A   2021-01-04    2.0
A   2021-01-05    3.0
A   2021-01-06    3.5    <
A   2021-01-07    4.0
B   2021-11-01    5.0
B   2021-11-02    6.0
B   2021-11-03    7.0    <
B   2021-11-04    8.0    <
B   2021-11-05    9.0
 

В реальной проблеме мне не нужны выходные дни (суббота и воскресенье), но я изложил проблему, как указано выше, чтобы предотвратить добавление дополнительных слоев (я могу легко отфильтровать выходные дни позже, если это необходимо). Однако, если не включать их в первую очередь, это может привести к более эффективному коду, поэтому я подумал, что также упомяну об этой проблеме. Спасибо за любую помощь!

Ответ №1:

Используйте DataFrame.groupby с повторной выборкой или Series.asfreq для пропущенных значений для дней, а затем интерполируйте по группам в лямбда-функции:

 df = (df.set_index('date')
        .groupby('cat')['value']
        .apply(lambda x: x.asfreq('d').interpolate())
        .reset_index())
print (df)
   cat       date     value
0    A 2021-01-01  1.000000
1    A 2021-01-02  1.333333
2    A 2021-01-03  1.666667
3    A 2021-01-04  2.000000
4    A 2021-01-05  3.000000
5    A 2021-01-06  3.500000
6    A 2021-01-07  4.000000
7    B 2021-11-01  5.000000
8    B 2021-11-02  6.000000
9    B 2021-11-03  7.000000
10   B 2021-11-04  8.000000
11   B 2021-11-05  9.000000
 

 df = (df.set_index('date')
        .groupby('cat')['value']
        .apply(lambda x: x.resample('d').first().interpolate())
        .reset_index())
print (df)
   cat       date     value
0    A 2021-01-01  1.000000
1    A 2021-01-02  1.333333
2    A 2021-01-03  1.666667
3    A 2021-01-04  2.000000
4    A 2021-01-05  3.000000
5    A 2021-01-06  3.500000
6    A 2021-01-07  4.000000
7    B 2021-11-01  5.000000
8    B 2021-11-02  6.000000
9    B 2021-11-03  7.000000
10   B 2021-11-04  8.000000
11   B 2021-11-05  9.000000
 

Или:

 f = lambda x: x.interpolate()
s = df.set_index('date').groupby('cat')['value'].resample('d').first().groupby(level=0).apply(f)
print (s)
cat  date      
A    2021-01-01    1.000000
     2021-01-02    1.333333
     2021-01-03    1.666667
     2021-01-04    2.000000
     2021-01-05    3.000000
     2021-01-06    3.500000
     2021-01-07    4.000000
B    2021-11-01    5.000000
     2021-11-02    6.000000
     2021-11-03    7.000000
     2021-11-04    8.000000
     2021-11-05    9.000000
Name: value, dtype: float64
 

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

1. Это сработало отлично. Также помогло мне узнать о том, как думать о проблемах такого типа. Спасибо!

Ответ №2:

Вы могли бы использовать вспомогательную функцию:

 def interpolate(d, on='date', vals=['value']):
    return (d.set_index(on).reindex(pd.date_range(d[on].min(), d[on].max()))
             [vals].interpolate()
             .rename_axis(on)
           ) 

df.groupby('cat').apply(interpolate).reset_index()
 

вывод:

    cat       date     value
0    A 2021-01-01  1.000000
1    A 2021-01-02  1.333333
2    A 2021-01-03  1.666667
3    A 2021-01-04  2.000000
4    A 2021-01-05  3.000000
5    A 2021-01-06  3.500000
6    A 2021-01-07  4.000000
7    B 2021-11-01  5.000000
8    B 2021-11-02  6.000000
9    B 2021-11-03  7.000000
10   B 2021-11-04  8.000000
11   B 2021-11-05  9.000000
 

Ответ №3:

Опция представляет собой комбинацию интерполяции с полным:

 # pip install git https://github.com/pyjanitor-devs/pyjanitor.git
import pandas as pd
import janitor

dates = dict(date = lambda df: pd.date_range(df.min(), df.max(), freq='1D'))

(df.complete(dates, by='cat', sort = True)
   .assign(value = lambda df: df.value.interpolate())
)
   cat       date     value
0    A 2021-01-01  1.000000
1    A 2021-01-02  1.333333
2    A 2021-01-03  1.666667
3    A 2021-01-04  2.000000
4    A 2021-01-05  3.000000
5    A 2021-01-06  3.500000
6    A 2021-01-07  4.000000
7    B 2021-11-01  5.000000
8    B 2021-11-02  6.000000
9    B 2021-11-03  7.000000
10   B 2021-11-04  8.000000
11   B 2021-11-05  9.000000
 

complete предоставляет недостающие значения, после чего мы затем выполняем интерполяцию по linear методу.

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

1. : D Я действительно подумал: » Теперь не хватает только решения pyjanitor, где sammywemmy? «. Ну, вот и все 😉