我有一大堆用CSV文件编写的数字,只需加载该数组的一部分。从概念上讲,我想调用np.genfromtxt()
然后对结果数组进行行切片,但是
MATLAB具有函数textscan()
,它可以获取文件描述符并只读取文件的一部分。在NumPy中有类似的东西吗?
目前,我定义了以下函数,该函数只读取满足给定条件的行:
def genfromtxt_cond(fname, cond=(lambda str: True)):
res = []
with open(fname) as file:
for line in file:
if cond(line):
res.append([float(s) for s in line.split()])
return np.array(res, dtype=np.float64)
此解决方案存在一些问题:
genfromtxt
检测类型,这些类型可能因列而异;还缺少值,转换器,跳过等; 是否有实现过滤的标准函数,或MATLAB的textscan
的某些对应物?
答案 0 :(得分:16)
我可以想到两种方法可以提供您要求的一些功能:
以块或/或n行/等的步幅读取文件:
您可以将generator
传递给numpy.genfromtxt以及numpy.loadtxt。这样,您可以高效地从文本文件中加载大型数据集,同时保留所有两个函数的方便解析功能。
仅从符合可以表示为正则表达式的条件的行中读取数据:
您可以使用numpy.fromregex并使用regular expression
精确定义应加载输入文件中给定行的哪些令牌。与模式不匹配的行将被忽略。
为了说明这两种方法,我将使用我的研究背景中的一个例子 我经常需要加载具有以下结构的文件:
6
generated by VMD
CM 5.420501 3.880814 6.988216
HM1 5.645992 2.839786 7.044024
HM2 5.707437 4.336298 7.926170
HM3 4.279596 4.059821 7.029471
OD1 3.587806 6.069084 8.018103
OD2 4.504519 4.977242 9.709150
6
generated by VMD
CM 5.421396 3.878586 6.989128
HM1 5.639769 2.841884 7.045364
HM2 5.707584 4.343513 7.928119
HM3 4.277448 4.057222 7.022429
OD1 3.588119 6.069086 8.017814
这些文件可能很大(GB),我只对数字数据感兴趣。所有数据块都具有相同的大小 - 在此示例中为6
- 它们始终由两行分隔。所以块的stride
是8
。
首先,我要定义一个过滤掉不需要的行的生成器:
def filter_lines(f, stride):
for i, line in enumerate(f):
if i%stride and (i-1)%stride:
yield line
然后我打开文件,创建一个filter_lines
- 生成器(这里我需要知道stride
),并将该生成器传递给genfromtxt
:
with open(fname) as f:
data = np.genfromtxt(filter_lines(f, 8),
dtype='f',
usecols=(1, 2, 3))
这很轻松。请注意,我可以使用usecols
来删除数据的第一列。以同样的方式,您可以使用genfromtxt
的所有其他功能 - 检测类型,从列到列的不同类型,缺失值,转换器等。
在此示例中,data.shape
为(204000, 3)
,而原始文件由272000
行组成。
这里generator
用于过滤均匀划线,但同样可以想象它根据(简单)标准过滤出不均匀的线条块。
以下是我要使用的regexp
:
regexp = r'\s+\w+' + r'\s+([-.0-9]+)' * 3 + r'\s*\n'
组 - 即在()
内 - 定义要从给定行提取的标记。
接下来,fromregex
完成工作并忽略与模式不匹配的行:
data = np.fromregex(fname, regexp, dtype='f')
结果与第一种方法完全相同。
答案 1 :(得分:1)
如果传递类型列表(格式条件),请使用try块并使用yield将genfromtxt用作生成器,我们应该能够复制textscan()
。
def genfromtext(fname, formatTypes):
with open(fname, 'r') as file:
for line in file:
try:
line = line.split(',') # Do you care about line anymore?
r = []
for type, cell in zip(formatTypes, line):
r.append(type(cell))
except:
pass # Fail silently on this line since we hit an error
yield r
编辑:我忘记了除了块。它现在运行正常,您可以使用genfromtext作为生成器(使用随机的CSV日志,我坐在那里):
>>> a = genfromtext('log.txt', [str, str, str, int])
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:29:26', '00:00:36', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:22:20', '00:08:14', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:31:05', '00:00:11', 3]
我应该注意到我正在使用zip
将逗号分割线和formatSpec压缩在一起,这将对两个列表进行整理(当其中一个列表用完项目时停止),这样我们就可以迭代了它们在一起,避免依赖于len(line)
或类似的循环。
答案 2 :(得分:0)
试图向OP展示评论。
def fread(name, cond):
with open(name) as file:
for line in file:
if cond(line):
yield line.split()
def a_genfromtxt_cond(fname, cond=(lambda str: True)):
"""Seems to work without need to convert to float."""
return np.array(list(fread(fname, cond)), dtype=np.float64)
def b_genfromtxt_cond(fname, cond=(lambda str: True)):
r = [[int(float(i)) for i in l] for l in fread(fname, cond)]
return np.array(r, dtype=np.integer)
a = a_genfromtxt_cond("tar.data")
print a
aa = b_genfromtxt_cond("tar.data")
print aa
输出
[[ 1. 2.3 4.5]
[ 4.7 9.2 6.7]
[ 4.7 1.8 4.3]]
[[1 2 4]
[4 9 6]
[4 1 4]]