Процентное изменение панд с использованием группы по

#python #pandas #dataframe

Вопрос:

Предположим, у меня есть следующий фрейм данных:

 df = pd.DataFrame({'city': ['a', 'a', 'a', 'b', 'b', 'c', 'd', 'd', 'd'], 
                   'year': [2013, 2014, 2016, 2015, 2016, 2013, 2016, 2017, 2018],
                  'value': [10, 12, 16, 20, 21, 11, 15, 13, 16]})
 

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

 city  year  value
   a  2013    NaN
   a  2014   0.20
   a  2016    NaN
   b  2015    NaN
   b  2016   0.05
   c  2013    NaN
   d  2016    NaN
   d  2017  -0.14
   d  2018   0.23
 

Я попытался использовать группу в городе, а затем использовать apply, но это не сработало:

 df.groupby('city').apply(lambda x: x.sort_values('year')['value'].pct_change()).reset_index()
 

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

ПРАВКА: Я не очень озабочен эффективностью, поэтому любое решение, которое решает проблему, действительно для меня.

Ответ №1:

Давайте попробуем лениво groupby() , использовать pct_change для изменений и diff для обнаружения скачка года:

 groups = df.sort_values('year').groupby(['city'])

df['pct_chg'] = (groups['value'].pct_change()
                    .where(groups['year'].diff()==1)
                )
 

Выход:

   city  year  value   pct_chg
0    a  2013     10       NaN
1    a  2014     12  0.200000
2    a  2016     16       NaN
3    b  2015     20       NaN
4    b  2016     21  0.050000
5    c  2013     11       NaN
6    d  2016     15       NaN
7    d  2017     13 -0.133333
8    d  2018     16  0.230769
 

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

1. Боже, как я люблю pandas .

Ответ №2:

Хотя ответ @Quang написан гораздо более элегантно и лаконично, я просто добавляю другой подход, использующий индексацию.

 sorted_df = df.sort_values(by=['city', 'year'])
sorted_df.loc[((sorted_df.year.diff() == 1) amp; 
              (sorted_df.city == sorted_df.city.shift(1))), 'pct_chg'] = sorted_df.value.pct_change()
 

мой подход быстрее, как вы можете видеть ниже, выполняется на вашем df, но синтаксис не такой красивый.

 %timeit #mine
1.44 ms ± 2.91 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit #@Quang's
2.23 ms ± 40.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)