Python 3从已排序的大型文件中连接数据

时间:2015-06-30 00:13:54

标签: python python-3.x

我有多个大文件(> 5M行数据),这些文件按唯一时间戳排序。所有文件几乎包含所有相同的时间戳,除了少数随机丢失的行(< 1000)。我想有效地将​​所有文件中的数据加入到每个时间戳一行的单个数据集中,最好使用生成器。

除了缺少的行,我可以使用zip:

def get_data(list_of_iterables):
    for data in zip(*list_of_iterables):
        yield data

但是,由于缺少一些行,我需要在时间戳上加入数据,而不是简单地压缩。我可以简单地忽略每个文件中没有匹配时间戳的任何行。

有几种pythonic方式可以在几行中实现此功能吗?

我的方法是依次推进每个迭代,直到它的时间戳不再小于迭代组的最大时间戳。每当所有时间戳匹配时,产生一行并推进所有迭代。但是,当我尝试实现这种方法时,逻辑看起来很混乱。

编辑:表现。

实现需要开始返回行而不先将所有数据读入内存。读取所有数据需要一段时间,很多时候只需要检查第一批数据。

4 个答案:

答案 0 :(得分:1)

我最后编写了以下代码来解决我的问题,结果证明比我预期的要轻:

def advance_values(iters):
    for it in iters:
        yield next(it)

def align_values(iters, values, key):
    for it, value in zip(iters, values):
        while (value[0],value[1]) < key:
            value = next(it)
        yield value

def merge_join(*iters):
    values = list(advance_values(iters))
    while True:
        if len(values) != len(iters):
            return
        tms = [(v[0],v[1]) for v in values]
        max_tm = max(tms)
        if all((v[0],v[1]) == max_tm for v in values):
            yield values
            values = list(advance_values(iters))
        else:
            values = list(align_values(iters, values, max_tm))

答案 1 :(得分:1)

如果list_of_iterables中的每个可迭代按timestamp排序,那么您可以使用heapq.merge()合并它们,同时考虑数据中的可能差距,并itertools.groupby()将记录与相同的时间戳:

from heapq import merge
from itertools import groupby
from operator import attrgetter

for timestamp, group in groupby(merge(*list_of_iterables), 
                                key=attrgetter('timestamp')):
    print(timestamp, list(group)) # same timestamp

实现产生组而不首先将所有数据读入内存。

答案 2 :(得分:0)

我的第一个猜测是使用带有时间戳作为键的字典,将行中的其余数据作为值,然后对于每个文件中的每一行,仅当具有相同时间戳的项目时才将其添加到字典中( key)还没有出现。

但是,如果你真的在处理巨型数据集(在这种情况下似乎就是这样),那么你在原始问题中提到的方法将是你最好的选择。

答案 3 :(得分:0)

好吧,我对这个问题很感兴趣(最近遇到了类似的问题),并且对它有所了解。你可以尝试这样的事情:

import io
import datetime
from csv import DictReader

file0 = io.StringIO('''timestamp,data
2015-06-01 10:00, data00
2015-06-01 11:00, data01
2015-06-01 12:00, data02
2015-06-01 12:30, data03
2015-06-01 13:00, data04
''')

file1 = io.StringIO('''timestamp,data
2015-06-01 09:00, data10
2015-06-01 10:30, data11
2015-06-01 11:00, data12
2015-06-01 12:30, data13
''')

class Data(object):

    def __init__(self):
        self.timestamp = None
        self.data = None

    @staticmethod
    def new_from_dict(dct=None):
        if dct is None:
            return None
        ret = Data()
        ret.data = dct['data'].strip()
        ret.timestamp = datetime.datetime.strptime(dct['timestamp'],
                                                   '%Y-%m-%d %H:%M')
        return ret

    def __lt__(self, other):
        if other is None:
            return False
        return self.timestamp < other.timestamp

    def __gt__(self, other):
        if other is None:
            return False
        return self.timestamp > other.timestamp

    def __str__(self):
        ret = '{0.__class__.__name__}'.format(self) +\
              '(timestamp={0.timestamp}, data={0.data})'.format(self)
        return ret


def next_or_none(reader):
    try:
        return Data.new_from_dict(next(reader))
    except StopIteration:
        return None


def yield_in_order(reader0, reader1):

    data0 = next_or_none(reader0)
    data1 = next_or_none(reader1)

    while not data0 == data1 == None:

        if data0 is None:
            yield None, data1
            data1 = next_or_none(reader1)
            continue
        if data1 is None:
            yield data0, None
            data0 = next_or_none(reader0)
            continue

        while data0 < data1:
            yield data0, None
            data0 = next_or_none(reader0)

        while data0 > data1:
            yield None, data1
            data1 = next_or_none(reader1)

        if data0 is not None and data1 is not None:
            if data0.timestamp == data1.timestamp:
                yield data0, data1
                data0 = next_or_none(reader0)
                data1 = next_or_none(reader1)

csv0 = DictReader(file0)
csv1 = DictReader(file1)

FMT = '{!s:50s} | {!s:50s}'
print(FMT.format('file0', 'file1'))
print(101*'-')
for dta0, dta1 in yield_in_order(csv0, csv1):
    print(FMT.format(dta0, dta1))

这仅适用于2个文件。