Numpy Средний структурированный массив

Предположим, что у меня есть структурированный массив студентов (строки) и результаты тестов (целые числа), где каждая запись — это оценка, которую конкретный студент получил за конкретный тест. Естественно, у каждого студента есть несколько записей в этом массиве.

Пример

import numpy
grades = numpy.array([('Mary', 96), ('John', 94), ('Mary', 88), ('Edgar', 89), ('John', 84)],
                     dtype=[('student', 'a50'), ('score', 'i')])

print grades
#[('Mary', 96) ('John', 94) ('Mary', 88) ('Edgar', 89) ('John', 84)]

Как легко вычислить средний балл каждого учащегося? Другими словами, как мне получить среднее значение массива в измерении «оценка»? я хотел бы сделать

grades.mean('score')

и вернуть Numpy

[('Mary', 92), ('John', 89), ('Edgar', 89)]

но Нампи жалуется

TypeError: an integer is required

Есть ли способ Numpy-esque сделать это легко? Я думаю, что это может включать просмотр структурированного массива с другим типом dtype. Любая помощь будет оценена по достоинству. Спасибо.

Редактировать

>>> grades = numpy.zeros(5, dtype=[('student', 'a50'), ('score', 'i'), ('testid', 'i'])
>>> grades[0] = ('Mary', 96, 1)
>>> grades[1] = ('John', 94, 1)
>>> grades[2] = ('Mary', 88, 2)
>>> grades[3] = ('Edgar', 89, 1)
>>> grades[4] = ('John', 84, 2)
>>> np.mean(grades, 'testid')
TypeError: an integer is required

person Jeremy    schedule 16.08.2012    source источник


Ответы (4)


NumPy не предназначен для группировки строк и применения агрегатных функций к этим группам. Ты мог:

  • используйте itertools.groupby и восстановите массив;
  • используйте Pandas, который основан на NumPy и отлично подходит для группировки; или же
  • добавьте еще одно измерение в массив для идентификатора теста (так что в этом случае будет массив 2x3, потому что похоже, что было два теста).

Вот решение itertools, но, как видите, оно довольно сложное и неэффективное. Я бы рекомендовал один из двух других методов.

np.array([(k, np.array(list(g), dtype=grades.dtype).view(np.recarray)['score'].mean())
          for k, g in groupby(np.sort(grades, order='student').view(np.recarray),
                              itemgetter('student'))], dtype=grades.dtype)
person ecatmur    schedule 16.08.2012
comment
Я не понимаю, как добавление еще одного измерения может помочь. - person Jeremy; 17.08.2012
comment
@Jeremy, дополнительное измерение предназначено для идентификатора теста. Итак, для 3 студентов и 2 тестов у вас есть массив 2x3. - person ecatmur; 17.08.2012
comment
Верно. Как оказалось, в моей программе уже есть измерение testid. Как это мне поможет? - person Jeremy; 18.08.2012
comment
@Jeremy, тогда вы можете вызвать np.mean(axis=1) по оси test-id. - person ecatmur; 18.08.2012
comment
@Jeremy, это будет `numpy.zeros((3, 2), dtype=[('student', 'a50'), ('score', 'i')])`, а затем grades[0, 0] = ('Mary', 96) и т. д. - person ecatmur; 18.08.2012
comment
давайте продолжим это обсуждение в чате - person Jeremy; 18.08.2012

matplotlib.mlab.rec_groupby был именно тем, что я искал.

person Jeremy    schedule 20.08.2012

Немного более быстрое и простое решение на основе itertools без использования view():

[(k,e['score'][list(g)].mean()) for k, g in groupby(argsort(e),e['student'].__getitem__ )]

Это та же идея, что и у ecatmur, но она работает с индексами, использующими argsort() вместо sort.

person gg349    schedule 16.08.2012

CollarByField(grades,'student') дает то, что вы хотите, после:

def collapseByField(e,collapsefield,keepFields=None,agg=None):
   import numpy as np
   assert isinstance(e,np.ndarray) # Structured array
   if agg is None:
       agg=np.mean
   if keepFields is None:
       newf=[(n,agg,n) for n in e.dtype.names if n not in (collapsefield)]
   import matplotlib as mpl
   return(mpl.mlab.rec_groupby(e,[collapsefield],newf))
person CPBL    schedule 20.10.2012