Сравните 2 .CSV с неизвестным количеством столбцов и имен

#python #csv

#python #csv

Вопрос:

и заранее спасибо за любые советы. Я здесь впервые, поэтому я сделаю все возможное, чтобы добавить всю необходимую информацию. Я также новичок в Python, занимался некоторыми онлайн-уроками и копированием / вставкой кода из StackOverflow, это FrankenCoding… Так что я, вероятно, неправильно подхожу к этому…

Мне нужно сравнить два файла CSV, в которых будет изменяться количество столбцов, совпадающих будет только 2 столбца (например, email_address в одном файле и EMAIL в другом). Оба файла будут иметь заголовки, однако имена этих заголовков могут измениться. Размеры файлов могут составлять от нескольких тысяч строк до 2 000 000, потенциально более 100 столбцов (но, скорее всего, их будет несколько).

Выводится в третий файл results.csv, содержащий всю информацию. Это может быть слияние (все уникальные записи), вычитание (удаление записей, присутствующих в одной или другой) или пересечение (все записи, присутствующие в обеих).

Я искал здесь и нашел много полезной информации, но все те, которые я видел, имели фиксированное количество столбцов в файлах. Я пробовал dict и dictreader, и я знаю, что ответ где-то там, но сейчас я немного смущен. Но поскольку я не добился никакого прогресса за несколько дней, и я могу посвятить этому не так много времени, я надеюсь, что смогу получить толчок в правильном направлении.

В идеале я хочу научиться делать это сам, что означает понимание того, как данные «перемещаются».

Извлечение CSV-файлов ниже, я не добавил больше столбцов, чем (я думаю) необходимо, набор данных, который у меня есть сейчас, будет соответствовать Originalid / UID или emailaddress / email, но это может быть не всегда так.

Original.csv

 "originalid","emailaddress",""
"12345678","Bob@mail.com",""
"23456789","NORMA@EMAIL.COM",""
"34567890","HENRY@some-mail.com",""
"45678901","Analisa@sports.com",""
"56789012","greta@mail.org",""
"67890123","STEVEN@EMAIL.ORG",""
 

Compare.CSV

 "email","","DATEOFINVALIDATION_WITH_TIME","OPTOUTDATE_WITH_TIME","EMAIL_USERS"
"Bob@mail.com",,,"true"
"NORMA@EMAIL.COM",,,"true"
"HENRY@some-mail.com",,,"true"
"Henrietta@AWESOME.CA",,,"true"
"NORMAN@sports.CA",,,"true"
"albertina@justemail.CA",,,"true"
 

Данные в results.csv должны содержать все столбцы из оригинала.CSV все столбцы в Compare.csv, но не соответствующий (электронная почта) :

 "originalid","emailaddress","","DATEOFINVALIDATION_WITH_TIME","OPTOUTDATE_WITH_TIME","EMAIL_USERS"
"12345678","Bob@mail.com","",,,"true"
"23456789","NORMA@EMAIL.COM","",,,"true"
"34567890","HENRY@some-mail.com","",,,"true"
 

Вот мои результаты в том виде, в каком они есть сейчас:

 email,,DATEOFINVALIDATION_WITH_TIME,OPTOUTDATE_WITH_TIME,EMAIL_USERS
Bob@mail.com,,,true,"['12345678', 'Bob@mail.com', '']"
NORMA@EMAIL.COM,,,true,"['23456789', 'NORMA@EMAIL.COM', '']"
HENRY@some-mail.com,,,true,"['34567890', 'HENRY@some-mail.com', '']"
 

И вот где я нахожусь с кодом, оператор print возвращает соответствующие данные из файлов на экран, но не в файл, поэтому я что-то там упускаю.
***** И я не получаю заголовки из исходного файла.csv, данные поступают.

 import csv

def get_column_from_file(filename, column_name):
    f = open(filename, 'r')
    reader = csv.reader(f)
    headers = next(reader, None)
    i = 0
    max = (len(headers))
    while i < max:
        if headers[i] == column_name:
            column_header = i
 #       print(headers[i])
        i = i   1
    return(column_header)

file_to_check = "Original.csv"
file_console = "Compare.csv"

column_to_read = get_column_from_file(file_console, 'email')
column_to_compare = get_column_from_file(file_to_check, 'emailaddress')

with open(file_console, 'r') as master:
    master_indices = dict((r[1], r) for i, r in enumerate(csv.reader(master)))

with open('Compare.csv', 'r') as hosts:
    with open('results.csv', 'w', newline='') as results:
        reader = csv.reader(hosts)
        writer = csv.writer(results)

        writer.writerow(next(reader, []))

        for row in reader:
            index = master_indices.get(row[0])
            if index is not None:
                print (row  [master_indices.get(row[0])])
                writer.writerow(row  [master_indices.get(row[0])])
 

Спасибо за ваше время!

Pat

Комментарии:

1. Может ли быть так, что последняя строка прокомментирована?

2. 🙂 Используйте инструкцию print, чтобы подтвердить, что я получаю нужные данные, я забыл удалить этот #. Тем не менее, «writer» по-прежнему возвращает меньше данных в файл результатов, после чего инструкция print возвращается на экран. Спасибо!

Ответ №1:

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

Вы не пытаетесь создать инструмент, который объединяет два CSV, вы пытаетесь организовать данные (которые поступают из CSV) в соответствии с общей ссылкой (адрес электронной почты) и вывести результат в формате CSV. Поскольку вы говорите о потенциально больших наборах данных ( 2 000 000 [строк] с потенциально 100 столбцами), признайте, что важно обращать внимание на асимптотическую среду выполнения. Если вы не знаете, что это такое, я рекомендую вам ознакомиться с нотацией Big-O и анализом асимптотических алгоритмов. Возможно, вы справитесь и без этого.

Сначала вы решаете, что из каждого CSV является вашим ключом. Вы уже сделали это, ’email’ для ‘Compare.csv’ и ’emailaddress’ из ‘Original.csv’. Теперь создайте себе функцию для создания словарей из CSV на основе ключа.

 def get_dict_from_csv(path_to_csv, key):
    with open(path_to_csv, 'r') as f:
        reader = csv.reader(f)
        headers, *rest = reader  # requires python3
    key_index = headers.index(key)  # find index of key
    # dictionary comprehensions are your friend, just think about what you want the dict to look like
    d = {row[key_index]: row[:key_index]   row[key_index 1:]  #  1 to skip the email entry
         for row in rest}
    headers.remove(key)
    d['HEADERS'] = headers  # add headers so you know what the information in the dict is
    return d
 

Теперь вы можете вызвать эту функцию для обоих ваших CSV.

 file_console_dict = get_dict_from_csv('Compare.csv', 'email')
file_to_check_dict = get_dict_from_csv('Original.csv', 'emailaddress')
 

Теперь у вас есть два dicts, которые содержат одну и ту же информацию. Теперь нам нужна функция для объединения их в один dict.

 def combine_dicts(*dicts):
    d, *rest = dicts  # requires python3
    # iteratively pull other dicts into the first one, d
    for r in rest:
        original_headers = d['HEADERS'][:]
        new_headers = r['HEADERS'][:]
        # copy headers
        d['HEADERS'].extend(new_headers)
        # find missing keys
        s = set(d.keys()) - set(r.keys())  # keys present in d but not in r
        for k in s:
            d[k].extend(['', ] * len(new_headers))
        del r['HEADERS']  # we don't want to copy this a second time in the loop below
        for k, v in r.items():
            # use setdefault in case the key didn't exist in the first dict
            d.setdefault(k, ['', ] * len(original_headers)).extend(v)
    return d

 

Теперь у вас есть один dict, который содержит всю нужную вам информацию, все, что вам нужно сделать, это записать его обратно в формате CSV.

 def write_dict_to_csv(output_file, d, include_key=False):
    with open(output_file, 'w', newline='') as results:
        writer = csv.writer(results)
        # email isn't in your HEADERS, so you'll need to add it
        if include_key:
            headers = ['email',]   d['HEADERS']
        else:
            headers = d['HEADERS']
        writer.writerow(headers)
        # now remove it from the dict so we can iterate over it without including it twice
        del d['HEADERS']
        for k, v in d.items():
            if include_key:
                row = [k,]   v
            else:
                row = v
            writer.writerow(row)
 

И это должно быть так. Вызвать все это просто

 file_console_dict = get_dict_from_csv('Compare.csv', 'email')
file_to_check_dict = get_dict_from_csv('Original.csv', 'emailaddress')
results_dict = combine_dicts(file_to_check_dict, file_console_dict)
write_dict_to_csv('results.csv', results_dict)
 

И вы можете легко увидеть, как это можно распространить на произвольное количество словарей.

Вы сказали, что не хотите, чтобы электронное письмо было в окончательном CSV. Для меня это нелогично, поэтому я сделал это опцией в write_dict_to_csv() на случай, если вы передумаете.

Когда я запускаю все вышеперечисленное, я получаю

 email,originalid,,,DATEOFINVALIDATION_WITH_TIME,OPTOUTDATE_WITH_TIME,EMAIL_USERS
Bob@mail.com,12345678,,,,true
NORMA@EMAIL.COM,23456789,,,,true
HENRY@some-mail.com,34567890,,,,true
Analisa@sports.com,45678901,,,,,
greta@mail.org,56789012,,,,,
STEVEN@EMAIL.ORG,67890123,,,,,
Henrietta@AWESOME.CA,,,,,true
NORMAN@sports.CA,,,,,true
albertina@justemail.CA,,,,,true
 

Комментарии:

1. О боже! Это выглядит потрясающе, и мне есть на что посмотреть! У вас нет времени протестировать его сегодня, но завтра первым делом! Огромное спасибо, Том!

2. Хорошо, я работаю над этим, и это так же здорово, как и изначально! Однако мне понадобится одно из 2 полей электронной почты в моих результатах. Я пытался сделать это сам, и я получаю все ЗАГОЛОВКИ, но не соответствующие им данные, я предполагаю, что это связано с индексом dict? Кроме того, эта строка заставляет меня ломать голову: d = {строка [key_index]: строка [:key_index] строка[key_index 1:] для строки в rest} . Я понимаю, что это k: v, но что происходит на стороне v? Спасибо миллион!

3. Если вы хотите включить электронное письмо в CSV (что, как я подозревал, вам понадобится), просто вызовите write_dict_to_csv() с include_key=True . Знакомы ли вы с необязательными параметрами в Python? Кроме того, строка, которую вы вызываете, может сбить с толку тех, кто не знаком с выразительным синтаксисом Python. Это называется пониманием словаря . Существуют также варианты понимания списков и генераторов, которые имеют схожий синтаксис. Я мог бы написать то же самое, что и для строки в rest: d.update({строка[key_index] : строка[:key_index] строка[key_index 1:]}).

4. Если вас смущает объединение списков, я пытаюсь присвоить значение электронной почте таким образом, чтобы оно пропускалось по электронной почте. Я знаю, в каком индексе находится электронное письмо в строке, оно находится в key_index . Объединение списков Python включает нижнюю границу, но не включает верхнюю границу (например, диапазон(5)[0:3] == [0, 1, 2]), итак, мы включаем первую половину строки, произнося строку[:key_index], а вторую половинустроки, выполнив строку[key_index 1:], пропустив key_index с плюсом 1. В python добавление двух списков просто объединяет их, как и следовало ожидать визуально.

5. Спасибо! Я установил для этого флага значение True, и я получаю все ЗАГОЛОВКИ, но не все данные. Я получаю «email» дважды (и у меня есть заголовок «адрес электронной почты», но нет данных для этого столбца.

Ответ №2:

Сейчас похоже, что вы используете writerow только один раз для заголовка:

 writer.writerow(next(reader, []))
 

Как отметил Франциско, раскомментирование этой последней строки может решить вашу проблему. Вы можете сделать это, удалив «#» в начале строки.

Комментарии:

1. Спасибо! Я удалил #. Пожалуйста, посмотрите мой ответ Франциско.