读取以第一列为键,其余为值的制表符分隔文件

Posted

技术标签:

【中文标题】读取以第一列为键,其余为值的制表符分隔文件【英文标题】:Read a tab separated file with first column as key and the rest as values 【发布时间】:2015-07-07 08:44:12 【问题描述】:

我有一个制表符分隔文件,其中包含 10 亿行(想象一下 200 列,而不是 3 列):

abc -0.123  0.6524  0.325
foo -0.9808 0.874   -0.2341 
bar 0.23123 -0.123124   -0.1232

我想创建一个字典,其中第一列中的字符串是键,其余的是值。我一直在这样做,但计算量很大:

import io

dictionary = 

with io.open('bigfile', 'r') as fin:
    for line in fin:
        kv = line.strip().split()
        k, v = kv[0], kv[1:]
        dictionary[k] = list(map(float, v))

我还能如何获得所需的字典?实际上,numpy 数组比浮点数列表更适合该值。

【问题讨论】:

【参考方案1】:

您可以使用pandas加载df,然后根据需要构造一个新的df然后调用to_dict

In [99]:

t="""abc -0.123  0.6524  0.325
foo -0.9808 0.874   -0.2341 
bar 0.23123 -0.123124   -0.1232"""
df = pd.read_csv(io.StringIO(t), sep='\s+', header=None)
df = pd.DataFrame(columns = df[0], data = df.ix[:,1:].values)
df.to_dict()
Out[99]:
'abc': 0: -0.12300000000000001,
  1: -0.98080000000000001,
  2: 0.23123000000000002,
 'bar': 0: 0.32500000000000001, 1: -0.2341, 2: -0.1232,
 'foo': 0: 0.65239999999999998, 1: 0.87400000000000011, 2: -0.123124

编辑

一种更动态的方法,可以减少构建临时 df 的需要:

In [121]:

t="""abc -0.123  0.6524  0.325
foo -0.9808 0.874   -0.2341 
bar 0.23123 -0.123124   -0.1232"""
# determine the number of cols, we'll use this in usecols
col_len = pd.read_csv(io.StringIO(t), sep='\s+', nrows=1).shape[1]
col_len
# read the first col we'll use this in names
cols = pd.read_csv(io.StringIO(t), sep='\s+', usecols=[0], header=None)[0].values
# now read and construct the df using the determined usecols and names from above
df = pd.read_csv(io.StringIO(t), sep='\s+', header=None, usecols = list(range(1, col_len)), names = cols)
df.to_dict()
Out[121]:
'abc': 0: -0.12300000000000001,
  1: -0.98080000000000001,
  2: 0.23123000000000002,
 'bar': 0: 0.32500000000000001, 1: -0.2341, 2: -0.1232,
 'foo': 0: 0.65239999999999998, 1: 0.87400000000000011, 2: -0.123124

进一步更新

其实你不需要第一次读取,列长反正可以通过第一列的列数隐式推导出来:

In [128]:

t="""abc -0.123  0.6524  0.325
foo -0.9808 0.874   -0.2341 
bar 0.23123 -0.123124   -0.1232"""
cols = pd.read_csv(io.StringIO(t), sep='\s+', usecols=[0], header=None)[0].values
df = pd.read_csv(io.StringIO(t), sep='\s+', header=None, usecols = list(range(1, len(cols)+1)), names = cols)
df.to_dict()
Out[128]:
'abc': 0: -0.12300000000000001,
  1: -0.98080000000000001,
  2: 0.23123000000000002,
 'bar': 0: 0.32500000000000001, 1: -0.2341, 2: -0.1232,
 'foo': 0: 0.65239999999999998, 1: 0.87400000000000011, 2: -0.123124

【讨论】:

然后我可以通过df['foo'].values() 来获得按功能键排序的列表吗?或者values() 会打乱列的顺序吗? 值总是按顺序排列的 呃,但是动态方法将读取十亿行 csv 文件三次?还是我在这里遗漏了什么? @JohnGalt nope 第一次读取它只读取一行以确定列数,第二次读取它只读取列,第三次读取只读取数据 @EdChum 非常好 - 我的错,没有清楚地看穿线条。道歉!【参考方案2】:

如果您指定列数,您可以使用numpy.genfromtxt() 函数:

import numpy as np

a = np.genfromtxt('bigfile.csv',dtype=str,usecols=(0)) 
b = np.genfromtxt('bigfile.csv',dtype=float,delimiter='\t',usecols=range(1,4)) 
                                                                             #^enter # of cols here

d = dict(zip(a,b.tolist()))    #if you want a numpy array, just remove .tolist()

print d

输出:

'abc': [-0.123, 0.6524, 0.325], 'bar': [0.23123, -0.123124, -0.1232], 'foo': [-0.9808, 0.874, -0.2341]

注意:要以编程方式查找 cols 的数量,您可以这样做:

with open('bigfile.csv', 'r') as f:
    num_cols = len(f.readline().split())

然后使用num_cols 作为usecols 参数。

【讨论】:

我从b = np.genfromtxt('bigfile',dtype='float',delimiter='\t',usecols=range(1,401) 得到Line #1 (got 1 columns instead of 400) 400 是列数。 @alvas 你确定实际上有 400 列由'\t' 分隔吗?另外,您不需要在float周围引用 @alvas 您还应该为'bigfile'添加一个扩展名,例如'bigfile.csv' 这很奇怪。同样的got 1 columns 发生在b = np.genfromtxt('bigfile.csv',dtype=float,delimiter='\t',usecols=range(1,401)) 上。它肯定是制表符分隔的,因为我可以在 unix 上执行 cut -f 并且肯定是来自 awk 'print NF;quit' bigfile.csv 的 401 列 @alvas 我刚刚尝试重现您的错误,它仅在恰好有 1 列时显示。老实说,我不知道有什么问题。如果您让我们查看文件会有所帮助,但我了解隐私问题。【参考方案3】:

您可以使用csv 模块来读取文件,以便拆分行,然后使用np.array 将浮点值转换为 numpy 数组对象:

import csv
import numpy as np
dictionary = 
with open('bigfile.csv', 'rb') as csvfile:
     spamreader = csv.reader(csvfile, delimiter='\t',)
     for row in spamreader:
        k, v = row[0], row[1:] #in python3 do  k,*v = row
        dictionary[k] = np.array(map(float, v))

【讨论】:

熊猫会更快或更高效吗? @alvas 对不起,我不熟悉熊猫!所以我不知道! 我想听听投反对票的原因,如果我错了,我可以编辑我的答案并改进它! 不是我 ;P . @alvas 我不知道为什么以及如何一些人无缘无故地否决答案!【参考方案4】:

使用Pandas 的一种方式。假设你这样做 df = pd.read_csv(file)df 就像

In [220]: df
Out[220]:
     k       a1        a2      a3
0  abc -0.12300  0.652400  0.3250
1  foo -0.98080  0.874000 -0.2341
2  bar  0.23123 -0.123124 -0.1232

我添加了虚拟列名,您可以在读取 csv 文件时灵活地更改它

那么您可以执行以下操作。

In [221]: df.set_index('k').T.to_dict('list')
Out[221]:
'abc': [-0.12300000000000001, 0.65239999999999998, 0.32500000000000001],
 'bar': [0.23123000000000002, -0.123124, -0.1232],
 'foo': [-0.98080000000000001, 0.87400000000000011, -0.2341]

【讨论】:

【参考方案5】:

对不起,这不是一个真正的答案,但评论太长了。

你说你有 10 亿行和 200 列浮点数。这意味着

的最小内存

109 * 200 * 8 = 1.6 1012 字节

不计算字典的开销,它提供了超过 1.5 G。

当然,您可以尝试使用numpy 数组而不是浮点数列表,但是每个数组都很小(200 个元素),所以我非常怀疑增益是否重要。

恕我直言,对于这么多数据,您不应该独立于处理数据的方式来考虑加载阶段,如果您真的需要一个包含 10 亿条记录的字典,每条记录有 200 个浮点值,您当前实现是正确的,numpy 数组也是正确的。

如果您可以将所有数据放在一个 numpy 数组中,并且确实使用 numpy 作为处理部分,那么您可以在进一步处理中获得重要收益,但在不了解更多信息的情况下,这只是推测。

【讨论】:

以上是关于读取以第一列为键,其余为值的制表符分隔文件的主要内容,如果未能解决你的问题,请参考以下文章

Pandas 在读取制表符分隔的数据时似乎忽略了第一列名称,给出 KeyError

如何让pandas停止跳过TSV文件中的第一个空白列?

数据文件的内容中,字段中间用制表符Tab键分隔。

excel保存为制表符分隔的文本文件 js无法完整读取

从制表符分隔的文本文件中读取日期

在 Spark R 中读取制表符分隔的文本文件