填充numpy数组的Pythonic方式
我发现自己经常需要解析很多数据文件(通常是.csv文件或类似格式),我会用csv读取器和一个for循环来逐行处理数据。数据通常是浮点数的表格,比如说:
reader = csv.reader(open('somefile.csv'))
header = reader.next()
res_list = [list() for i in header]
for line in reader:
for i in range(len(line)):
res_list[i].append(float(line[i]))
result_dict = dict(zip(header,res_list)) #so we can refer by column title
这种方法还不错,可以把每一列放到一个单独的列表里,但我更希望默认的数据容器是numpy数组,因为99%的情况下,这些数字会被用到各种处理脚本或函数中,使用numpy数组会让我工作起来更轻松。
不过,numpy的append(arr, item)
方法并不是在原地添加的,这意味着我每次都得重新创建数组来放每个表格中的数据(这很慢,也没必要)。我也可以在处理完数据列后,把它们放进一个数组里(这就是我现在在做的),但有时候我并不清楚自己到底什么时候算是处理完了文件,可能之后还需要往列表里添加东西。
我在想有没有什么更简单的方法(用“pythonic”这个常用词来说)来处理数据表,或者动态地填充数组(底层是列表)而不需要一直复制数组。
(另外,挺烦的就是大家一般用列来组织数据,但csv
是按行读取的。如果读取器能加个read_column参数(我知道这样效率可能不高),我觉得很多人会避免写像上面那样的冗余代码来解析csv数据文件。)
3 个回答
为了高效地将数据加载到一个NumPy数组中,我喜欢使用NumPy的fromiter函数。
在这个情况下的优点有:
像流一样加载数据,
可以提前指定结果数组的数据类型,
可以提前分配一个空的输出数组,然后用可迭代对象中的数据填充它。
第一个优点是这个函数的特点——fromiter只接受可迭代的输入数据——而后两个优点是通过传递给fromiter的第二和第三个参数dtype和count来实现的。
>>> import numpy as NP
>>> # create some data to load:
>>> import random
>>> source_iterable = (random.choice(range(100)) for c in range(20))
>>> target = NP.fromiter(source_iterable, dtype=NP.int8, count=v.size)
>>> target
array([85, 28, 37, 4, 23, 5, 47, 17, 78, 40, 28, 5, 69, 47, 15, 92,
41, 33, 33, 98], dtype=int8)
如果你不想使用可迭代对象来加载数据,你仍然可以使用NumPy的empty和empty_like函数来提前分配目标数组的内存。
>>> source_vec = NP.random.rand(10)
>>> target = NP.empty_like(source_vec)
>>> target[:] = source_vec
>>> target
array([ 0.5472, 0.5085, 0.0803, 0.4757, 0.4831, 0.3054, 0.1024,
0.9073, 0.6863, 0.3575])
另外,你可以通过调用empty来创建一个空的(已分配内存的)数组,只需传入你想要的形状。与empty_like不同,这个函数允许你指定数据类型:
>>> target = NP.empty(shape=s.shape, dtype=NP.float)
>>> target
array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
>>> target[:] = source
>>> target
array([ 0.5472, 0.5085, 0.0803, 0.4757, 0.4831, 0.3054, 0.1024,
0.9073, 0.6863, 0.3575])
我觉得要在你现有的基础上做很大的改进是比较困难的。Python 的列表创建和添加元素都比较简单,而 NumPy 数组创建起来就贵一些,而且根本没有 .append()
这个方法。所以你最好的选择就是像现在这样先创建列表,然后在需要的时候再转换成 np.array()
。
有几点小建议:
用
[]
来创建列表比用list()
快一点。不过这个速度差别非常小,几乎可以忽略不计。如果你在循环中并不使用循环的索引,可以用
_
来命名这个变量,这样可以说明你并不需要它。通常来说,直接遍历一个序列比先找出序列的长度、再用
range()
创建一个范围,然后多次索引序列要好。你可以用enumerate()
来获取索引,如果你需要索引的话。
把这些建议结合起来,我觉得这是一个稍微改进的版本。但其实和你原来的代码几乎没有变化,我也想不出什么特别好的改进方法。
reader = csv.reader(open('somefile.csv'))
header = reader.next()
res_list = [ [] for _ in header]
for row in reader:
for i, val in enumerate(row):
res_list[i].append(float(val))
# build dict so we can refer by column title
result_dict = dict((n, res_list[i]) for i, n in enumerate(header))
这里有一个叫做 numpy.loadtxt
的东西:
X = numpy.loadtxt('somefile.csv', delimiter=',')
补充说明:如果你有一个 numpy 数组的列表,
X = [scipy.array(line.split(','), dtype='float')
for line in open('somefile.csv', 'r')]