如何从 Python 中的两对文件中获取枢轴线?

alv*_*vas 6 python csv performance dictionary memory-efficient

如何从两个制表符分隔的文件中获取枢轴线?,有一种使用 unix 命令从两个文件中透视行的快速方法。

如果我们有两对文件:

  • f1af1b
  • f2af2b

目标是提供一个 3 列制表符分隔的文件,其中包括:

  • f1a / f2a
  • f1b
  • f2b

f1a / f2a文件中同时出现在f1a和中的行在哪里f1b

我尝试了以下可行的方法,但如果文件非常大,则将需要大量内存来存储f1f2字典。例如具有数十亿行的文件。

import sys
from tqdm import tqdm 

f1a, f1b, f2a, f2b = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]


# Read first pair of file into memory.
with open(f1a) as fin_f1a, open(f1a) as fin_f1b:
  f1 = {s.strip().replace('\t', ' ') :t.strip().replace('\t', ' ') for s, t in tqdm(zip(fin_f1a, fin_f1b))}

with open(s2) as fin_f2a, open(t2) as fin_f2b:
  f2 = {s.strip().replace('\t', ' ') :t.strip().replace('\t', ' ') for s, t in tqdm(zip(fin_f2a, fin_f2b))}


with open('pivoted.tsv', 'w') as fout:
  for s in tqdm(f1.keys() & f2.keys()):
    print('\t'.join([s, f1[s], f2[s]]), end='\n', file=fout)
Run Code Online (Sandbox Code Playgroud)

有没有更快/更好/更容易的方法来在 Python 中实现相同的 3 列制表符分隔文件?是否有库可以对大文件有效地执行此类操作?


使用turicreate.SFrame,我还可以这样做:

from turicreate import SFrame

f1a, f1b, f2a, f2b = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]

sf1a = SFrame.read_csv(f1a, delimited='\0', header=False)
sf1b = SFrame.read_csv(f1b, delimited='\0', header=False) 

sf2a = SFrame.read_csv(f2a, delimited='\0', header=False)
sf2b = SFrame.read_csv(f2b, delimited='\0', header=False)

sf1 = sf1a.join(sf1b) 
sf2 = sf2a.join(sf2b)

sf = sf1.join(sf2, on='X1', how='left') 
sf.save('pivoted')
Run Code Online (Sandbox Code Playgroud)

Bob*_*Bob 5

通用合并

ZIP功能无法存储iterables的整个副本。所以我们可以安全地使用它。

假设您有两个按第一列升序排序的可迭代对象,您可以按如下方式连接两个表。

def merge(t1, t2):
    end = object()
    end_ = end, None
    a1, b1 = next(t1, end_)
    a2, b2 = next(t2, end_)
    while a1 is not end and a2 is not end:
        if a1 < a2:
            a1, b1 = next(t1, end_)
        elif a1 > a2:
            a2, b2 = next(t2, end_)
        else:
            yield a1, b1, b2
            a1, b1 = next(t1, end_)
            a2, b2 = next(t2, end_)
Run Code Online (Sandbox Code Playgroud)

Merge 使用两个迭代器调用并生成第三个迭代器,并且每次只需要存储每个迭代器的一个元素。

def merge(t1, t2):
    end = object()
    end_ = end, None
    a1, b1 = next(t1, end_)
    a2, b2 = next(t2, end_)
    while a1 is not end and a2 is not end:
        if a1 < a2:
            a1, b1 = next(t1, end_)
        elif a1 > a2:
            a2, b2 = next(t2, end_)
        else:
            yield a1, b1, b2
            a1, b1 = next(t1, end_)
            a2, b2 = next(t2, end_)
Run Code Online (Sandbox Code Playgroud)
[(0, 1, 'a'), (1, 1, 'b'), (3, 2, 'd')]
Run Code Online (Sandbox Code Playgroud)

扫描和书写

为了防止整个文件被存储,我有一个扫描方法,该方法将一次读取yield每个文件的一行。

list(merge(iter([(0, 1), (1, 1), (3, 2)]), 
  iter([(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')])))
Run Code Online (Sandbox Code Playgroud)

然后你可以用这种方式解决你的问题(未经测试,我没有你的文件)

with open('pivoted.tsv', 'w') as fout:
    t1 = scan_by_name(f1a, f1b)
    t2 = scan_by_name(f2a, f2b)
    for row in merge(t1, t2):
        print('\t'.join(row), end='\n', file=fout)
Run Code Online (Sandbox Code Playgroud)