Чтение и запись CSV-файлов в Python

Оглавление

Давайте посмотрим правде в глаза: вам нужно получать информацию в и из ваших программ не только с помощью клавиатуры и консоли. Обмен информацией через текстовые файлы - распространенный способ обмена информацией между программами. Одним из самых популярных форматов для обмена данными является формат CSV. Но как его использовать?

Давайте проясним одну вещь: вам не нужно (и вы не будете) создавать свой собственный парсер CSV с нуля. Есть несколько вполне приемлемых библиотек, которые вы можете использовать. Библиотека Python csv подойдет для большинства случаев. Если ваша работа требует большого количества данных или численного анализа, библиотека pandas также имеет возможности разбора CSV, что должно справиться с остальным.

В этой статье вы узнаете, как читать, обрабатывать и разбирать CSV из текстовых файлов с помощью Python. Вы увидите, как работают файлы CSV, познакомитесь с важной библиотекой csv, встроенной в Python, и увидите, как работает разбор CSV с помощью библиотеки pandas.

Итак, начнем!

Что такое CSV-файл?

Файл CSV (Comma Separated Values file) - это тип обычного текстового файла, в котором используется определенная структуризация для организации табличных данных. Поскольку это обычный текстовый файл, он может содержать только реальные текстовые данные - другими словами, печатаемые символы ASCII или Unicode.

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

column 1 name,column 2 name, column 3 name
first row data 1,first row data 2,first row data 3
second row data 1,second row data 2,second row data 3
...

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

В общем случае символ-разделитель называется разделителем, и запятая - не единственный используемый символ. Другие популярные разделители включают символы табуляции (\t), двоеточия (:) и полуточия (;). Для правильного разбора CSV-файла необходимо знать, какой разделитель используется.

Откуда берутся CSV-файлы?

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

С файлами CSV очень легко работать программно. Любой язык, поддерживающий ввод текстовых файлов и работу со строками (например, Python), может работать с CSV-файлами напрямую.

Parsing CSV Files With Python's Built-in CSV Library

Библиотека csv предоставляет функциональность для чтения из CSV-файлов и записи в них. Разработанная для работы с CSV-файлами, генерируемыми Excel, она легко адаптируется для работы с различными форматами CSV. Библиотека csv содержит объекты и другой код для чтения, записи и обработки данных из CSV-файлов и в них.

Чтение CSV-файлов с помощью csv

Чтение из CSV-файла осуществляется с помощью объекта reader. CSV-файл открывается как текстовый файл с помощью встроенной в Python функции open(), которая возвращает объект file. Затем он передается объекту reader, который выполняет всю работу.

Вот employee_birthday.txt файл:

name,department,birthday month
John Smith,Accounting,November
Erica Meyers,IT,March

Вот код для его чтения:

import csv

with open('employee_birthday.txt') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    line_count = 0
    for row in csv_reader:
        if line_count == 0:
            print(f'Column names are {", ".join(row)}')
            line_count += 1
        else:
            print(f'\t{row[0]} works in the {row[1]} department, and was born in {row[2]}.')
            line_count += 1
    print(f'Processed {line_count} lines.')

Это приводит к следующему результату:

Column names are name, department, birthday month
    John Smith works in the Accounting department, and was born in November.
    Erica Meyers works in the IT department, and was born in March.
Processed 3 lines.

Каждая строка, возвращаемая reader, представляет собой список элементов String, содержащих данные, найденные путем удаления разделителей. Первая возвращаемая строка содержит имена столбцов, которые обрабатываются особым образом.

Чтение CSV-файлов в словарь с помощью csv

Вместо того чтобы работать со списком отдельных элементов String, вы можете читать CSV-данные непосредственно в словарь (технически, Ordered Dictionary).

Опять же, наш входной файл employee_birthday.txt выглядит следующим образом:

name,department,birthday month
John Smith,Accounting,November
Erica Meyers,IT,March

Вот код, чтобы прочитать его как словарь на этот раз:

import csv

with open('employee_birthday.txt', mode='r') as csv_file:
    csv_reader = csv.DictReader(csv_file)
    line_count = 0
    for row in csv_reader:
        if line_count == 0:
            print(f'Column names are {", ".join(row)}')
            line_count += 1
        print(f'\t{row["name"]} works in the {row["department"]} department, and was born in {row["birthday month"]}.')
        line_count += 1
    print(f'Processed {line_count} lines.')

Это приводит к тому же результату, что и раньше:

Column names are name, department, birthday month
    John Smith works in the Accounting department, and was born in November.
    Erica Meyers works in the IT department, and was born in March.
Processed 3 lines.

Откуда взялись ключи словаря? Предполагается, что первая строка CSV-файла содержит ключи, которые используются для построения словаря. Если в вашем CSV-файле их нет, вы должны указать свои собственные ключи, задав необязательный параметр fieldnames в виде списка, содержащего их.

Дополнительный Python CSV reader Параметры

Объект reader может обрабатывать различные стили CSV-файлов, задавая дополнительные параметры, некоторые из которых приведены ниже:

  • delimiter задает символ, используемый для разделения каждого поля. По умолчанию используется запятая (',').

  • quotechar задает символ, используемый для окружения полей, содержащих символ-разделитель. По умолчанию используется двойная кавычка (' " ').

  • escapechar задает символ, используемый для экранирования символа-разделителя, в случае, если кавычки не используются. По умолчанию символ не используется.

Эти параметры заслуживают более подробного объяснения. Предположим, вы работаете со следующим employee_addresses.txt файлом:

name,address,date joined
john smith,1132 Anywhere Lane Hoboken NJ, 07030,Jan 4
erica meyers,1234 Smith Lane Hoboken NJ, 07030,March 2

Этот CSV-файл содержит три поля: name, address и date joined, которые разделены запятыми. Проблема заключается в том, что данные для поля address также содержат запятую для обозначения почтового индекса.

Есть три разных способа справиться с этой ситуацией:

  • Используйте другой разделитель
    Таким образом, запятую можно смело использовать в самих данных. Для указания нового разделителя используется необязательный параметр delimiter.

  • Обернуть данные в кавычки
    Специальный характер выбранного вами разделителя игнорируется в строках, заключенных в кавычки. Поэтому вы можете указать символ, используемый для кавычек, с помощью необязательного параметра quotechar. Если этот символ также не встречается в данных, то все в порядке.

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

Запись CSV-файлов с помощью csv

Вы также можете записывать в CSV-файл, используя объект writer и метод .write_row():

import csv

with open('employee_file.csv', mode='w') as employee_file:
    employee_writer = csv.writer(employee_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)

    employee_writer.writerow(['John Smith', 'Accounting', 'November'])
    employee_writer.writerow(['Erica Meyers', 'IT', 'March'])

Необязательный параметр quotechar указывает writer, какой символ использовать для кавычек в полях при записи. Однако, будет ли использоваться кавычки или нет, определяется дополнительным параметром quoting:

  • Если для quoting установлено значение csv.QUOTE_MINIMAL, то .writerow() будет заключать поля в кавычки, только если они содержат delimiter или quotechar. Это случай по умолчанию.
  • Если для quoting установлено значение csv.QUOTE_ALL, то .writerow() будет заключать в кавычки все поля.
  • Если quoting установлено значение csv.QUOTE_NONNUMERIC, то .writerow() будет заключать в кавычки все поля, содержащие текстовые данные, и преобразовывать все числовые поля к типу данных float.
  • Если quoting имеет значение csv.QUOTE_NONE, то .writerow() будет экранировать разделители вместо того, чтобы заключать их в кавычки. В этом случае вы также должны указать значение необязательного параметра escapechar.

Перечитывание файла в виде обычного текста показывает, что файл создается следующим образом:

John Smith,Accounting,November
Erica Meyers,IT,March

Запись CSV-файла из словаря с помощью csv

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

import csv

with open('employee_file2.csv', mode='w') as csv_file:
    fieldnames = ['emp_name', 'dept', 'birth_month']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerow({'emp_name': 'John Smith', 'dept': 'Accounting', 'birth_month': 'November'})
    writer.writerow({'emp_name': 'Erica Meyers', 'dept': 'IT', 'birth_month': 'March'})

В отличие от DictReader, параметр fieldnames необходим при записи словаря. Это имеет смысл, если подумать: без списка fieldnames DictWriter не может знать, какие ключи использовать для извлечения значений из ваших словарей. Он также использует ключи в fieldnames для записи первой строки в виде имен столбцов.

Приведенный выше код генерирует следующий выходной файл:

emp_name,dept,birth_month
John Smith,Accounting,November
Erica Meyers,IT,March

Разбор CSV-файлов с помощью библиотеки pandas

Конечно, библиотека Python CSV - не единственная игра в городе. Чтение CSV-файлов возможно и в pandas. Настоятельно рекомендуется, если вам нужно проанализировать большое количество данных.

pandas - это библиотека Python с открытым исходным кодом, которая предоставляет высокопроизводительные инструменты для анализа данных и простые в использовании структуры данных. pandas доступна для всех инсталляций Python, но является ключевой частью дистрибутива Anaconda и отлично работает в Jupyter notebook для обмена данными, кодом, результатами анализа, визуализациями и описательным текстом.

Установка pandas и его зависимостей в Anaconda легко выполняется:

$ conda install pandas

Как и использование pip/pipenv для других установок Python:

$ pip install pandas

Мы не будем вдаваться в подробности того, как работает pandas и как его использовать. Для получения подробной информации об использовании pandas для чтения и анализа больших массивов данных ознакомьтесь с превосходной статьей Шантну Тивари о работе с большими файлами Excel в pandas.

Чтение CSV-файлов с помощью pandas

Чтобы продемонстрировать возможности pandas CSV, я создал немного более сложный файл для чтения, который называется hrdata.csv. Он содержит данные о сотрудниках компании:

Name,Hire Date,Salary,Sick Days remaining
Graham Chapman,03/15/14,50000.00,10
John Cleese,06/01/15,65000.00,8
Eric Idle,05/12/14,45000.00,10
Terry Jones,11/01/13,70000.00,3
Terry Gilliam,08/12/14,48000.00,7
Michael Palin,05/23/13,66000.00,8

Чтение CSV в pandas DataFrame происходит быстро и просто:

import pandas
df = pandas.read_csv('hrdata.csv')
print(df)

Вот и все: три строки кода, и только одна из них выполняет реальную работу. pandas.read_csv() открывает, анализирует и считывает предоставленный CSV-файл и сохраняет данные в DataFrame. Печать DataFrame приводит к следующему результату:

             Name Hire Date   Salary  Sick Days remaining
0  Graham Chapman  03/15/14  50000.0                   10
1     John Cleese  06/01/15  65000.0                    8
2       Eric Idle  05/12/14  45000.0                   10
3     Terry Jones  11/01/13  70000.0                    3
4   Terry Gilliam  08/12/14  48000.0                    7
5   Michael Palin  05/23/13  66000.0                    8

Вот несколько моментов, на которые стоит обратить внимание:

  • Во-первых, pandas распознал, что первая строка CSV содержит имена столбцов, и использовал их автоматически. Я называю это добротой.
  • Однако pandas также использует нулевые целочисленные индексы в DataFrame. Это потому, что мы не сказали ему, каким должен быть наш индекс.
  • Далее, если вы посмотрите на типы данных наших столбцов, то увидите, что pandas правильно преобразовал столбцы Salary и Sick Days remaining в числа, но столбец Hire Date по-прежнему является String. Это легко подтвердить в интерактивном режиме:

    >>> print(type(df['Hire Date'][0]))
    <class 'str'>
    

Давайте рассмотрим эти вопросы по очереди. Чтобы использовать другой столбец в качестве индекса DataFrame, добавьте необязательный параметр index_col:

import pandas
df = pandas.read_csv('hrdata.csv', index_col='Name')
print(df)

Теперь поле Name является нашим DataFrame индексом:

               Hire Date   Salary  Sick Days remaining
Name                                                  
Graham Chapman  03/15/14  50000.0                   10
John Cleese     06/01/15  65000.0                    8
Eric Idle       05/12/14  45000.0                   10
Terry Jones     11/01/13  70000.0                    3
Terry Gilliam   08/12/14  48000.0                    7
Michael Palin   05/23/13  66000.0                    8

Далее давайте исправим тип данных поля Hire Date. Вы можете заставить pandas читать данные как дату с помощью необязательного параметра parse_dates, который определяется как список имен столбцов, которые следует рассматривать как даты:

import pandas
df = pandas.read_csv('hrdata.csv', index_col='Name', parse_dates=['Hire Date'])
print(df)

Обратите внимание на разницу в выводе:

                Hire Date   Salary  Sick Days remaining
Name                                                   
Graham Chapman 2014-03-15  50000.0                   10
John Cleese    2015-06-01  65000.0                    8
Eric Idle      2014-05-12  45000.0                   10
Terry Jones    2013-11-01  70000.0                    3
Terry Gilliam  2014-08-12  48000.0                    7
Michael Palin  2013-05-23  66000.0                    8

Теперь дата форматируется правильно, что легко подтвердить в интерактивном режиме:

>>> print(type(df['Hire Date'][0]))
<class 'pandas._libs.tslibs.timestamps.Timestamp'>

Если в ваших CSV-файлах нет имен колонок в первой строке, вы можете использовать дополнительный параметр names, чтобы предоставить список имен колонок. Вы также можете использовать этот параметр, если хотите переопределить имена столбцов, указанные в первой строке. В этом случае вы также должны указать pandas.read_csv() на игнорирование существующих имен столбцов с помощью необязательного параметра header=0:

import pandas
df = pandas.read_csv('hrdata.csv', 
            index_col='Employee', 
            parse_dates=['Hired'], 
            header=0, 
            names=['Employee', 'Hired','Salary', 'Sick Days'])
print(df)

Обратите внимание, что, поскольку имена столбцов изменились, столбцы, указанные в дополнительных параметрах index_col и parse_dates, также должны быть изменены. Теперь мы получим следующий результат:

                    Hired   Salary  Sick Days
Employee                                     
Graham Chapman 2014-03-15  50000.0         10
John Cleese    2015-06-01  65000.0          8
Eric Idle      2014-05-12  45000.0         10
Terry Jones    2013-11-01  70000.0          3
Terry Gilliam  2014-08-12  48000.0          7
Michael Palin  2013-05-23  66000.0          8

Запись CSV-файлов с помощью pandas

Конечно, если вы не можете снова извлечь данные из pandas, это не принесет вам особой пользы. Записать DataFrame в CSV-файл так же просто, как и прочитать его. Давайте запишем данные с новыми именами столбцов в новый CSV-файл:

import pandas
df = pandas.read_csv('hrdata.csv', 
            index_col='Employee', 
            parse_dates=['Hired'],
            header=0, 
            names=['Employee', 'Hired', 'Salary', 'Sick Days'])
df.to_csv('hrdata_modified.csv')

Единственное отличие этого кода от приведенного выше кода чтения заключается в том, что вызов print(df) был заменен на df.to_csv() с указанием имени файла. Новый CSV-файл выглядит следующим образом:

Employee,Hired,Salary,Sick Days
Graham Chapman,2014-03-15,50000.0,10
John Cleese,2015-06-01,65000.0,8
Eric Idle,2014-05-12,45000.0,10
Terry Jones,2013-11-01,70000.0,3
Terry Gilliam,2014-08-12,48000.0,7
Michael Palin,2013-05-23,66000.0,8

Заключение

Если вы понимаете основы чтения CSV-файлов, то вы никогда не окажетесь в затруднительном положении, когда вам придется иметь дело с импортом данных. С большинством задач по чтению, обработке и записи CSV легко справится базовая библиотека csv Python. Если вам нужно прочитать и обработать большое количество данных, библиотека pandas также предоставляет возможности быстрой и простой работы с CSV.

Существуют ли другие способы разбора текстовых файлов? Конечно! Такие библиотеки, как ANTLR, PLY и PlyPlus, могут справиться с сильным разбором, а если простые String манипуляции не помогают, всегда есть регулярные выражения.

Но это темы для других статей...

Вернуться на верх