NumPy平均值结构化数组

4
假设我有一个结构化数组,其中包含学生(字符串)和测试成绩(整数),每个条目都是特定学生在特定测试中获得的分数。每个学生在这个数组中都有多个条目。

示例

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)]

我如何轻松计算每个学生的平均分数?换句话说,我如何取出'score'维度数组的平均值?我想这样做:

grades.mean('score')

并让Numpy返回

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

但是Numpy抱怨。
TypeError: an integer is required

有没有类似于Numpy的简便方法来实现这个呢?我想可能需要使用一个不同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
4个回答

4
NumPy并不适用于将行分组并对这些组应用聚合函数。你可以:
  • 使用itertools.groupby并重构数组;
  • 使用基于NumPy的Pandas,它很擅长分组;或者
  • 为测试ID添加另一个维度(因此此情况将是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)

我不明白增加另一个维度会如何有所帮助。 - Jeremy
@Jeremy,额外的维度是为了测试ID。因此,对于3个学生和2个测试,您有一个2x3的数组。 - ecatmur
好的。恰巧在我的程序中,我已经有了一个测试ID维度。那对我有什么帮助呢? - Jeremy
@Jeremy,然后您可以在测试ID轴上调用np.mean(axis=1) - ecatmur
@Jeremy,这将是 numpy.zeros((3, 2), dtype=[('student', 'a50'), ('score', 'i')]) 然后 grades[0, 0] = ('Mary', 96) 等等。 - ecatmur
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/15482/discussion-between-jeremy-and-ecatmur - Jeremy

1

matplotlib.mlab.rec_groupby 正是我所需要的。


0

在编程中,使用collapseByField(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))

0

基于itertools的一种更快、更简单的解决方案,而不使用view(),如下:

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

这与ecatmur的想法相同,但是使用argsort()代替sort()来处理索引。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接