Найти и удалить дубликаты в файле CSV

1

У меня есть большой файл CSV (1,8 ГБ) с тремя столбцами. Каждая строка содержит две строки и числовое значение. Проблема в том, что они дублируются, но меняются местами. Пример:

Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
DEF,ABC,123

Желаемый результат будет выглядеть так:

Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454

потому что третья строка содержит ту же информацию, что и первая строка.

РЕДАКТИРОВАТЬ

Данные в основном выглядят так (строки в первых двух столбцах и числовое значение в третьем, 40 миллионов строк):

Blockquote

  • 2
    Вы пробовали что-нибудь?
  • 0
    Да, поиск уникальных строк и их фильтрация в командной строке unix, но это не работает.
Показать ещё 4 комментария
Теги:
csv
awk
duplicates

6 ответов

0
Лучший ответ

Примечание. Этот вопрос был задан до того, как OP изменил тег для тега .

Если вы не возражаете против порядка элементов, который вы можете сделать:

with open("in.csv", "r") as file:
    lines = set()
    for line in file:
        lines.add(frozenset(line.strip("\n").split(",")))

with open("out.csv", "w") as file:
    for line in lines:
        file.write(",".join(line)+"\n")

Выход:

Col2,COL1,Col3
EFG,454,ABC
DEF,123,ABC

Обратите внимание, что вы можете обработать первую строку (заголовки) особым образом, чтобы не потерять их порядок.

Но если порядок имеет значение, вы можете использовать код из раздела Поддержание порядка элементов в замороженном наборе:

from itertools import filterfalse

def unique_everseen(iterable, key=None):
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element        

with open("in.csv", "r") as file:
    lines = []
    for line in file:
        lines.append(line.strip("\n").split(","))

with open("out.csv", "w") as file:
    for line in unique_everseen(lines, key=frozenset):
        file.write(",".join(line)+"\n")

Выход:

COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454

ОП сказал, что оба кода, похоже, не работают с большими файлами (1,8 Гб). Я думаю, это может быть связано с тем, что оба кода хранят файл в списке с использованием оперативной памяти, а файл объемом 1,8 ГБ может занимать все доступное пространство в памяти.

Чтобы решить это, я сделал еще несколько попыток. К сожалению, я должен сказать, что все они очень медленные по сравнению с первой попыткой. Первые коды жертвуют потреблением оперативной памяти ради скорости, но следующие коды жертвуют скоростью, процессором и жестким диском для меньшего потребления оперативной памяти (вместо того, чтобы использовать весь размер файла в оперативной памяти, они занимают менее 50 Мб).

Поскольку во всех этих примерах требуется более интенсивное использование жесткого диска, желательно иметь файлы "input" и "output" на разных жестких дисках.

Моя первая попытка использовать меньше ОЗУ с модулем shelve:

import shelve, os
with shelve.open("tmp") as db:
    with open("in.csv", "r") as file:
        for line in file:
            l = line.strip("\n").split(",")
            l.sort()
            db[",".join(l)] = l

    with open("out.csv", "w") as file:
        for v in db.values():
            file.write(",".join(v)+"\n")

os.remove("temp.bak")
os.remove("temp.dat")
os.remove("temp.dir")

К сожалению, этот код занимает в сотни раз больше, чем первые два кода, которые используют оперативную память.

Еще одна попытка:

with open("in.csv", "r") as fileRead:
    # total = sum(1 for _ in fileRead)
    # fileRead.seek(0)
    # i = 0
    with open("out.csv", "w") as _:
        pass
    with open("out.csv", "r+") as fileWrite:
        for lineRead in fileRead:
            # i += 1
            line = lineRead.strip("\n").split(",")
            lineSet = set(line)
            write = True
            fileWrite.seek(0)
            for lineWrite in fileWrite:
                if lineSet == set(lineWrite.strip("\n").split(",")):
                    write = False
            if write:
                pass
                fileWrite.write(",".join(line)+"\n")
            # if i / total * 100 % 1 == 0: print(f"{i / total * 100}% ({i} / {total})")

Это немного быстрее, но не намного.

Если ваш компьютер имеет несколько ядер, вы можете попробовать использовать многопроцессорность:

from multiprocessing import Process, Queue, cpu_count
from os import remove

def slave(number, qIn, qOut):
    name = f"slave-{number}.csv"
    with open(name, "w") as file:
        pass
    with open(name, "r+") as file:
        while True:
            if not qIn.empty():
                get = qIn.get()
                if get == False:
                    qOut.put(name)
                    break
                else:
                    write = True
                    file.seek(0)                    
                    for line in file:
                        if set(line.strip("\n").split(",")) == get[1]:
                            write = False
                            break
                    if write:
                        file.write(get[0])

def master():
    qIn = Queue(1)
    qOut = Queue()
    slaves = cpu_count()
    slavesList = []

    for n in range(slaves):
        slavesList.append(Process(target=slave, daemon=True, args=(n, qIn, qOut)))
    for s in slavesList:
        s.start()

    with open("in.csv", "r") as file:
        for line in file:
            lineSet = set(line.strip("\n").split(","))
            qIn.put((line, lineSet))
        for _ in range(slaves):
            qIn.put(False)

    for s in slavesList:
        s.join()

    slavesList = []

    with open(qOut.get(), "r+") as fileMaster:
        for x in range(slaves-1):
            file = qOut.get()
            with open(file, "r") as fileSlave:
                for lineSlave in fileSlave:
                    lineSet = set(lineSlave.strip("\n").split(","))
                    write = True
                    fileMaster.seek(0)
                    for lineMaster in fileMaster:
                        if set(lineMaster.strip("\n").split(",")) == lineSet:
                            write = False
                            break
                    if write:
                        fileMaster.write(lineSlave)

            slavesList.append(Process(target=remove, daemon=True, args=(file,)))
            slavesList[-1].start()

    for s in slavesList:
        s.join()

Как вы видите, у меня есть неутешительная задача сказать вам, что обе мои попытки работают очень медленно. Я надеюсь, что вы найдете лучший подход, в противном случае для выполнения операций с 1,8 ГБ данных потребуются часы, если не дни (реальное время будет в первую очередь зависеть от количества повторных значений, что сокращает время).

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

lines = set()
maxLines = 1000 # This is the amount of lines that will be stored at the same time on RAM. Higher numbers are faster but requeires more RAM on the computer
perfect = True
with open("in.csv", "r") as fileRead:
    total = sum(1 for _ in fileRead)
    fileRead.seek(0)
    i = 0
    with open("tmp.csv", "w") as fileWrite:            
        for line in fileRead:
            if (len(lines) < maxLines):                    
                lines.add(frozenset(line.strip("\n").split(",")))
                i += 1
                if i / total * 100 % 1 == 0: print(f"Reading {i / total * 100}% ({i} / {total})")
            else:
                perfect = False
                j = 0
                for line in lines:
                    j += 1
                    fileWrite.write(",".join(line) + "\n")
                    if i / total * 100 % 1 == 0: print(f"Storing {i / total * 100}% ({i} / {total})")
                lines = set()

if (not perfect):
   use_one_of_the_above_methods() # Remember to read the tmp.csv and not the in.csv

Это может повысить скорость. Вы можете изменить maxLines на любое число, которое вам нравится, помните, что чем выше число, тем выше скорость (не уверен, что действительно большие числа делают наоборот), но выше потребление оперативной памяти.

  • 2
    Привет, Эндер. Смотри, спасибо, это тоже работает. Так что в случае, если кто-то является большим поклонником Python -> Этот ответ также работает. Я предпочел ответ Джеймса Брауна только по той причине, что на моей машине он работает быстрее, а файл очень большой, поэтому для его запуска требуется время. Но спасибо nonethelles. :)
  • 0
    Не волнуйтесь @BeBo, производительность очень важна! Специально для данных объемом 1,8 ГБ! Кстати, я думаю, вам не следует удалять тег python для вашего вопроса, поскольку он делает недействительными все предыдущие ответы.
Показать ещё 15 комментариев
4

Можете ли вы справиться с awk:

$ awk -F, '++seen[$3]==1' file

Выход:

COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454

Explaied:

$ awk -F, '      # set comma as field delimiter
++seen[$3]==1    # count instances of the third field to hash, printing only first
' file

Обновление:

$ awk -F, '++seen[($1<$2?$1 FS $2:$2 FS $1)]==1' file

Выход:

COL1,Col2,Col3
ABC,DEF,123
ABC,EFG,454

Он хэширует каждую встреченную комбинацию первого и второго полей, так что "ABC,DEF"=="DEF,ABC" и считает их, печатая только первое. ($1<$2?$1 FS $2:$2 FS $1): если первое поле меньше второго, то хеш 1st,2nd - 2nd,1st хэш 2nd,1st.

  • 0
    По какой-то причине это фильтрует только первые 6000 записей. и удаляет остальное или сбрасывает его.
  • 0
    Интересно. Какой у тебя есть awk?
Показать ещё 12 комментариев
2

Исходя из описания проблемы, мандат для строки, которая НЕ пропускается, - это когда первое и второе поля в любом порядке при объединении должны быть уникальными. Если так, то ниже awk поможет

awk -F, '{seen[$1,$2]++;seen[$2,$1]++}seen[$1,$2]==1 && seen[$2,$1]==1' filename

Пример ввода

Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
DEF,ABC,123
GHI,ABC,123
DEF,ABC,123
ABC,GHI,123
DEF,GHI,123

Пример вывода

Col1,Col2,Col3
ABC,DEF,123
ABC,EFG,454
GHI,ABC,123
DEF,GHI,123
  • 0
    Благодарю. Это похоже на работу с моим набором тестовых данных. По какой-то странной причине он создает тот же выходной файл с моим фактическим набором данных: тот же размер, те же записи. Почему это не работает с моим большим файлом данных? Я что-то пропустил? :-(
  • 0
    @BeBo Не уверен, что реальная причина. Также вы можете принять более идиотский ответ от \ @ james-brown, который первым решил вашу проблему. Это просто еще одна версия этого ответа, по сути, делающая то же самое.
Показать ещё 4 комментария
0

Вы должны сделать некоторую сортировку вашего файла, найти дубликаты будет очень легко.

0

Если вы хотите использовать саму библиотеку csv: -

Вы можете использовать DictReader и DictWriter.

Import csv
 def main():
 """Read csv file, delete duplicates and write it.""" 
     with open('test.csv', 'r',newline='') as inputfile: 
           with open('testout.csv', 'w', newline='') as outputfile: 
               duplicatereader = csv.DictReader(inputfile, delimiter=',') 
               uniquewrite = csv.DictWriter(outputfile, fieldnames=['address', 'floor', 'date', 'price'], delimiter=',') 
                uniquewrite.writeheader()
                keysread = []
               for row in duplicatereader:
                     key = (row['date'], row['price'])
                     if key not in keysread:
                              print(row) 
                              keysread.append(key)
                              uniquewrite.writerow(row)
 if __name__ == '__main__': 
     main()
-1
  • Прочитайте файл CSV
  • Для каждой строки в CSV:
    • Сортировать элементы в строке
    • Используйте эту отсортированную строку, чтобы сохранить строку в словаре
    • Если отсортированная строка уже есть в словаре, пропустите эту строку
  • Распечатайте значения в словаре

Ещё вопросы

Сообщество Overcoder
Наверх
Меню