Группа Pandas по среднему значению только положительных значений

#python #pandas

#python #pandas

Вопрос:

Как получить среднее значение только положительных значений после groupby в pandas?

MWE:

 import numpy as np
import pandas as pd

flights = pd.read_csv('https://github.com/bhishanpdl/Datasets/blob/master/nycflights13.csv?raw=true')
print(flights.shape)
print(flights.iloc[:2,:4])
print()

not_cancelled = flights.dropna(subset=['dep_delay','arr_delay'])

df = (not_cancelled.groupby(['year','month','day'])['arr_delay']
      .mean().reset_index()
     )

df['avg_delay2'] = df[df.arr_delay>0]['arr_delay'].mean()

print(df.head())
  

Это дает все значения avg_delay2 как 16.66.

(336776, 19)
год месяц день dep_time
0 2013 1 1 517.0
1 2013 1 1 533.0

год месяц день arr_delay avg_delay2
0 2013 1 1 12.651023 16.665681
1 2013 1 2 12.692888 16.665681
2 2013 1 3 5.733333 16.665681
3 2013 1 4 -1.932819 16.665681
4 2013 1 5 -1.525802 16.665681

Что НЕВЕРНО.

 # sanity check
a = not_cancelled.query(""" year==2013 amp; month ==1 amp; day ==1 """)['arr_delay']
a = a[a>0]
a.mean() # 32.48156182212581
  

Когда я делаю то же самое в R:

 library(nycflights13)

not_cancelled = flights %>% 
    filter( !is.na(dep_delay), !is.na(arr_delay))

df = not_cancelled  %>%  
    group_by(year,month,day) %>%  
    summarize(
        # average delay
        avg_delay1 = mean(arr_delay),
        # average positive delay
        avg_delay2 = mean(arr_delay[arr_delay>0]))

head(df)
  

Это дает правильный вывод для avg_delay2.

год месяц день avg_delay1 avg_delay2
2013 1 1 12.651023 32.48156
2013 1 2 12.692888 32.02991
2013 1 3 5.733333 27.66087
2013 1 4 -1.932819 28.30976
2013 1 5 -1.525802 22.55882
2013 1 6 4.236429 24.37270

Как это сделать в Pandas?

Ответ №1:

Я бы отфильтровал позитив, прежде чем groupby

 df = (not_cancelled[not_cancelled.arr_delay >0].groupby(['year','month','day'])['arr_delay']
      .mean().reset_index()
     )
df.head()
  

потому что, как и в вашем коде, df это отдельный фрейм данных после завершения groupby операции, и

 df['avg_delay2'] = df[df.arr_delay>0]['arr_delay'].mean()
  

присвоите то же значение df['avg_delay2']

Редактировать: Подобно R, вы можете сделать и то, и другое за один снимок, используя agg :

 def mean_pos(x):
    return x[x>0].mean()

df = (not_cancelled.groupby(['year','month','day'])['arr_delay']
      .agg({'arr_delay': 'mean', 'arr_delay_2': mean_pos})
     )
df.head()
  

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

1. Я сделал аналогичную вещь, мне потребовалось некоторое время, чтобы сделать это на python, в то время как это была всего лишь еще одна строка в R. У меня всегда было НЕПРАВИЛЬНОЕ впечатление, что pandas это почти полезно как dplyr . Я ошибаюсь.

2. @astro123 смотрите мой обновленный ответ для краткой версии.

3. Мы не можем использовать dictionary в будущих версиях pandas. Он выдает предупреждение об устаревании в pandas 23.

4. Я этого не осознавал. В этом случае просто сделайте .agg(['mean', mean_pos]) , будут два новых столбца ['mean', 'mean_pos'] , и вы можете переименовать их по своему усмотрению.

Ответ №2:

Обратите внимание, что начиная с pandas 23, использование dictionary в gropby agg устарело и будет удалено в будущем, поэтому мы не можем использовать этот метод.

Предупреждение

 df = (not_cancelled.groupby(['year','month','day'])['arr_delay']
      .agg({'arr_delay': 'mean', 'arr_delay_2': mean_pos})
     )

FutureWarning: using a dict on a Series for aggregation
is deprecated and will be removed in a future version.
  

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

Создайте новый столбец, в котором все неположительные значения nans, затем выполните обычную groupby.

 import numpy as np
import pandas as pd

# read data
flights = pd.read_csv('https://github.com/bhishanpdl/Datasets/blob/master/nycflights13.csv?raw=true')

# select flights that are not cancelled
df = flights.dropna(subset=['dep_delay','arr_delay'])

# create new column to fill non-positive with nans
df['arr_delay_pos'] = df['arr_delay']
df.loc[df.arr_delay_pos <= 0,'arr_delay_pos'] = np.nan
df.groupby(['year','month','day'])[['arr_delay','arr_delay_pos']].mean().reset_index().head()
  

Это дает:

    year  month  day  arr_delay  arr_delay_positive
0  2013      1    1  12.651023           32.481562
1  2013      1    2  12.692888           32.029907
2  2013      1    3   5.733333           27.660870
3  2013      1    4  -1.932819           28.309764
4  2013      1    5  -1.525802           22.558824
  

Проверка работоспособности

 # sanity check
a = not_cancelled.query(""" year==2013 amp; month ==1 amp; day ==1 """)['arr_delay']
a = a[a>0]
a.mean() # 32.48156182212581