填充numpy数组的Pythonic方式

6 投票
3 回答
13303 浏览
提问于 2025-04-17 01:52

我发现自己经常需要解析很多数据文件(通常是.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 个回答

2

为了高效地将数据加载到一个NumPy数组中,我喜欢使用NumPy的fromiter函数。

在这个情况下的优点有:

  • 像流一样加载数据

  • 可以提前指定结果数组的数据类型,

  • 可以提前分配一个空的输出数组,然后用可迭代对象中的数据填充它。

第一个优点是这个函数的特点——fromiter只接受可迭代的输入数据——而后两个优点是通过传递给fromiter的第二和第三个参数dtypecount来实现的。

>>> 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的emptyempty_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])
3

我觉得要在你现有的基础上做很大的改进是比较困难的。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))
8

这里有一个叫做 numpy.loadtxt 的东西:

X = numpy.loadtxt('somefile.csv', delimiter=',')

文档链接。


补充说明:如果你有一个 numpy 数组的列表,

X = [scipy.array(line.split(','), dtype='float') 
     for line in open('somefile.csv', 'r')]

撰写回答