Python - 从文件获取列迭代器(无需读取整个文件)

5

我的主要目标是从一个巨大的浮点数矩阵中按列计算中位数。例如:

a = numpy.array(([1,1,3,2,7],[4,5,8,2,3],[1,6,9,3,2]))

numpy.median(a, axis=0)

Out[38]: array([ 1.,  5.,  8.,  2.,  3.])

矩阵太大了,无法在Python内存中容纳(约5TB),因此我将其保存在csv文件中。所以我想逐列运行并计算中位数。
有没有办法让我获取列迭代器而不必读取整个文件?
对于计算矩阵中位数的任何其他想法也欢迎。谢谢!

2
请参阅:https://dev59.com/HHNA5IYBdhLWcg3wNa6T - Hans Then
4个回答

3

如果您能够将每一列都放入内存中(似乎您是这么暗示的),那么以下方法可能适用:

import itertools
import csv

def columns(file_name):
   with open(file_name) as file:
       data = csv.reader(file)
       columns = len(next(data))
   for column in range(columns):
       with open(file_name) as file:
           data = csv.reader(file)
           yield [row[column] for row in data]

这个方法的实现是先找出有多少列,然后遍历整个文件,每次取出当前列的所有行中对应的项。这意味着,最多同时使用一列加上一行的内存大小。这是一个非常简单的生成器。请注意,我们必须不断重新打开文件,因为在遍历完成后迭代器会被耗尽。


如果重新打开文件是一个问题,只需将 with 移到 for 循环外部,并在内部执行 file.seek(0) - Mu Mind
@MuMind 这是一个很好的替代方案,可以避免一遍又一遍地重新打开文件(并且这也意味着你可以传递一个文件对象,以防出于某种原因没有文件名)。 - Gareth Latty

1

使用CSV文件可能没有直接的方法来完成您的请求(除非我误解了您),问题在于,除非文件特别设计为具有固定宽度行,否则任何文件都没有“列”的有意义含义。 CSV文件通常不是这样设计的。 在磁盘上,它们只是一个巨大的字符串:

>>> import csv
>>> with open('foo.csv', 'wb') as f:
...     writer = csv.writer(f)
...     for i in range(0, 100, 10):
...         writer.writerow(range(i, i + 10))
... 
>>> with open('foo.csv', 'r') as f:
...     f.read()
... 
'0,1,2,3,4,5,6,7,8,9\r\n10,11,12,13,14,15,16,17,18,19\r\n20..(output truncated)..

正如您所看到的,列字段没有可预测的对齐方式;第二列从索引2开始,但在下一行中,列的宽度增加了一个,导致对齐出现问题。当输入长度变化时,情况更糟。结果是CSV读取器必须读取整个文件,丢弃您不使用的数据。(如果您不介意,那就是答案-逐行读取整个文件,丢弃您不需要使用的数据。)

如果您不介意浪费一些空间,并知道您的数据不会超过某个固定的宽度,您可以创建一个具有固定宽度字段的文件,然后您可以使用偏移量在其中进行查找。但是,一旦您这样做了,您可能会开始使用真正的数据库。PyTables 似乎是许多人存储NumPy数组的首选。


1
如果你要多次进行这个操作,CSV 格式并不是一个好的选择来保存数据。 - Mu Mind
@senderle 我的目标是DB。你知道numpy.loadtxt(file_path, usecols=[1,2,3])现在是否可行吗? - dbaron
@dbaron,这取决于你所说的“do the trick”的含义。我非常确定usecols=[1, 2, 3]可以避免一次性将整个矩阵加载到内存中,从这个意义上说,是的。我也非常确定它会逐行读取整个文件,丢弃未使用的数据,从这个意义上说,不是的。 - senderle

1

我会通过初始化N个空文件(每列一个),来完成此操作。然后逐行读取矩阵,并将每个列条目发送到正确的文件中。处理完整个矩阵后,返回并按顺序计算每个文件的中位数。

这基本上使用文件系统进行矩阵转置。一旦转置,计算每行的中位数就很容易了。


1
谢谢您的回复!我的矩阵大小约为5TB,恐怕我没有足够的存储空间来完成这个任务 :( - dbaron

0

您可以使用桶排序将每个磁盘上的列进行排序,而无需将它们全部读入内存。然后,您只需选择中间值即可。

或者,您可以使用UNIX的awksort命令在选择中位数之前拆分和排序您的列。


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