【问题标题】:Removing duplicate rows across millions of compressed CSV files while keeping one piece of information from duplicated rows删除数百万个压缩 CSV 文件中的重复行,同时保留重复行中的一条信息
【发布时间】:2019-11-20 13:49:12
【问题描述】:

拥有约 1000 万个 GZipped CSV 文件的集合,每个文件都包含 100-1000 行和 >2000 列。每个文件还包含一个标题。

在每个 CSV 文件中都有两个重要的列,“ID”和“目标”。

我正在尝试删除具有重复“目标”的行,但将要删除的行中的 ID 与不会被删除的行一起保留。

例如

输入:

CSV1
|   ID  |  Target                      |
|-------|------------------------------|
| IX213 | C1=CC(=CC=C1CC(=O)C(=O)O)O   |
| IX412 | CN1C=NC2=C1C(=O)N(C(=O)N2C)C |

CSV2
|   ID  |  Target                      |
|-------|------------------------------|
| BC144 | CN1C=NC2=C1C(=O)N(C(=O)N2C)C |
| BC155 | C(CC(=O)O)C(C(=O)O)N         |

输出:

CSV1*
|   ID         |  Target                      |
|--------------|------------------------------|
| IX213        | C1=CC(=CC=C1CC(=O)C(=O)O)O   |
| IX412; BC144 | CN1C=NC2=C1C(=O)N(C(=O)N2C)C |

CSV2*
|   ID  |  Target                      |
|-------|------------------------------|
| BC155 | C(CC(=O)O)C(C(=O)O)N         |

这对于使用 Pandas (Python) 等的少量文件来说是很简单的,但希望有人可能有更好的方法来处理具有数十亿条目的数百万个文件。

【问题讨论】:

  • 您是只成对合并文件,还是需要将所有文件合并到第一个?
  • 您是否探索过为此使用数据库?假设这不是您将对这些数据执行的唯一操作,对其进行规范化和索引以进行随机访问应该会在便利性和执行速度方面提供快速而可观的回报(即使像我一样,您并不特别喜欢 SQL )。
  • # 您是只合并文件成对,还是需要将所有文件合并到第一个? # 对所有条目进行重复数据删除并将所有重复项合并为一个。 - 没有尝试过为此使用 SQL,但我应该硬着头皮去做这件事可能是对的,因为我将不止一次使用它。

标签: python algorithm hash duplicates


【解决方案1】:

我会编写一个程序,它会遍历一个 gzip 压缩的 csv 文件并写入以下 4 列:target id file row

对每个文件运行此程序,您将获得约 1000 万个小文件。

假设您以分布式方式执行此操作,接下来我会将文件合并为一个大文件,并使用 unix 排序实用程序对其进行排序。 (警告您要执行 LC_ALL=C sort foo.txt,因为 C 语言环境更快,并且产生更合理的结果。有关更多信息,请参阅 sort not sorting as expected (space and locale)。)

现在处理该文件并决定保留哪个文件很容易。您可以使用列file row target id is_keep removed_ids 写出一个文件。确保用前导零写出该行,因此您应该写000042 而不是42removed_ids 是您从其他文件中删除的那些,如果您保留了这个。 (前导零的数量应该足以满足您最大的文件。即使 asciibetical 顺序匹配数字顺序。)

再次对该文件进行排序,然后将其分解为每个文件决定的文件。

鉴于原始 gzip 文件以及该文件要保留哪些行,以及如果您保留它要存储哪些 ID,处理您的原始文件以删除/保留行并记录您删除的内容很容易。我强烈建议进行完整性检查以验证目标/ID/行是否全部匹配。除非完整性检查通过,否则不要删除原件。


如果您是分布式处理的忠实拥护者,那么从排序到 map-reduce 的转换非常简单。如果您有该设置的基础架构,则不妨使用它。如果没有,我会建议这种排序文件方法,仅使用并行化处理第一个/最后一个的所有单个文件。

【讨论】:

  • 完全正确:使用现有的工具。甚至可能有一个标准工具可以提取字段。例如,csvtool 在 Ubuntu 中可用。我在这里唯一要更改的是将初始程序附加到单个文件,而不是写入必须然后组合的单个文件。
  • @JimMischel 我考虑了单个文件与多个文件,做了一个背面的信封,发现我们正在处理数 TB 的数据,并采用了让我们利用这一事实的方法第一步和最后一步令人尴尬地平行。
【解决方案2】:

尽管数据量可能看起来很庞大,但我认为如果您保留所需的适量数据,您可以按顺序迭代所有文件。例如,您可以跟踪与具有此类目标的第一个 ID 和 ID 别名 关系的唯一目标的关系(例如,ID IX412 对应于 BC144)。这样,您的解决方案可能如下所示:

import csv

filenames = [...]
target_ids = {}
aliases = {}

for filename in filenames:
    with open(filename, 'r') as file_in:
        reader = csv.DictReader(file_in)
        for row in reader:
            if row['Target'] in target_ids:
                aliases[row['ID']] = target_ids[row['Target']]
                remove_row(row)  # Do whatever you may require
            else:
                target_ids[row['Target']] = row['ID']

请注意,拥有 10M 键值对的 dict 非常容易处理。

如果这仍然不适合内存,您可以使用 shelve 而不是 dicts,以便将相应的数据存储在 HDD 中。你可以这样做:

import csv
import shelve

filenames = [...]

with shelve.open('target_ids') as target_ids, shelve.open('aliases') as aliases:
    for filename in filenames:
        with open(filename, 'r') as file_in:
            reader = csv.DictReader(file_in)
            for row in reader:
                if row['Target'] in target_ids:
                    aliases[row['ID']] = target_ids[row['Target']]
                    remove_row(row)  # Do whatever you may require
                else:
                    target_ids[row['Target']] = row['ID']

shelve 的缺点在于常规 dict 的速度。

【讨论】:

  • 您需要保持 2³² ID 的顺序,如果保持 单个大小写字母+数字,则每个 ID 至少 6 或 7 个字符 - 大约 60 GB。加上与“目标”值一样多的>2000 columns 行。 2019 年单节点陡峭。
  • @greybeard 请注意,我们不需要使用这种方法跟踪完整的行。最坏的情况(根本没有重复的目标):我们将有大约 10M *(100 到 1000)个 ID-Target 对。仍然它可能不适合记忆,如果是这样,我会用书架替换字典
  • we don't need to keep track of the full rows 我读到remove the rows with duplicate "target" 每个“目标”保留一整行 - “我们”可以在第二遍中创建所需的输出,写入所有 ID遇到的每个“目标”以及行的其余部分如果“目标”在查找中,则立即将其删除。
  • 可以试一试,绝对最大值应该是 2.1B 密钥对,假设没有任何重复,但实际估计应该更像 ~1.3B
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 2019-01-30
  • 2016-09-05
  • 2014-06-23
  • 1970-01-01
  • 2020-06-30
  • 2017-12-08
相关资源
最近更新 更多