Найти и удалить дубликаты в файле CSV - PullRequest
1 голос
/ 06 апреля 2019

У меня большой файл 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

Ответы [ 4 ]

4 голосов
/ 06 апреля 2019

Можете ли вы справиться с 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.

2 голосов
/ 07 апреля 2019

Из описания проблемы, мандат для строки, которая НЕ должна быть пропущена, - это когда первое и второе поля в любом порядке при объединении должны быть уникальными.Если это так, то ниже 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 голосов
/ 06 апреля 2019

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

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

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})")

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

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

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

0 голосов
/ 06 апреля 2019

Если вы хотите использовать саму библиотеку 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...