Удаление дублирующихся строк в миллионах сжатых файлов CSV, сохраняя при этом один фрагмент информации из дублированных строк - PullRequest
0 голосов
/ 11 июля 2019

Имеет коллекцию ~ 10 миллионов GZipped CSV-файлов, каждый из которых имеет от 100 до 1000 строк и> 2000 столбцов.Каждый файл также содержит заголовок.

В каждом CSV-файле есть два важных столбца: «ID» и «target».

Я пытаюсь удалить строки с дубликатом «target»но сохраните идентификатор из строки, которая будет удалена, со строкой, которая не будет удалена.

Например,

Ввод:

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) или тому подобного, но надеялся, что кто-то мог бы иметь намного лучший способ сделать это для миллионов файлов с миллиардами записей.

Ответы [ 2 ]

2 голосов
/ 11 июля 2019

Я написал бы программу, которая проходит через gsipped csv-файл и записывает следующие 4 столбца: target id file row.

Запустите это для каждого файла, и вы получите ~ 10 миллионов маленьких файлов.

Предполагая, что вы НЕ делаете это распределенным способом, я бы затем объединил файлы в один большой файл и отсортировал его с помощью утилиты сортировки unix.(Предупреждение, что вы хотите сделать LC_ALL=C sort foo.txt, потому что локаль C работает быстрее и дает более разумные результаты. См. sort, сортировка не соответствует ожидаемой (пробел и локаль) для получения дополнительной информации.)

Теперьэтот файл легко обработать и решить, какой из них оставить.Вы можете записать файл со столбцами file row target id is_keep removed_ids.Обязательно напишите строку с ведущими нулями, поэтому вместо 42 вы бы написали 000042.removed_ids - это те, которые вы удалили из других файлов, если сохранили этот.(Количество старших нулей должно быть достаточно для вашего самого большого файла. То есть, чтобы асцибетический порядок соответствовал числовому порядку.)

Сортируйте этот файл еще раз, а затем разбейте его на файлы для каждого решения.

Учитывая исходный файл gzip и этот файл, какие строки хранить, и какие идентификаторы хранить, если вы сохраните его, легко обработать ваши исходные файлы, чтобы удалить / сохранить строки и записать то, что вы удалили.Я настоятельно рекомендую проверить правильность проверки, что все target / id / row совпадают.И не удаляйте оригиналы, если не пройдена эта проверка работоспособности.


Если вы большой поклонник распределенной обработки, перевод от сортировки к уменьшению карты прост.Если у вас есть инфраструктура для этого, вы можете использовать ее.Если нет, я бы предложил этот подход с сортированными файлами, используя только распараллеливание для обработки всех отдельных файлов в первый / последний.

1 голос
/ 11 июля 2019

Несмотря на то, что объем данных может показаться ошеломляющим, я думаю, что вы можете последовательно перебирать все файлы, если сохраняете только то количество данных, которое вам нужно.Например, вы можете отслеживать отношения с уникальными целями с первым идентификатором с такой целью и отношением псевдонимов 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']

Обратите внимание, что наличие dict с парами ключ-значение 10М является чем-то идеально подходящим.

Если это все еще не подходитв памяти вы можете использовать shelve вместо dicts, чтобы соответствующие данные сохранялись на жестком диске.Вы могли бы сделать что-то вроде:

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 в отношении обычной скорости -

...