Spec-Zone.ru › pandas 2

Инструменты ввода-вывода (текст, CSV, HDF5, …)

API pandas для ввода-вывода — это набор функций верхнего уровня reader, к которым можно обратиться, как к pandas.read_csv(), которые обычно возвращают объект pandas. Соответствующие функции writer — это методы объектов, к которым обращаются, как к DataFrame.to_csv(). Ниже приведена таблица доступных readers и writers.

Тип формата

Описание данных

Чтение

Запись

текст

CSV

read_csv

to_csv

текст

Файл с фиксированной шириной

read_fwf

текст

JSON

read_json

to_json

текст

HTML

read_html

to_html

текст

LaTeX

Styler.to_latex

текст

XML

read_xml

to_xml

текст

Буфер обмена

read_clipboard

to_clipboard

двоичные данные

MS Excel

read_excel

to_excel

двоичные данные

OpenDocument

read_excel

двоичные данные

Формат HDF5

read_hdf

to_hdf

двоичные данные

Формат Feather

read_feather

to_feather

двоичные данные

Формат Parquet

read_parquet

to_parquet

двоичные данные

Формат ORC

read_orc

to_orc

двоичные данные

Stata

read_stata

to_stata

двоичные данные

SAS

read_sas

двоичные данные

SPSS

read_spss

двоичные данные

Формат Python Pickle

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery

read_gbq

to_gbq

Здесь представлено сравнение производительности некоторых методов ввода-вывода.

Примечание

Для примеров, использующих класс StringIO, убедитесь, что вы импортировали его с помощью from io import StringIO для Python 3.

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

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

Параметры парсинга

read_csv() принимает следующие распространённые аргументы:

Основные

filepath_or_buffer:разные

Путь к файлу (строка, путь, или объект с методом read()), URL (включая http, ftp и S3), или любой объект с методом read() (например, открытый файл или StringIO).

sep:строка, по умолчанию ',' для read_csv(), \t для read_table()

Разделитель. Если sep — None, C-движок не может автоматически определить разделитель, но Python-движок парсинга может, поэтому будет использоваться последний, который автоматически определит разделитель с помощью встроенного анализатора Python, csv.Sniffer. Кроме того, разделители длиной более 1 символа и отличные от '\s+' будут интерпретироваться как регулярные выражения и также будут форсировать использование Python-движка парсинга. Обратите внимание, что разделители-регулярные выражения могут игнорировать данные в кавычках. Пример регулярного выражения: '\\r\\t'.

delimiter:строка, по умолчанию None

Альтернативное имя аргумента для sep.

delim_whitespace:булево, по умолчанию False

Указывает, будет ли использоваться пробел (например, ' ' или '\t') в качестве разделителя. Эквивалентно установке sep='\s+'. Если этот параметр установлен в True, для параметра delimiter ничего не должно передаваться.

Расположение и имена столбцов и индексов

header:int или список целых чисел, по умолчанию 'infer'

Номер(а) строки(ок) для использования в качестве имён столбцов и начала данных. По умолчанию имена столбцов определяются автоматически: если имена не переданы, поведение идентично header=0, и имена столбцов определяются из первой строки файла; если имена столбцов явно переданы, поведение идентично header=None. Явно передайте header=0, чтобы заменить существующие имена.

Значение header может быть списком целых чисел, определяющих расположение строк для создания Многоуровневого индекса по столбцам, например, [0,1,3]. Промежуточные строки, которые не указаны, будут пропущены (например, 2 в этом примере пропущена). Обратите внимание, что этот параметр игнорирует прокомментированные строки и пустые строки, если skip_blank_lines=True, поэтому header=0 обозначает первую строку данных, а не первую строку файла.

names:последовательность, по умолчанию None

Список имён столбцов для использования. Если файл не содержит строки заголовка, то вы должны явно передать header=None. Повторения в этом списке недопустимы.

index_col:int, str, последовательность int / str или False, необязательно, по умолчанию None

Столбец(ы) для использования в качестве меток строк DataFrame, заданный в виде имени строки или индекса столбца. Если задана последовательность int / str, используется Многоуровневый индекс.

Примечание

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

Значение по умолчанию None указывает pandas на попытку определения автоматически. Если количество полей в строке заголовка столбцов равно количеству полей в теле файла данных, то используется индекс по умолчанию. Если оно больше, то первые столбцы используются в качестве индекса, так что оставшееся количество полей в теле равно количеству полей в заголовке.

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

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

usecols:последовательность или вызываемый объект, по умолчанию None

Возвращает подмножество столбцов. Если последовательность, все элементы должны быть либо позиционными (т. е. целочисленными индексами столбцов документа), либо строками, соответствующими именам столбцов, предоставленными пользователем в names или определёнными из строки(ок) заголовка документа. Если names заданы, строки(ки) заголовка документа не учитываются. Например, допустимым параметром последовательности usecols было бы [0, 1, 2] или ['foo', 'bar', 'baz'].

Порядок элементов игнорируется, поэтому usecols=[0, 1] равносильно [1, 0]. Для создания DataFrame из data с сохранением порядка элементов используйте pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']] для столбцов в порядке ['foo', 'bar'] или pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']] для порядка ['bar', 'foo'].

Если это вызываемый объект, функция вызываемого объекта будет оцениваться по именам столбцов, возвращая имена, где вызываемый объект возвращает True:

In [1]: import pandas as pd

In [2]: from io import StringIO

In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [4]: pd.read_csv(StringIO(data))
Out[4]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"])
Out[5]: 
  col1  col3
0    a     1
1    a     2
2    c     3

Использование этого параметра приводит к значительно более быстрому парсингу и меньшей потребности в памяти при использовании движка c. Движок Python сначала загружает данные, прежде чем решить, какие столбцы нужно удалить.

Общие параметры разбора

dtype:Имя типа или словарь столбец -> тип, по умолчанию None

Тип данных для данных или столбцов. Например, {'a': np.float64, 'b': np.int32, 'c': 'Int64'} Используйте str или object вместе с соответствующими параметрами na_values, чтобы сохранить и не интерпретировать dtype. Если указаны преобразователи, они будут применяться ВМЕСТО преобразования dtype.

Добавлено в версии 1.5.0: Поддержка defaultdict была добавлена. Укажите defaultdict в качестве входных данных, где значение по умолчанию определяет тип данных столбцов, которые не указаны явно.

dtype_backend:{“numpy_nullable”, “pyarrow”}, по умолчанию DataFrame с поддержкой NumPy

Какой dtype_backend использовать, например, должен ли DataFrame иметь массивы NumPy, nullable dtypes используются для всех типов данных, которые имеют реализацию nullable, когда установлен “numpy_nullable”, pyarrow используется для всех типов данных, если установлен “pyarrow”.

dtype_backends всё ещё находятся в стадии разработки.

Добавлено в версии 2.0.

engine:{'c', 'python', 'pyarrow'}

Используемый движок парсера. Движки C и pyarrow быстрее, а движок python в настоящее время более функционален. Многопоточность в настоящее время поддерживается только движком pyarrow.

Добавлено в версии 1.4.0: Движок “pyarrow” был добавлен как экспериментальный движок, и некоторые функции не поддерживаются или могут не работать правильно с этим движком.

converters:словарь, по умолчанию None

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

true_values:список, по умолчанию None

Значения, которые следует рассматривать как True.

false_values:список, по умолчанию None

Значения, которые следует рассматривать как False.

skipinitialspace:булево значение, по умолчанию False

Пропустить пробелы после разделителя.

skiprows:подобный списку или целое число, по умолчанию None

Номера строк для пропуска (с индексом 0) или количество строк для пропуска (int) в начале файла.

Если это вызываемая функция, вызываемая функция будет оцениваться по индексам строк, возвращая True, если строка должна быть пропущена, и False в противном случае:

In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [7]: pd.read_csv(StringIO(data))
Out[7]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
  col1 col2  col3
0    a    b     2
skipfooter:целое число, по умолчанию 0

Количество строк в нижней части файла, которые нужно пропустить (не поддерживается с engine=’c’).

nrows:целое число, по умолчанию None

Количество строк файла для чтения. Полезно для чтения фрагментов больших файлов.

low_memory:булево значение, по умолчанию True

Внутренне обрабатывать файл частями, что приводит к меньшему использованию памяти при разборе, но, возможно, к смешанному определению типов. Чтобы гарантировать отсутствие смешанных типов, установите False или укажите тип с параметром dtype. Обратите внимание, что весь файл читается в единый DataFrame, используйте параметры chunksize или iterator, чтобы возвращать данные частями. (Только допустимо с парсером C)

memory_map:булево значение, по умолчанию False

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

Обработка значений NA и пропусков данных

na_values:скаляр, str, список или словарь, по умолчанию None

Дополнительные строки, распознаваемые как NA/NaN. Если передан словарь, значения NA специфичны для каждого столбца. Смотрите значения NA const ниже для списка значений, интерпретируемых как NaN по умолчанию.

keep_default_na:логический тип, по умолчанию True

Включать или не включать значения NaN по умолчанию при парсинге данных. В зависимости от того, передается ли na_values, поведение следующее:

  • Если keep_default_na равен True, и na_values указаны, то na_values добавляется к значениям NaN по умолчанию, используемым для парсинга.

  • Если keep_default_na равен True, и na_values не указаны, для парсинга используются только значения NaN по умолчанию.

  • Если keep_default_na равен False, и na_values указаны, для парсинга используются только значения NaN, указанные na_values.

  • Если keep_default_na равен False, и na_values не указаны, ни одна строка не будет распознаваться как NaN.

Обратите внимание, что если na_filter передается как False, параметры keep_default_na и na_values будут проигнорированы.

na_filter:логический тип, по умолчанию True

Обнаружение маркеров пропущенных значений (пустые строки и значения na_values). В данных без значений NA передача na_filter=False может улучшить производительность чтения большого файла.

verbose:логический тип, по умолчанию False

Указывает количество значений NA, помещенных в нечисленные столбцы.

skip_blank_lines:логический тип, по умолчанию True

Если True, пропускайте пустые строки вместо интерпретации как NaN.

Обработка дат и времени

parse_dates:логический тип или список целых чисел или имен или список списков или словарь, по умолчанию False.
  • Если True -> попытаться разобрать индекс.

  • Если [1, 2, 3] -> попытаться разобрать столбцы 1, 2, 3 как отдельные столбцы дат.

  • Если [[1, 3]] -> объединить столбцы 1 и 3 и разобрать как один столбец дат.

  • Если {'foo': [1, 3]} -> разобрать столбцы 1, 3 как даты и назвать результат ‘foo’.

Примечание

Существует быстрый путь для дат в формате iso8601.

infer_datetime_format:логический тип, по умолчанию False

Если True и parse_dates включены для столбца, попробуйте определить формат даты и времени, чтобы ускорить обработку.

Устарело начиная с версии 2.0.0: Жесткая версия этого аргумента теперь по умолчанию, передача его не оказывает никакого эффекта.

keep_date_col:логический тип, по умолчанию False

Если True и parse_dates определяют объединение нескольких столбцов, сохранить исходные столбцы.

date_parser:функция, по умолчанию None

Функция для преобразования последовательности столбцов строк в массив экземпляров дат и времени. По умолчанию используется dateutil.parser.parser для преобразования. pandas попытается вызвать date_parser тремя различными способами, переходя к следующему, если произойдет исключение: 1) Передать один или несколько массивов (как определено parse_dates) в качестве аргументов; 2) конкатенировать (по строкам) строковые значения из столбцов, определенных parse_dates, в один массив и передать его; и 3) вызвать date_parser один раз для каждой строки, используя одну или несколько строк (соответствующих столбцам, определенным parse_dates) в качестве аргументов.

Устарело начиная с версии 2.0.0: Используйте date_format вместо этого или читайте как object, а затем применяйте to_datetime() по мере необходимости.

date_format:строка или словарь столбец -> формат, по умолчанию None

Если используется совместно с parse_dates, даты будут анализироваться в соответствии с этим форматом. Для чего-либо более сложного, пожалуйста, прочитайте как object, а затем примените to_datetime() по мере необходимости.

Новое в версии 2.0.0.

dayfirst:логический тип, по умолчанию False

Даты формата ДД/ММ, международный и европейский формат.

cache_dates:логический тип, по умолчанию True

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

Итерация

iterator:boolean, default False

Возвращает объект TextFileReader для итерации или получения фрагментов с помощью get_chunk().

chunksize:int, default None

Возвращает объект TextFileReader для итерации. См. итерацию и фрагментацию ниже.

Правила цитирования, сжатие и формат файла

compression:{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict}, default 'infer'

Для динамического распаковки данных на диске. Если ‘infer’, то используйте gzip, bz2, zip, xz или zstandard, если filepath_or_buffer является путём, заканчивающимся на ‘.gz’, ‘.bz2’, ‘.zip’, ‘.xz’, ‘.zst’ соответственно, и не распаковывайте иначе. Если используется ‘zip’, ZIP-файл должен содержать только один файл данных для чтения. Установите значение None для отключения распаковки. Также может быть словарь с ключом 'method', установленным в одно из значений {'zip', 'gzip', 'bz2', 'zstd'} и другие пары «ключ-значение» передаются в zipfile.ZipFile, gzip.GzipFile, bz2.BZ2File или zstandard.ZstdDecompressor. В качестве примера, можно передать следующее для более быстрого сжатия и создания воспроизводимого gzip архива: compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}.

Изменено в версии 1.2.0: Предыдущие версии передавали записи словаря для ‘gzip’ в gzip.open.

thousands:str, default None

Разделитель тысяч.

decimal:str, default '.'

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

float_precision:string, default None

Определяет, какой преобразователь C-движок должен использовать для значений с плавающей точкой. Доступные варианты: None для обычного преобразователя, high для преобразователя высокой точности и round_trip для преобразователя обратного преобразования.

lineterminator:str (length 1), default None

Символ для разделения файла на строки. Действителен только с C-парсером.

quotechar:str (length 1)

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

quoting:int or csv.QUOTE_* instance, default 0

Управление поведением цитирования полей в соответствии со значениями csv.QUOTE_*. Используйте одно из значений QUOTE_MINIMAL (0), QUOTE_ALL (1), QUOTE_NONNUMERIC (2) или QUOTE_NONE (3).

doublequote:boolean, default True

Когда указано quotechar и quoting не QUOTE_NONE, указывает, интерпретировать ли два последовательных элемента quotechar внутри поля как один элемент quotechar.

escapechar:str (length 1), default None

Односимвольная строка, используемая для экранирования разделителя, когда цитирование равно QUOTE_NONE.

comment:str, default None

Указывает, что остальная часть строки не должна анализироваться. Если он находится в начале строки, строка будет полностью проигнорирована. Этот параметр должен быть одиночным символом. Как и пустые строки (пока skip_blank_lines=True), полностью прокомментированные строки игнорируются параметром header, но не skiprows. Например, если comment='#', парсинг ‘#empty\na,b,c\n1,2,3’ с header=0 даст в результате ‘a,b,c’, которая будет обработана как заголовок.

encoding:str, default None

Кодировка для использования для UTF при чтении/записи (например, 'utf-8'). Список стандартных кодировок Python.

dialect:str or csv.Dialect instance, default None

Если предоставлен, этот параметр переопределит значения (по умолчанию или нет) для следующих параметров: delimiter, doublequote, escapechar, skipinitialspace, quotechar и quoting. Если необходимо переопределить значения, будет выведено предупреждение ParserWarning. См. документацию csv.Dialect для получения более подробной информации.

Обработка ошибок

on_bad_lines:(‘error’, ‘warn’, ‘skip’), default ‘error’

Указывает, что делать при обнаружении плохой строки (строки с чрезмерным количеством полей). Допустимые значения:

  • ‘error’, генерировать исключение ParserError при обнаружении плохой строки.

  • ‘warn’, выводить предупреждение при обнаружении плохой строки и пропускать эту строку.

  • ‘skip’, пропускать плохие строки без генерации исключений или предупреждений.

Добавлен в версии 1.3.0.

Указание типов данных столбцов

Вы можете указать тип данных для всего DataFrame или отдельных столбцов:

In [9]: import numpy as np

In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"

In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11

In [12]: df = pd.read_csv(StringIO(data), dtype=object)

In [13]: df
Out[13]: 
   a   b   c    d
0  1   2   3    4
1  5   6   7    8
2  9  10  11  NaN

In [14]: df["a"][0]
Out[14]: '1'

In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})

In [16]: df.dtypes
Out[16]: 
a      int64
b     object
c    float64
d      Int64
dtype: object

К счастью, pandas предлагает более одного способа гарантировать, что ваши столбцы содержат только один dtype. Если вы не знакомы с этими понятиями, вы можете узнать больше о типах данных здесь, а о преобразованиях типов в pandas здесь.

Например, вы можете использовать аргумент converters функции read_csv():

In [17]: data = "col_1\n1\n2\n'A'\n4.22"

In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})

In [19]: df
Out[19]: 
  col_1
0     1
1     2
2   'A'
3  4.22

In [20]: df["col_1"].apply(type).value_counts()
Out[20]: 
col_1
<class 'str'>    4
Name: count, dtype: int64

Или вы можете использовать функцию to_numeric() для принудительного преобразования типов после чтения данных,

In [21]: df2 = pd.read_csv(StringIO(data))

In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")

In [23]: df2
Out[23]: 
   col_1
0   1.00
1   2.00
2    NaN
3   4.22

In [24]: df2["col_1"].apply(type).value_counts()
Out[24]: 
col_1
<class 'float'>    4
Name: count, dtype: int64

что преобразует все успешно прочитанные значения в числа с плавающей точкой, а некорректно прочитанные оставит как NaN.

В конечном счете, то, как вы будете обрабатывать чтение столбцов со смешанными типами данных, зависит от ваших конкретных потребностей. В приведенном выше примере, если вы хотите NaN аномалии в данных, то функция to_numeric() — вероятно, лучший вариант. Однако, если вы хотите, чтобы все данные были преобразованы, независимо от типа, то использование аргумента converters функции read_csv() определенно стоит попробовать.

Примечание

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

In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))

In [26]: df = pd.DataFrame({"col_1": col_1})

In [27]: df.to_csv("foo.csv")

In [28]: mixed_df = pd.read_csv("foo.csv")

In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]: 
col_1
<class 'int'>    737858
<class 'str'>    262144
Name: count, dtype: int64

In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')

приведет к тому, что mixed_df будет содержать тип данных int для некоторых фрагментов столбца и str для других из-за смешанных типов данных в прочитанных данных. Важно отметить, что весь столбец будет помечен типом dtype object, который используется для столбцов со смешанными типами данных.

Установление dtype_backend="numpy_nullable" приведет к применению nullable типов данных для каждого столбца.

In [31]: data = """a,b,c,d,e,f,g,h,i,j
   ....: 1,2.5,True,a,,,,,12-31-2019,
   ....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
   ....: """
   ....: 

In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])

In [33]: df
Out[33]: 
   a    b      c  d     e     f     g     h          i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA> 2019-12-31  <NA>
1  3  4.5  False  b     6   7.5  True     a 2019-12-31  <NA>

In [34]: df.dtypes
Out[34]: 
a             Int64
b           Float64
c           boolean
d    string[python]
e             Int64
f           Float64
g           boolean
h    string[python]
i    datetime64[ns]
j             Int64
dtype: object

Определение типа данных категорий

Categorical столбцы могут быть распарсены непосредственно, указав dtype='category' или dtype=CategoricalDtype(categories, ordered).

In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [36]: pd.read_csv(StringIO(data))
Out[36]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]: 
col1    object
col2    object
col3     int64
dtype: object

In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]: 
col1    category
col2    category
col3    category
dtype: object

Отдельные столбцы могут быть распарсены как Categorical с помощью спецификации в виде словаря:

In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]: 
col1    category
col2      object
col3       int64
dtype: object

Указание dtype='category' приведет к неупорядоченному Categorical, чьи categories — это уникальные значения, наблюдаемые в данных. Для большего контроля над категориями и порядком создайте CategoricalDtype заранее и передайте его для dtype данного столбца.

In [40]: from pandas.api.types import CategoricalDtype

In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)

In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]: 
col1    category
col2      object
col3       int64
dtype: object

При использовании dtype=CategoricalDtype, «неожиданные» значения за пределами dtype.categories обрабатываются как пропущенные значения.

In [43]: dtype = CategoricalDtype(["a", "b", "d"])  # No 'c'

In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]: 
0      a
1      a
2    NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']

Это соответствует поведению Categorical.set_categories().

Примечание

С dtype='category' полученные категории всегда будут распарсены как строки (тип данных object). Если категории числовые, их можно преобразовать с помощью функции to_numeric(), или, как уместно, с помощью другого преобразователя, например, to_datetime().

Когда dtype — это CategoricalDtype с однородными categories (все числовые, все даты и т. д.), преобразование выполняется автоматически.

In [45]: df = pd.read_csv(StringIO(data), dtype="category")

In [46]: df.dtypes
Out[46]: 
col1    category
col2    category
col3    category
dtype: object

In [47]: df["col3"]
Out[47]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']

In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)

In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)

In [50]: df["col3"]
Out[50]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]

Наименование и использование столбцов

Обработка названий столбцов

Файл может или не может содержать строку заголовка. pandas предполагает, что первая строка должна использоваться в качестве названий столбцов:

In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [53]: pd.read_csv(StringIO(data))
Out[53]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

Указав аргумент names в сочетании с header, можно указать другие имена для использования и указать, следует ли удалять строку заголовка (если она есть):

In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]: 
   foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]: 
  foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9

Если заголовок находится в строке, отличной от первой, передайте номер строки в header. Это пропустит предшествующие строки:

In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

Примечание

По умолчанию имена столбцов вычисляются: если имена не переданы, поведение идентично header=0, и имена столбцов вычисляются из первой непустой строки файла. Если имена столбцов явно переданы, поведение идентично header=None.

Обработка дублирующихся имён

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

In [59]: data = "a,b,a\n0,1,2\n3,4,5"

In [60]: pd.read_csv(StringIO(data))
Out[60]: 
   a  b  a.1
0  0  1    2
1  3  4    5

Больше нет дублирующихся данных, поскольку дублирующиеся столбцы «X», …, «X» становятся «X», «X.1», …, «X.N».

Фильтрация столбцов (usecols)

Аргумент usecols позволяет вам выбрать любой подмножество столбцов в файле, используя имена столбцов, номера позиций или вызываемую функцию:

In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [62]: pd.read_csv(StringIO(data))
Out[62]: 
   a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]: 
   a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]: 
   a  c
0  1  3
1  4  6
2  7  9

Аргумент usecols также можно использовать для указания столбцов, которые не следует использовать в конечном результате:

In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

В этом случае вызываемая функция указывает, что мы исключаем столбцы «a» и «c» из результата.

Комментарии и пустые строки

Игнорирование комментариев и пустых строк

Если параметр comment указан, то полностью прокомментированные строки будут проигнорированы. По умолчанию также будут проигнорированы полностью пустые строки.

In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c
  
# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
   a  b  c
0  1  2  3
1  4  5  6

Если skip_blank_lines=False, то read_csv не будет игнорировать пустые строки:

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
     a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0

Предупреждение

Наличие проигнорированных строк может создать неоднозначность с номерами строк; параметр header использует номера строк (игнорируя прокомментированные/пустые строки), в то время как skiprows использует номера строк (включая прокомментированные/пустые строки):

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
   A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
   a  b  c
0  1  2  3

Если и header, и skiprows указаны, header будет относиться к концу skiprows. Например:

In [76]: data = (
   ....:     "# empty\n"
   ....:     "# second empty line\n"
   ....:     "# third emptyline\n"
   ....:     "X,Y,Z\n"
   ....:     "1,2,3\n"
   ....:     "A,B,C\n"
   ....:     "1,2.,4.\n"
   ....:     "5.,NaN,10.0\n"
   ....: )
   ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0


In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
     A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0

Комментарии

Иногда в файл могут быть включены комментарии или метаданные:

In [79]: data = (
   ....:     "ID,level,category\n"
   ....:     "Patient1,123000,x # really unpleasant\n"
   ....:     "Patient2,23000,y # wouldn't take his medicine\n"
   ....:     "Patient3,1234018,z # awesome"
   ....: )
   ....: 

In [80]: with open("tmp.csv", "w") as fh:
   ....:     fh.write(data)
   ....: 

In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome

По умолчанию парсер включает комментарии в выходные данные:

In [82]: df = pd.read_csv("tmp.csv")

In [83]: df
Out[83]: 
         ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome

Мы можем подавить комментарии, используя ключевое слово comment:

In [84]: df = pd.read_csv("tmp.csv", comment="#")

In [85]: df
Out[85]: 
         ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 

Работа с данными Unicode

Аргумент encoding следует использовать для закодированных данных Unicode, что приведет к декодированию байтовых строк в Unicode в результате:

In [86]: from io import BytesIO

In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"

In [88]: data = data.decode("utf8").encode("latin-1")

In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")

In [90]: df
Out[90]: 
      word  length
0  Träumen       7
1    Grüße       5

In [91]: df["word"][1]
Out[91]: 'Grüße'

Некоторые форматы, которые кодируют все символы как несколько байтов, например, UTF-16, не будут правильно анализироваться без указания кодировки. Полный список стандартных кодировок Python.

Индексные столбцы и заключительные разделители

Если в файле есть один или больше столбцов данных, чем количество имен столбцов, первый столбец будет использоваться в качестве имён строк DataFrame:

In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [93]: pd.read_csv(StringIO(data))
Out[93]: 
        a    b     c
4   apple  bat   5.7
8  orange  cow  10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]: 
            a    b     c
index                   
4       apple  bat   5.7
8      orange  cow  10.0

Обычно этого поведения можно достичь, используя параметр index_col.

Есть некоторые исключительные случаи, когда файл подготовлен с разделителями в конце каждой строки данных, что вводит в заблуждение парсер. Чтобы явно отключить вывод индексного столбца и отбросить последний столбец, передайте index_col=False:

In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [98]: pd.read_csv(StringIO(data))
Out[98]: 
        a    b   c
4   apple  bat NaN
8  orange  cow NaN

In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]: 
   a       b    c
0  4   apple  bat
1  8  orange  cow

Если анализируется подмножество данных с помощью параметра usecols, то спецификация index_col основана на этом подмножестве, а не на исходных данных.

In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]: 
     b   c
4  bat NaN
8  cow NaN

In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]: 
     b   c
4  bat NaN
8  cow NaN

Обработка дат

Указание столбцов дат

Для лучшей работы с данными типа datetime, read_csv() использует ключевые аргументы parse_dates и date_format, что позволяет пользователям указывать различные столбцы и форматы дат/времени для преобразования входных текстовых данных в объекты datetime.

Простейший случай — просто передать parse_dates=True:

In [104]: with open("foo.csv", mode="w") as f:
   .....:     f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
   .....: 

# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [106]: df
Out[106]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)

Часто требуется хранить данные даты и времени отдельно или хранить различные поля дат отдельно. Ключевой аргумент parse_dates можно использовать для указания комбинации столбцов для извлечения дат и/или времени.

Вы можете указать список списков столбцов для parse_dates, результирующие столбцы дат будут добавлены в начало вывода (чтобы не повлиять на существующий порядок столбцов), а новые имена столбцов будут являться конкатенацией имён компонентных столбцов:

In [108]: data = (
   .....:     "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
   .....:     "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
   .....:     "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
   .....:     "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
   .....:     "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
   .....:     "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
   .....: )
   .....: 

In [109]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [111]: df
Out[111]: 
                  1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

По умолчанию парсер удаляет компонентные столбцы дат, но вы можете сохранить их с помощью ключевого аргумента keep_date_col:

In [112]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
   .....: )
   .....: 

In [113]: df
Out[113]: 
                  1_2                 1_3     0  ...          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  ...   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  ...   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  ...   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  ...   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  ...   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  ...   23:00:00   22:56:00 -0.59

[6 rows x 7 columns]

Обратите внимание, что если вы хотите объединить несколько столбцов в один столбец дат, необходимо использовать вложенный список. Другими словами, parse_dates=[1, 2] указывает, что второй и третий столбцы должны быть обработаны как отдельные столбцы дат, в то время как parse_dates=[[1, 2]] означает, что два столбца должны быть обработаны в один столбец.

Вы также можете использовать словарь для указания пользовательских имён столбцов:

In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [116]: df
Out[116]: 
              nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

Важно помнить, что если несколько текстовых столбцов должны быть обработаны в один столбец дат, то новый столбец добавляется в начало данных. Указание index_col основано на этом новом наборе столбцов, а не на исходных столбцах данных:

In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [118]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=date_spec, index_col=0
   .....: )  # index is the nominal column
   .....: 

In [119]: df
Out[119]: 
                                 actual     0     4
nominal                                            
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

Примечание

Если столбец или индекс содержит необрабатываемую дату, весь столбец или индекс будут возвращены неизменёнными как тип данных object. Для нестандартного разбора datetime используйте to_datetime() после pd.read_csv.

Примечание

read_csv имеет быстрый путь для разбора строк datetime в формате iso8601, например “2000-01-01T00:01:02+00:00” и аналогичные вариации. Если вы сможете организовать хранение datetime в этом формате, время загрузки значительно ускорится, наблюдалось примерно в ~20 раз.

Устарело начиная с версии 2.2.0: Объединение столбцов дат внутри read_csv устарело. Используйте pd.to_datetime для соответствующих столбцов результатов вместо этого.

Функции разбора дат

Наконец, парсер позволяет указать пользовательскую date_format. С точки зрения производительности, вы должны попробовать эти методы разбора дат в таком порядке:

  1. Если вы знаете формат, используйте date_format, например: date_format="%d/%m/%Y" или date_format={column_name: "%d/%m/%Y"}.

  2. Если у вас разные форматы для разных столбцов или вы хотите передать дополнительные параметры (например, utc) в to_datetime, то вы должны считать свои данные как object тип данных, а затем использовать to_datetime.

Разбор CSV с смешанными часовыми поясами

pandas не может напрямую представить столбец или индекс со смешанными часовыми поясами. Если ваш CSV-файл содержит столбцы со смешанными часовыми поясами, результат по умолчанию будет столбцом object-dtype со строками, даже с parse_dates. Чтобы обработать значения со смешанными часовыми поясами как столбец datetime, считайте их как object тип данных, а затем вызовите to_datetime() с utc=True.

In [120]: content = """\
   .....: a
   .....: 2000-01-01T00:00:00+05:00
   .....: 2000-01-01T00:00:00+06:00"""
   .....: 

In [121]: df = pd.read_csv(StringIO(content))

In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]

Определение формата даты и времени

Вот несколько примеров строк с датой и временем, которые можно угадать (все представляют 30 декабря 2011 года в 00:00:00):

  • “20111230”

  • “2011/12/30”

  • “20111230 00:00:00”

  • “12/30/2011 00:00:00”

  • “30/Dec/2011 00:00:00”

  • “30/Декабря/2011 00:00:00”

Обратите внимание, что определение формата чувствительно к dayfirst. С dayfirst=True он предположит, что “01/12/2011” — это 1 декабря. С dayfirst=False (по умолчанию) он предположит, что “01/12/2011” — это 12 января.

Если вы пытаетесь разобрать столбец строк с датами, pandas попытается угадать формат из первого элемента, отличного от NaN, а затем разобрать оставшуюся часть столбца с этим форматом. Если pandas не сможет угадать формат (например, если ваша первая строка — '01 December US/Pacific 2000'), будет выведено предупреждение, и каждая строка будет разобрана индивидуально с помощью dateutil.parser.parse. Наиболее безопасный способ разбора дат — явно задать format=.

In [124]: df = pd.read_csv(
   .....:     "foo.csv",
   .....:     index_col=0,
   .....:     parse_dates=True,
   .....: )
   .....: 

In [125]: df
Out[125]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

В случае, если в одном столбце смешаны форматы дат и времени, вы можете передать format='mixed'

In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")

In [127]: df = pd.read_csv(data)

In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')

In [129]: df
Out[129]: 
        date
0 2000-01-12
1 2000-01-13

или, если все ваши форматы дат и времени являются ISO8601 (возможно, не идентично отформатированными):

In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")

In [131]: df = pd.read_csv(data)

In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')

In [133]: df
Out[133]: 
                 date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00

Международные форматы дат

Хотя форматы дат в США имеют тенденцию быть MM/DD/YYYY, во многих международных форматах используется DD/MM/YYYY. Для удобства предоставляется ключевое слово dayfirst:

In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"

In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [136]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]: 
        date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]: 
        date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c

Запись CSV в объекты бинарных файлов

Новое в версии 1.2.0.

df.to_csv(..., mode="wb") позволяет записывать CSV в объект файла, открытого в двоичном режиме. В большинстве случаев нет необходимости указывать mode, так как Pandas автоматически определит, открыт ли объект файла в текстовом или двоичном режиме.

In [139]: import io

In [140]: data = pd.DataFrame([0, 1, 2])

In [141]: buffer = io.BytesIO()

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")

Указание метода для преобразования чисел с плавающей точкой

Параметр float_precision может быть указан для использования определенного преобразователя чисел с плавающей точкой во время разбора с помощью C движка. Варианты — обычный преобразователь, преобразователь высокой точности и преобразователь обратного преобразования (который гарантирует обратное преобразование значений после записи в файл). Например:

In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

In [144]: data = "a,b,c\n1,2,{0}".format(val)

In [145]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision=None,
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[145]: 5.551115123125783e-17

In [146]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision="high",
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[146]: 5.551115123125783e-17

In [147]: abs(
   .....:     pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
   .....:     - float(val)
   .....: )
   .....: 
Out[147]: 0.0

Разделители тысяч

Для больших чисел, которые были записаны с разделителями тысяч, вы можете установить ключевое слово thousands в строку длиной 1, чтобы целые числа были обработаны правильно:

По умолчанию числа с разделителями тысяч будут обработаны как строки:

In [148]: data = (
   .....:     "ID|level|category\n"
   .....:     "Patient1|123,000|x\n"
   .....:     "Patient2|23,000|y\n"
   .....:     "Patient3|1,234,018|z"
   .....: )
   .....: 

In [149]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [150]: df = pd.read_csv("tmp.csv", sep="|")

In [151]: df
Out[151]: 
         ID      level category
0  Patient1    123,000        x
1  Patient2     23,000        y
2  Patient3  1,234,018        z

In [152]: df.level.dtype
Out[152]: dtype('O')

Ключевое слово thousands позволяет корректно обрабатывать целые числа:

In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")

In [154]: df
Out[154]: 
         ID    level category
0  Patient1   123000        x
1  Patient2    23000        y
2  Patient3  1234018        z

In [155]: df.level.dtype
Out[155]: dtype('int64')

Значения NA

Для управления значениями, которые обрабатываются как пропущенные значения (обозначенные NaN), укажите строку в na_values. Если вы укажете список строк, то все значения в нём будут считаться пропущенными значениями. Если вы укажете число (целое число, например float, как 5.0 или вещественное число, как integer, как 5), соответствующие эквивалентные значения также будут означать пропущенное значение (в этом случае эффективно [5.0, 5] распознаются как NaN).

Чтобы полностью переопределить значения по умолчанию, которые распознаются как пропущенные, укажите keep_default_na=False.

По умолчанию NaN распознаваемые значения — ['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', ''].

Рассмотрим несколько примеров:

pd.read_csv("path_to_file.csv", na_values=[5])

В примере выше 5 и 5.0 будут распознаваться как NaN, в дополнение к значениям по умолчанию. Строка сначала интерпретируется как числовое 5, а затем как NaN.

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])

Выше только пустое поле будет распознаваться как NaN.

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])

Выше и NA, и 0 как строки будут NaN.

pd.read_csv("path_to_file.csv", na_values=["Nope"])

Значения по умолчанию, помимо строки "Nope" распознаются как NaN.

Бесконечность

inf подобные значения будут распарсованы как np.inf (положительная бесконечность), а -inf как -np.inf (отрицательная бесконечность). Это игнорирует регистр значения, означая, что Inf также будет распарсовано как np.inf.

Булевы значения

Общие значения True, False, TRUE и FALSE распознаются как булевы. Иногда может потребоваться распознать другие значения как булевы. Для этого используйте опции true_values и false_values следующим образом:

In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"

In [157]: print(data)
a,b,c
1,Yes,2
3,No,4

In [158]: pd.read_csv(StringIO(data))
Out[158]: 
   a    b  c
0  1  Yes  2
1  3   No  4

In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]: 
   a      b  c
0  1   True  2
1  3  False  4

Обработка «плохих» строк

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

In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError                               Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
   1013 kwds_defaults = _refine_defaults_read(
   1014     dialect,
   1015     delimiter,
   (...)
   1022     dtype_backend=dtype_backend,
   1023 )
   1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
    623     return parser
    625 with parser:
--> 626     return parser.read(nrows)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
   1916 nrows = validate_integer("nrows", nrows)
   1917 try:
   1918     # error: "ParserBase" has no attribute "read"
   1919     (
   1920         index,
   1921         columns,
   1922         col_dict,
-> 1923     ) = self._engine.read(  # type: ignore[attr-defined]
   1924         nrows
   1925     )
   1926 except Exception:
   1927     self.close()

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
    232 try:
    233     if self.low_memory:
--> 234         chunks = self._reader.read_low_memory(nrows)
    235         # destructive to chunks
    236         data = _concatenate_chunks(chunks)

File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()

File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()

File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()

File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()

File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4

Можно выбрать пропуск плохих строк:

In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]: 
   a  b   c
0  1  2   3
1  8  9  10

Новая функция в версии 1.4.0.

Или передать вызываемую функцию для обработки плохой строки, если engine="python". Плохая строка будет списком строк, разделённых sep:

In [164]: external_list = []

In [165]: def bad_lines_func(line):
   .....:     external_list.append(line)
   .....:     return line[-3:]
   .....: 

In [166]: external_list
Out[166]: []

Примечание

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

In [167]: bad_lines_func = lambda line: print(line)

In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'

In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'

In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]: 
     name            type
0  name a  a is of type a

В этом случае строка не была обработана, так как «плохая строка» вызвана символом экранирования.

Также можно использовать параметр usecols для удаления лишних данных столбцов, которые появляются в некоторых строках, но не в других:

In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
   1013 kwds_defaults = _refine_defaults_read(
   1014     dialect,
   1015     delimiter,
   (...)
   1022     dtype_backend=dtype_backend,
   1023 )
   1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
    617 _validate_names(kwds.get("names", None))
    619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
    622 if chunksize or iterator:
    623     return parser

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
   1617     self.options["has_index_names"] = kwds["has_index_names"]
   1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
   1895     raise ValueError(msg)
   1897 try:
-> 1898     return mapping[engine](f, **self.options)
   1899 except Exception:
   1900     if self.handles is not None:

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
    152     # error: Cannot determine type of 'names'
    153     if len(self.names) < len(usecols):  # type: ignore[has-type]
    154         # error: Cannot determine type of 'names'
--> 155         self._validate_usecols_names(
    156             usecols,
    157             self.names,  # type: ignore[has-type]
    158         )
    160 # error: Cannot determine type of 'names'
    161 self._validate_parse_dates_presence(self.names)  # type: ignore[has-type]

File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
    977 missing = [c for c in usecols if c not in names]
    978 if len(missing) > 0:
--> 979     raise ValueError(
    980         f"Usecols do not match columns, columns expected but not found: "
    981         f"{missing}"
    982     )
    984 return usecols

ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]

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

In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]: 
        a                b   c   d
0    name             type NaN NaN
1  name a   a is of type a NaN NaN
2  name b  b is of type b" NaN NaN

Диалект

Ключевое слово dialect обеспечивает большую гибкость в указании формата файла. По умолчанию используется диалект Excel, но можно указать либо имя диалекта, либо экземпляр csv.Dialect.

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

In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"

In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f

По умолчанию read_csv использует диалект Excel и рассматривает двойную кавычку как символ кавычек, что приводит к ошибке при обнаружении новой строки перед закрывающей двойной кавычкой.

Можно обойти эту проблему, используя dialect:

In [175]: import csv

In [176]: dia = csv.excel()

In [177]: dia.quoting = csv.QUOTE_NONE

In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]: 
       label1 label2 label3
index1     "a      c      e
index2      b      d      f

Все параметры диалекта можно указать отдельно в качестве ключевых аргументов:

In [179]: data = "a,b,c~1,2,3~4,5,6"

In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]: 
   a  b  c
0  1  2  3
1  4  5  6

Ещё одним распространённым параметром диалекта является skipinitialspace для пропуска пробелов после разделителя:

In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"

In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6

In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]: 
   a  b  c
0  1  2  3
1  4  5  6

Парсеры прилагают все усилия, чтобы «сделать всё правильно» и не быть хрупкими. Вывод типов — очень важная деталь. Если столбец может быть приведен к целочисленному типу без изменения содержимого, парсер это сделает. Любые нечисловые столбцы будут передаваться как тип «объект» как и в других объектах pandas.

Кавычки и символы экранирования

Кавычки (и другие символы экранирования) во вложенных полях могут обрабатываться различными способами. Один из способов — использовать обратные слэши; для правильной обработки этих данных необходимо указать параметр escapechar:

In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'

In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5

In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]: 
                               a  b
0  hello, "Bob", nice to see you  5

Файлы с фиксированной шириной столбцов

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

  • colspecs: Список пар (кортежей), задающих пределы фиксированных по ширине полей каждой строки как полуоткрытых интервалов (т.е. [от, до[ ). Строковое значение ‘infer’ может быть использовано для инструкции парсеру попытаться определить спецификации столбцов из первых 100 строк данных. По умолчанию, если не указано иное, происходит вычисление.

  • widths: Список ширины полей, которые могут быть использованы вместо ‘colspecs’, если интервалы являются непрерывными.

  • delimiter: Символы, которые следует рассматривать как символы-заполнители в файле с фиксированной шириной. Могут быть использованы для указания символа-заполнителя полей, если он не является пробелами (например, ‘~’).

Рассмотрим типичный файл данных с фиксированной шириной:

In [187]: data1 = (
   .....:     "id8141    360.242940   149.910199   11950.7\n"
   .....:     "id1594    444.953632   166.985655   11788.4\n"
   .....:     "id1849    364.136849   183.628767   11806.2\n"
   .....:     "id1230    413.836124   184.375703   11916.8\n"
   .....:     "id1948    502.953953   173.237159   12468.3"
   .....: )
   .....: 

In [188]: with open("bar.csv", "w") as f:
   .....:     f.write(data1)
   .....: 

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

# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]

In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)

In [191]: df
Out[191]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

Обратите внимание, как парсер автоматически выбирает имена столбцов X.<номер столбца>, когда указан аргумент header=None. В качестве альтернативы, вы можете указать только ширину столбцов для непрерывных столбцов:

# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]

In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)

In [194]: df
Out[194]: 
        0           1           2        3
0  id8141  360.242940  149.910199  11950.7
1  id1594  444.953632  166.985655  11788.4
2  id1849  364.136849  183.628767  11806.2
3  id1230  413.836124  184.375703  11916.8
4  id1948  502.953953  173.237159  12468.3

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

По умолчанию read_fwf попытается определить colspecs файла, используя первые 100 строк файла. Это возможно только в тех случаях, когда столбцы выровнены и правильно разделены указанным delimiter (по умолчанию разделитель — пробел).

In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [196]: df
Out[196]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

read_fwf поддерживает параметр dtype для указания типов анализируемых столбцов, отличных от вычисленных.

In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]: 
1    float64
2    float64
3    float64
dtype: object

In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]: 
0     object
1    float64
2     object
3    float64
dtype: object

Индексы

Файлы с «неявным» столбцом индекса

Рассмотрим файл с на одной записью меньше в заголовке, чем количество столбцов данных:

In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"

In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

In [201]: with open("foo.csv", "w") as f:
   .....:     f.write(data)
   .....: 

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

In [202]: pd.read_csv("foo.csv")
Out[202]: 
          A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5

Обратите внимание, что даты не были автоматически проанализированы. В этом случае вам нужно сделать так же, как и раньше:

In [203]: df = pd.read_csv("foo.csv", parse_dates=True)

In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)

Чтение индекса с MultiIndex

Предположим, у вас есть данные, индексированные по двум столбцам:

In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'

In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5

In [207]: with open("mindex_ex.csv", mode="w") as f:
   .....:     f.write(data)
   .....: 

Аргумент index_col для read_csv может принимать список номеров столбцов, чтобы преобразовать несколько столбцов в MultiIndex для индекса возвращаемого объекта:

In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])

In [209]: df
Out[209]: 
            zit  xit
year indiv          
1977 A      1.2  0.6
     B      1.5  0.5

In [210]: df.loc[1977]
Out[210]: 
       zit  xit
indiv          
A      1.2  0.6
B      1.5  0.5

Чтение столбцов с MultiIndex

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

In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))

In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))

In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)

In [214]: df.to_csv("mi.csv")

In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0


In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]: 
c                    1                  2
d                    a                  b
a   Unnamed: 2_level_2 Unnamed: 3_level_2
1                  1.0                1.0
2 b                1.0                1.0
3 c                1.0                1.0
4 d                1.0                1.0

read_csv также может интерпретировать более распространенный формат индексов с несколькими столбцами.

In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"

In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [219]: with open("mi2.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]: 
     a         b   c    
     q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12

Примечание

Если index_col не указан (например, у вас нет индекса или вы его создали с помощью df.to_csv(..., index=False)), то какие-либо names в индексе столбцов будут утрачены.

Автоматическое определение разделителя

read_csv может определять файлы с разделителями (не обязательно с запятыми), используя класс csv.Sniffer из модуля csv. Для этого необходимо указать sep=None.

In [221]: df = pd.DataFrame(np.random.randn(10, 4))

In [222]: df.to_csv("tmp2.csv", sep=":", index=False)

In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]: 
          0         1         2         3
0  0.469112 -0.282863 -1.509059 -1.135632
1  1.212112 -0.173215  0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929  1.071804
3  0.721555 -0.706771 -1.039575  0.271860
4 -0.424972  0.567020  0.276232 -1.087401
5 -0.673690  0.113648 -1.478427  0.524988
6  0.404705  0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312  0.844885
8  1.075770 -0.109050  1.643563 -1.469388
9  0.357021 -0.674600 -1.776904 -0.968914

Чтение нескольких файлов для создания одного DataFrame

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

Итерация по файлам по частям

Предположим, вам нужно итерироваться по файлу (возможно, очень большому) лениво, а не загружать весь файл в память, например, так:

In [224]: df = pd.DataFrame(np.random.randn(10, 4))

In [225]: df.to_csv("tmp.csv", index=False)

In [226]: table = pd.read_csv("tmp.csv")

In [227]: table
Out[227]: 
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
4  0.410835  0.813850  0.132003 -0.827317
5 -0.076467 -1.187678  1.130127 -1.436737
6 -1.413681  1.607920  1.024180  0.569605
7  0.875906 -2.211372  0.974466 -2.006747
8 -0.410001 -0.078638  0.545952 -1.219217
9 -1.226825  0.769804 -1.281247 -0.727707

Указав chunksize для read_csv, значением возврата будет итерируемый объект типа TextFileReader:

In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
   .....:     print(reader)
   .....:     for chunk in reader:
   .....:         print(chunk)
   .....: 
<pandas.io.parsers.readers.TextFileReader object at 0x7ff2e5421db0>
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
          0         1         2         3
4  0.410835  0.813850  0.132003 -0.827317
5 -0.076467 -1.187678  1.130127 -1.436737
6 -1.413681  1.607920  1.024180  0.569605
7  0.875906 -2.211372  0.974466 -2.006747
          0         1         2         3
8 -0.410001 -0.078638  0.545952 -1.219217
9 -1.226825  0.769804 -1.281247 -0.727707

Изменено в версии 1.2: read_csv/json/sas возвращает менеджер контекста при итерации по файлу.

Указание iterator=True также вернёт TextFileReader объект:

In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
   .....:     print(reader.get_chunk(5))
   .....: 
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
4  0.410835  0.813850  0.132003 -0.827317

Указание движка анализатора

Pandas поддерживает три движка: C, Python и экспериментальный pyarrow (требует пакет pyarrow). В целом, pyarrow-движок быстрее при больших объёмах данных и по скорости сопоставим с C-движком в большинстве других случаев. Python-движок, как правило, медленнее, чем pyarrow и C-движки в большинстве случаев. Тем не менее, pyarrow-движок значительно менее устойчив, чем C-движок, у которого отсутствуют некоторые функции по сравнению с Python-движком.

Возможно, pandas использует C-анализатор (указан как engine='c'), но может перейти к Python, если указаны не поддерживаемые C-опции.

В настоящее время не поддерживаются опции C- и pyarrow-движков:

  • sep, отличные от одного символа (например, разделители regex)

  • skipfooter

  • sep=None с delim_whitespace=False

Указание любой из вышеперечисленных опций приведёт к ParserWarning, если не выбран Python-движок явно, используя engine='python'.

Опции, которые не поддерживаются pyarrow-движком и не покрываются списком выше:

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • delim_whitespace

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • infer_datetime_format

  • verbose

  • skipinitialspace

  • low_memory

Указание этих опций с engine='pyarrow' вызовет ValueError.

Чтение/запись удаленных файлов

Вы можете передать URL для чтения или записи удаленных файлов во многие функции ввода-вывода pandas — следующий пример демонстрирует чтение файла CSV:

df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")

Новое в версии 1.3.0.

Заголовок по умолчанию можно отправить вместе с HTTP(s)-запросами, передав словарь соответствий ключей и значений заголовков в качестве аргумента storage_options, как показано ниже:

headers = {"User-Agent": "pandas"}
df = pd.read_csv(
    "https://download.bls.gov/pub/time.series/cu/cu.item",
    sep="\t",
    storage_options=headers
)

Все URL, которые не являются локальными файлами или HTTP(s), обрабатываются с помощью fsspec, если он установлен, и его различными реализациями файловых систем (включая Amazon S3, Google Cloud, SSH, FTP, webHDFS...). Некоторые из этих реализаций потребуют установки дополнительных пакетов, например, для URL S3 требуется библиотека s3fs:

df = pd.read_json("s3://pandas-test/adatafile.json")

При работе с удаленными системами хранения вам может потребоваться дополнительная настройка с переменными окружения или конфигурационными файлами в особых местах. Например, чтобы получить доступ к данным в вашем ведре S3, вам необходимо определить учетные данные одним из способов, перечисленных в документации S3Fs. То же самое касается нескольких систем хранения, и вы должны следовать ссылкам в fsimpl1 для реализаций, встроенных в fsspec, и fsimpl2 для тех, которые не включены в основное распределение fsspec.

Вы также можете передавать параметры непосредственно драйверу бэкенда. Поскольку fsspec не использует переменную окружения AWS_S3_HOST, мы можем напрямую определить словарь, содержащий endpoint_url, и передать объект в параметр storage:

storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)

Дополнительные примеры конфигураций и документации можно найти в документации S3Fs.

Если у вас нет учетных данных S3, вы по-прежнему можете получить доступ к общедоступным данным, указав анонимное подключение, например

Новое в версии 1.2.0.

pd.read_csv(
    "s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
    "-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"anon": True},
)

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

pd.read_csv(
    "simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
    "SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"s3": {"anon": True}},
)

где мы указываем, что параметр «anon» предназначен для части «s3» реализации, а не для реализации кэширования. Обратите внимание, что кэширование выполняется в временной папке только на время сеанса, но вы также можете указать постоянное хранилище.

Выгрузка данных

Запись в формате CSV

Объекты Series и DataFrame имеют метод экземпляра to_csv, который позволяет сохранить содержимое объекта в виде файла с разделителями запятых. Функция принимает несколько аргументов. Только первый является обязательным.

  • path_or_buf: Строковый путь к файлу для записи или объект файла. Если объект файла, он должен быть открыт с помощью newline=''

  • sep : Разделитель полей для выходного файла (по умолчанию “,”)

  • na_rep: Строковое представление пропущенного значения (по умолчанию ‘’)

  • float_format: Строковый формат для чисел с плавающей точкой

  • columns: Столбцы для записи (по умолчанию None)

  • header: Нужно ли записывать имена столбцов (по умолчанию True)

  • index: Нужно ли записывать имена строк (индексы) (по умолчанию True)

  • index_label: Метки столбцов для столбцов индекса, если необходимо. Если None (по умолчанию), и header и index равны True, используются имена индекса. (Последовательность должна быть указана, если DataFrame использует MultiIndex).

  • mode : Режим записи Python, по умолчанию ‘w’

  • encoding: Строка, представляющая кодировку, которую следует использовать, если содержимое не ASCII, для версий Python до 3

  • lineterminator: Последовательность символов, обозначающая конец строки (по умолчанию os.linesep)

  • quoting: Установка правил цитирования, как в модуле csv (по умолчанию csv.QUOTE_MINIMAL). Обратите внимание, что если вы установили float_format, то числа с плавающей точкой преобразуются в строки, и csv.QUOTE_NONNUMERIC будет рассматривать их как нечисловые

  • quotechar: Символ, используемый для цитирования полей (по умолчанию ‘”’)

  • doublequote: Управление цитированием quotechar в полях (по умолчанию True)

  • escapechar: Символ, используемый для экранирования sep и quotechar при необходимости (по умолчанию None)

  • chunksize: Количество строк для записи за раз

  • date_format: Строковый формат для объектов datetime

Запись отформатированной строки

Объект DataFrame имеет метод экземпляра to_string, который позволяет контролировать строковое представление объекта. Все аргументы необязательны:

  • buf по умолчанию None, например, объект StringIO

  • columns по умолчанию None, столбцы для записи

  • col_space по умолчанию None, минимальная ширина каждого столбца.

  • na_rep по умолчанию NaN, представление значения NA

  • formatters по умолчанию None, словарь (по столбцам) функций, каждая из которых принимает один аргумент и возвращает отформатированную строку

  • float_format по умолчанию None, функция, которая принимает один (float) аргумент и возвращает отформатированную строку; применяется к числам с плавающей точкой в DataFrame.

  • sparsify по умолчанию True, установить в False для DataFrame с иерархическим индексом, чтобы вывести каждый ключ MultiIndex в каждой строке.

  • index_names по умолчанию True, выведет имена индексов

  • index по умолчанию True, выведет индекс (т.е. метки строк)

  • header по умолчанию True, выведет метки столбцов

  • justify по умолчанию left, выведет заголовки столбцов выровненными влево или вправо

Объект Series также имеет метод to_string, но только с аргументами buf, na_rep, float_format. Также есть аргумент length, который, если установлен в True, дополнительно выведет длину Series.

JSON

Чтение и запись файлов и строк в формате JSON.

Запись JSON

Series или DataFrame можно преобразовать в допустимую строку JSON. Используйте to_json с необязательными параметрами:

  • path_or_buf : путь к файлу или буфер для записи вывода. Может быть None, в этом случае возвращается строка JSON.

  • orient :

    Series:
    • по умолчанию index

    • допустимые значения {split, records, index}

    DataFrame:
    • по умолчанию columns

    • допустимые значения {split, records, index, columns, values, table}

    Формат строки JSON

    split

    словарь, похожий на {индекс -> [индекс], столбцы -> [столбцы], данные -> [значения]}

    records

    список, похожий на [{столбец -> значение}, … , {столбец -> значение}]

    index

    словарь, похожий на {индекс -> {столбец -> значение}}

    columns

    словарь, похожий на {столбец -> {индекс -> значение}}

    values

    просто массив значений

    table

    соответствует схеме таблицы JSON

  • date_format : строка, тип преобразования даты, ‘epoch’ для временной метки, ‘iso’ для ISO8601.

  • double_precision : количество десятичных знаков для кодирования чисел с плавающей точкой, по умолчанию 10.

  • force_ascii : принудительно кодировать строку в ASCII, по умолчанию True.

  • date_unit : единица измерения времени для кодирования, управляет точностью временных меток и ISO8601. Одно из значений ‘s’, ‘ms’, ‘us’ или ‘ns’ для секунд, миллисекунд, микросекунд и наносекунд соответственно. По умолчанию ‘ms’.

  • default_handler : обработчик, вызываемый, если объект не может быть преобразован в подходящий для JSON формат. Принимает один аргумент, который является объектом для преобразования, и возвращает сериализуемый объект.

  • lines : если records orient, тогда каждый элемент записи будет записан в отдельной строке как json.

  • mode : строка, режим записи при записи в путь. ‘w’ для записи, ‘a’ для добавления. По умолчанию ‘w’

Обратите внимание, что NaN, NaT и None будут преобразованы в null, а datetime объекты будут преобразованы на основе параметров date_format и date_unit.

In [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [231]: json = dfj.to_json()

In [232]: json
Out[232]: '{"A":{"0":-0.1213062281,"1":0.6957746499,"2":0.9597255933,"3":-0.6199759194,"4":-0.7323393705},"B":{"0":-0.0978826728,"1":0.3417343559,"2":-1.1103361029,"3":0.1497483186,"4":0.6877383895}}'

Параметры ориентирования

Существует ряд различных параметров для формата результирующего файла/строки JSON. Рассмотрим следующие DataFrame и Series:

In [233]: dfjo = pd.DataFrame(
   .....:     dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
   .....:     columns=list("ABC"),
   .....:     index=list("xyz"),
   .....: )
   .....: 

In [234]: dfjo
Out[234]: 
   A  B  C
x  1  4  7
y  2  5  8
z  3  6  9

In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")

In [236]: sjo
Out[236]: 
x    15
y    16
z    17
Name: D, dtype: int64

Ориентированный на столбцы (по умолчанию для DataFrame) сериализует данные как вложенные объекты JSON, с метками столбцов, выступающими в качестве основного индекса:

In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'

# Not available for Series

Ориентированный на индексы (по умолчанию для Series) аналогичен ориентированному на столбцы, но метки индекса теперь являются основными:

In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'

In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'

Ориентированный на записи сериализует данные в массив JSON записей «столбец —> значение», метки индекса не включаются. Это полезно для передачи DataFrame данных в библиотеки построения графиков, например, в библиотеку JavaScript d3.js:

In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'

In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'

Ориентированный на значения — это базовый параметр, который сериализует данные во вложенные массивы JSON только значений, метки столбцов и индексов не включаются:

In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'

# Not available for Series

Ориентированный на разделы сериализует данные в объект JSON, содержащий отдельные записи для значений, индекса и столбцов. Имя также включено для Series:

In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'

In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'

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

Примечание

Любой параметр ориентирования, который кодируется в объект JSON, не сохранит порядок меток индекса и столбцов при сериализации туда и обратно. Если вы хотите сохранить порядок меток, используйте параметр split, так как он использует упорядоченные контейнеры.

Обработка дат

Запись в формате даты ISO:

In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [246]: dfd["date"] = pd.Timestamp("20130101")

In [247]: dfd = dfd.sort_index(axis=1, ascending=False)

In [248]: json = dfd.to_json(date_format="iso")

In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

Запись в формате даты ISO с микросекундами:

In [250]: json = dfd.to_json(date_format="iso", date_unit="us")

In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

Маркеры времени эпохи, в секундах:

In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")

In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

Запись в файл с индексом даты и столбцом даты:

In [254]: dfj2 = dfj.copy()

In [255]: dfj2["date"] = pd.Timestamp("20130101")

In [256]: dfj2["ints"] = list(range(5))

In [257]: dfj2["bools"] = True

In [258]: dfj2.index = pd.date_range("20130101", periods=5)

In [259]: dfj2.to_json("test.json")

In [260]: with open("test.json") as fh:
   .....:     print(fh.read())
   .....: 
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}

Поведение по умолчанию

Если сериализатор JSON не может обработать содержимое контейнера напрямую, он будет действовать следующим образом:

  • если тип данных не поддерживается (например, np.complex_), то default_handler, если задано, будет вызываться для каждого значения, в противном случае генерируется исключение.

  • если объект не поддерживается, он попытается сделать следующее:

    • проверить, определен ли для объекта метод toDict, и вызвать его. Метод toDict должен возвращать dict, который затем будет сериализован в JSON.

    • вызвать default_handler, если он был предоставлен.

    • преобразовать объект в dict, пройдя по его содержимому. Однако это часто приводит к ошибке OverflowError или к неожиданным результатам.

В общем случае, лучшим подходом для объектов или типов данных, которые не поддерживаются, является предоставление default_handler. Например:

>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json()  # raises
RuntimeError: Unhandled numpy dtype 15

можно обработать, указав простой default_handler:

In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'

Чтение JSON

Чтение строки JSON в объект pandas может принимать несколько параметров. Парсер попытается разобрать DataFrame, если typ не задано или задано None. Чтобы явно принудительно выполнить Series парсинг, передайте typ=series

  • filepath_or_buffer : ВАЛИДНАЯ строка JSON или дескриптор файла / StringIO. Строка может быть URL. Допустимые схемы URL включают http, ftp, S3 и file. Для URL файлов ожидается хост. Например, локальный файл может быть file://localhost/path/to/table.json

  • typ : тип объекта для извлечения (ряд или таблица), по умолчанию ‘frame’

  • orient :

    Ряд :
    • по умолчанию index

    • разрешённые значения {split, records, index}

    Таблица
    • по умолчанию columns

    • разрешённые значения {split, records, index, columns, values, table}

    Формат строки JSON

    split

    похожий на словарь {индекс -> [индекс], столбцы -> [столбцы], данные -> [значения]}

    records

    список, похожий на [{столбец -> значение}, …, {столбец -> значение}]

    index

    похожий на словарь {индекс -> {столбец -> значение}}

    columns

    похожий на словарь {столбец -> {индекс -> значение}}

    values

    только массив значений

    table

    соблюдение схемы табличной схемы

  • dtype : если True, вывести типы, если словарь столбец-тип, использовать их, если False, то не выводить типы, по умолчанию True, применяется только к данным.

  • convert_axes : boolean, попробовать преобразовать оси в правильные типы, по умолчанию True

  • convert_dates : список столбцов для парсинга дат; Если True, то попытаться разобрать столбцы, похожие на дату, по умолчанию True.

  • keep_default_dates : boolean, по умолчанию True. При парсинге дат разобрать столбцы, похожие на дату, по умолчанию.

  • precise_float : boolean, по умолчанию False. Включить использование функции с большей точностью (strtod) при декодировании строк в двойные значения. По умолчанию (False) используется быстрая, но менее точная встроенная функциональность.

  • date_unit : строка, единица измерения временной метки для определения преобразования дат. По умолчанию None. По умолчанию точность временной метки будет определяться, если этого не требуется, передайте одно из ‘s’, ‘ms’, ‘us’ или ‘ns’ для принудительного установления точности временной метки соответственно к секундам, миллисекундам, микросекундам или наносекундам.

  • lines : считывает файл как один объект JSON на строку.

  • encoding : кодировка для декодирования py3 байтов.

  • chunksize : при использовании в сочетании с lines=True, возвращает итератор, который считывает pandas.api.typing.JsonReader строк за итерацию.

  • engine: Либо "ujson", встроенный парсер JSON, либо "pyarrow", который перенаправляет на pyarrow’s pyarrow.json.read_json. "pyarrow" доступен только при lines=True

Парсер поднимет одно из ValueError/TypeError/AssertionError, если JSON не может быть проанализирован.

Если был использован не по умолчанию orient при кодировании в JSON, убедитесь, что тот же параметр передан здесь, чтобы декодирование дало осмысленные результаты. См. Параметры ориентации для обзора.

Преобразование данных

По умолчанию convert_axes=True, dtype=True и convert_dates=True будут пытаться разобрать оси и все данные в соответствующие типы, включая даты. Если вам нужно переопределить определённые типы данных, передайте словарь в dtype. convert_axes следует устанавливать в False только если вам нужно сохранить числовые значения в виде строк (например, ‘1’, ‘2’) в осях.

Примечание

Значения больших целых чисел могут быть преобразованы в даты, если convert_dates=True, и данные и/или имена столбцов имеют «похожий на дату» вид. Точный порог зависит от date_unit. «Похожий на дату» означает, что имя столбца соответствует одному из следующих критериев:

  • он оканчивается на '_at'

  • он оканчивается на '_time'

  • он начинается с 'timestamp'

  • он является 'modified'

  • он является 'date'

Предупреждение

При чтении данных JSON автоматическое приведение к типам данных имеет некоторые особенности:

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

  • столбец, содержавший данные типа float, будет преобразован в integer, если это можно сделать безопасно, например, столбец типа 1.

  • столбцы булевых значений будут преобразованы в integer при восстановлении

Поэтому иногда вам может потребоваться указать конкретные типы данных через параметр dtype.

Чтение из JSON-строки:

In [262]: from io import StringIO

In [263]: pd.read_json(StringIO(json))
Out[263]: 
   date         B         A
0     1  0.403310  0.176444
1     1  0.301624 -0.154951
2     1 -1.369849 -2.179861
3     1  1.462696 -0.954208
4     1 -0.826591 -1.743161

Чтение из файла:

In [264]: pd.read_json("test.json")
Out[264]: 
                   A         B  date  ints  bools
2013-01-01 -0.121306 -0.097883  1356     0   True
2013-01-02  0.695775  0.341734  1356     1   True
2013-01-03  0.959726 -1.110336  1356     2   True
2013-01-04 -0.619976  0.149748  1356     3   True
2013-01-05 -0.732339  0.687738  1356     4   True

Не преобразовывать данные (но преобразовывать оси и даты):

In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]: 
A        object
B        object
date     object
ints     object
bools    object
dtype: object

Указать типы данных для преобразования:

In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]: 
A        float32
B        float64
date       int64
ints       int64
bools       int8
dtype: object

Сохранить строковые индексы:

In [267]: from io import StringIO

In [268]: si = pd.DataFrame(
   .....:     np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
   .....: )
   .....: 

In [269]: si
Out[269]: 
     0    1    2    3
0  0.0  0.0  0.0  0.0
1  0.0  0.0  0.0  0.0
2  0.0  0.0  0.0  0.0
3  0.0  0.0  0.0  0.0

In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')

In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')

In [272]: json = si.to_json()

In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)

In [274]: sij
Out[274]: 
   0  1  2  3
0  0  0  0  0
1  0  0  0  0
2  0  0  0  0
3  0  0  0  0

In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')

In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')

Даты, записанные в наносекундах, необходимо читать обратно в наносекундах:

In [277]: from io import StringIO

In [278]: json = dfj2.to_json(date_unit="ns")

# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")

In [280]: dfju
Out[280]: 
                            A         B        date  ints  bools
1356998400000000000 -0.121306 -0.097883  1356998400     0   True
1357084800000000000  0.695775  0.341734  1356998400     1   True
1357171200000000000  0.959726 -1.110336  1356998400     2   True
1357257600000000000 -0.619976  0.149748  1356998400     3   True
1357344000000000000 -0.732339  0.687738  1356998400     4   True

# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))

In [282]: dfju
Out[282]: 
                   A         B       date  ints  bools
2013-01-01 -0.121306 -0.097883 2013-01-01     0   True
2013-01-02  0.695775  0.341734 2013-01-01     1   True
2013-01-03  0.959726 -1.110336 2013-01-01     2   True
2013-01-04 -0.619976  0.149748 2013-01-01     3   True
2013-01-05 -0.732339  0.687738 2013-01-01     4   True

# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [284]: dfju
Out[284]: 
                   A         B        date  ints  bools
2013-01-01 -0.121306 -0.097883  1356998400     0   True
2013-01-02  0.695775  0.341734  1356998400     1   True
2013-01-03  0.959726 -1.110336  1356998400     2   True
2013-01-04 -0.619976  0.149748  1356998400     3   True
2013-01-05 -0.732339  0.687738  1356998400     4   True

Установив аргумент dtype_backend, можно управлять типом данных по умолчанию, используемым для результирующей DataFrame.

In [285]: data = (
   .....:  '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
   .....:  '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
   .....:  '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
   .....: )
   .....: 

In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [287]: df
Out[287]: 
   a    b      c  d     e     f     g     h           i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA>  12-31-2019  None
1  3  4.5  False  b     6   7.5  True     a  12-31-2019  None

In [288]: df.dtypes
Out[288]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object

Нормализация

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

In [289]: data = [
   .....:     {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
   .....:     {"name": {"given": "Mark", "family": "Regner"}},
   .....:     {"id": 2, "name": "Faye Raker"},
   .....: ]
   .....: 

In [290]: pd.json_normalize(data)
Out[290]: 
    id name.first name.last name.given name.family        name
0  1.0     Coleen      Volk        NaN         NaN         NaN
1  NaN        NaN       NaN       Mark      Regner         NaN
2  2.0        NaN       NaN        NaN         NaN  Faye Raker
In [291]: data = [
   .....:     {
   .....:         "state": "Florida",
   .....:         "shortname": "FL",
   .....:         "info": {"governor": "Rick Scott"},
   .....:         "county": [
   .....:             {"name": "Dade", "population": 12345},
   .....:             {"name": "Broward", "population": 40000},
   .....:             {"name": "Palm Beach", "population": 60000},
   .....:         ],
   .....:     },
   .....:     {
   .....:         "state": "Ohio",
   .....:         "shortname": "OH",
   .....:         "info": {"governor": "John Kasich"},
   .....:         "county": [
   .....:             {"name": "Summit", "population": 1234},
   .....:             {"name": "Cuyahoga", "population": 1337},
   .....:         ],
   .....:     },
   .....: ]
   .....: 

In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]: 
         name  population    state shortname info.governor
0        Dade       12345  Florida        FL    Rick Scott
1     Broward       40000  Florida        FL    Rick Scott
2  Palm Beach       60000  Florida        FL    Rick Scott
3      Summit        1234     Ohio        OH   John Kasich
4    Cuyahoga        1337     Ohio        OH   John Kasich

Параметр max_level обеспечивает больший контроль над уровнем, до которого должна производиться нормализация. При max_level=1 следующий фрагмент кода выполняет нормализацию до первого уровня вложенности переданного словаря.

In [293]: data = [
   .....:     {
   .....:         "CreatedBy": {"Name": "User001"},
   .....:         "Lookup": {
   .....:             "TextField": "Some text",
   .....:             "UserField": {"Id": "ID001", "Name": "Name001"},
   .....:         },
   .....:         "Image": {"a": "b"},
   .....:     }
   .....: ]
   .....: 

In [294]: pd.json_normalize(data, max_level=1)
Out[294]: 
  CreatedBy.Name Lookup.TextField                    Lookup.UserField Image.a
0        User001        Some text  {'Id': 'ID001', 'Name': 'Name001'}       b

JSON, разделенный по строкам

pandas может читать и записывать файлы JSON, разделенные по строкам, которые часто используются в конвейерах обработки данных с Hadoop или Spark.

Для файлов JSON, разделенных по строкам, pandas также может вернуть итератор, который читает по chunksize строкам за раз. Это может быть полезно для больших файлов или для чтения из потока.

In [295]: from io import StringIO

In [296]: jsonl = """
   .....:     {"a": 1, "b": 2}
   .....:     {"a": 3, "b": 4}
   .....: """
   .....: 

In [297]: df = pd.read_json(StringIO(jsonl), lines=True)

In [298]: df
Out[298]: 
   a  b
0  1  2
1  3  4

In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'

# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
   .....:     reader
   .....:     for chunk in reader:
   .....:         print(chunk)
   .....: 
Empty DataFrame
Columns: []
Index: []
   a  b
0  1  2
   a  b
1  3  4

Файлы JSON, разделенные по строкам, также можно читать с помощью pyarrow-читателя, указав engine="pyarrow".

In [301]: from io import BytesIO

In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")

In [303]: df
Out[303]: 
   a  b
0  1  2
1  3  4

Новое в версии 2.0.0.

END_OF_DOCUMENT_MARKER

Схема таблицы

Схема таблицы — это спецификация для описания табличных наборов данных как объекта JSON. JSON содержит информацию о названиях полей, типах и других атрибутах. Вы можете использовать ориентацию table для построения строки JSON с двумя полями, schema и data.

In [304]: df = pd.DataFrame(
   .....:     {
   .....:         "A": [1, 2, 3],
   .....:         "B": ["a", "b", "c"],
   .....:         "C": pd.date_range("2016-01-01", freq="d", periods=3),
   .....:     },
   .....:     index=pd.Index(range(3), name="idx"),
   .....: )
   .....: 

In [305]: df
Out[305]: 
     A  B          C
idx                 
0    1  a 2016-01-01
1    2  b 2016-01-02
2    3  c 2016-01-03

In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'

Поле schema содержит ключ fields, который сам содержит список пар «имя столбца — тип», включая Index или MultiIndex (см. ниже список типов). Поле schema также содержит поле primaryKey, если (мульти)индекс уникален.

Второе поле, data, содержит сериализованные данные с ориентацией records. Индекс включён, а все даты и время отформатированы в формате ISO 8601, как требуется спецификацией Схемы таблицы.

Полный список поддерживаемых типов описан в спецификации Схемы таблицы. Эта таблица показывает сопоставление с типами pandas:

Тип pandas

Тип схемы таблицы

int64

целое число

float64

число

bool

булево

datetime64[ns]

дата и время

timedelta64[ns]

продолжительность

categorical

любой

object

строка

Несколько замечаний по сгенерированной схеме таблицы:

  • Объект schema содержит поле pandas_version. Оно содержит версию диалекта схемы pandas и будет увеличиваться при каждой ревизии.

  • Все даты преобразуются в UTC при сериализации. Даже значения с таймзоной без таймзоны, которые обрабатываются как UTC с смещением 0.

    In [307]: from pandas.io.json import build_table_schema
    
    In [308]: s = pd.Series(pd.date_range("2016", periods=4))
    
    In [309]: build_table_schema(s)
    Out[309]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • Даты и время с таймзоной (до сериализации) включают дополнительное поле tz с именем таймзоны (например, 'US/Central').

    In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
    
    In [311]: build_table_schema(s_tz)
    Out[311]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • Периоды преобразуются в метки времени до сериализации, и поэтому имеют такое же поведение преобразования в UTC. Кроме того, периоды будут содержать дополнительное поле freq с частотой периода, например, 'A-DEC'.

    In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
    
    In [313]: build_table_schema(s_per)
    Out[313]: 
    {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • Категориальные значения используют тип any и ограничение enum, перечисляющее набор возможных значений. Кроме того, включено поле ordered:

    In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
    
    In [315]: build_table_schema(s_cat)
    Out[315]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values',
       'type': 'any',
       'constraints': {'enum': ['a', 'b']},
       'ordered': False}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • Поле primaryKey, содержащее массив меток, включено если индекс уникален:

    In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [317]: build_table_schema(s_dupe)
    Out[317]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'}
    
  • Поведение primaryKey аналогично с MultiIndexes, но в этом случае primaryKey — это массив:

    In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [319]: build_table_schema(s_multi)
    Out[319]: 
    {'fields': [{'name': 'level_0', 'type': 'string'},
      {'name': 'level_1', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': FrozenList(['level_0', 'level_1']),
     'pandas_version': '1.4.0'}
    
  • Именование по умолчанию примерно соответствует следующим правилам:

    • Для рядов используется object.name. Если он отсутствует, то используется имя values

    • Для DataFrames используется строковое представление имени столбца

    • Для Index (не MultiIndex) используется index.name, со значением по умолчанию index, если оно None.

    • Для MultiIndex используется mi.names. Если у какого-либо уровня нет имени, то используется level_<i>.

read_json также принимает orient='table' в качестве аргумента. Это позволяет сохранять метаданные, такие как типы данных и имена индексов, с возможностью обратного преобразования.

In [320]: df = pd.DataFrame(
   .....:     {
   .....:         "foo": [1, 2, 3, 4],
   .....:         "bar": ["a", "b", "c", "d"],
   .....:         "baz": pd.date_range("2018-01-01", freq="d", periods=4),
   .....:         "qux": pd.Categorical(["a", "b", "c", "c"]),
   .....:     },
   .....:     index=pd.Index(range(4), name="idx"),
   .....: )
   .....: 

In [321]: df
Out[321]: 
     foo bar        baz qux
idx                        
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [322]: df.dtypes
Out[322]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

In [323]: df.to_json("test.json", orient="table")

In [324]: new_df = pd.read_json("test.json", orient="table")

In [325]: new_df
Out[325]: 
     foo bar        baz qux
idx                        
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [326]: new_df.dtypes
Out[326]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

Обратите внимание, что строка «index» в качестве имени Index не поддерживает обратное преобразование, также как и любые имена, начинающиеся с 'level_' в MultiIndex. Они используются по умолчанию в DataFrame.to_json() для обозначения пропущенных значений, и последующее чтение не может различить намерение.

In [327]: df.index.name = "index"

In [328]: df.to_json("test.json", orient="table")

In [329]: new_df = pd.read_json("test.json", orient="table")

In [330]: print(new_df.index.name)
None

При использовании orient='table' вместе с определёнными пользователем ExtensionArray, сгенерированная схема будет содержать дополнительный ключ extDtype в соответствующем элементе fields. Этот дополнительный ключ не является стандартным, но позволяет выполнять обратные преобразования JSON для типов расширений (например, read_json(df.to_json(orient="table"), orient="table")).

Ключ extDtype содержит имя расширения, если вы правильно зарегистрировали ExtensionDtype, pandas будет использовать это имя для поиска в реестре и повторного преобразования сериализованных данных в ваш пользовательский тип данных.

HTML

Чтение содержимого HTML

Предупреждение

Мы настоятельно рекомендуем прочитать раздел Особенности разбора HTML-таблиц ниже, касающийся проблем с парсерами BeautifulSoup4/html5lib/lxml.

Функция верхнего уровня read_html() может принимать строку/файл/URL HTML и будет парсить HTML-таблицы в список pandas DataFrames. Давайте рассмотрим несколько примеров.

Примечание

read_html возвращает list объектов DataFrame, даже если в содержимом HTML содержится только одна таблица.

Чтение URL без опций:

In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[                         Bank NameBank           CityCity StateSt  ...              Acquiring InstitutionAI Closing DateClosing FundFund
 0                    Almena State Bank             Almena      KS  ...                          Equity Bank    October 23, 2020    10538
 1           First City Bank of Florida  Fort Walton Beach      FL  ...            United Fidelity Bank, fsb    October 16, 2020    10537
 2                 The First State Bank      Barboursville      WV  ...                       MVB Bank, Inc.       April 3, 2020    10536
 3                   Ericson State Bank            Ericson      NE  ...           Farmers and Merchants Bank   February 14, 2020    10535
 4     City National Bank of New Jersey             Newark      NJ  ...                      Industrial Bank    November 1, 2019    10534
 ..                                 ...                ...     ...  ...                                  ...                 ...      ...
 558                 Superior Bank, FSB           Hinsdale      IL  ...                Superior Federal, FSB       July 27, 2001     6004
 559                Malta National Bank              Malta      OH  ...                    North Valley Bank         May 3, 2001     4648
 560    First Alliance Bank & Trust Co.         Manchester      NH  ...  Southern New Hampshire Bank & Trust    February 2, 2001     4647
 561  National State Bank of Metropolis         Metropolis      IL  ...              Banterra Bank of Marion   December 14, 2000     4646
 562                   Bank of Honolulu           Honolulu      HI  ...                   Bank of the Orient    October 13, 2000     4645

 [563 rows x 7 columns]]

Примечание

Данные с указанного выше URL меняются каждую понедельник, поэтому полученные данные могут немного отличаться.

Чтение URL с передачей заголовков вместе с HTTP-запросом:

In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0   Accept-Encoding:             identity
 1              Host:         www.sump.org
 2        User-Agent:    Python-urllib/3.8
 3        Connection:                close]
In [324]: headers = {
In [325]:    'User-Agent':'Mozilla Firefox v14.0',
In [326]:    'Accept':'application/json',
In [327]:    'Connection':'keep-alive',
In [328]:    'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0        User-Agent: Mozilla Firefox v14.0
 1    AcceptEncoding:   gzip,  deflate,  br
 2            Accept:      application/json
 3        Connection:             keep-alive
 4              Auth:  Bearer 2*/f3+fe68df*4]

Примечание

Мы видим выше, что переданные нами заголовки отражаются в HTTP-запросе.

Чтение содержимого файла из указанного выше URL и передача его read_html в виде строки:

In [331]: html_str = """
   .....:          <table>
   .....:              <tr>
   .....:                  <th>A</th>
   .....:                  <th colspan="1">B</th>
   .....:                  <th rowspan="1">C</th>
   .....:              </tr>
   .....:              <tr>
   .....:                  <td>a</td>
   .....:                  <td>b</td>
   .....:                  <td>c</td>
   .....:              </tr>
   .....:          </table>
   .....:      """
   .....: 

In [332]: with open("tmp.html", "w") as f:
   .....:     f.write(html_str)
   .....: 

In [333]: df = pd.read_html("tmp.html")

In [334]: df[0]
Out[334]: 
   A  B  C
0  a  b  c

Вы также можете передать экземпляр StringIO, если хотите:

In [335]: dfs = pd.read_html(StringIO(html_str))

In [336]: dfs[0]
Out[336]: 
   A  B  C
0  a  b  c

Примечание

Следующие примеры не выполняются IPython-интерпретатором из-за того, что наличие большого количества функций, осуществляющих доступ к сети, замедляет сборку документации. Если вы обнаружите ошибку или пример, который не выполняется, пожалуйста, не стесняйтесь сообщать об этом на странице проблем GitHub pandas pandas GitHub issues page.

Чтение URL и поиск таблицы, содержащей определенный текст:

match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)

Укажите строку заголовка (по умолчанию <th> или <td> элементы, расположенные внутри <thead>, используются для формирования индекса столбцов, если в <thead> содержатся несколько строк, создается многоуровневый индекс); если указано, строка заголовка берется из данных за вычетом проанализированных элементов заголовка (<th> элементов).

dfs = pd.read_html(url, header=0)

Укажите столбец индекса:

dfs = pd.read_html(url, index_col=0)

Укажите количество строк, которые нужно пропустить:

dfs = pd.read_html(url, skiprows=0)

Укажите количество строк, которые нужно пропустить, используя список (range также работает):

dfs = pd.read_html(url, skiprows=range(2))

Укажите атрибут HTML:

dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0]))  # Should be True

Укажите значения, которые должны быть преобразованы в NaN:

dfs = pd.read_html(url, na_values=["No Acquirer"])

Укажите, нужно ли сохранить стандартный набор значений NaN:

dfs = pd.read_html(url, keep_default_na=False)

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

url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
    url_mcc,
    match="Telekom Albania",
    header=0,
    converters={"MNC": str},
)

Используйте некоторую комбинацию вышеперечисленного:

dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)

Чтение выходных данных pandas to_html (с некоторой потерей точности чисел с плавающей запятой):

df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)

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

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])

Или вы можете передать flavor='lxml' без списка:

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")

Однако, если у вас установлены bs4 и html5lib и вы передаете None или ['lxml', 'bs4'], то разбор, скорее всего, будет успешным. Обратите внимание, что как только разбор будет успешным, функция вернёт значение.

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])

Ссылки можно извлечь из ячеек вместе с текстом, используя extract_links="all".

In [337]: html_table = """
   .....: <table>
   .....:   <tr>
   .....:     <th>GitHub</th>
   .....:   </tr>
   .....:   <tr>
   .....:     <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
   .....:   </tr>
   .....: </table>
   .....: """
   .....: 

In [338]: df = pd.read_html(
   .....:     StringIO(html_table),
   .....:     extract_links="all"
   .....: )[0]
   .....: 

In [339]: df
Out[339]: 
                                   (GitHub, None)
0  (pandas, https://github.com/pandas-dev/pandas)

In [340]: df[("GitHub", None)]
Out[340]: 
0    (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object

In [341]: df[("GitHub", None)].str[1]
Out[341]: 
0    https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object

Новое в версии 1.5.0.

Запись в файлы HTML

DataFrame объекты имеют метод to_html, который отображает содержимое DataFrame в виде HTML-таблицы. Аргументы функции такие же, как и в методе to_string, описанном выше.

Примечание

Для краткости здесь показаны не все возможные параметры для DataFrame.to_html. Полный список параметров см. в DataFrame.to_html().

Примечание

В среде, поддерживающей отображение HTML, такой как Jupyter Notebook, display(HTML(...))` отобразит исходный HTML в данной среде.

In [342]: from IPython.display import display, HTML

In [343]: df = pd.DataFrame(np.random.randn(2, 2))

In [344]: df
Out[344]: 
          0         1
0 -0.345352  1.314232
1  0.690579  0.995761

In [345]: html = df.to_html()

In [346]: print(html)  # raw 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.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

In [347]: display(HTML(html))
<IPython.core.display.HTML object>

Аргумент columns ограничит отображаемые столбцы:

In [348]: html = df.to_html(columns=[0])

In [349]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.345352</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
    </tr>
  </tbody>
</table>

In [350]: display(HTML(html))
<IPython.core.display.HTML object>

float_format принимает вызываемый объект Python для управления точностью значений с плавающей запятой:

In [351]: html = df.to_html(float_format="{0:.10f}".format)

In [352]: print(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.3453521949</td>
      <td>1.3142323796</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.6905793352</td>
      <td>0.9957609037</td>
    </tr>
  </tbody>
</table>

In [353]: display(HTML(html))
<IPython.core.display.HTML object>

bold_rows сделает метки строк по умолчанию жирными, но вы можете отключить это:

In [354]: html = df.to_html(bold_rows=False)

In [355]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>-0.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

In [356]: display(HTML(html))
<IPython.core.display.HTML object>

Аргумент classes предоставляет возможность задавать CSS-классы полученной HTML-таблице. Обратите внимание, что эти классы добавляются к существующему классу 'dataframe'.

In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

Аргумент render_links предоставляет возможность добавлять гиперссылки в ячейки, содержащие URL-адреса.

In [358]: url_df = pd.DataFrame(
   .....:     {
   .....:         "name": ["Python", "pandas"],
   .....:         "url": ["https://www.python.org/", "https://pandas.pydata.org"],
   .....:     }
   .....: )
   .....: 

In [359]: html = url_df.to_html(render_links=True)

In [360]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>name</th>
      <th>url</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>Python</td>
      <td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
    </tr>
    <tr>
      <th>1</th>
      <td>pandas</td>
      <td><a href="https://pandas.pydata.org" target="_blank">https://pandas.pydata.org</a></td>
    </tr>
  </tbody>
</table>

In [361]: display(HTML(html))
<IPython.core.display.HTML object>

Наконец, аргумент escape позволяет контролировать, будут ли символы “<”, “>” и “&” экранироваться в результирующем HTML (по умолчанию это True). Чтобы получить HTML без экранированных символов, передайте escape=False

In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})

Экранированные:

In [363]: html = df.to_html()

In [364]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&amp;</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td>&lt;</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>&gt;</td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

In [365]: display(HTML(html))
<IPython.core.display.HTML object>

Не экранированные:

In [366]: html = df.to_html(escape=False)

In [367]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td><</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>></td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

In [368]: display(HTML(html))
<IPython.core.display.HTML object>

Примечание

Некоторые браузеры могут не отображать разницы в представлении двух предыдущих HTML-таблиц.

Особенности разбора HTML-таблиц

Существуют проблемы с версионированием библиотек, используемых для разбора HTML-таблиц в функции pandas io верхнего уровня read_html.

Проблемы с lxml

  • Преимущества

    • lxml очень быстрый.

    • lxml требует правильной установки Cython.

  • Недостатки

    • lxml не гарантирует результаты разбора, если ему не предоставлен строго валидный разметка.

    • Учитывая вышесказанное, мы позволили вам, пользователю, использовать backend lxml, но этот backend будет использовать html5lib, если lxml не сможет выполнить разбор.

    • Поэтому настоятельно рекомендуется установить и BeautifulSoup4, и html5lib, чтобы получить валидный результат (при условии, что все остальное валидно), даже если lxml не удастся.

Проблемы с BeautifulSoup4 с использованием lxml в качестве бэкенда

  • Вышеупомянутые проблемы также актуальны, поскольку BeautifulSoup4 по сути является просто оболочкой вокруг бэкенда парсера.

Проблемы с BeautifulSoup4 с использованием html5lib в качестве бэкенда

  • Преимущества

    • html5lib намного более либерален, чем lxml, и, следовательно, гораздо лучше справляется с реальной разметкой, а не, например, просто отбрасывает элемент без уведомления.

    • html5lib автоматически генерирует валидную разметку HTML5 из невалидной. Это очень важно для разбора HTML-таблиц, поскольку гарантирует валидный документ. Однако это не означает, что он «правильный», так как процесс исправления разметки не имеет единого определения.

    • html5lib — чистый Python и не требует дополнительных шагов сборки помимо собственной установки.

  • Недостатки

    • Самый большой недостаток использования html5lib — это его медлительность. Однако следует учитывать тот факт, что многие таблицы в сети недостаточно велики, чтобы время работы алгоритма разбора имело значение. Скорее всего, узким местом будет процесс чтения исходного текста из URL через веб, то есть ввод-вывод (IO). Для очень больших таблиц это может быть не так.

LaTeX

Новое в версии 1.3.0.

В настоящее время нет методов чтения из LaTeX, только методы вывода.

Запись в файлы LaTeX

Примечание

Объекты DataFrame и Styler в настоящее время имеют метод to_latex. Рекомендуется использовать метод Styler.to_latex() вместо DataFrame.to_latex() из-за большей гибкости условного форматирования в первом случае и возможной будущей устарелости второго.

Ознакомьтесь с документацией Styler.to_latex, где приведены примеры условного форматирования и объяснено действие его ключевых аргументов.

Для простого применения достаточно следующего шаблона.

In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])

In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}

Для форматирования значений перед выводом используйте цепочку метода Styler.format.

In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}

XML

Чтение XML

Новое в версии 1.3.0.

Функция верхнего уровня read_xml() может принимать строку/файл/URL XML и будет парсить узлы и атрибуты в pandas DataFrame.

Примечание

Поскольку нет стандартной структуры XML, где типы дизайна могут варьироваться по-разному, read_xml лучше всего работает с более плоскими и неглубокими версиями. Если документ XML глубоко вложен, используйте функцию stylesheet для преобразования XML в более плоскую версию.

Давайте рассмотрим несколько примеров.

Чтение строки XML:

In [372]: from io import StringIO

In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
   .....: <bookstore>
   .....:   <book category="cooking">
   .....:     <title lang="en">Everyday Italian</title>
   .....:     <author>Giada De Laurentiis</author>
   .....:     <year>2005</year>
   .....:     <price>30.00</price>
   .....:   </book>
   .....:   <book category="children">
   .....:     <title lang="en">Harry Potter</title>
   .....:     <author>J K. Rowling</author>
   .....:     <year>2005</year>
   .....:     <price>29.99</price>
   .....:   </book>
   .....:   <book category="web">
   .....:     <title lang="en">Learning XML</title>
   .....:     <author>Erik T. Ray</author>
   .....:     <year>2003</year>
   .....:     <price>39.95</price>
   .....:   </book>
   .....: </bookstore>"""
   .....: 

In [374]: df = pd.read_xml(StringIO(xml))

In [375]: df
Out[375]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

Чтение URL без опций:

In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [377]: df
Out[377]: 
   category              title                  author  year  price      cover
0   cooking   Everyday Italian     Giada De Laurentiis  2005  30.00       None
1  children       Harry Potter            J K. Rowling  2005  29.99       None
2       web  XQuery Kick Start  Vaidyanathan Nagarajan  2003  49.99       None
3       web       Learning XML             Erik T. Ray  2003  39.95  paperback

Чтение содержимого файла “books.xml” и передача его read_xml как строки:

In [378]: file_path = "books.xml"

In [379]: with open(file_path, "w") as f:
   .....:     f.write(xml)
   .....: 

In [380]: with open(file_path, "r") as f:
   .....:     df = pd.read_xml(StringIO(f.read()))
   .....: 

In [381]: df
Out[381]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

Чтение содержимого “books.xml” как экземпляра StringIO или BytesIO и передача его read_xml:

In [382]: with open(file_path, "r") as f:
   .....:     sio = StringIO(f.read())
   .....: 

In [383]: df = pd.read_xml(sio)

In [384]: df
Out[384]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95
In [385]: with open(file_path, "rb") as f:
   .....:     bio = BytesIO(f.read())
   .....: 

In [386]: df = pd.read_xml(bio)

In [387]: df
Out[387]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

Даже чтение XML из ведер AWS S3, таких как NIH NCBI PMC Article Datasets, предоставляющие биомедицинские и биологические научные журналы:

In [388]: df = pd.read_xml(
   .....:     "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
   .....:     xpath=".//journal-meta",
   .....: )
   .....: 

In [389]: df
Out[389]: 
              journal-id              journal-title       issn  publisher
0  Cardiovasc Ultrasound  Cardiovascular Ultrasound  1476-7120        NaN

С lxml по умолчанию как parser, вы получаете доступ к полному функциональному XML-библиотеке, которая расширяет API Python's ElementTree. Одним из мощных инструментов является возможность выборочного или условного запроса узлов с более выразительным XPath:

In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")

In [391]: df
Out[391]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99

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

In [392]: df = pd.read_xml(file_path, elems_only=True)

In [393]: df
Out[393]: 
              title               author  year  price
0  Everyday Italian  Giada De Laurentiis  2005  30.00
1      Harry Potter         J K. Rowling  2005  29.99
2      Learning XML          Erik T. Ray  2003  39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)

In [395]: df
Out[395]: 
   category
0   cooking
1  children
2       web

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

Например, ниже XML содержит пространство имен с префиксом, doc, и URI в https://example.com. Для парсинга doc:row узлов, необходимо использовать namespaces.

In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <doc:data xmlns:doc="https://example.com">
   .....:   <doc:row>
   .....:     <doc:shape>square</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides>4.0</doc:sides>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>circle</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides/>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>triangle</doc:shape>
   .....:     <doc:degrees>180</doc:degrees>
   .....:     <doc:sides>3.0</doc:sides>
   .....:   </doc:row>
   .....: </doc:data>"""
   .....: 

In [397]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//doc:row",
   .....:                  namespaces={"doc": "https://example.com"})
   .....: 

In [398]: df
Out[398]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

Аналогично, XML-документ может иметь пространство имен по умолчанию без префикса. Если не присвоить временное имя для корректного URI, не будет возвращено никаких узлов и будет возбуждено исключение ValueError. Но присвоение любого временного имени для корректного URI позволяет парсить узлы.

In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <data xmlns="https://example.com">
   .....:  <row>
   .....:    <shape>square</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides>4.0</sides>
   .....:  </row>
   .....:  <row>
   .....:    <shape>circle</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides/>
   .....:  </row>
   .....:  <row>
   .....:    <shape>triangle</shape>
   .....:    <degrees>180</degrees>
   .....:    <sides>3.0</sides>
   .....:  </row>
   .....: </data>"""
   .....: 

In [400]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//pandas:row",
   .....:                  namespaces={"pandas": "https://example.com"})
   .....: 

In [401]: df
Out[401]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

Однако, если XPath не ссылается на имена узлов, такие как значение по умолчанию, /*, то namespaces не требуется.

Примечание

Поскольку xpath определяет родителя контента, подлежащего парсингу, парсятся только непосредственные потомки, которые включают дочерние узлы или текущие атрибуты. Поэтому read_xml не будет парсить текст внуков или других потомков, и не будет парсить атрибуты потомков. Чтобы получить контент на более низком уровне, нужно настроить xpath на более низкий уровень. Например,

In [402]: xml = """
   .....: <data>
   .....:   <row>
   .....:     <shape sides="4">square</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="0">circle</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="3">triangle</shape>
   .....:     <degrees>180</degrees>
   .....:   </row>
   .....: </data>"""
   .....: 

In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [404]: df
Out[404]: 
      shape  degrees
0    square      360
1    circle      360
2  triangle      180

показывает, что атрибут sides элемента shape не был пропарсен должным образом, поскольку этот атрибут находится в потомке элемента row, а не в элементе row. Другими словами, атрибут sides является потомком внука элемента row. Однако, xpath нацелен на элемент row, что охватывает только его дочерние элементы и атрибуты.

С lxml в качестве парсера, вы можете сплющить вложенные XML-документы с помощью скрипта XSLT, который также может быть строками/файлами/URL. В качестве справки, XSLT — это язык специального назначения, написанный в специальном XML-файле, который может преобразовывать исходные XML-документы в другие XML, HTML, даже текст (CSV, JSON и т. д.) с помощью процессора XSLT.

Например, рассмотрим эту несколько вложенную структуру поездок по "L" Чикаго, где станции и поездки содержат данные в своих разделах. С помощью XSLT ниже, lxml может преобразовать исходный вложенный документ в более плоский вывод (как показано ниже для демонстрации) для более простого парсинга в DataFrame:

In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:   <row>
   .....:     <station id="40850" name="Library"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="41700" name="Washington/Wabash"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="40380" name="Clark/Lake"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:  </response>"""
   .....: 

In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/response">
   .....:       <xsl:copy>
   .....:         <xsl:apply-templates select="row"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:       <xsl:copy>
   .....:         <station_id><xsl:value-of select="station/@id"/></station_id>
   .....:         <station_name><xsl:value-of select="station/@name"/></station_name>
   .....:         <xsl:copy-of select="month|rides/*"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:    <row>
   .....:       <station_id>40850</station_id>
   .....:       <station_name>Library</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>41700</station_id>
   .....:       <station_name>Washington/Wabash</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>40380</station_id>
   .....:       <station_name>Clark/Lake</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:    </row>
   .....:  </response>"""
   .....: 

In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)

In [409]: df
Out[409]: 
   station_id       station_name  ... avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  ...              534.0                     417.2
1       41700  Washington/Wabash  ...             1909.8                    1438.6
2       40380         Clark/Lake  ...             1657.0                    1453.8

[3 rows x 6 columns]

Для очень больших XML-файлов, размер которых может достигать сотен мегабайт или гигабайт, pandas.read_xml() поддерживает разбор таких больших файлов с использованием lxml’s iterparse и etree’s iterparse, которые являются эффективными с точки зрения памяти методами для итерации по XML-дереву и извлечения определенных элементов и атрибутов без загрузки всего дерева в память.

Введено в версии 1.5.0.

Для использования этой функции необходимо передать физический путь к файлу XML в read_xml и использовать аргумент iterparse. Файлы не должны быть сжаты или указывать на онлайн-источники, а должны храниться на локальном диске. Также iterparse должен быть словарем, где ключ — это повторяющиеся узлы в документе (которые становятся строками), а значение — список любых элементов или атрибутов, являющихся потомками (т.е. дочерними, внучатыми) повторяющегося узла. Поскольку в этом методе XPath не используется, потомки не обязательно должны иметь одинаковые отношения друг с другом. Ниже показан пример чтения данных из последнего архива данных статьи Википедии (размером более 12 ГБ+).

In [1]: df = pd.read_xml(
...         "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
...         iterparse = {"page": ["title", "ns", "id"]}
...     )
...     df
Out[2]:
                                                     title   ns        id
0                                       Gettysburg Address    0     21450
1                                                Main Page    0     42950
2                            Declaration by United Nations    0      8435
3             Constitution of the United States of America    0      8435
4                     Declaration of Independence (Israel)    0     17858
...                                                    ...  ...       ...
3578760               Page:Black cat 1897 07 v2 n10.pdf/17  104    219649
3578761               Page:Black cat 1897 07 v2 n10.pdf/43  104    219649
3578762               Page:Black cat 1897 07 v2 n10.pdf/44  104    219649
3578763      The History of Tom Jones, a Foundling/Book IX    0  12084291
3578764  Page:Shakespeare of Stratford (1926) Yale.djvu/91  104     21450

[3578765 rows x 3 columns]

Запись XML

Введено в версии 1.3.0.

Объекты DataFrame имеют метод экземпляра to_xml, который отображает содержимое DataFrame в виде XML-документа.

Примечание

Этот метод не поддерживает специальные свойства XML, включая DTD, CData, XSD схемы, инструкции обработки, комментарии и другие. Поддерживаются только пространства имен на уровне корня. Однако stylesheet позволяет изменять дизайн после начального вывода.

Давайте рассмотрим несколько примеров.

Запись XML без опций:

In [410]: geom_df = pd.DataFrame(
   .....:     {
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

Запись XML с новым корнем и именем строки:

In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
  <objects>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </objects>
  <objects>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </objects>
  <objects>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </objects>
</geometry>

Запись XML, ориентированная на атрибуты:

In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row index="0" shape="square" degrees="360" sides="4.0"/>
  <row index="1" shape="circle" degrees="360"/>
  <row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>

Запись смешанного XML с элементами и атрибутами:

In [414]: print(
   .....:     geom_df.to_xml(
   .....:         index=False,
   .....:         attr_cols=['shape'],
   .....:         elem_cols=['degrees', 'sides'])
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row shape="square">
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row shape="circle">
    <degrees>360</degrees>
    <sides/>
  </row>
  <row shape="triangle">
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

Любой DataFrames с иерархическими столбцами будет сплюснут для имен XML-элементов, уровни которых разделены символом подчеркивания:

In [415]: ext_geom_df = pd.DataFrame(
   .....:     {
   .....:         "type": ["polygon", "other", "polygon"],
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
   .....:                                  columns='type',
   .....:                                  values=['degrees', 'sides'],
   .....:                                  aggfunc='sum')
   .....: 

In [417]: pvt_df
Out[417]: 
         degrees         sides        
type       other polygon other polygon
shape                                 
circle     360.0     NaN   0.0     NaN
square       NaN   360.0   NaN     4.0
triangle     NaN   180.0   NaN     3.0

In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <shape>circle</shape>
    <degrees_other>360.0</degrees_other>
    <degrees_polygon/>
    <sides_other>0.0</sides_other>
    <sides_polygon/>
  </row>
  <row>
    <shape>square</shape>
    <degrees_other/>
    <degrees_polygon>360.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>4.0</sides_polygon>
  </row>
  <row>
    <shape>triangle</shape>
    <degrees_other/>
    <degrees_polygon>180.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>3.0</sides_polygon>
  </row>
</data>

Запись XML с пространством имен по умолчанию:

In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data xmlns="https://example.com">
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

Запись XML с префиксом пространства имен:

In [420]: print(
   .....:     geom_df.to_xml(namespaces={"doc": "https://example.com"},
   .....:                    prefix="doc")
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<doc:data xmlns:doc="https://example.com">
  <doc:row>
    <doc:index>0</doc:index>
    <doc:shape>square</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides>4.0</doc:sides>
  </doc:row>
  <doc:row>
    <doc:index>1</doc:index>
    <doc:shape>circle</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides/>
  </doc:row>
  <doc:row>
    <doc:index>2</doc:index>
    <doc:shape>triangle</doc:shape>
    <doc:degrees>180</doc:degrees>
    <doc:sides>3.0</doc:sides>
  </doc:row>
</doc:data>

Запись XML без объявления или красивой печати:

In [421]: print(
   .....:     geom_df.to_xml(xml_declaration=False,
   .....:                    pretty_print=False)
   .....: )
   .....: 
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>

Запись XML и преобразование со стилизованным листом:

In [422]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/data">
   .....:      <geometry>
   .....:        <xsl:apply-templates select="row"/>
   .....:      </geometry>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:      <object index="{index}">
   .....:        <xsl:if test="shape!='circle'">
   .....:            <xsl:attribute name="type">polygon</xsl:attribute>
   .....:        </xsl:if>
   .....:        <xsl:copy-of select="shape"/>
   .....:        <property>
   .....:            <xsl:copy-of select="degrees|sides"/>
   .....:        </property>
   .....:      </object>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
  <object index="0" type="polygon">
    <shape>square</shape>
    <property>
      <degrees>360</degrees>
      <sides>4.0</sides>
    </property>
  </object>
  <object index="1">
    <shape>circle</shape>
    <property>
      <degrees>360</degrees>
      <sides/>
    </property>
  </object>
  <object index="2" type="polygon">
    <shape>triangle</shape>
    <property>
      <degrees>180</degrees>
      <sides>3.0</sides>
    </property>
  </object>
</geometry>

Заключительные замечания по XML

  • Все XML-документы соответствуют спецификациям W3C. Как парсеры etree, так и lxml не смогут разобрать документ разметки, который не является правильно сформированным или не соответствует правилам синтаксиса XML. Следует помнить, что HTML не является XML-документом, если не соответствует спецификациям XHTML. Однако другие популярные типы разметки, включая KML, XAML, RSS, MusicML, MathML, соответствуют схемам XML.

  • По этой причине, если ваше приложение создает XML перед операциями pandas, используйте соответствующие библиотеки DOM, такие как etree и lxml для построения необходимого документа, а не с помощью конкатенации строк или регулярных выражений. Всегда помните, что XML — это специальный текстовый файл с правилами разметки.

  • В случае с очень большими XML-файлами (несколько сотен мегабайт до гигабайт), XPath и XSLT могут стать ресурсоёмкими операциями. Убедитесь, что у вас достаточно оперативной памяти для чтения и записи больших XML-файлов (примерно в 5 раз больше, чем размер текста).

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

  • Парсер etree поддерживает все функции как read_xml, так и to_xml, за исключением сложного XPath и любого XSLT. Хотя ограниченный по функциям, etree по-прежнему является надёжным и способным парсером и строителем дерева. Его производительность может быть немного ниже, чем у lxml для больших файлов, но это практически незаметно для файлов небольшого и среднего размера.

Файлы Excel

Метод read_excel() может читать файлы Excel 2007+ (.xlsx) используя модуль openpyxl Python. Файлы Excel 2003 (.xls) можно читать, используя xlrd. Бинарные файлы Excel (.xlsb) можно читать, используя pyxlsb. Все форматы можно читать, используя движок calamine. Метод экземпляра to_excel() используется для сохранения DataFrame в Excel. В целом, семантика аналогична работе с данными csv. См. раздел справочник для некоторых продвинутых стратегий.

Примечание

При engine=None будет использоваться следующий алгоритм определения движка:

  • Если path_or_buffer — это формат OpenDocument (.odf, .ods, .odt), то будет использован odf.

  • В противном случае, если path_or_buffer — это формат xls, то будет использован xlrd.

  • В противном случае, если path_or_buffer — это формат xlsb, то будет использован pyxlsb.

  • В противном случае будет использован openpyxl.

Чтение файлов Excel

В самом простом случае, read_excel принимает путь к файлу Excel, и sheet_name, указывающий, какой лист нужно проанализировать.

При использовании параметра engine_kwargs, pandas передаст эти аргументы в движок. Для этого важно знать, какую функцию pandas использует внутри.

  • Для движка openpyxl pandas использует openpyxl.load_workbook() для чтения (.xlsx) и (.xlsm) файлов.

  • Для движка xlrd pandas использует xlrd.open_workbook() для чтения (.xls) файлов.

  • Для движка pyxlsb pandas использует pyxlsb.open_workbook() для чтения (.xlsb) файлов.

  • Для движка odf pandas использует odf.opendocument.load() для чтения (.ods) файлов.

  • Для движка calamine pandas использует python_calamine.load_workbook() для чтения (.xlsx), (.xlsm), (.xls), (.xlsb), (.ods) файлов.

# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")

ExcelFile класс

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

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")

Класс ExcelFile также может использоваться как менеджер контекста.

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

Свойство sheet_names сгенерирует список имён листов в файле.

Основной случай использования класса ExcelFile — это парсинг нескольких листов с разными параметрами:

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)

Обратите внимание, что если параметры парсинга одинаковы для всех листов, то список имён листов можно просто передать в read_excel без потери производительности.

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)

ExcelFile также может быть вызван с объектом xlrd.book.Book в качестве параметра. Это позволяет пользователю контролировать, как считывается файл Excel. Например, листы можно загружать по требованию, вызывая xlrd.open_workbook() с on_demand=True.

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

Указание листов

Примечание

Второй аргумент — это sheet_name, а не ExcelFile.sheet_names.

Примечание

Атрибут ExcelFile sheet_names предоставляет доступ к списку листов.

  • Аргументы sheet_name позволяют указать лист или листы для чтения.

  • Значение по умолчанию для sheet_name — 0, что означает чтение первого листа.

  • Передайте строку, чтобы сослаться на имя конкретного листа в книге.

  • Передайте целое число, чтобы сослаться на индекс листа. Индексы следуют соглашениям Python, начиная с 0.

  • Передайте список строк или целых чисел, чтобы получить словарь указанных листов.

  • Передайте None, чтобы получить словарь всех доступных листов.

# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])

Использование индекса листа:

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])

Использование всех значений по умолчанию:

# Returns a DataFrame
pd.read_excel("path_to_file.xls")

Использование None для получения всех листов:

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)

Использование списка для получения нескольких листов:

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])

read_excel может читать более одного листа, установив sheet_name либо на список имён листов, либо на список позиций листов, или None для чтения всех листов. Листы можно указывать по индексу листа или имени листа, используя целое число или строку соответственно.

Чтение многоуровневого индекса

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

Например, чтобы считать многоуровневый индекс без имён:

In [424]: df = pd.DataFrame(
   .....:     {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
   .....:     index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
   .....: )
   .....: 

In [425]: df.to_excel("path_to_file.xlsx")

In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [427]: df
Out[427]: 
     a  b
a c  1  5
  d  2  6
b c  3  7
  d  4  8

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

In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])

In [429]: df.to_excel("path_to_file.xlsx")

In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [431]: df
Out[431]: 
           a  b
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

Если исходный файл содержит многоуровневые индексы и столбцы, необходимо передать списки, определяющие каждый уровень в index_col и header:

In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])

In [433]: df.to_excel("path_to_file.xlsx")

In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])

In [435]: df
Out[435]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

Пропущенные значения в столбцах, указанных в index_col, будут заполнены вперёд, чтобы обеспечить обратимость с to_excel для merged_cells=True. Чтобы избежать заполнения вперёд пропущенных значений, используйте set_index после чтения данных вместо index_col.

Парсинг определённых столбцов

Часто пользователи вставляют столбцы для временных вычислений в Excel, и вам может не потребоваться считывать эти столбцы. read_excel принимает ключевое слово usecols, позволяющее указать подмножество столбцов для парсинга.

Можно указать набор столбцов и диапазонов Excel через запятую в виде строки:

pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")

Если usecols — список целых чисел, предполагается, что это индексы столбцов файла для парсинга.

pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])

Порядок элементов игнорируется, поэтому usecols=[0, 1] эквивалентно [1, 0].

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

pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])

Порядок элементов игнорируется, поэтому usecols=['baz', 'joe'] эквивалентно ['joe', 'baz'].

Если usecols — вызываемый объект, функция будет вызвана для имён столбцов, возвращая имена, для которых функция вернула значение True.

pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())

Парсинг дат

Значения типа даты/времени обычно автоматически преобразуются в соответствующий тип данных при чтении файла Excel. Но если у вас есть столбец строк, которые выглядят как даты (но фактически не отформатированы как даты в Excel), вы можете использовать ключевое слово parse_dates для преобразования этих строк в даты:

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])

Преобразователи ячеек

Возможно преобразовывать содержимое ячеек Excel с помощью параметра converters. Например, для преобразования столбца в булевы значения:

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})

Этот параметр обрабатывает пропущенные значения и рассматривает исключения в преобразователях как пропущенные данные. Преобразования применяются к каждой ячейке по отдельности, а не к столбцу в целом, поэтому тип массива не гарантируется. Например, столбец целых чисел с пропущенными значениями не может быть преобразован в массив с целым типом данных, потому что NaN — это строго тип float. Можно вручную маскировать пропущенные данные, чтобы восстановить целочисленный тип:

def cfun(x):
    return int(x) if x else -1


pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})

Указание типов данных

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

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})

Запись файлов Excel

Запись файлов Excel в диск

Чтобы записать объект DataFrame в лист файла Excel, можно использовать метод to_excel. Аргументы в основном такие же, как и у to_csv, описанных выше, первым аргументом является имя файла Excel, а необязательным вторым аргументом — имя листа, в который следует записать DataFrame. Например:

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")

Файлы с расширением .xlsx будут записаны с использованием xlsxwriter (если доступно) или openpyxl.

DataFrame будет записано таким образом, чтобы имитировать вывод REPL. index_label будет размещено во второй строке вместо первой. Вы можете разместить его в первой строке, установив параметр merge_cells в to_excel() на значение False:

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)

Чтобы записать отдельные DataFrames в отдельные листы одного файла Excel, можно передать параметр ExcelWriter.

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2")

При использовании параметра engine_kwargs pandas передаст эти аргументы движку. Для этого важно знать, какую функцию pandas использует внутри.

  • Для движка openpyxl pandas использует openpyxl.Workbook() для создания нового листа и openpyxl.load_workbook() для добавления данных в существующий лист. Движок openpyxl записывает в файлы (.xlsx) и (.xlsm).

  • Для движка xlsxwriter pandas использует xlsxwriter.Workbook() для записи в файлы (.xlsx).

  • Для движка odf pandas использует odf.opendocument.OpenDocumentSpreadsheet() для записи в файлы (.ods).

Запись файлов Excel в память

pandas поддерживает запись файлов Excel в объекты типа буфера, такие как StringIO или BytesIO, используя ExcelWriter.

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read()

Примечание

engine является необязательным, но рекомендуется. Установка движка определяет версию создаваемого файла книги. Установка engine='xlrd' создаст файл книги формата Excel 2003 (xls). Использование 'openpyxl' или 'xlsxwriter' создаст файл книги формата Excel 2007 (xlsx). Если параметр опущен, создается файл книги формата Excel 2007.

Движки записи файлов Excel

pandas выбирает движок для записи файлов Excel двумя способами:

  1. ключевой аргумент engine

  2. расширение имени файла (через значение по умолчанию, указанное в параметрах конфигурации)

По умолчанию pandas использует XlsxWriter для .xlsx, openpyxl для .xlsm. Если у вас установлено несколько движков, можно установить движок по умолчанию, настроив параметры конфигурации io.excel.xlsx.writer и io.excel.xls.writer. pandas будет использовать openpyxl для файлов .xlsx, если Xlsxwriter недоступен.

Чтобы указать, какой движок вы хотите использовать, можно передать ключевой аргумент engine в to_excel и в ExcelWriter. Встроенные движки:

  • openpyxl: требуется версия 2.4 или выше

  • xlsxwriter

# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")

# Or via pandas configuration.
from pandas import options  # noqa: E402

options.io.excel.xlsx.writer = "xlsxwriter"

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")

Стиль и форматирование

Вид и ощущения создаваемых с помощью pandas листов Excel можно изменить, используя следующие параметры в методе DataFrame’s to_excel.

  • float_format: Строка форматирования для чисел с плавающей точкой (по умолчанию None).

  • freeze_panes: Кортеж из двух целых чисел, представляющих последнюю строку и последний столбец для заморозки. Каждый из этих параметров базируется на единице, так что (1, 1) заморозит первую строку и первый столбец (по умолчанию None).

Использование движка Xlsxwriter предоставляет множество вариантов управления форматом листа Excel, созданного с помощью метода to_excel. Отличные примеры можно найти в документации Xlsxwriter здесь: https://xlsxwriter.readthedocs.io/working_with_pandas.html

END_OF_DOCUMENT_MARKER

OpenDocument электронные таблицы

Методы io для файлов Excel также поддерживают чтение и запись электронных таблиц OpenDocument с использованием модуля odfpy. Семантика и возможности чтения и записи электронных таблиц OpenDocument соответствуют возможностям работы с файлами Excel с использованием engine='odf'. Необходимо установить необязательную зависимость «odfpy».

Метод read_excel() может читать электронные таблицы OpenDocument

# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")

Аналогично, метод to_excel() может записывать электронные таблицы OpenDocument

# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")

Бинарные файлы Excel (.xlsb)

Метод read_excel() также может читать бинарные файлы Excel с использованием модуля pyxlsb. Семантика и возможности чтения бинарных файлов Excel в основном соответствуют возможностям работы с файлами Excel с использованием engine='pyxlsb'. pyxlsb не распознает типы данных datetime в файлах и вернёт числа с плавающей точкой вместо этого (вы можете использовать calamine, если вам нужно распознавать типы datetime).

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")

Примечание

В настоящее время pandas поддерживает только чтение бинарных файлов Excel. Запись не реализована.

Calamine (файлы Excel и ODS)

Метод read_excel() может читать файлы Excel (.xlsx, .xlsm, .xls, .xlsb) и электронные таблицы OpenDocument (.ods) с использованием модуля python-calamine. Этот модуль представляет собой обвязку для библиотеки Rust calamine и в большинстве случаев быстрее других движков. Необходимо установить необязательную зависимость «python-calamine».

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")

Буфер обмена

Удобный способ получения данных — использование метода read_clipboard(), который получает содержимое буфера обмена и передает его методу read_csv. Например, вы можете скопировать следующий текст в буфер обмена (CTRL-C на многих операционных системах):

  A B C
x 1 4 p
y 2 5 q
z 3 6 r

И затем импортировать данные непосредственно в DataFrame, вызвав:

>>> clipdf = pd.read_clipboard()
>>> clipdf
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

Метод to_clipboard можно использовать для записи содержимого DataFrame в буфер обмена. После чего вы можете вставить содержимое буфера обмена в другие приложения (CTRL-V на многих операционных системах). Ниже показано, как записать DataFrame в буфер обмена и прочитать его обратно.

>>> df = pd.DataFrame(
...     {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )

>>> df
  A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

Мы видим, что получили тот же контент, который ранее записали в буфер обмена.

Примечание

Возможно, вам потребуется установить xclip или xsel (с PyQt5, PyQt4 или qtpy) на Linux, чтобы использовать эти методы.

Загрузка/сохранение

Все объекты pandas оснащены методами to_pickle, которые используют модуль Python cPickle для сохранения структур данных на диск в формате pickle.

In [436]: df
Out[436]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

In [437]: df.to_pickle("foo.pkl")

Функция read_pickle в пространстве имён pandas может использоваться для загрузки любого закодированного в формате pickle объекта pandas (или любого другого закодированного объекта) из файла:

In [438]: pd.read_pickle("foo.pkl")
Out[438]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

Предупреждение

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

См.: https://docs.python.org/3/library/pickle.html

Предупреждение

read_pickle() гарантирует обратную совместимость только с несколькими предыдущими версиями.

Сжатые файлы pickle

read_pickle(), DataFrame.to_pickle() и Series.to_pickle() могут читать и записывать сжатые файлы pickle. Типы сжатия gzip, bz2, xz, zstd поддерживаются для чтения и записи. Формат файла zip поддерживает только чтение и должен содержать только один файл данных для чтения.

Тип сжатия может быть явным параметром или выводиться из расширения файла. Если ‘infer’, используется gzip, bz2, zip, xz, zstd, если имя файла заканчивается на '.gz', '.bz2', '.zip', '.xz' или '.zst', соответственно.

Параметр сжатия также может быть словарем dict для передачи параметров протоколу сжатия. Он должен содержать ключ 'method', установленный в имя протокола сжатия, которое должно быть одним из {'zip', 'gzip', 'bz2', 'xz', 'zstd'}. Все остальные пары «ключ-значение» передаются в подлежащую библиотеку сжатия.

In [439]: df = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(1000),
   .....:         "B": "foo",
   .....:         "C": pd.date_range("20130101", periods=1000, freq="s"),
   .....:     }
   .....: )
   .....: 

In [440]: df
Out[440]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

Использование явного типа сжатия:

In [441]: df.to_pickle("data.pkl.compress", compression="gzip")

In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")

In [443]: rt
Out[443]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

Вывод типа сжатия из расширения:

In [444]: df.to_pickle("data.pkl.xz", compression="infer")

In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")

In [446]: rt
Out[446]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

По умолчанию используется ‘infer’:

In [447]: df.to_pickle("data.pkl.gz")

In [448]: rt = pd.read_pickle("data.pkl.gz")

In [449]: rt
Out[449]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

In [450]: df["A"].to_pickle("s1.pkl.bz2")

In [451]: rt = pd.read_pickle("s1.pkl.bz2")

In [452]: rt
Out[452]: 
0     -0.317441
1     -1.236269
2      0.896171
3     -0.487602
4     -0.082240
         ...   
995   -0.171092
996    1.786173
997   -0.575189
998    0.820750
999   -1.256530
Name: A, Length: 1000, dtype: float64

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

In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})

msgpack

Поддержка pandas для msgpack была удалена в версии 1.0.0. Рекомендуется использовать pickle вместо этого.

В качестве альтернативы можно использовать формат сериализации Arrow IPC для передачи по сети объектов pandas. Документацию по pyarrow см. здесь.

HDF5 (PyTables)

Это похожий на словарь объект, который читает и записывает pandas с использованием высокопроизводительного формата HDF5 с помощью отличной библиотеки PyTables. Смотрите пособие для некоторых продвинутых стратегий

Предупреждение

pandas использует PyTables для чтения и записи файлов HDF5, что позволяет сериализовать данные типа object с помощью pickle. Загрузка данных с использованием pickle, полученных из ненадежных источников, может быть небезопасной.

См.: https://docs.python.org/3/library/pickle.html для получения дополнительной информации.

In [454]: store = pd.HDFStore("store.h5")

In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

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

In [456]: index = pd.date_range("1/1/2000", periods=8)

In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])

# store.put('s', s) is an equivalent method
In [459]: store["s"] = s

In [460]: store["df"] = df

In [461]: store
Out[461]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

В текущей или последующей сессии Python вы можете получить сохранённые объекты:

# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

Удаление объекта, указанного ключом:

# store.remove('df') is an equivalent method
In [464]: del store["df"]

In [465]: store
Out[465]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

Закрытие хранилища и использование менеджера контекста:

In [466]: store.close()

In [467]: store
Out[467]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

In [468]: store.is_open
Out[468]: False

# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
   .....:     store.keys()
   .....: 

API чтения/записи

HDFStore поддерживает API верхнего уровня, используя read_hdf для чтения и to_hdf для записи, аналогично тому, как работают read_csv и to_csv.

In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})

In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)

In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]: 
   A  B
3  3  3
4  4  4

HDFStore по умолчанию не будет удалять строки, все значения в которых отсутствуют. Это поведение можно изменить, установив dropna=True.

In [473]: df_with_missing = pd.DataFrame(
   .....:     {
   .....:         "col1": [0, np.nan, 2],
   .....:         "col2": [1, np.nan, np.nan],
   .....:     }
   .....: )
   .....: 

In [474]: df_with_missing
Out[474]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")

In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [477]: df_with_missing.to_hdf(
   .....:     "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
   .....: )
   .....: 

In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]: 
   col1  col2
0   0.0   1.0
2   2.0   NaN

Фиксированный формат

В приведенных выше примерах показано хранение с использованием put, которое записывает HDF5 в PyTables в фиксированном формате массива, называемом форматом fixed. Эти типы хранилищ не могут быть добавлены после записи (хотя вы можете их просто удалить и переписать). Они также не поддерживают запросы; они должны быть получены целиком. Они также не поддерживают DataFrames с не уникальными именами столбцов. Форматы fixed предлагают очень быструю запись и немного более быстрое чтение, чем хранилища table. Этот формат задаётся по умолчанию при использовании put или to_hdf или format='fixed' или format='f'.

Предупреждение

Формат fixed вызовет исключение TypeError, если вы попытаетесь получить данные с помощью where:

In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")

In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")

File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    447                 raise ValueError(
    448                     "key must be provided when HDF5 "
    449                     "file contains multiple datasets."
    450                 )
    451         key = candidate_only_group._v_pathname
--> 452     return store.select(
    453         key,
    454         where=where,
    455         start=start,
    456         stop=stop,
    457         columns=columns,
    458         iterator=iterator,
    459         chunksize=chunksize,
    460         auto_close=auto_close,
    461     )
    462 except (ValueError, TypeError, LookupError):
    463     if not isinstance(path_or_buf, HDFStore):
    464         # if there is an error, close the store if we opened it.

File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    892 # create the iterator
    893 it = TableIterator(
    894     self,
    895     s,
   (...)
    903     auto_close=auto_close,
    904 )
--> 906 return it.get_result()

File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
   2026     where = self.where
   2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
   2030 self.close()
   2031 return results

File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
    889 def func(_start, _stop, _where):
--> 890     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
   3270 def read(
   3271     self,
   3272     where=None,
   (...)
   3276 ) -> DataFrame:
   3277     # start, stop applied to rows, so 0th axis only
-> 3278     self.validate_read(columns, where)
   3279     select_axis = self.obj_type()._get_block_manager_axis(0)
   3281     axes = []

File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
   2917     raise TypeError(
   2918         "cannot pass a column specification when reading "
   2919         "a Fixed format store. this store must be selected in its entirety"
   2920     )
   2921 if where is not None:
-> 2922     raise TypeError(
   2923         "cannot pass a where specification when reading "
   2924         "from a Fixed format store. this store must be selected in its entirety"
   2925     )

TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety

Формат таблицы

HDFStore поддерживает другой формат на диске, формат table. Концептуально table похож на DataFrame, с строками и столбцами. К table можно добавлять данные в той же или другой сессиях. Кроме того, поддерживаются операции удаления и запросов. Этот формат задаётся с помощью format='table' или format='t' для append или put или to_hdf.

Этот формат также можно задать в качестве опции pd.set_option('io.hdf.default_format','table'), чтобы по умолчанию put/append/to_hdf хранил данные в формате table.

In [481]: store = pd.HDFStore("store.h5")

In [482]: df1 = df[0:4]

In [483]: df2 = df[4:]

# append data (creates a table automatically)
In [484]: store.append("df", df1)

In [485]: store.append("df", df2)

In [486]: store
Out[486]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# select the entire object
In [487]: store.select("df")
Out[487]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'

Примечание

Вы также можете создать table, передав format='table' или format='t' в операцию put.

Иерархические ключи

Ключи к хранилищу могут быть указаны как строка. Они могут быть в формате иерархического имени пути (например, foo/bar/bah), что создаст иерархию подхранилищ (или Groups в терминологии PyTables). Ключи могут быть указаны без ведущего ‘/’ и всегда являются абсолютными (например, ‘foo’ относится к ‘/foo’). Операции удаления могут удалить всё в подхранилище и ниже, поэтому будьте осторожны.

In [489]: store.put("foo/bar/bah", df)

In [490]: store.append("food/orange", df)

In [491]: store.append("food/apple", df)

In [492]: store
Out[492]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']

# remove all nodes under this level
In [494]: store.remove("food")

In [495]: store
Out[495]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

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

In [496]: for (path, subgroups, subkeys) in store.walk():
   .....:     for subgroup in subgroups:
   .....:         print("GROUP: {}/{}".format(path, subgroup))
   .....:     for subkey in subkeys:
   .....:         key = "/".join([path, subkey])
   .....:         print("KEY: {}".format(key))
   .....:         print(store.get(key))
   .....: 
GROUP: /foo
KEY: /df
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

Предупреждение

Иерархические ключи не могут быть получены как точечный (атрибутный) доступ, как описано выше для элементов, хранящихся под узлом корня.

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
    611 """allow attribute access to get stores"""
    612 try:
--> 613     return self.get(name)
    614 except (KeyError, ClosedFileError):
    615     pass

File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
    811 if group is None:
    812     raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)

File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
   1877 def _read_group(self, group: Node):
-> 1878     s = self._create_storer(group)
   1879     s.infer_axes()
   1880     return s.read()

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
   1750         tt = "generic_table"
   1751     else:
-> 1752         raise TypeError(
   1753             "cannot create a storer if the object is not existing "
   1754             "nor a value are passed"
   1755         )
   1756 else:
   1757     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
  children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]

Вместо этого используйте явные ключи на основе строк:

In [499]: store["foo/bar/bah"]
Out[499]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

Хранение типов

Хранение смешанных типов в таблице

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

Передача min_itemsize={`values`: size} в качестве параметра append установит больший минимум для столбцов строк. Хранение floats, strings, ints, bools, datetime64 в настоящее время поддерживается. Для столбцов строк передача nan_rep = 'nan' в append изменит представление nan по умолчанию на диске (что преобразуется в/из np.nan), по умолчанию это nan.

In [500]: df_mixed = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(8),
   .....:         "B": np.random.randn(8),
   .....:         "C": np.array(np.random.randn(8), dtype="float32"),
   .....:         "string": "string",
   .....:         "int": 1,
   .....:         "bool": True,
   .....:         "datetime64": pd.Timestamp("20010102"),
   .....:     },
   .....:     index=list(range(8)),
   .....: )
   .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})

In [503]: df_mixed1 = store.select("df_mixed")

In [504]: df_mixed1
Out[504]: 
          A         B         C  ... int  bool                    datetime64
0  0.013747 -1.166078 -1.292080  ...   1  True 1970-01-01 00:00:00.978393600
1 -0.712009  0.247572  1.526911  ...   1  True 1970-01-01 00:00:00.978393600
2 -0.645096  1.687406  0.288504  ...   1  True 1970-01-01 00:00:00.978393600
3       NaN       NaN  0.097771  ...   1  True                           NaT
4       NaN       NaN  1.536408  ...   1  True                           NaT
5 -0.023202  0.043702  0.926790  ...   1  True 1970-01-01 00:00:00.978393600
6  2.359782  0.088224 -0.676448  ...   1  True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724  ...   1  True 1970-01-01 00:00:00.978393600

[8 rows x 7 columns]

In [505]: df_mixed1.dtypes.value_counts()
Out[505]: 
float64           2
float32           1
object            1
int64             1
bool              1
datetime64[ns]    1
Name: count, dtype: int64

# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]: 
/df_mixed/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
  "values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
  "values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
  "values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
  "values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
  "values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
  byteorder := 'little'
  chunkshape := (689,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

Хранение DataFrames с MultiIndex

Хранение DataFrames с MultiIndex в виде таблиц очень похоже на хранение/выбор из однородного индекса DataFrames.

In [507]: index = pd.MultiIndex(
   .....:    levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:    codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....:    names=["foo", "bar"],
   .....: )
   .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
                A         B         C
foo bar                              
bar one  0.410395  0.618321  0.560398
    two  1.434027 -0.033270  0.343197

Примечание

Ключевое слово index зарезервировано и не может использоваться в качестве имени уровня.

Запросы

Запрос таблицы

select и delete операции имеют необязательный критерий, который можно указать, чтобы выбрать/удалить только подмножество данных. Это позволяет иметь очень большую таблицу на диске и извлекать только часть данных.

Запрос задаётся с помощью класса Term внутри, как булево выражение.

  • index и columns являются поддерживаемыми индексаторами для DataFrames.

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

  • Имя уровня в MultiIndex, с именем по умолчанию level_0, level_1, … если не указано.

Допустимые операторы сравнения:

=, ==, !=, >, >=, <, <=

Допустимые булевы выражения комбинируются с:

  • | : или

  • & : и

  • ( и ) : для группировки

Эти правила похожи на то, как булевы выражения используются в pandas для индексации.

Примечание

  • = будет автоматически расширено до оператора сравнения ==

  • ~ — это оператор не, но может использоваться только в очень ограниченных случаях

  • Если передан список/кортеж выражений, они будут объединены через &

Вот допустимые выражения:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

indexers находятся слева от подвыражения:

columns, major_axis, ts

Правая часть подвыражения (после оператора сравнения) может быть:

  • функциями, которые будут вычислены, например, Timestamp('2012-02-01')

  • строками, например, "bar"

  • типами данных, подобными дате, например, 20130101 или "20130101"

  • списками, например, "['A', 'B']"

  • переменными, определёнными в локальном пространстве имён, например, date

Примечание

Передача строки в запрос путём её интерполяции в выражение запроса не рекомендуется. Просто присвойте интересующую строку переменной и используйте эту переменную в выражении. Например, сделайте так

string = "HolyMoly'"
store.select("df", "index == string")

вместо этого

string = "HolyMoly'"
store.select('df', f'index == {string}')

Последнее не будет работать и вызовет SyntaxError. Обратите внимание, что в переменной string есть одинарная кавычка, за которой следует двойная кавычка.

Если вам необходимо выполнить интерполяцию, используйте спецификатор формата '%r'

store.select("df", "index == %r" % string)

который процитирует string.

Вот некоторые примеры:

In [513]: dfq = pd.DataFrame(
   .....:     np.random.randn(10, 4),
   .....:     columns=list("ABCD"),
   .....:     index=pd.date_range("20130101", periods=10),
   .....: )
   .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True)

Используйте булевы выражения с вычислением функций в строке.

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
                   A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304

Используйте ссылку на столбец в строке.

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
                   A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641

Ключевое слово columns может быть предоставлено для выбора списка столбцов, которые будут возвращены, это эквивалентно передаче 'columns=list_of_columns_to_filter':

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
                   A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222

start и stop параметры могут быть указаны для ограничения общего пространства поиска. Они относятся к общему количеству строк в таблице.

Примечание

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

select вызовет SyntaxError, если выражение запроса неверно.

Запрос timedelta64[ns]

Вы можете хранить и запрашивать с помощью типа timedelta64[ns]. Условия могут быть указаны в формате: <float>(<unit>), где число с плавающей точкой может быть со знаком (и дробным), а единица может быть D,s,ms,us,ns для timedelta. Вот пример:

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Timestamp("20130101"),
   .....:         "B": [
   .....:             pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
   .....:             for i in range(10)
   .....:         ],
   .....:     }
   .....: )
   .....: 

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]: 
           A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]: 
                              A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50

Запрос MultiIndex

Выбор из MultiIndex может быть осуществлен с помощью имени уровня.

In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])

In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]: 
                A         B         C
foo bar                              
baz two -1.646063 -0.695847 -0.429156

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

In [526]: index = pd.MultiIndex(
   .....:     levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:     codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....: )
   .....: 

In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [528]: df_mi_2
Out[528]: 
                  A         B         C
foo one   -0.219582  1.186860 -1.437189
    two    0.053768  1.872644 -1.469813
    three -0.564201  0.876341  0.407749
bar one   -0.232583  0.179812  0.922152
    two   -1.820952 -0.641360  2.133239
baz two   -0.941248 -0.136307 -1.271305
    three -0.099774 -0.061438 -0.845172
qux one    0.465793  0.756995 -0.541690
    two   -0.802241  0.877657 -2.553831
    three  0.094899 -2.319519  0.293601

In [529]: store.append("df_mi_2", df_mi_2)

# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]: 
                A         B         C
foo two  0.053768  1.872644 -1.469813

Индексирование

Вы можете создать/изменить индекс для таблицы с помощью create_table_index после того, как данные уже находятся в таблице (после операции append/put). Создание индекса таблицы настоятельно рекомендуется. Это значительно ускорит ваши запросы, когда вы используете select с индексируемым измерением в качестве where.

Примечание

Индексы автоматически создаются для индексируемых и любых указанных вами столбцов данных. Это поведение можно отключить, передав index=False в append.

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')

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

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)

Затем создайте индекс после завершения добавления.

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)
  autoindex := True
  colindexes := {
    "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close()

См. здесь, как создать полностью отсортированный индекс (CSI) в существующем хранилище.

Запрос по столбцам данных

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

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
                   A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
                   A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2),
  "C": Float64Col(shape=(), dflt=0.0, pos=3),
  "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
  "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
  byteorder := 'little'
  chunkshape := (1680,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}

Существует некоторое снижение производительности при преобразовании большого количества столбцов в data columns, поэтому пользователю предоставляется возможность назначить их. Кроме того, вы не можете изменить столбцы данных (ни индексируемые) после первой операции добавления/записи (Конечно, вы можете просто считать данные и создать новую таблицу!).

Итератор

Вы можете передать iterator=True или chunksize=number_in_a_chunk в select и select_as_multiple, чтобы вернуть итератор по результатам. По умолчанию возвращается 50 000 строк в блоке.

In [557]: for df in store.select("df", chunksize=3):
   .....:     print(df)
   .....: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
                   A         B         C
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
                   A         B         C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

Примечание

Вы также можете использовать итератор с read_hdf, который откроет, а затем автоматически закроет хранилище по завершении итерации.

for df in pd.read_hdf("store.h5", "df", chunksize=3):
    print(df)

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

Вот рецепт для генерации запроса и его использования для создания блоков возврата равного размера.

In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})

In [559]: dfeq
Out[559]: 
   number
0       1
1       2
2       3
3       4
4       5
5       6
6       7
7       8
8       9
9      10

In [560]: store.append("dfeq", dfeq, data_columns=["number"])

In [561]: def chunks(l, n):
   .....:     return [l[i: i + n] for i in range(0, len(l), n)]
   .....: 

In [562]: evens = [2, 4, 6, 8, 10]

In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")

In [564]: for c in chunks(coordinates, 2):
   .....:     print(store.select("dfeq", where=c))
   .....: 
   number
1       2
3       4
   number
5       6
7       8
   number
9      10

Расширенные запросы

Выбор одного столбца

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

In [565]: store.select_column("df_dc", "index")
Out[565]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
3   2000-01-04
4   2000-01-05
5   2000-01-06
6   2000-01-07
7   2000-01-08
Name: index, dtype: datetime64[ns]

In [566]: store.select_column("df_dc", "string")
Out[566]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object
Выбор координат

Иногда вам нужно получить координаты (т.е. местоположения индексов) вашего запроса. Это возвращает Index результирующих местоположений. Эти координаты также могут быть переданы в последующие операции where.

In [567]: df_coord = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [568]: store.append("df_coord", df_coord)

In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [570]: c
Out[570]: 
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
       ...
       990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
      dtype='int64', length=268)

In [571]: store.select("df_coord", where=c)
Out[571]: 
                   0         1
2002-01-02  0.007717  1.168386
2002-01-03  0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208  1.260503
...              ...       ...
2002-09-22 -1.139583  0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24  0.059018  1.775482
2002-09-25  1.242255 -0.055457
2002-09-26  0.410317  2.194489

[268 rows x 2 columns]
Выбор с помощью маски where

Иногда ваш запрос может включать создание списка строк для выбора. Обычно этот mask будет результирующей index операции индексирования. Этот пример выбирает месяцы в datetimeindex, которые равны 5.

In [572]: df_mask = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [573]: store.append("df_mask", df_mask)

In [574]: c = store.select_column("df_mask", "index")

In [575]: where = c[pd.DatetimeIndex(c).month == 5].index

In [576]: store.select("df_mask", where=where)
Out[576]: 
                   0         1
2000-05-01  1.479511  0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03  0.900321  0.049695
2000-05-04  0.614266 -1.077151
2000-05-05  0.233881  0.493246
...              ...       ...
2002-05-27  0.294122  0.457407
2002-05-28 -1.102535  1.215650
2002-05-29 -0.432911  0.753606
2002-05-30 -1.105212  2.311877
2002-05-31  2.567296  2.610691

[93 rows x 2 columns]
Объект Storer

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

In [577]: store.get_storer("df_dc").nrows
Out[577]: 8

Запросы к нескольким таблицам

Методы append_to_multiple и select_as_multiple могут выполнять добавление/выбор из нескольких таблиц одновременно. Идея состоит в том, чтобы иметь одну таблицу (назовём её таблицей-селектором), по которой вы индексируете большинство/все столбцы и выполняете свои запросы. Другие таблицы — это таблицы данных с индексом, соответствующим индексу таблицы-селектора. Затем вы можете выполнить очень быстрый запрос к таблице-селектору, получив при этом много данных. Этот метод аналогичен наличию очень широкой таблицы, но обеспечивает более эффективные запросы.

Метод append_to_multiple разбивает заданный единственный DataFrame на несколько таблиц в соответствии с d, словарем, который сопоставляет имена таблиц со списком «столбцов», которые вы хотите в этой таблице. Если None используется вместо списка, в этой таблице будут оставшиеся не указанные столбцы из заданного DataFrame. Аргумент selector определяет, какая таблица является таблицей-селектором (из которой вы можете выполнять запросы). Аргумент dropna удалит строки из входного DataFrame, чтобы обеспечить синхронизацию таблиц. Это означает, что если строка для одной из таблиц, в которые производится запись, полностью np.nan, эта строка будет удалена из всех таблиц.

Если dropna имеет значение False, ПОЛЬЗОВАТЕЛЬ НЕСУЩ ЗАВИСИМОСТЬ ОТ СИНХРОНИЗАЦИИ ТАБЛИЦ. Помните, что строки, полностью np.Nan, не записываются в HDFStore. Если вы выберете вызвать dropna=False, некоторые таблицы могут иметь больше строк, чем другие, и поэтому select_as_multiple может не сработать или вернёт неожиданные результаты.

In [578]: df_mt = pd.DataFrame(
   .....:     np.random.randn(8, 6),
   .....:     index=pd.date_range("1/1/2000", periods=8),
   .....:     columns=["A", "B", "C", "D", "E", "F"],
   .....: )
   .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
   .....:     {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
   .....: )
   .....: 

In [582]: store
Out[582]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
                   A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
                   C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
   .....:     ["df1_mt", "df2_mt"],
   .....:     where=["A>0", "B>0"],
   .....:     selector="df1_mt",
   .....: )
   .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []

Удаление из таблицы

Вы можете удалить из таблицы выборочно, указав where. При удалении строк важно понимать, что PyTables удаляет строки, удаляя их, а затем перемещая последующие данные. Таким образом, удаление может быть очень дорогостоящей операцией в зависимости от ориентации ваших данных. Для достижения оптимальной производительности желательно, чтобы удаляемый размер был первым в indexables.

Данные упорядочены (на диске) с точки зрения indexables. Вот простой пример. Вы храните данные типа "панель", с датами в major_axis и идентификаторами в minor_axis. Данные затем чередуются следующим образом:

  • date_1
    • id_1

    • id_2

    • .

    • id_n

  • date_2
    • id_1

    • .

    • id_n

Должно быть ясно, что операция удаления по major_axis будет относительно быстрой, так как один блок удаляется, а затем перемещаются последующие данные. С другой стороны, операция удаления по minor_axis будет очень дорогостоящей. В этом случае было бы практически быстрее переписать таблицу, используя where, который выбирает все, кроме отсутствующих данных.

Предупреждение

Обратите внимание, что HDF5 НЕ ВОЗВРАЩАЕТ ПАМЯТЬ в файлах h5 автоматически. Таким образом, многократное удаление (или удаление узлов) и повторное добавление БУДЕТ ВЕСТИ К УВЕЛИЧЕНИЮ РАЗМЕРА ФАЙЛА.

Для переупаковки и очистки файла используйте ptrepack.

Примечания и замечания

Сжатие

PyTables позволяет сжимать хранимые данные. Это относится ко всем видам хранилищ, а не только к таблицам. Для управления сжатием используются два параметра: complevel и complib.

  • complevel указывает, как и насколько сильно сжимать данные. complevel=0 и complevel=None отключают сжатие, а 0<complevel<10 включает сжатие.

  • complib указывает, какую библиотеку сжатия использовать. Если ничего не указано, используется библиотека по умолчанию zlib. Библиотека сжатия обычно оптимизирована для достижения высокой степени сжатия или скорости, а результаты зависят от типа данных. Выбор типа сжатия зависит от ваших конкретных потребностей и данных. Список поддерживаемых библиотек сжатия:

    • zlib: Библиотека сжатия по умолчанию. Классическая библиотека сжатия, достигающая высокой степени сжатия, но несколько медленная.

    • lzo: Быстрое сжатие и распаковку.

    • bzip2: Высокая степень сжатия.

    • blosc: Быстрое сжатие и распаковку.

      Поддержка альтернативных сжимающих алгоритмов blosc:

      • blosc:blosclz Это сжимающий алгоритм по умолчанию для blosc

      • blosc:lz4: Компактный, популярный и быстрый сжимающий алгоритм.

      • blosc:lz4hc: Модифицированная версия LZ4, обеспечивающая более высокую степень сжатия в ущерб скорости.

      • blosc:snappy: Популярный сжимающий алгоритм, используемый во многих местах.

      • blosc:zlib: Классический; несколько медленнее предыдущих, но достигающий более высокой степени сжатия.

      • blosc:zstd: Очень сбалансированный кодек; он обеспечивает наилучшую степень сжатия среди других вышеперечисленных, и при достаточно высокой скорости.

    Если complib определено как что-то другое, чем перечисленные библиотеки, возникает исключение ValueError.

Примечание

Если библиотека, указанная с помощью параметра complib, отсутствует на вашей платформе, сжатие по умолчанию устанавливается на zlib без дальнейших действий.

Включить сжатие для всех объектов в файле:

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
)

Или сжатие "на лету" (это относится только к таблицам) в хранилищах, где сжатие не включено:

store.append("df", df, complib="zlib", complevel=5)

ptrepack

PyTables обеспечивает лучшую производительность записи, когда таблицы сжимаются после записи, а не с самого начала. Вы можете использовать предоставленную утилиту PyTables ptrepack. Кроме того, ptrepack может изменять уровни сжатия после факта.

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5

Кроме того, ptrepack in.h5 out.h5 переупакует файл, чтобы позволить вам повторно использовать ранее удаленное пространство. В качестве альтернативы можно просто удалить файл и снова записать его или использовать метод copy.

Предостережения

Предупреждение

HDFStore не потокобезопасен для записи. Базовое PyTables поддерживает только одновременные чтения (через потоки или процессы). Если вам нужно чтение и запись одновременно, вам необходимо сериализовать эти операции в одном потоке в одном процессе. В противном случае вы повредите данные. См. (GH 2397) для получения дополнительной информации.

  • Если вы используете блокировки для управления доступом к записи между несколькими процессами, вы можете использовать fsync() перед освобождением блокировок записи. Для удобства вы можете использовать store.flush(fsync=True) для выполнения этой задачи за вас.

  • После создания table столбцы (DataFrame) фиксируются; можно добавлять только точно такие же столбцы.

  • Обратите внимание, что часовые пояса (например, pytz.timezone('US/Eastern')) не обязательно совпадают в разных версиях библиотек часовых поясов. Таким образом, если данные локализованы в определенном часовом поясе в HDFStore с использованием одной версии библиотеки часовых поясов, и эти данные обновляются с другой версией, данные будут преобразованы в UTC, поскольку эти часовые пояса не считаются равными. Либо используйте одну и ту же версию библиотеки часовых поясов, либо используйте tz_convert с обновленным определением часового пояса.

Предупреждение

PyTables отобразит NaturalNameWarning, если имя столбца нельзя использовать в качестве селектора атрибута. Естественные идентификаторы содержат только буквы, цифры и символы подчеркивания и не могут начинаться с цифры. Другие идентификаторы нельзя использовать в предложении where и, как правило, не рекомендуются.

Типы данных

HDFStore будет сопоставлять тип данных object с PyTables базовым типом данных. Это означает, что следующие типы данных работают:

Тип

Представляет пропущенные значения

floating : float64, float32, float16

np.nan

integer : int64, int32, int8, uint64,uint32, uint8

boolean

datetime64[ns]

NaT

timedelta64[ns]

NaT

categorical : см. раздел ниже

object : strings

np.nan

Столбцы unicode не поддерживаются и БУДУТ ВЫЗЫВАТЬ ОШИБКУ.

Данные категорий

Вы можете записывать данные, содержащие category типы данных в HDFStore. Запросы работают так же, как если бы это был массив object. Однако данные с типом category хранятся более эффективно.

In [586]: dfcat = pd.DataFrame(
   .....:     {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
   .....: )
   .....: 

In [587]: dfcat
Out[587]: 
   A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
   A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object

Столбцы строк

min_itemsize

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

Передайте min_itemsize при первом создании таблицы, чтобы заранее указать минимальную длину определенного столбца строк. min_itemsize может быть целым числом или словарем, сопоставляющим имя столбца с целым числом. Вы можете передать values в качестве ключа, чтобы разрешить всем индексируемым или данным_столбцам иметь этот min_itemsize.

Передача словаря min_itemsize приведет к автоматическому созданию всех переданных столбцов как данных_столбцов.

Примечание

Если вы не передаете никаких data_columns, то min_itemsize будет максимальной длиной любой переданной строки.

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
     A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
  byteorder := 'little'
  chunkshape := (963,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
  "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
  byteorder := 'little'
  chunkshape := (1598,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "A": Index(6, mediumshuffle, zlib(1)).is_csi=False}

nan_rep

Столбцы строк будут сериализовать np.nan (пропущенное значение) с представлением строки nan_rep. По умолчанию это строковое значение nan. Вы можете случайно преобразовать фактическое nan значение в пропущенное значение.

In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [601]: dfss
Out[601]: 
     A
0  foo
1  bar
2  nan

In [602]: store.append("dfss", dfss)

In [603]: store.select("dfss")
Out[603]: 
     A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")

In [605]: store.select("dfss2")
Out[605]: 
     A
0  foo
1  bar
2  nan

Производительность

  • Формат tables имеет штраф производительности при записи по сравнению с хранилищами fixed. Преимущество заключается в возможности добавления/удаления и запроса (возможно, очень больших объемов данных). Время записи, как правило, больше по сравнению с обычными хранилищами. Время запроса может быть очень быстрым, особенно по индексированной оси.

  • Вы можете передать chunksize=<int> в append, указав размер куска записи (по умолчанию 50000). Это значительно снизит использование памяти при записи.

  • Вы можете передать expectedrows=<int> в первый append, чтобы установить ОБЩЕЕ количество строк, которые PyTables ожидает. Это позволит оптимизировать производительность чтения/записи.

  • В таблицы можно записать дубликаты строк, но они будут отфильтрованы при выборе (выбираются последние элементы; таким образом, таблица уникальна по парам «главный-второстепенный»).

  • Будет поднято исключение PerformanceWarning, если вы пытаетесь сохранить типы, которые будут сериализованы PyTables (а не сохранены как собственные типы). См. здесь для получения дополнительной информации и некоторых решений.

Feather

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

Feather разработан для точной сериализации и десериализации DataFrames, поддерживая все типы данных pandas, включая расширенные типы, такие как категориальные и datetime с часовыми поясами.

Некоторые особенности:

  • Формат НЕ запишет Index или MultiIndex для DataFrame и выдаст ошибку, если предоставлен не по умолчанию. Вы можете .reset_index() для сохранения индекса или .reset_index(drop=True) для его игнорирования.

  • Не поддерживаются дублирующие имена столбцов и имена столбцов, не являющиеся строками.

  • Фактические объекты Python в столбцах типа object не поддерживаются. При попытке сериализации они вызовут полезное сообщение об ошибке.

См. Полную документацию.

In [606]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.Categorical(list("abc")),
   .....:         "g": pd.date_range("20130101", periods=3),
   .....:         "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "i": pd.date_range("20130101", periods=3, freq="ns"),
   .....:     }
   .....: )
   .....: 

In [607]: df
Out[607]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

In [608]: df.dtypes
Out[608]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

Запись в файл feather.

In [609]: df.to_feather("example.feather")

Чтение из файла feather.

In [610]: result = pd.read_feather("example.feather")

In [611]: result
Out[611]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

# we preserve dtypes
In [612]: result.dtypes
Out[612]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

Parquet

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

Parquet разработан для точного сериализации и десериализации DataFrame, поддерживая все типы данных pandas, включая расширенные типы данных, такие как datetime с часовым поясом.

Несколько замечаний.

  • Дублирование имен столбцов и имена столбцов, не являющиеся строками, не поддерживаются.

  • pyarrow движок всегда записывает индекс в выходные данные, но fastparquet записывает только индексы, отличные от значения по умолчанию. Этот дополнительный столбец может создавать проблемы для потребителей, не являющихся pandas, которые не ожидают его. Вы можете принудительно включить или исключить индексы с помощью аргумента index, независимо от базового движка.

  • Имена уровней индекса, если указаны, должны быть строками.

  • В pyarrow движке типы данных категорий для типов данных, не являющихся строками, могут быть сериализованы в parquet, но десериализуются как их примитивный тип данных.

  • pyarrow движок сохраняет флаг ordered типов данных категорий со строковыми типами. fastparquet не сохраняет флаг ordered.

  • Неподдерживаемые типы включают Interval и фактические типы Python-объектов. Эти попытки сериализации приведут к полезному сообщению об ошибке. Period тип поддерживается с pyarrow >= 0.16.0.

  • pyarrow движок сохраняет расширенные типы данных, такие как нулевой целочисленный и строковый тип данных (требуется pyarrow >= 0.16.0 и требуются расширенные типы для реализации необходимых протоколов, см. документацию по расширенным типам).

Вы можете указать engine для направления сериализации. Это может быть одно из pyarrow, или fastparquet, или auto. Если движок НЕ указан, то проверяется опция pd.options.io.parquet.engine; если она также auto, то используется pyarrow, а в качестве последнего варианта используется fastparquet.

См. документацию для pyarrow и fastparquet.

Примечание

Эти движки очень похожи и должны читать/записывать практически идентичные файлы формата parquet. pyarrow>=8.0.0 поддерживает данные timedelta, fastparquet>=0.1.4 поддерживает даты и время с учетом часового пояса. Эти библиотеки отличаются наличием различных зависимостей (fastparquet с использованием numba, а pyarrow использует библиотеку c).

In [613]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.date_range("20130101", periods=3),
   .....:         "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "h": pd.Categorical(list("abc")),
   .....:         "i": pd.Categorical(list("abc"), ordered=True),
   .....:     }
   .....: )
   .....: 

In [614]: df
Out[614]: 
   a  b  c    d      e          f                         g  h  i
0  a  1  3  4.0   True 2013-01-01 2013-01-01 00:00:00-05:00  a  a
1  b  2  4  5.0  False 2013-01-02 2013-01-02 00:00:00-05:00  b  b
2  c  3  5  6.0   True 2013-01-03 2013-01-03 00:00:00-05:00  c  c

In [615]: df.dtypes
Out[615]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

Запись в файл parquet.

In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")

In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")

Чтение из файла parquet.

In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")

In [620]: result.dtypes
Out[620]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

Установив аргумент dtype_backend, вы можете контролировать значения по умолчанию типов данных, используемых для результирующего DataFrame.

In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [622]: result.dtypes
Out[622]: 
a                                      string[pyarrow]
b                                       int64[pyarrow]
c                                       uint8[pyarrow]
d                                      double[pyarrow]
e                                        bool[pyarrow]
f                               timestamp[ns][pyarrow]
g                timestamp[ns, tz=US/Eastern][pyarrow]
h    dictionary<values=string, indices=int32, order...
i    dictionary<values=string, indices=int32, order...
dtype: object

Примечание

Обратите внимание, что это не поддерживается для fastparquet.

Чтение только определенных столбцов файла parquet.

In [623]: result = pd.read_parquet(
   .....:     "example_fp.parquet",
   .....:     engine="fastparquet",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [624]: result = pd.read_parquet(
   .....:     "example_pa.parquet",
   .....:     engine="pyarrow",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [625]: result.dtypes
Out[625]: 
a    object
b     int64
dtype: object

Обработка индексов

Сериализация DataFrame в parquet может включать неявный индекс в качестве одного или нескольких столбцов в выходном файле. Таким образом, этот код:

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow")

создаёт файл parquet с тремя столбцами, если вы используете pyarrow для сериализации: a, b и __index_level_0__. Если вы используете fastparquet, индекс может или не может быть записан в файл.

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

Если вы хотите исключить индексы DataFrame при записи, передайте index=False в to_parquet():

In [628]: df.to_parquet("test.parquet", index=False)

Это создаёт файл parquet только с двумя ожидаемыми столбцами, a и b. Если у вашего DataFrame есть пользовательский индекс, вы его не получите при загрузке этого файла в DataFrame.

Передача index=True всегда запишет индекс, даже если это не является стандартным поведением для используемого движка.

Разбиение файлов Parquet

Parquet поддерживает разбиение данных на основе значений одного или нескольких столбцов.

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)

path указывает родительскую директорию, в которую будут сохранены данные. partition_cols — это имена столбцов, по которым будет происходить разбиение набора данных. Столбцы разбиваются в порядке их указания. Разбиения на основе партиций определяются уникальными значениями в столбцах партиций. Приведённый пример создаёт разбиение набора данных, которое может выглядеть следующим образом:

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ...

ORC

Аналогично формату parquet, формат ORC — это двоичная колоночная сериализация для фреймов данных. Он разработан для повышения эффективности чтения фреймов данных. pandas предоставляет как читатель, так и записыватель для формата ORC, read_orc() и to_orc(). Это требует библиотеки pyarrow.

Предупреждение

  • Сильно рекомендуется устанавливать pyarrow с помощью conda из-за некоторых проблем, возникающих с pyarrow.

  • to_orc() требует pyarrow>=7.0.0.

  • read_orc() и to_orc() пока не поддерживаются в Windows, вы можете найти подходящие среды на странице установки дополнительных зависимостей.

  • Для поддерживаемых типов данных обратитесь к поддерживаемым функциям ORC в Arrow.

  • В настоящее время часовые пояса в столбцах datetime не сохраняются при преобразовании DataFrame в файлы ORC.

In [631]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "d": [True, False, True],
   .....:         "e": pd.date_range("20130101", periods=3),
   .....:     }
   .....: )
   .....: 

In [632]: df
Out[632]: 
   a  b    c      d          e
0  a  1  4.0   True 2013-01-01
1  b  2  5.0  False 2013-01-02
2  c  3  6.0   True 2013-01-03

In [633]: df.dtypes
Out[633]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

Запись в файл orc.

In [634]: df.to_orc("example_pa.orc", engine="pyarrow")

Чтение из файла orc.

In [635]: result = pd.read_orc("example_pa.orc")

In [636]: result.dtypes
Out[636]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

Чтение только определённых столбцов из файла orc.

In [637]: result = pd.read_orc(
   .....:     "example_pa.orc",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [638]: result.dtypes
Out[638]: 
a    object
b     int64
dtype: object

SQL-запросы

Модуль pandas.io.sql предоставляет набор обёртки для запросов, чтобы облегчить получение данных и снизить зависимость от API конкретной базы данных.

В случае возможности пользователи могут сначала выбрать драйверы Apache Arrow ADBC. Эти драйверы должны обеспечить наилучшую производительность, обработку значений NULL и определение типов.

В версии 2.2.0: Добавлена нативная поддержка драйверов ADBC

Полный список драйверов ADBC и их состояние разработки можно найти в документации ADBC Driver Implementation Status.

Если драйвер ADBC недоступен или имеет недостающую функциональность, пользователи должны установить SQLAlchemy вместе с библиотекой драйвера их базы данных. Примерами таких драйверов являются psycopg2 для PostgreSQL или pymysql для MySQL. Для SQLite это включено в стандартную библиотеку Python по умолчанию. Обзор поддерживаемых драйверов для каждого диалекта SQL можно найти в документации SQLAlchemy.

Если SQLAlchemy не установлен, вы можете использовать sqlite3.Connection вместо объекта SQLAlchemy engine, connection или строки URI.

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

Основные функции:

read_sql_table(table_name, con[, schema, ...])

Чтение таблицы SQL базы данных в DataFrame.

read_sql_query(sql, con[, index_col, ...])

Чтение SQL-запроса в DataFrame.

read_sql(sql, con[, index_col, ...])

Чтение SQL-запроса или таблицы базы данных в DataFrame.

DataFrame.to_sql(name, con, *[, schema, ...])

Запись записей, хранящихся в DataFrame, в базу данных SQL.

Примечание

Функция read_sql() является удобной оберткой вокруг read_sql_table() и read_sql_query() (и для обратной совместимости) и будет делегировать выполнение конкретной функции в зависимости от предоставленного входного данных (имя таблицы базы данных или SQL-запрос). Имена таблиц не нужно заключать в кавычки, если они содержат специальные символы.

В следующем примере мы используем SQL-базу данных SQlite. Вы можете использовать временную базу данных SQLite, где данные хранятся в «памяти».

Для подключения с помощью драйвера ADBC вам необходимо установить adbc_driver_sqlite используя ваш менеджер пакетов. После установки вы можете использовать интерфейс DBAPI, предоставляемый драйвером ADBC, для подключения к вашей базе данных.

import adbc_driver_sqlite.dbapi as sqlite_dbapi

# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
     df = pd.read_sql_table("data", conn)

Для подключения с помощью SQLAlchemy вы используете функцию create_engine() для создания объекта engine из URI базы данных. Вам нужно создать engine только один раз на каждую базу данных, к которой вы подключаетесь. Дополнительную информацию о create_engine() и формате URI см. в примерах ниже и в документации SQLAlchemy documentation

In [639]: from sqlalchemy import create_engine

# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")

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

with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table("data", conn)

Предупреждение

При открытии подключения к базе данных вы также несете ответственность за его закрытие. Последствия оставления подключения открытым могут включать блокировку базы данных или другие нарушения работы.

Запись DataFrames

Предполагая, что следующие данные находятся в DataFrame data, мы можем вставить их в базу данных с помощью to_sql().

id

Дата

Col_1

Col_2

Col_3

26

2012-10-18

X

25.7

True

42

2012-10-19

Y

-12.4

False

63

2012-10-20

Z

5.73

True

In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
   .....:     (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
   .....:     (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
   .....:     (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
   .....: ]
   .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
   id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3

В некоторых базах данных запись больших DataFrames может привести к ошибкам из-за превышения ограничений размера пакетов. Этого можно избежать, установив параметр chunksize при вызове to_sql. Например, следующее записывает data в базу данных порциями по 1000 строк за раз:

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3

Типы данных SQL

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

Для наилучшего сохранения типов базы данных пользователям рекомендуется использовать драйверы ADBC, когда они доступны. Система типов Arrow предлагает более широкий спектр типов, которые более точно соответствуют типам баз данных, чем историческая система типов pandas/NumPy. Для иллюстрации, вот этот (неполный) список типов, доступных в разных базах данных и бэкендах pandas:

numpy/pandas

arrow

postgres

sqlite

int16/Int16

int16

SMALLINT

INTEGER

int32/Int32

int32

INTEGER

INTEGER

int64/Int64

int64

BIGINT

INTEGER

float32

float32

REAL

REAL

float64

float64

DOUBLE PRECISION

REAL

object

string

TEXT

TEXT

bool

bool_

BOOLEAN

datetime64[ns]

timestamp(us)

TIMESTAMP

datetime64[ns,tz]

timestamp(us,tz)

TIMESTAMPTZ

date32

DATE

month_day_nano_interval

INTERVAL

binary

BINARY

BLOB

decimal128

DECIMAL [1]

list

ARRAY [1]

struct

COMPOSITE TYPE

[1]

Примечания

[1] (1,2,3)

Не реализовано на момент написания, но теоретически возможно

Если вы заинтересованы в сохранении типов базы данных наилучшим образом на протяжении всего жизненного цикла вашей DataFrame, пользователям рекомендуется использовать аргумент dtype_backend="pyarrow" функции read_sql().

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")

Это предотвратит преобразование ваших данных в традиционную систему типов pandas/NumPy, которая часто преобразует типы SQL таким образом, что делает их невозможным обработать обратно.

В случае, если драйвер ADBC недоступен, to_sql() будет пытаться сопоставить ваши данные с подходящим типом данных SQL на основе типа данных данных. Когда у вас есть столбцы типа object, pandas будет пытаться определить тип данных.

Вы всегда можете переопределить значения по умолчанию, указав желаемый тип SQL для любого из столбцов с помощью аргумента dtype. Этот аргумент требует словарь, сопоставляющий имена столбцов со SQLAlchemy-типами (или строками для режима обратной совместимости sqlite3). Например, чтобы использовать тип sqlalchemy String вместо стандартного типа Text для строковых столбцов:

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3

Примечание

Из-за ограниченной поддержки timedelta в разных вариантах баз данных, столбцы с типом timedelta64 будут записываться как целые значения в виде наносекунд в базу данных, и будет выведено предупреждение. Единственное исключение из этого правила — использование драйвера ADBC PostgreSQL, в этом случае timedelta будет записан в базу данных как INTERVAL.

Примечание

Столбцы типа category будут преобразованы в плотное представление, как вы получите с помощью np.asarray(categorical) (например, для категорий строк это даёт массив строк). Из-за этого при повторном чтении таблицы базы данных категориальная переменная не создаётся.

Типы данных DateTime

Используя ADBC или SQLAlchemy, to_sql() способен записывать данные типа DateTime, которые являются либо локальными, либо с учётом часового пояса. Однако, конечные данные, хранящиеся в базе данных, в конечном счёте зависят от поддерживаемого типа данных для данных DateTime используемой системы базы данных.

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

База данных

Типы данных SQL DateTime

Поддержка часовых поясов

SQLite

TEXT

Нет

MySQL

TIMESTAMP или DATETIME

Нет

PostgreSQL

TIMESTAMP или TIMESTAMP WITH TIME ZONE

Да

При записи данных с учётом часового пояса в базы данных, не поддерживающие часовые пояса, данные будут записаны как временные метки без учёта часового пояса, которые находятся в местном времени относительно часового пояса.

read_sql_table() также способен читать данные типа DateTime, которые являются либо с учётом часового пояса, либо без него. При чтении типов TIMESTAMP WITH TIME ZONE, pandas преобразует данные в UTC.

Метод вставки

Параметр method управляет используемой SQL-командой вставки. Возможные значения:

  • None: Использует стандартную SQL-команду INSERT (одна на строку).

  • 'multi': Передаёт несколько значений в одной команде INSERT. Она использует специальный синтаксис SQL, не поддерживаемый всеми бэкендами. Это обычно обеспечивает лучшую производительность для аналитических баз данных, таких как Presto и Redshift, но имеет худшую производительность для традиционных SQL-бэкендов, если таблица содержит много столбцов. Для получения дополнительной информации см. документацию SQLAlchemy документацию.

  • вызываемый объект с сигнатурой (pd_table, conn, keys, data_iter): Это можно использовать для реализации более производительного метода вставки, основанного на специфических функциях диалекта бэкенда.

Пример вызываемого объекта, использующего PostgreSQL команду COPY:

# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
    """
    Execute SQL statement inserting data

    Parameters
    ----------
    table : pandas.io.sql.SQLTable
    conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
    keys : list of str
        Column names
    data_iter : Iterable that iterates the values to be inserted
    """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf)

Чтение таблиц

read_sql_table() будет читать таблицу базы данных, учитывая имя таблицы и, необязательно, подмножество столбцов для чтения.

Примечание

Для использования read_sql_table(), необходимо установить драйвер ADBC или необязательную зависимость SQLAlchemy.

In [650]: pd.read_sql_table("data", engine)
Out[650]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

Примечание

Драйверы ADBC будут напрямую отображать типы базы данных на типы Arrow. Для других драйверов обратите внимание, что pandas определяет типы столбцов (dtypes) из выходных данных запроса, а не из схемы физической базы данных. Например, предположим, что userid — это столбец целого типа в таблице. Тогда, интуитивно, select userid ... вернёт серию целых чисел, а select cast(userid as text) ... вернёт серию с типом object (строка). Соответственно, если вывод запроса пуст, то все результирующие столбцы будут возвращены как серии с типом object (поскольку это самый общий случай). Если вы ожидаете, что ваш запрос иногда будет генерировать пустой результат, вы можете явно привести типы данных после, чтобы гарантировать целостность dtypes.

Вы также можете указать имя столбца в качестве DataFrame индекса и указать подмножество столбцов для чтения.

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
    index       Date Col_1  Col_2  Col_3
id                                      
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
  Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73

И вы можете явно форсировать разбор столбцов как дат:

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

При необходимости вы можете явно указать строку формата или словарь аргументов для передачи в pandas.to_datetime():

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)

Вы можете проверить, существует ли таблица, используя has_table()

Поддержка схем

Чтение из различных схем и запись в них поддерживаются через ключевое слово schema в функциях read_sql_table() и to_sql(). Однако следует отметить, что это зависит от типа базы данных (в sqlite нет схем). Например:

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")

Запросы

Вы можете выполнять запросы с помощью простого SQL в функции read_sql_query(). В этом случае вы должны использовать соответствующий вариант SQL для вашей базы данных. При использовании SQLAlchemy вы также можете передать конструкции языка выражений SQLAlchemy, которые независимы от базы данных.

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1

Конечно, вы можете указать более «сложный» запрос.

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
   id Col_1  Col_2
0  42     Y  -12.5

Функция read_sql_query() поддерживает аргумент chunksize. Указав его, вы получите итератор по частям результата запроса:

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
   .....:     print(chunk)
   .....: 
          a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
          a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
          a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
          a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399

Примеры подключения к движку

Для подключения с помощью SQLAlchemy вы используете функцию create_engine() для создания объекта движка из URI базы данных. Вам нужно создать движок только один раз для каждой базы данных, к которой вы подключаетесь.

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:tiger@127.0.0.1:1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")

Для получения дополнительной информации см. примеры в документации SQLAlchemy документации

Расширенные запросы SQLAlchemy

Вы можете использовать конструкции SQLAlchemy для описания вашего запроса.

Используйте sqlalchemy.text() для указания параметров запроса нейтральным для бэкенда способом

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
   .....:     sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
   .....: )
   .....: 
Out[660]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1

Если у вас есть описание базы данных в SQLAlchemy, вы можете выразить условия WHERE с помощью выражений SQLAlchemy

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
   .....:     "data",
   .....:     metadata,
   .....:     sa.Column("index", sa.Integer),
   .....:     sa.Column("Date", sa.DateTime),
   .....:     sa.Column("Col_1", sa.String),
   .....:     sa.Column("Col_2", sa.Float),
   .....:     sa.Column("Col_3", sa.Boolean),
   .....: )
   .....: 

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []

Вы можете комбинировать выражения SQLAlchemy с параметрами, передаваемыми в read_sql(), используя sqlalchemy.bindparam()

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]: 
   index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True

Поддержка Sqlite по умолчанию

Использование sqlite поддерживается без использования SQLAlchemy. Этот режим требует адаптер Python для работы с базами данных, соответствующий Python DB-API.

Вы можете создать подключения следующим образом:

import sqlite3

con = sqlite3.connect(":memory:")

А затем выполнить следующие запросы:

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)

Google BigQuery

Пакет pandas-gbq предоставляет функциональность для чтения/записи из Google BigQuery.

pandas интегрируется с этим внешним пакетом. Если pandas-gbq установлен, вы можете использовать методы pandas pd.read_gbq и DataFrame.to_gbq, которые вызовут соответствующие функции из pandas-gbq.

Полную документацию можно найти здесь.

Формат Stata

Запись в формате Stata

Метод DataFrame.to_stata() запишет DataFrame в файл .dta. Версия формата этого файла всегда 115 (Stata 12).

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta")

Файлы данных Stata имеют ограниченную поддержку типов данных; только строки длиной 244 символа или меньше, int8, int16, int32, float32 и float64 могут быть сохранены в файлах .dta. Кроме того, Stata резервирует определённые значения для представления пропущенных данных. Экспорт значения, отличного от пропущенного, выходящего за пределы разрешённого диапазона в Stata для определённого типа данных, приведёт к повторному типу переменной на следующий больший размер. Например, значения int8 ограничены в Stata значениями от -127 до 100, поэтому переменные со значениями выше 100 вызовут преобразование к типу int16. Пропущенные значения nan в типах данных с плавающей запятой сохраняются как основной тип пропущенных данных (. в Stata).

Примечание

Экспорт пропущенных значений для целочисленных типов данных невозможен.

Модуль Stata для записи корректно обрабатывает другие типы данных, включая int64, bool, uint8, uint16, uint32, преобразуя их к наименьшему поддерживаемому типу, который может представлять данные. Например, данные с типом uint8 будут преобразованы к типу int8, если все значения меньше 100 (верхняя граница для значений, не являющихся пропущенными, int8 в Stata), или, если значения выходят за этот диапазон, переменная преобразуется к типу int16.

Предупреждение

Преобразование из int64 в float64 может привести к потере точности, если значения int64 больше, чем 2**53.

Предупреждение

StataWriter и DataFrame.to_stata() поддерживают только строки с фиксированной шириной, содержащие до 244 символов — ограничение, наложенное форматом файла dta версии 115. Попытка записать файлы Stata dta со строками длиной более 244 символов вызывает исключение ValueError.

Чтение из формата Stata

Функция верхнего уровня read_stata будет читать файл dta и возвращать либо DataFrame, либо pandas.api.typing.StataReader, которые могут использоваться для постепенного чтения файла.

In [669]: pd.read_stata("stata.dta")
Out[669]: 
   index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340

Указание chunksize приводит к экземпляру pandas.api.typing.StataReader, который может использоваться для чтения chunksize строк файла за раз. Объект StataReader может использоваться как итератор.

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
   .....:     for df in reader:
   .....:         print(df.shape)
   .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3)

Для более точного управления используйте iterator=True и укажите chunksize при каждом вызове read().

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
   .....:     chunk1 = reader.read(5)
   .....:     chunk2 = reader.read(5)
   .....: 

В настоящее время index извлекается как столбец.

Параметр convert_categoricals указывает, следует ли читать метки значений и использовать их для создания переменной Categorical из них. Метки значений также могут быть получены функцией value_labels, которая требует вызова read() перед использованием.

Параметр convert_missing указывает, следует ли сохранять представления пропущенных значений в Stata. Если False (по умолчанию), пропущенные значения представлены как np.nan. Если True, пропущенные значения представлены с помощью объектов StataMissingValue, и столбцы, содержащие пропущенные значения, будут иметь тип данных object.

Примечание

read_stata() и StataReader поддерживают форматы .dta 113-115 (Stata 10-12), 117 (Stata 13) и 118 (Stata 14).

Примечание

Указание preserve_dtypes=False приведет к повышению точности до стандартных типов данных pandas: int64 для всех целочисленных типов и float64 для данных с плавающей точкой. По умолчанию типы данных Stata сохраняются при импорте.

Примечание

Все объекты StataReader, созданные функцией read_stata() (при использовании iterator=True или chunksize) или созданные вручную, должны использоваться как управляющие контекстом (например, оператор with). Хотя метод close() доступен, его использование не поддерживается. Он не входит в общедоступный API и будет удален в будущих версиях без предупреждения.

Данные категорий

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

Предупреждение

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

Данные с метками аналогичным образом могут быть импортированы из файлов данных Stata как переменные Categorical с использованием ключевого аргумента convert_categoricals (True по умолчанию). Ключевой аргумент order_categoricals (True по умолчанию) определяет, являются ли импортированные переменные Categorical упорядоченными.

Примечание

При импорте категориальных данных значения переменных в файле данных Stata не сохраняются, поскольку переменные Categorical всегда используют целочисленные типы данных между -1 и n-1, где n — количество категорий. Если исходные значения в файле данных Stata требуются, их можно импортировать, установив convert_categoricals=False, что импортирует исходные данные (но не метки переменных). Исходные значения могут быть сопоставлены с импортированными категориальными данными, так как существует простое соответствие между исходными значениями данных Stata и кодами категорий импортированных категориальных переменных: пропущенным значениям присваивается код -1, наименьшему исходному значению присваивается 0, второму наименьшему — 1 и так далее, пока наибольшему исходному значению не будет присвоен код n-1.

Примечание

Stata поддерживает частично помеченные ряды. Эти ряды имеют метки значений для некоторых, но не для всех значений данных. Импорт частично помеченного ряда создаст Categorical со строковыми категориями для помеченных значений и числовыми категориями для значений без метки.

Форматы SAS

Функция верхнего уровня read_sas() может читать (но не записывать) файлы формата SAS XPORT (.xpt) и SAS7BDAT (.sas7bdat).

Файлы SAS содержат только два типа значений: текстовые ASCII и значения с плавающей запятой (обычно 8 байт, но иногда усеченные). Для файлов xport автоматического преобразования типов к целым числам, датам или категориальным значениям не происходит. Для файлов SAS7BDAT коды формата могут позволить автоматически преобразовать переменные даты в даты. По умолчанию весь файл читается и возвращается как DataFrame.

Укажите chunksize или используйте iterator=True, чтобы получить объекты-читатели (XportReader или SAS7BDATReader) для поэтапного чтения файла. Объекты-читатели также имеют атрибуты, которые содержат дополнительную информацию о файле и его переменных.

Чтение файла SAS7BDAT:

df = pd.read_sas("sas_data.sas7bdat")

Получение итератора и чтение файла XPORT 100 000 строк за раз:

def do_something(chunk):
    pass


with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
    for chunk in rdr:
        do_something(chunk)

Спецификация формата файлов xport доступна на сайте SAS.

Официальной документации по формату SAS7BDAT нет.

Форматы SPSS

Функция верхнего уровня read_spss() может читать (но не записывать) файлы формата SPSS SAV (.sav) и ZSAV (.zsav).

Файлы SPSS содержат имена столбцов. По умолчанию весь файл читается, категориальные столбцы преобразуются в pd.Categorical, и возвращается DataFrame со всеми столбцами.

Укажите параметр usecols, чтобы получить подмножество столбцов. Укажите convert_categoricals=False, чтобы избежать преобразования категориальных столбцов в pd.Categorical.

Чтение файла SPSS:

df = pd.read_spss("spss_data.sav")

Извлечение подмножества столбцов, содержащихся в usecols из файла SPSS и избегание преобразования категориальных столбцов в pd.Categorical:

df = pd.read_spss(
    "spss_data.sav",
    usecols=["foo", "bar"],
    convert_categoricals=False,
)

Дополнительную информацию о форматах файлов SAV и ZSAV можно найти здесь.

Другие форматы файлов

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

netCDF

xarray предоставляет структуры данных, вдохновленные pandas DataFrame для работы с многомерными наборами данных, с акцентом на формате файлов netCDF и легким преобразованием в pandas и из pandas.

Учет производительности

Это неформальное сравнение различных методов ввода/вывода, использующее pandas 0.24.2. Временные интервалы зависят от компьютера, и небольшие различия следует игнорировать.

In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})

In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A    1000000 non-null float64
B    1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB

Ниже будут использоваться следующие тестовые функции для сравнения производительности нескольких методов ввода/вывода:

import numpy as np

import os

sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})

sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})


def test_sql_write(df):
    if os.path.exists("test.sql"):
        os.remove("test.sql")
    sql_db = sqlite3.connect("test.sql")
    df.to_sql(name="test_table", con=sql_db)
    sql_db.close()


def test_sql_read():
    sql_db = sqlite3.connect("test.sql")
    pd.read_sql_query("select * from test_table", sql_db)
    sql_db.close()


def test_hdf_fixed_write(df):
    df.to_hdf("test_fixed.hdf", key="test", mode="w")


def test_hdf_fixed_read():
    pd.read_hdf("test_fixed.hdf", "test")


def test_hdf_fixed_write_compress(df):
    df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")


def test_hdf_fixed_read_compress():
    pd.read_hdf("test_fixed_compress.hdf", "test")


def test_hdf_table_write(df):
    df.to_hdf("test_table.hdf", key="test", mode="w", format="table")


def test_hdf_table_read():
    pd.read_hdf("test_table.hdf", "test")


def test_hdf_table_write_compress(df):
    df.to_hdf(
        "test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
    )


def test_hdf_table_read_compress():
    pd.read_hdf("test_table_compress.hdf", "test")


def test_csv_write(df):
    df.to_csv("test.csv", mode="w")


def test_csv_read():
    pd.read_csv("test.csv", index_col=0)


def test_feather_write(df):
    df.to_feather("test.feather")


def test_feather_read():
    pd.read_feather("test.feather")


def test_pickle_write(df):
    df.to_pickle("test.pkl")


def test_pickle_read():
    pd.read_pickle("test.pkl")


def test_pickle_write_compress(df):
    df.to_pickle("test.pkl.compress", compression="xz")


def test_pickle_read_compress():
    pd.read_pickle("test.pkl.compress", compression="xz")


def test_parquet_write(df):
    df.to_parquet("test.parquet")


def test_parquet_read():
    pd.read_parquet("test.parquet")

При записи три самых быстрых функции — это test_feather_write, test_hdf_fixed_write и test_hdf_fixed_write_compress.

In [4]: %timeit test_sql_write(df)
3.29 s ± 43.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [5]: %timeit test_hdf_fixed_write(df)
19.4 ms ± 560 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: %timeit test_hdf_fixed_write_compress(df)
19.6 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [7]: %timeit test_hdf_table_write(df)
449 ms ± 5.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [8]: %timeit test_hdf_table_write_compress(df)
448 ms ± 11.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [9]: %timeit test_csv_write(df)
3.66 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [10]: %timeit test_feather_write(df)
9.75 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [11]: %timeit test_pickle_write(df)
30.1 ms ± 229 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]: %timeit test_pickle_write_compress(df)
4.29 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [13]: %timeit test_parquet_write(df)
67.6 ms ± 706 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

При чтении три самые быстрые функции — это test_feather_read, test_pickle_read и test_hdf_fixed_read.

In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Файлы test.pkl.compress, test.parquet и test.feather занимают наименьшее место на диске (в байтах).

29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983  Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144  Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/io.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API