groupby mean и median

Имеется датафрейм:

df.head()

     title    birthday     today      age
0   Group 1  1997-07-24  2020-05-04 8320 days
1   Group 1  1997-07-24  2020-05-04 8320 days
2   Group 1  1997-07-24  2020-05-04 8320 days
3   Group 1  1997-07-24  2020-05-04 8320 days
4   Group 1  1997-07-24  2020-05-04 8320 days

<class 'pandas.core.frame.DataFrame'>
Int64Index: 329739 entries, 0 to 350675
Data columns (total 4 columns):
title       329739 non-null object
birthday    329739 non-null datetime64[ns]
today       329739 non-null datetime64[ns]
age         329739 non-null timedelta64[ns]
dtypes: datetime64[ns](2), object(1), timedelta64[ns](1)
memory usage: 22.6+ MB

Мне необходимо узнать максимальный, минимальный, средний и медианный возраст на каждом курсе. Но применение

df[['age', 'title']].groupby('title').agg(['max', 'min', 'mean', 'median'])

Выдает ошибку

ValueError: no results

Однако считаются максимальные и минимальные значения, а также mean и median без группировки:

df['age'].median()

Timedelta('10684 days 00:00:00')

Заранее благодарю!


Ответы (2 шт):

Автор решения: MaxU

Проблема в том, что тип данных timedelta64[ns] - нечисловой и поэтому Pandas не позволяет делать агрегирование. Поэтому можно сначала создать виртуальный числовой столбец (например вычленить число дней из timedelta64[ns]) и тогда уже делать арифметику с ним:

In [51]: (df
          .assign(days=df["age"].dt.days)
          .groupby('title')
          ["days"]
          .agg(['max', 'min', 'mean', 'median']))
Out[51]:
          max   min  mean  median
title
Group 1  8320  8320  8320    8320
→ Ссылка
Автор решения: MarianD

Короткое решение (одно из возможных) - примените методы серии:

df[['age', 'title']].groupby('title').agg(['max', 'min', pd.Series.mean, pd.Series.median])

Результат:

                                                age
               max        min       mean     median
  title                 
Group 1  8320 days  8320 days  8320 days  8320 days

Анализ проблемы и объяснение:

Многие методы класса DataFrame имеют ключевой параметр numeric_only; у метод класса Series такого параметра нет.

Примеры таких методов: Все, которые вы применили: .max(), .min(), .mean(), .median().

Параметр numeric_only определяет столбцы, ко которым метод будет приниматься. У него 3 возможные значения:

  • None (стандартное значение) - то же самое, как False,
  • True - только столбцы типа int, float и bool,
  • False - все столбцы, для которых метод умеет работать.

При применении методов класса DataFrame к объектам типа DataFrameGroupBy (ваш случай) используется индивидуальное значение параметра numeric_only в зависимости от конкретного метода, например в вашем случае:

  • для метода .max(): стандартное значение (None),
  • для метода .min(): стандартное значение (None),
  • для метода .mean(): .mean(numeric_only=True)
  • для метода .median(): .median(numeric_only=True)

Вы применяете эти 4 методы для вашего столбца 'age', который типа ни int, ни float, ни bool. Значит, он будет исключен из применения методов .mean() и .median(), что мешает сделать комплектный результат в виде таблицы со всеми 4 вами требуемыми столбцами и выдает ошибку

ValueError: no results 

Как это обойти — несколько примеров:

  1. Применить методы класса Series, которые работают всегда, когда возможно

    • прямо (как я это сделал в начале ответа):

      df[['age', 'title']].groupby('title').agg(['max', 'min',
                                                 pd.Series.mean, pd.Series.median])
      
    • использованием этой идеи для вами созданной или ламбда функции, например

      def my_mean(series):
          return series.mean()
      
      df[['age', 'title']].groupby('title').agg(['max', 'min',
                                                 my_mean,
                                                 lambda s: s.median()])
      
  2. Использованием методов класса DataFrame с параметром numeric_only=False для создания (и в последствии применения) собственной функций, например так:

    from functools import partial
    
    my_mean   = partial(pd.DataFrame.mean,   numeric_only=False)
    my_median = partial(pd.DataFrame.median, numeric_only=False)
    
    df[['age', 'title']].groupby('title').agg(['max', 'min', my_mean, my_median])
    
  3. Использовать функции NumPy (ведь это в заднем плане делает сам pandas - всегда, и в предыдущих двух пунктах), но косвенно, чтобы pandas это «не заметил», например:

    def my_mean(x):
         return np.mean(x)
    
    my_median = lambda x: np.median(x)
    
    df[['age', 'title']].groupby('title').agg(['max', 'min', my_mean, my_median])
    
→ Ссылка