用Pythonic的方式填充numpy数组

6

我经常使用csv reader和for循环遍历每一行,解析大量数据文件(通常是.csv文件或类似文件)。这些数据通常都是浮点型表格数据,例如:

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次中有99次的数字被传输到各种处理脚本/函数中,使用numpy列表能够让我的生活更轻松。
numpy的append(arr, item)不会就地添加元素,因此需要为表中的每个点重新创建数组(这是缓慢且不必要的)。我也可以遍历数据列的列表,并在完成后将它们包装成一个数组(这就是我一直在做的),但有时并不那么清晰,不知道何时完成文件解析,可能需要在以后的某个时间点附加内容到列表中。
我想知道是否有一些 less-boiler-heavy 的方式(用过度使用的“pythonic”短语来描述),以类似的方式处理数据表,或者动态地填充数组(其中底层容器为列表),而不需要一直复制数组。
(另外注意:通常人们使用列来组织数据,但是csv读入行,如果读取器包含read_column参数(是的,我知道它不会非常高效),我认为很多人都会避免像上面那样的模板代码来解析csv数据文件。)
3个回答

8

这里有一个函数叫做numpy.loadtxt:

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

文档。


编辑:针对numpy数组列表,

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

1
我之前考虑过这个问题,但它有一些问题,特别是需要在整个数组中保持行长度相同。尽管我的小片段假设相同,但并不总是发生(例如空行表示数据收集突发之间的间隔)。 - crasic

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

NumPy...根本不提供.append()方法。NumPy确实有一个append方法。它的工作方式与Python的append方法更或多或少相同,只是它不是“原地”的。 - doug
@doug: a = np.array(range(3)) 成功了。然后,a.append(4) 给出消息 AttributeError: 'numpy.ndarray' object has no attribute 'append'。如果NumPy数组有一个.append()方法,那么我在这里做错了什么? - steveha
所以尝试这个:a = NP.random.randint(0, 10, 5); a = NP.append(a, [2, 3]). 因此,NumPy具有一个append函数而不是一个append方法——这使得您在上面的回答中的说法完全正确! - doug
在回答之前,我通常会先尝试一下。不过,我没有想到还有一个非方法的append()函数! - steveha

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

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