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

978

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

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

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

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

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

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

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

ЧтениеЗапись
read_csvto_csv
read_excelto_excel
read_hdfto_hdf
read_sqlto_sql
read_jsonto_json
read_htmlto_html
read_statato_stata
read_clipboardto_clipboard
read_pickleto_pickle
read_msgpackto_msgpack (экспериментальный)
read_gbqto_gbq (экспериментальный)

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

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

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

Python data course

Такой тип файлов — самый распространенный источник данных, который легко расшифровывать и интерпретировать. Для этого 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
whiteredbluegreenanimal
01523cat
12785dog
23367horse
32283duck
44421mouse

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

>>> pd.read_table('ch05_01.csv',sep=',')
whiteredbluegreenanimal
01523cat
12785dog
23367horse
32283duck
44421mouse

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

Создадим файл 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')
1523cat
02785dog
13367horse
22283duck
34421mouse
44421mouse

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

>>> pd.read_csv('ch05_02.csv', header=None)
01234
01523cat
12785dog
23367horse
32283duck
44421mouse

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

>>> pd.read_csv('ch05_02.csv', names=['white','red','blue','green','animal'])
whiteredbluegreenanimal
01523cat
12785dog
23367horse
32283duck
44421mouse

В более сложных случаях когда нужно создать 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'])
item1item2item3
colorstatus
blackup346
down267
whiteup555
down332
left121
redup222
down114

Использованием 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')
whiteredbluegreen
01523
12785
23367

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

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

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

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

000END123AAA122
001END124BBB321
002END125CCC333
>>> pd.read_table('ch05_05.txt', sep='\D+', header=None, engine='python')
012
00123122
11124321
22125333

Еще один распространенный пример — удаление из данных отдельных строк при извлечении. Так, не всегда нужны заголовки или комментарии. Благодаря параметру 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])
whiteredbluegreenanimal
01523cat
12785dog
23367horse
32283duck
44421mouse

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

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

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

>>> pd.read_csv('ch05_02.csv',skiprows=[2],nrows=3,header=None)
01234
01523cat
12785dog
22283duck

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

Например, нужно суммировать значения в колонке каждой третьей строки и затем вставить результат в объект 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: 0ballmugpaperpenpencil
0blue6.0NaNNaN6.0NaN
1greenNaNNaNNaNNaNNaN
2redNaNNaNNaNNaNNaN
3white20.0NaNNaN20.0NaN
4yellow19.0NaNNaN19.0NaN
>>> 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
updownrightleft
white0.4203780.5333640.7589680.132560
black0.7117750.3755980.9368470.495377
red0.6305470.9985880.5924960.076336
blue0.3087520.1580570.6477390.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: 0updownrightleft
0white0.4203780.5333640.7589680.132560
1black0.7117750.3755980.9368470.495377
2red0.6305470.9985880.5924960.076336
3blue0.3087520.1580570.6477390.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]
#NomeExpLivelliright
01Fabio Nelli17521NaN
12admin9029NaN
23BrunoOrsini2124NaN
247248emilibassi1NaN
248249mehrbano1NaN
249250NIKITA PANCHAL1NaN

Чтение данных из 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)
AuthorTitleGenrePricePublishDate
0Ross, MarkXML CookbookComputer23.562014-01-22
1Bracket, BarbaraXML for DummiesComputer35.952014-12-16

Тест на знание python

Какой будет результат выполнения этого кода?
Что выведет этот код?
Какой будет результат выполнения кода — print(+-1) ?
Какой будет результат выполнения кода — print(type(1J)) ?
Что выведет этот код?
Александр
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ. Пишу на популярные темы: веб-разработка, работа с базами данных, data sciense и другие...