Чтение и запись данных (cvs, txt, HTML, XML) / pd 7

Вы уже знакомы с библиотекой pandas и ее базовой функциональностью по анализу данных. Также знаете, что в ее основе лежат два типа данных: Dataframe и Series. На их основе выполняется большая часть взаимодействия с данными, вычислений и анализа.

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

Этот раздел посвящен функциям API I/O (ввода/вывода), которые pandas предоставляет для чтения и записи данных прямо в виде объектов Dataframe. Начнем с текстовых файлов, а затем перейдем к более сложным бинарным форматам.

А в конце узнаем, как взаимодействовать с распространенными базами данных, такими как SQL и NoSQL, используя для этого реальные примеры. Разберем, как считывать данные из базы данных, сохраняя их в виде Dataframe.

Инструменты API I/O

pandas — библиотека, предназначенная для анализа данных, поэтому логично предположить, что она в первую очередь используется для вычислений и обработки данных. Процесс записи и чтения данных на/с внешние файлы — это часть обработки. Даже на этом этапе можно выполнять определенные операции, готовя данные к взаимодействию.

Первый шаг очень важен, поэтому для него представлен полноценный инструмент в библиотеке, называемый API I/O. Функции из него можно разделить на две категории: для чтения и для записи.

Чтение Запись
read_csv to_csv
read_excel to_excel
read_hdf to_hdf
read_sql to_sql
read_json to_json
read_html to_html
read_stata to_stata
read_clipboard to_clipboard
read_pickle to_pickle
read_msgpack to_msgpack (экспериментальный)
read_gbq to_gbq (экспериментальный)

CSV и текстовые файлы

Все привыкли к записи и чтению файлов в текстовой форме. Чаще всего они представлены в табличной форме. Если значения в колонке разделены запятыми, то это формат CSV (значения, разделенные запятыми), который является, наверное, самым известным форматом.

Другие формы табличных данных могут использовать в качестве разделителей пробелы или отступы. Они хранятся в текстовых файлах разных типов (обычно с расширением .txt).

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

  • read_csv
  • read_table
  • to_csv

Чтение данных из CSV или текстовых файлов

Самая распространенная операция по взаимодействию с данными при анализе данных — чтение их из файла CSV или как минимум текстового файла.

Для этого сперва нужно импортировать отдельные библиотеки.

>>> import numpy as np 
>>> import pandas as pd

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

white,red,blue,green,animal
1,5,2,3,cat
2,7,8,5,dog
3,3,6,7,horse
2,2,8,3,duck
4,4,2,1,mouse

Поскольку разделителем в файле выступают запятые, можно использовать функцию read_csv() для чтения его содержимого и добавления в объект Dataframe.

>>> csvframe = pd.read_csv('ch05_01.csv')
>>> csvframe
white red blue green animal
0 1 5 2 3 cat
1 2 7 8 5 dog
2 3 3 6 7 horse
3 2 2 8 3 duck
4 4 4 2 1 mouse

Это простая операция. Файлы CSV — это табличные данные, где значения одной колонки разделены запятыми. Поскольку это все еще текстовые файлы, то подойдет и функция read_table(), но в таком случае нужно явно указывать разделитель.

>>> pd.read_table('ch05_01.csv',sep=',')
white red blue green animal
0 1 5 2 3 cat
1 2 7 8 5 dog
2 3 3 6 7 horse
3 2 2 8 3 duck
4 4 4 2 1 mouse

В этом примере все заголовки, обозначающие названия колонок, определены в первой строчке. Но это не всегда работает именно так. Иногда сами данные начинаются с первой строки.

Создадим файл ch05_02.csv

1,5,2,3,cat
2,7,8,5,dog
3,3,6,7,horse
2,2,8,3,duck
4,4,2,1,mouse
>>> pd.read_csv('ch05_02.csv')
1 5 2 3 cat
0 2 7 8 5 dog
1 3 3 6 7 horse
2 2 2 8 3 duck
3 4 4 2 1 mouse
4 4 4 2 1 mouse

В таком случае нужно убедиться, что pandas не присвоит названиям колонок значения первой строки, передав None параметру header.

>>> pd.read_csv('ch05_02.csv', header=None)
0 1 2 3 4
0 1 5 2 3 cat
1 2 7 8 5 dog
2 3 3 6 7 horse
3 2 2 8 3 duck
4 4 4 2 1 mouse

Также можно самостоятельно определить названия, присвоив список меток параметру names.

>>> pd.read_csv('ch05_02.csv', names=['white','red','blue','green','animal'])
white red blue green animal
0 1 5 2 3 cat
1 2 7 8 5 dog
2 3 3 6 7 horse
3 2 2 8 3 duck
4 4 4 2 1 mouse

В более сложных случаях когда нужно создать Dataframe с иерархической структурой на основе данных из файла CSV, можно расширить возможности функции read_csv() добавив параметр index_col, который конвертирует колонки в значения индексов.

Чтобы лучше разобраться с этой особенностью, создайте новый CSV-файл с двумя колонками, которые будут индексами в иерархии. Затем сохраните его в рабочую директорию под именем ch05_03.csv.

Создадим файл ch05_03.csv

color,status,item1,item2,item3
black,up,3,4,6
black,down,2,6,7
white,up,5,5,5
white,down,3,3,2
white,left,1,2,1
red,up,2,2,2
red,down,1,1,4
>>> pd.read_csv('ch05_03.csv', index_col=['color','status'])
item1 item2 item3
color status
black up 3 4 6
down 2 6 7
white up 5 5 5
down 3 3 2
left 1 2 1
red up 2 2 2
down 1 1 4

Использованием RegExp для парсинга файлов TXT

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

Чтобы лучше понимать regexp и то, как их использовать для разделения данных, начнем с простого примера. Например, предположим, что файл TXT имеет значения, разделенные пробелами и отступами хаотично. В таком случае regexp подойдут идеально, ведь они позволяют учитывать оба вида разделителей. Подстановочный символ /s* отвечает за все символы пробелов и отступов (если нужны только отступы, то используется /t), а * указывает на то, что символов может быть несколько. Таким образом значения могут быть разделены большим количеством пробелов.

. Любой символ за исключением новой строки
\d Цифра
\D Не-цифровое значение
\s Пробел
\S Не-пробельное значение
\n Новая строка
\t Отступ
\uxxxx Символ Unicode в шестнадцатеричном виде

Возьмем в качестве примера случай, где значения разделены отступами или пробелами в хаотическом порядке.

Создадим файл ch05_04.txt

white red blue green
 1 5 2 3
 2 7 8 5
 3 3 6 7
>>> pd.read_table('ch05_04.txt',sep='\s+', engine='python')
white red blue green
0 1 5 2 3
1 2 7 8 5
2 3 3 6 7

Результатом будет идеальный Dataframe, в котором все значения корректно отсортированы.

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

В следующем примере необходимо извлечь цифровую часть из файла TXT, в котором последовательность символов перемешана с буквами.

Не забудьте задать параметр None для параметра header, если в файле нет заголовков колонок.
Создадим файл ch05_05.txt

000END123AAA122
001END124BBB321
002END125CCC333
>>> pd.read_table('ch05_05.txt', sep='\D+', header=None, engine='python')
0 1 2
0 0 123 122
1 1 124 321
2 2 125 333

Еще один распространенный пример — удаление из данных отдельных строк при извлечении. Так, не всегда нужны заголовки или комментарии. Благодаря параметру skiprows можно исключить любые строки, просто присвоим ему массив с номерами строк, которые не нужно парсить.

Обратите внимание на способ использования параметра. Если нужно исключить первые пять строк, то необходимо писать skiprows = 5, но для удаления только пятой строки — [5].
Создадим файл ch05_06.txt

########### LOG FILE ############
This file has been generated by automatic system
white,red,blue,green,animal
12-Feb-2015: Counting of animals inside the house
1,5,2,3,cat
2,7,8,5,dog
13-Feb-2015: Counting of animals outside the house
3,3,6,7,horse
2,2,8,3,duck
4,4,2,1,mouse
>>> pd.read_table('ch05_06.txt',sep=',',skiprows=[0,1,3,6])
white red blue green animal
0 1 5 2 3 cat
1 2 7 8 5 dog
2 3 3 6 7 horse
3 2 2 8 3 duck
4 4 4 2 1 mouse

Чтение файлов TXT с разделением на части

При обработке крупных файлов или необходимости использовать только отдельные их части часто требуется считывать их кусками. Это может пригодится, если необходимо воспользоваться перебором или же целый файл не нужен.

Если требуется получить лишь часть файла, можно явно указать количество требуемых строк. Благодаря параметрам nrows и skiprows можно выбрать стартовую строку n (n = SkipRows) и количество строк, которые нужно считать после (nrows = 1).

>>> pd.read_csv('ch05_02.csv',skiprows=[2],nrows=3,header=None)
0 1 2 3 4
0 1 5 2 3 cat
1 2 7 8 5 dog
2 2 2 8 3 duck

Еще одна интересная и распространенная операция — разбитие на части того куска текста, который требуется парсить. Затем для каждой части может быть выполнена конкретная операция для получения перебора части за частью.

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

>>> out = pd.Series()
>>> i = 0
>>> pieces = pd.read_csv('ch05_01.csv',chunksize=3)
>>> for piece in pieces:
...     out.set_value(i,piece['white'].sum())
...     i = i + 1
...
>>> out
0 6
1 6
dtype: int64

Запись данных в CSV

В дополнение к чтению данных из файла, распространенной операцией является запись в файл данных, полученных, например, в результате вычислений или просто из структуры данных.

Например, нужно записать данные из объекта Dataframe в файл CSV. Для этого используется функция to_csv(), принимающая в качестве аргумента имя файла, который будет сгенерирован.

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
		 index = ['red', 'blue', 'yellow', 'white'],
		 columns = ['ball', 'pen', 'pencil', 'paper'])
>>> frame.to_csv('ch05_07.csv')

Если открыть новый файл ch05_07.csv, сгенерированный библиотекой pandas, то он будет напоминать следующее:

,ball,pen,pencil,paper
0,1,2,3
4,5,6,7
8,9,10,11
12,13,14,15

На предыдущем примере видно, что при записи Dataframe в файл индексы и колонки отмечаются в файле по умолчанию. Это поведение можно изменить с помощью параметров index и header. Им нужно передать значение False.

>>> frame.to_csv('ch05_07b.csv', index=False, header=False)

Файл ch05_07b.csv

1,2,3
5,6,7
9,10,11
13,14,15

Важно запомнить, что при записи файлов значения NaN из структуры данных представлены в виде пустых полей в файле.

>>> frame3 = pd.DataFrame([[6,np.nan,np.nan,6,np.nan],
... 	      [np.nan,np.nan,np.nan,np.nan,np.nan],
... 	      [np.nan,np.nan,np.nan,np.nan,np.nan],
...	      [20,np.nan,np.nan,20.0,np.nan],
... 	      [19,np.nan,np.nan,19.0,np.nan]
... 	      ],
... 		     index=['blue','green','red','white','yellow'],
...		     columns=['ball','mug','paper','pen','pencil'])
>>> frame3
Unnamed: 0 ball mug paper pen pencil
0 blue 6.0 NaN NaN 6.0 NaN
1 green NaN NaN NaN NaN NaN
2 red NaN NaN NaN NaN NaN
3 white 20.0 NaN NaN 20.0 NaN
4 yellow 19.0 NaN NaN 19.0 NaN
>>> frame3.to_csv('ch05_08.csv')
,ball,mug,paper,pen,pencil
blue,6.0,,,6.0,
green,,,,,
red,,,,,
white,20.0,,,20.0,
yellow,19.0,,,19.0,

Но их можно заменить на любое значение, воспользовавшись параметром na_rep из функции to_csv. Это может быть NULL, 0 или то же NaN.

>>> frame3.to_csv('ch05_09.csv', na_rep ='NaN')
,ball,mug,paper,pen,pencil
blue,6.0,NaN,NaN,6.0,NaN
green,NaN,NaN,NaN,NaN,NaN
red,NaN,NaN,NaN,NaN,NaN
white,20.0,NaN,NaN,20.0,NaN
yellow,19.0,NaN,NaN,19.0,NaN

Примечание: в предыдущих примерах использовались только объекты Dataframe, но все функции применимы и по отношению к Series.

Чтение и запись файлов HTML

pandas предоставляет соответствующую пару функций API I/O для формата HTML.

  • read_html()
  • to_html()

Эти две функции очень полезны. С их помощью можно просто конвертировать сложные структуры данных, такие как Dataframe, прямо в таблицы HTML, не углубляясь в синтаксис.

Обратная операция тоже очень полезна, потому что сегодня веб является одним из основных источников информации. При этом большая часть информации не является «готовой к использованию», будучи упакованной в форматы TXT или CSV. Необходимые данные чаще всего представлены лишь на части страницы. Так что функция для чтения окажется полезной очень часто.

Такая деятельность называется парсингом (веб-скрапингом). Этот процесс становится фундаментальным элементом первого этапа анализа данных: поиска и подготовки.

Примечание: многие сайты используют HTML5 для предотвращения ошибок недостающих модулей или сообщений об ошибках. Настоятельно рекомендуется использовать модуль html5lib в Anaconda.
conda install html5lib

Запись данных в HTML

При записи Dataframe в HTML-таблицу внутренняя структура объекта автоматически конвертируется в сетку вложенных тегов <th>, <tr> и <td>, сохраняя иерархию. Для этой функции даже не нужно знать HTML.

Поскольку структуры данных, такие как Dataframe, могут быть большими и сложными, это очень удобно иметь функцию, которая сама создает таблицу на странице. Вот пример.

Сначала создадим простейший Dataframe. Дальше с помощью функции to_html() прямо конвертируем его в таблицу HTML.

>>> frame = pd.DataFrame(np.arange(4).reshape(2,2))

Поскольку функции API I/O определены в структуре данных pandas, вызывать to_html() можно прямо к экземпляру Dataframe.

>>> print(frame.to_html())
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>1</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2</td>
      <td>3</td>
    </tr>
  </tbody>
</table>

Результат — готовая таблица HTML, сохранившая всю внутреннюю структуру.

В следующем примере вы увидите, как таблицы автоматически появляются в файле HTML. В этот раз сделаем объект более сложным, добавив в него метки индексов и названия колонок.

>>> frame = pd.DataFrame(np.random.random((4,4)),
... 			index = ['white','black','red','blue'],
... 			columns = ['up','down','right','left'])
>>> frame
up down right left
white 0.420378 0.533364 0.758968 0.132560
black 0.711775 0.375598 0.936847 0.495377
red 0.630547 0.998588 0.592496 0.076336
blue 0.308752 0.158057 0.647739 0.907514

Теперь попробуем написать страницу HTML с помощью генерации строк. Это простой пример, но он позволит разобраться с функциональностью pandas прямо в браузере.

Сначала создадим строку, которая содержит код HTML-страницы.

>>> s = ['<HTML>']
>>> s.append('<HEAD><TITLE>My DataFrame</TITLE></HEAD>')
>>> s.append('<BODY>')
>>> s.append(frame.to_html())
>>> s.append('</BODY></HTML>')
>>> html = ''.join(s)

Теперь когда метка html содержит всю необходимую разметку, можно писать прямо в файл myFrame.html:

>>> html_file = open('myFrame.html','w')
>>> html_file.write(html)
>>> html_file.close()

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

Пример таблицы pandas на html

Чтение данных из HTML-файла

pandas может с легкостью генерировать HTML-таблицы на основе данных Dataframe. Обратный процесс тоже возможен. Функция read_html() осуществляет парсинг HTML и ищет таблицу. В случае успеха она конвертирует ее в Dataframe, который можно использовать в процессе анализа данных.

Если точнее, то read_html() возвращает список объектов Dataframe, даже если таблица одна. Источник может быть разных типов. Например, может потребоваться прочитать HTML-файл в любой папке. Или попробовать парсить HTML из прошлого примера:

>>> web_frames = pd.read_html('myFrame.html')
>>> web_frames[0]
Unnamed: 0 up down right left
0 white 0.420378 0.533364 0.758968 0.132560
1 black 0.711775 0.375598 0.936847 0.495377
2 red 0.630547 0.998588 0.592496 0.076336
3 blue 0.308752 0.158057 0.647739 0.907514

Все теги, отвечающие за формирование таблицы в HTML в финальном объекте не представлены. web_frames — это список Dataframe, хотя в этом случае объект был всего один. К нему можно обратиться стандартным путем. Здесь достаточно лишь указать на него через индекс 0.

Но самый распространенный режим работы функции read_html() — прямой парсинг ссылки. Таким образом страницы парсятся прямо, а из них извлекаются таблицы.

Например, дальше будет вызвана страница, на которой есть HTML-таблица, показывающая рейтинг с именами и баллами.

>>> ranking = pd.read_html('https://www.meccanismocomplesso.org/en/
meccanismo-complesso-sito-2/classifica-punteggio/')
>>> ranking[0]
# Nome Exp Livelli right
0 1 Fabio Nelli 17521 NaN
1 2 admin 9029 NaN
2 3 BrunoOrsini 2124 NaN
247 248 emilibassi 1 NaN
248 249 mehrbano 1 NaN
249 250 NIKITA PANCHAL 1 NaN

Чтение данных из XML

В списке функции API I/O нет конкретного инструмента для работы с форматом XML (Extensible Markup Language). Тем не менее он очень важный, поскольку многие структурированные данные представлены именно в нем. Но это и не проблема, ведь в Python есть много других библиотек (помимо pandas), которые подходят для чтения и записи данных в формате XML.

Одна их них называется lxml и она обеспечивает идеальную производительность при парсинге даже самых крупных файлов. Этот раздел будет посвящен ее использованию, интеграции с pandas и способам получения Dataframe с нужными данными. Больше подробностей о lxml есть на официальном сайте http://lxml.de/index.html.

Возьмем в качестве примера следующий файл. Сохраните его в рабочей директории с названием books.xml.

<?xml version="1.0"?>
<Catalog>
 <Book id="ISBN9872122367564">
   <Author>Ross, Mark</Author>
   <Title>XML Cookbook</Title>
   <Genre>Computer</Genre>
   <Price>23.56</Price>
   <PublishDate>2014-22-01</PublishDate>
 </Book>
 <Book id="ISBN9872122367564">
   <Author>Bracket, Barbara</Author>
   <Title>XML for Dummies</Title>
   <Genre>Computer</Genre>
   <Price>35.95</Price>
   <PublishDate>2014-12-16</PublishDate>
 </Book>
</Catalog>

В этом примере структура файла будет конвертирована и преподнесена в виде Dataframe. В первую очередь нужно импортировать субмодуль objectify из библиотеки.

>>> from lxml import objectify

Теперь нужно всего лишь использовать его функцию parse().

>>> xml = objectify.parse('books.xml')
>>> xml
<lxml.etree._ElementTree object at 0x0000000009734E08>

Результатом будет объект tree, который является внутренней структурой данных модуля lxml.

Чтобы познакомиться с деталями этого типа, пройтись по его структуре или выбирать элемент за элементом, в первую очередь нужно определить корень. Для этого используется функция getroot().

>>> root = xml.getroot()

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

>>> root.Book.Author
'Ross, Mark'
>>> root.Book.PublishDate
'2014-22-01'

В такой способ доступ к узлам можно получить индивидуально. А getchildren() обеспечит доступ ко всем дочерним элементами.

>>> root.getchildren()
[<Element Book at 0x9c66688>, <Element Book at 0x9c66e08>]

При использовании атрибута tag вы получаете название соответствующего тега из родительского узла.

>>> [child.tag for child in root.Book.getchildren()]
['Author', 'Title', 'Genre', 'Price', 'PublishDate']

А text покажет значения в этих тегах.

>>> [child.text for child in root.Book.getchildren()]
['Ross, Mark', 'XML Cookbook', 'Computer', '23.56', '2014-22-01']

Но вне зависимости от возможности двигаться по структуре lxml.etree, ее нужно конвертировать в Dataframe. Воспользуйтесь следующей функцией, которая анализирует содержимое eTree и заполняет им Dataframe строчка за строчкой.

>>> def etree2df(root):
...     column_names = []
...     for i in range(0, len(root.getchildren()[0].getchildren())):
...         column_names.append(root.getchildren()[0].getchildren()[i].tag)
...     xmlframe = pd.DataFrame(columns=column_names)
...     for j in range(0, len(root.getchildren())):
...         obj = root.getchildren()[j].getchildren()
...         texts = []
...         for k in range(0, len(column_names)):
...             texts.append(obj[k].text)
...         row = dict(zip(column_names, texts))
...         row_s = pd.Series(row)
...         row_s.name = j
...         xmlframe = xmlframe.append(row_s)
...     return xmlframe
>>> etree2df(root)
Author Title Genre Price PublishDate
0 Ross, Mark XML Cookbook Computer 23.56 2014-01-22
1 Bracket, Barbara XML for Dummies Computer 35.95 2014-12-16

Появились вопросы? Задайте на Яндекс Кью

У блога есть сообщество на Кью >> Python Q <<. Там я и другие участники отвечаем на вопросы по python, программированию и контенту сайта.

Обучение Python и Data Science

Профессия Python-разработчик / Skillbox

Профессия Python-разработчик / Skillbox

7 313 2 925 ₽/мес.
Профессия Python Fullstack / Skillbox

Профессия Python Fullstack / Skillbox

6 569 2 627 ₽/мес.
Профессия Data Scientist / Skillbox

Профессия Data Scientist / Skillbox

9 187 3 675 ₽/мес.
Python-фреймворк на Django / Skillbox

Python-фреймворк на Django / Skillbox

818 ₽/мес.
Профессия DS: машинное обучение / Skillbox

Профессия DS: машинное обучение / Skillbox

6172 2469 ₽/мес.
Профессия DS: анализ данных / Skillbox

Профессия DS: анализ данных / Skillbox

6172 2469 ₽/мес.

Вам помогла эта статья? Поделитесь в соцсетях или блоге. Репосты помогают сайту развиться.

Александр эксперт Яндекс.Кью
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ.
Мои контакты: