ndarray的行列字段名怎么设置?
我是一名计算机科学老师,想用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 个回答
你可以看看pandas这个模块,它正好能满足你的需求。http://pandas.pydata.org
为了输入和存储数据,我会使用关系型数据库(比如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}
根据你的描述,你可能需要用一种不同的数据结构,而不是标准的numpy数组。ndarray
并不太适合这个用途……它们不是电子表格。
不过,最近有很多关于一种适合这个用途的numpy数组的新研究。这里有关于DataArrays的描述。不过,这个功能要等一段时间才能完全融入numpy中……
即将推出的numpy DataArrays部分基于一个项目,叫做“larry”(是“标记数组”的缩写)。这个项目听起来正是你想要做的……(有命名的行和列,但其他方面表现得像普通的numpy数组。)它应该足够稳定可以使用,(根据我有限的尝试,它确实很不错!)但要记住,最终它可能会被numpy内置的类取代。
尽管如此,你可以利用numpy数组的简单索引返回的是该数组的一个视图这一点,来创建一个同时提供两种接口的类……
另外,如果你决定自己动手的话,@unutbu上面的建议是另一种(更简单直接)处理方法。