ndarray的行列字段名怎么设置?

7 投票
3 回答
3342 浏览
提问于 2025-04-16 05:20

我是一名计算机科学老师,想用NumPy为自己创建一个简单的成绩册。不过,我觉得如果能创建一个用字段名称作为行和列的ndarray,写代码会更简单。以下是我目前的进展:

import numpy as np
num_stud = 23
num_assign = 2
grades = np.zeros(num_stud, dtype=[('assign 1','i2'), ('assign 2','i2')]) #etc
gv = grades.view(dtype='i2').reshape(num_stud,num_assign)

所以,如果我的第一个学生在“作业1”上得了97分,我可以这样写:

grades[0]['assign 1'] = 97
gv[0][0] = 97

另外,我还可以这样做:

np.mean( grades['assign 1'] ) # class average for assignment 1
np.sum( gv[0] ) # total points for student 1

这些都能正常工作。但是我无法弄明白如何用学生的ID号码来指代某个特定的学生(假设我的两个学生的ID如下):

grades['123456']['assign 2'] = 95
grades['314159']['assign 2'] = 83

...或者也许可以创建一个不同字段名称的第二个视图?

np.sum( gview2['314159'] ) # total points for the student with the given id

我知道我可以创建一个字典,把学生ID映射到索引,但这似乎不太可靠,而且有点麻烦。我希望能有比这个更好的方法:

id2i = { '123456': 0, '314159': 1 }
np.sum( gv[ id2i['314159'] ] )

如果有更好的设计,我也愿意重新构建一下。因为我对NumPy还很陌生,写的代码也不多,所以如果我做错了,重新开始也是可以考虑的。

确实需要每天为超过一百个学生的所有作业分数求和,还要计算标准差和其他统计数据。而且,我希望这些操作能在几秒钟内完成。

提前感谢任何建议。

3 个回答

5

你可以看看pandas这个模块,它正好能满足你的需求。http://pandas.pydata.org

7

为了输入和存储数据,我会使用关系型数据库(比如sqlite、MySQL或Postgresql)。这样做的话,你可以轻松写多个程序,以不同的方式分析数据。sqlite数据库可以通过多种编程语言和图形/命令行界面访问。你的数据不会依赖于某种特定的编程语言(这和存储numpy数组不一样)。

Python内置支持sqlite

SQL提供了一种方便易读的语言,可以用来处理你的数据(例如:“班级1的作业1的所有分数是多少?给出前10名的分数。谁得到了这些分数?班级1的平均分比班级2高吗?”)。数据库表可以轻松容纳多个班级和多个学期的数据。

在输入数据时,图形用户界面可能是最方便的。对于sqlite,有sqlitebrowser(不过我在这方面经验不多,可能还有更好的选择)。对于MySQL,我喜欢使用phpmyadmin,而对于Postgresql,则是phppgadmin。

一旦你输入了数据,就可以使用Python模块(例如sqlite3、MySQLdb、pyscopg2)来访问数据库,并发出SQL查询。然后可以将数据放入列表或numpy数组中。接着,你可以使用numpy来计算统计数据。

补充一下,对于小数据集,速度或内存占用真的没有问题。你不需要把数据存储在numpy数组中才能调用numpy/scipy的统计函数。

例如,你可以从数据库中提取数据到Python列表中,然后将这个列表传给numpy的函数:

sql='SELECT * FROM grades WHERE assignment=%s'
args=['assign1']
data=cursor.fetchall(sql,args)
scores=zip(*data)[0]   
ave_score=np.mean(scores)

如果grades是一个numpy结构化数组,你就无法通过这种方式访问值:

grades['123456']['assign 2']

因为列是通过名称访问的,而行是通过整数访问的。

不过我认为这并不会造成太大障碍。原因是:你想为一个学生做的所有事情(比如找出所有作业的总分),你可能也想为每个学生做。

所以使用numpy的诀窍在于——利用它的强大——是写向量化的公式或使用适用于所有行的numpy函数,而不是逐行循环。与其关注个别学生或个别作业,不如让numpy鼓励你从更大的角度思考(例如所有学生、所有作业),并进行适用于所有人的计算。

正如你在处理视图时所看到的,其实不使用结构化数组会更好,而是选择一个普通的2维numpy数组:

假设列代表(2)个作业,行代表(4)个学生。

In [36]: grades=np.random.random((4,2))

In [37]: grades
Out[37]: 
array([[ 0.42951657,  0.81696305],
       [ 0.2298493 ,  0.05389136],
       [ 0.12036423,  0.78142328],
       [ 0.5029192 ,  0.75186565]])

这里有一些统计数据:

In [38]: sum_of_all_assignments = grades.sum(axis=1)

In [39]: sum_of_all_assignments
Out[39]: array([ 1.24647962,  0.28374066,  0.90178752,  1.25478485])

In [40]: average_of_all_assignments = grades.mean(axis=1)

In [41]: average_of_all_assignments
Out[41]: array([ 0.62323981,  0.14187033,  0.45089376,  0.62739242])

In [42]: average_assignment_score = grades.mean(axis=0)

In [43]: average_assignment_score 
Out[43]: array([ 0.32066233,  0.60103583])

现在假设这些是学生的名字:

In [44]: student_names=['harold','harry','herb','humphrey']

为了将学生名字与他们的平均分匹配,你可以创建一个字典:

In [45]: dict(zip(student_names,average_of_all_assignments))
Out[45]: 
{'harold': 0.62323981076528523,
 'harry': 0.14187032892653173,
 'herb': 0.45089375919011698,
 'humphrey': 0.62739242488169067}

同样,对于作业也是如此:

In [46]: assignment_names=['assign 1','assign 2']

In [47]: dict(zip(assignment_names,average_assignment_score))
Out[47]: {'assign 1': 0.32066232713749887, 'assign 2': 0.60103583474431344}
11

根据你的描述,你可能需要用一种不同的数据结构,而不是标准的numpy数组。ndarray并不太适合这个用途……它们不是电子表格。

不过,最近有很多关于一种适合这个用途的numpy数组的新研究。这里有关于DataArrays的描述。不过,这个功能要等一段时间才能完全融入numpy中……

即将推出的numpy DataArrays部分基于一个项目,叫做“larry”(是“标记数组”的缩写)。这个项目听起来正是你想要做的……(有命名的行和列,但其他方面表现得像普通的numpy数组。)它应该足够稳定可以使用,(根据我有限的尝试,它确实很不错!)但要记住,最终它可能会被numpy内置的类取代。

尽管如此,你可以利用numpy数组的简单索引返回的是该数组的一个视图这一点,来创建一个同时提供两种接口的类……

另外,如果你决定自己动手的话,@unutbu上面的建议是另一种(更简单直接)处理方法。

撰写回答