Получить среднее значение массива numpy с помощью pandas groupby

#python #pandas #dataframe #numpy

Вопрос:

У меня есть фрейм данных, в котором один столбец представляет собой numpy массив чисел. Например,

 import numpy as np
import pandas as pd

df = pd.DataFrame.from_dict({
    'id': [1, 1, 2, 2, 3, 3, 3, 4, 4],
    'data': [np.array([0.43, 0.32, 0.19]),
             np.array([0.41, 0.11, 0.21]),
             np.array([0.94, 0.35, 0.14]),
             np.array([0.78, 0.92, 0.45]),
             np.array([0.32, 0.63, 0.48]),
             np.array([0.17, 0.12, 0.15]),
             np.array([0.54, 0.12, 0.16]),
             np.array([0.48, 0.16, 0.19]),
             np.array([0.14, 0.47, 0.01])]
})
 

Я хочу получить groupby столбец идентификатора и агрегировать его, взяв среднее значение по элементам массива. Сначала разбить массив на части невозможно, так как он имеет длину 300, а у меня более 200 000 строк. Когда я это делаю df.groupby('id').mean() , я получаю сообщение об ошибке «Нет числовых типов для агрегирования». Я могу получить среднее значение по элементам списков , используя df['data'].mean() , поэтому я думаю, что должен быть способ сделать сгруппированное среднее значение. Чтобы уточнить, я хочу, чтобы вывод был массивом для каждого значения идентификатора. Каждый элемент в результирующем массиве должен быть средним значением значений элементов в соответствующей позиции внутри каждой группы. В приведенном примере результат должен быть:

 pd.DataFrame.from_dict({
    'id': [1, 2,3,4],
    'data': [np.array([0.42, 0.215, 0.2]),
             np.array([0.86, 0.635, 0.29500000000000004]),
             np.array([0.3433333333333333, 0.29, 0.26333333333333336]),
             np.array([0.31, 0.315, 0.1])]
})
 

Может кто-нибудь подсказать, как я мог бы это сделать? Спасибо!

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

1. Как должен выглядеть результат?

Ответ №1:

Подразумевайте это дважды, один раз на уровне массива и один раз на уровне группы:

 df['data'].map(np.mean).groupby(df['id']).mean().reset_index()
 

    id      data
0   1  0.278333
1   2  0.596667
2   3  0.298889
3   4  0.241667
 

Основываясь на комментариях, вы можете сделать:

 pd.DataFrame(df['data'].tolist(),index=df['id']).mean(level=0).agg(np.array,1)

id
1                                 [0.42, 0.215, 0.2]
2                 [0.86, 0.635, 0.29500000000000004]
3    [0.3433333333333333, 0.29, 0.26333333333333336]
4                                 [0.31, 0.315, 0.1]
dtype: object
 

Или:

 df.groupby("id")['data'].apply(np.mean)
 

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

1. Я должен был быть более ясным, я хочу, чтобы вывод был массивом (в примере он был бы длиной четыре), где каждый элемент является средним значением элементов в этой позиции.

2. @AndrejKesely Да, это так, но np.mean в моей версии, только что протестированной и отредактированной 🙂 только значит, что дает мне DataError: No numeric types to aggregate

3. Using the level keyword in DataFrame and Series aggregations is deprecated Должно быть pd.DataFrame(df['data'].tolist(),index=df['id']).groupby(level=0).mean().agg(np.array,1) для будущих версий.

Ответ №2:

Во-первых, разделение массива возможно, поскольку ваше текущее хранилище требует хранения сложного объекта со всеми значениями в пределах фрейма данных. Это займет намного больше места, чем просто хранение плоского 2D-массива

 # Your current memory usage
df.memory_usage(deep=True).sum()
1352

# Create a new DataFrame (really just overwrite `df` but keep separate for illustration)
df1 = pd.concat([df['id'], pd.DataFrame(df['data'].tolist())], 1)
#   id     0     1     2
#0   1  0.43  0.32  0.19
#1   1  0.41  0.11  0.21
#2   2  0.94  0.35  0.14
#3   2  0.78  0.92  0.45
#4   3  0.32  0.63  0.48
#5   3  0.17  0.12  0.15
#6   3  0.54  0.12  0.16
#7   4  0.48  0.16  0.19
#8   4  0.14  0.47  0.01
 

Да, это выглядит больше, но это не с точки зрения памяти, на самом деле это меньше. Коэффициент 3x здесь немного экстремален, для больших кадров данных с длинными массивами он, вероятно, составит 95% памяти. И все же это должно быть меньше.

 df1.memory_usage(deep=True).sum()
#416
 

И теперь ваша агрегация является обычной groupby mean , столбцы указывают местоположение в массиве

 df1.groupby('id').mean()
#           0      1         2
#id                           
#1   0.420000  0.215  0.200000
#2   0.860000  0.635  0.295000
#3   0.343333  0.290  0.263333
#4   0.310000  0.315  0.100000
 

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

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

2. @anky, не уверен насчет памяти и индекса, я никогда в это не заглядывал. К сожалению mean(level=) , это устарело, потому что кажется, что они идут по пути войны, чтобы упростить api pandas и удалить все избыточные способы выполнения одной и той же операции (r.i.p. lookup: (), так что вам все равно понадобится .groupby('id').mean() (хорошая мысль id может быть, по крайней мере, в индексе)

Ответ №3:

Сгруппируйте по среднему значению для массива, где выводом является массив среднего значения

  df['data'].map(np.array).groupby(df['id']).mean().reset_index()
 

Выход:

    id                                             data
0   1                               [0.42, 0.215, 0.2]
1   2               [0.86, 0.635, 0.29500000000000004]
2   3  [0.3433333333333333, 0.29, 0.26333333333333336]
3   4                               [0.31, 0.315, 0.1]
 

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

1. Какова ваша версия для панд, которую я получаю DataError: No numeric types to aggregate , когда запускаю это в '1.1.3'

2. панды==1.3.2 numpy==1.21.2

3. Это интересно, так как похоже на мой первый ответ, но дает другой результат. Не могу сказать, что панды меняются с каждой версией :/

Ответ №4:

Ты всегда можешь .apply иметь в виду глупость.

 df.groupby('id')['data'].apply(np.mean).apply(np.mean)

# returns:
id
1    0.278333
2    0.596667
3    0.298889
4    0.241667
Name: data, dtype: float64