#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. Спасибо! Я удалил #. Пожалуйста, посмотрите мой ответ Франциско.