Вывод re.findall() в CSV в дополнение ко всем входным данным

Я пытаюсь сохранить результаты поиска regex .findall() в csv, но у меня возникают трудности с добавлением результата в выходной файл.

Поскольку я все еще очень новичок в Python, я пытаюсь ограничить эту проблему использованием только библиотек csv и re, но если есть гораздо более простой способ (например, в pandas), это также было бы полезно знать.


  1. Как можно скопировать все содержимое входного CSV в выходной CSV и добавить почтовый индекс/найденное регулярное выражение в строку, в которой оно было найдено?

  2. Существуют ли какие-либо очевидные формы проверки ошибок или другие вещи, которые мне не хватает?

  3. Существует ли лучший метод для автоматического добавления заголовка входного CSV в выходной CSV без их явного указания?

  4. Можно ли это сделать с помощью DictWriter? Как я изначально и пытался.


import csv, re

pattern = r'[A-Z]{1,2}[0-9R][0-9A-Z]?[0-9][A-Z]{2}'
postcodes = []
with open(r'Postcode/addressin.csv', 'r') as csvinput:
    csv_reader = csv.DictReader(csvinput)

    with open(r'Postcode/addressout.csv', 'w', newline='') as csvoutput:
        fieldnames = ['Address', 'Name']
        csv_writer = csv.writer(csvoutput)

        csv_writer.writerow(fieldnames)

        for line in csv_reader:
            postcodes = re.findall(pattern, line["Address"])
            csv_writer.writerow(postcodes)

Пример данных:

Address,Name,Lat,Long,2016 Sales,Type
48  Park Avenue, LATTON, SN6 4SZ,Nikki Yellowbeard,-23.17549,36.74641,9727,AA
IV21 1TD 116  Walwyn Rd CHARLESTOWN,Jonh Doe,-10.98309,156.41854,11932,AE

person Scottie    schedule 30.06.2019    source источник
comment
предоставление примера данных для входных и выходных файлов CSV было бы полезно при ответе на вопрос.   -  person dralshehri    schedule 30.06.2019


Ответы (3)


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

(.*),(.*),\s*([0-9.-]+)\s*,\s*([0-9.]+)\s*,([0-9]{4,5}(?:-[0-9]{4})?)\s*,\s*([A-Z]{2})

может быть подходом для изучения.


Демо


Почтовые индексы США обычно имеют следующие форматы:

([0-9]{5}(?:-[0-9]{4})?)

просто для демонстрации я включил:

[0-9]{4,5}

что вы можете просто удалить это.

Пример

import re

regex = r"(.*),(.*),\s*([0-9.-]+)\s*,\s*([0-9.]+)\s*,([0-9]{4,5}(?:-[0-9]{4})?)\s*,\s*([A-Z]{2})"

test_str = ("Address,Name,Lat,Long,2016 Sales,Type\n"
    "48  Park Avenue, LATTON, SN6 4SZ,Nikki Yellowbeard,-23.17549,36.74641,9727,AA\n"
    "IV21 1TD 116  Walwyn Rd CHARLESTOWN,Jonh Doe,-10.98309,156.41854,11932,AE")

matches = re.finditer(regex, test_str, re.MULTILINE)

for matchNum, match in enumerate(matches, start=1):
    
    print ("Match {matchNum} was found at {start}-{end}: {match}".format(matchNum = matchNum, start = match.start(), end = match.end(), match = match.group()))
    
    for groupNum in range(0, len(match.groups())):
        groupNum = groupNum + 1
        
        print ("Group {groupNum} found at {start}-{end}: {group}".format(groupNum = groupNum, start = match.start(groupNum), end = match.end(groupNum), group = match.group(groupNum)))

Если бы мы не проверяли значения, то просто это выражение

(.*),(.*),(.*),(.*),(.*),(.*)

возможно, сработает.

Демо

person Emma    schedule 30.06.2019
comment
Интересно, но выражение регулярного выражения, которое я использую, похоже, работает хорошо. Основная проблема заключается в экспорте конечного результата в csv с добавлением почтового индекса в отдельный столбец с каждой строкой. - person Scottie; 30.06.2019
comment
Регулярное выражение выглядит как одно из почтовых индексов Великобритании или Канады (или, я предполагаю, случайных других нынешних или бывших стран-членов Содружества). Принуждение почтовых индексов только для США к данным, в которых их явно нет, просто кажется империалистическим. (Спросите нас о попытках написать наши адреса в формах заказов на американских сайтах!) - person tripleee; 01.07.2019

вам, вероятно, лучше прочитать ваш входной CSV-файл во фрейм данных, а затем использовать pandas.str.extract() для извлечения вашего почтового кода из столбца адреса.

  1. читать csv: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html
  2. извлечь почтовый индекс: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.extract.html
  3. напишите csv: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html
person Raphael    schedule 30.06.2019
comment
Это полезно для дальнейшего использования. Все еще надеюсь узнать, существует ли решение только с csv и re. - person Scottie; 30.06.2019

CSV в вашем образце недействителен; похоже, что вам не хватает кавычек вокруг поля адреса.

Кроме того, re.findall() может возвращать более одного результата - CSV не может вместить более одного значения в столбец (и когда вы пытаетесь, вы попадаете в беспорядок, из которого пытаетесь выбраться сейчас); в общем случае лучшим решением является нормализация ваших данных, чтобы каждое поле содержало минимальную атомарную часть данных, которую нельзя разделить на более мелкие единицы информации.

Если вы пытаетесь представить вложенные или иерархические данные, возможно, обратите внимание на JSON или XML вместо CSV в качестве формата хранения.

С учетом этого, вот рефакторинг, который добавляет одно поле в конец каждой строки и встраивает в это поле список почтовых индексов, разделенных точкой с запятой (или вообще ничего, если совпадение с регулярным выражением было неудачным).

import csv, re

# Precompile the pattern
pattern = reccompile(r'[A-Z]{1,2}[0-9R][0-9A-Z]?[0-9][A-Z]{2}')

with open(r'Postcode/addressin.csv', 'r') as csvinput, open(r'Postcode/addressout.csv', 'w') as csvoutput:
    csv_reader = csv.DictReader(csvinput)
    csv_writer = csv.writer(csvoutput)

    outputfieldnames = ['Address', 'Name', 'Postcode']
    csv_writer.writerow(outputfieldnames)

    for line in csv_reader:
        postcodes = ';'.join(pattern.findall(line["Address"]))
        csv_writer.writerow([line["Address"], line["Name"], postcodes])
person tripleee    schedule 01.07.2019