Инструменты ввода-вывода (текст, CSV, HDF5, …)
API pandas для ввода-вывода представляет собой набор функций верхнего уровня reader, вызываемых, например, как pandas.read_csv(), которые обычно возвращают объект pandas. Соответствующие функции writer — это методы объектов, вызываемые, например, как DataFrame.to_csv(). Ниже приведена таблица доступных функций readers и writers.
Тип формата | Описание данных | Чтение | Запись |
|---|---|---|---|
текст | |||
текст | Текстовый файл фиксированной ширины | NA | |
текст | |||
текст | |||
текст | NA | ||
текст | |||
текст | Локальный буфер обмена | ||
двоичный | |||
двоичный | NA | ||
двоичный | |||
двоичный | |||
двоичный | |||
двоичный | |||
двоичный | |||
двоичный | NA | ||
двоичный | NA | ||
двоичный | |||
SQL | |||
SQL | Google BigQuery;:ref:read_gbq<io.bigquery>;:ref:to_gbq<io.bigquery> |
Здесь приведено неформальное сравнение производительности некоторых из этих методов ввода-вывода.
Примечание
В примерах, использующих класс StringIO, не забудьте импортировать его с помощью from io import StringIO для Python 3.
CSV и текстовые файлы
Основная функция для чтения текстовых файлов (также называемых плоскими файлами) — read_csv(). Некоторые продвинутые стратегии описаны в сборнике рецептов.
Параметры разбора
read_csv() принимает следующие распространённые аргументы:
Основные
- filepath_or_buffer:various
-
Путь к файлу (
str,pathlib.Pathилиpy:py._path.local.LocalPath), URL (включая адреса http, ftp и S3) либо любой объект с методомread()(например, открытый файл илиStringIO). - sep:str, по умолчанию ',' для read_csv(), \t для read_table()
-
Используемый разделитель. Если sep —
None, движок C не может автоматически определить разделитель, но это может сделать движок разбора Python; в этом случае будет использоваться последний, который автоматически определит разделитель с помощью встроенного средства Python для распознавания,csv.Sniffer. Кроме того, разделители длиной более одного символа и отличные от'\s+'будут интерпретироваться как регулярные выражения и также потребуют использования движка разбора Python. Учтите, что при использовании разделителей-регулярных выражений кавычки в данных могут игнорироваться. Пример регулярного выражения:'\\r\\t'. - delimiter:str, по умолчанию None
-
Альтернативное имя аргумента sep.
- delim_whitespace:boolean, по умолчанию False
-
Указывает, будут ли пробельные символы (например,
' 'или'\t') использоваться в качестве разделителя. Эквивалентно установкеsep='\s+'. Если для этого параметра задано значениеTrue, параметрdelimiterне следует указывать.
Расположение и имена столбцов и индекса
- header:int или list of ints, по умолчанию 'infer'
-
Номер(а) строки, которые следует использовать в качестве имён столбцов и начала данных. По умолчанию имена столбцов определяются автоматически: если имена не переданы, поведение идентично
header=0, и имена столбцов определяются по первой строке файла; если имена столбцов заданы явно, поведение идентичноheader=None. Явно передайтеheader=0, чтобы заменить существующие имена.Для задания расположения строк, используемых в качестве уровней MultiIndex столбцов, параметр header может принимать список целых чисел, например
[0,1,3]. Промежуточные строки, которые не указаны, будут пропущены (например, в этом примере пропускается строка 2). Учтите, что этот параметр игнорирует строки-комментарии и пустые строки, если заданоskip_blank_lines=True, поэтому header=0 обозначает первую строку данных, а не первую строку файла. - names:array-like, по умолчанию None
-
Список имён столбцов. Если файл не содержит строки заголовка, следует явно передать
header=None. Повторяющиеся значения в этом списке не допускаются. - index_col:int, str, последовательность int / str или False, необязательный, по умолчанию None
-
Столбец(ы), которые следует использовать в качестве меток строк
DataFrame; их можно задать как строковым именем, так и индексом столбца. Если передана последовательность int / str, используется MultiIndex.Примечание
Можно использовать
index_col=False, чтобы принудительно указать pandas не использовать первый столбец в качестве индекса, например, если файл имеет неверный формат и в конце каждой строки стоят разделители.Значение
Noneпо умолчанию указывает pandas определить это автоматически. Если число полей в строке заголовка столбцов совпадает с числом полей в строках данных, используется индекс по умолчанию. Если полей в заголовке больше, первые столбцы используются в качестве индекса, чтобы оставшееся число полей в данных совпадало с числом полей в заголовке.Для определения числа столбцов, которые войдут в индекс, используется первая строка после заголовка. Если в последующих строках столбцов меньше, чем в первой строке, недостающие значения заполняются
NaN.Этого можно избежать с помощью
usecols. Это гарантирует, что столбцы будут взяты без изменений, а конечные данные будут проигнорированы. - usecols:list-like или callable, по умолчанию None
-
Возвращает подмножество столбцов. Если задан объект типа list-like, все его элементы должны быть либо позиционными (то есть целочисленными индексами столбцов документа), либо строками, соответствующими именам столбцов, заданным пользователем в
namesили определённым по строке(ам) заголовка документа. Если заданыnames, строки заголовка документа не учитываются. Например, допустимым параметромusecolsтипа list-like будет[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, чтобы сохранить тип данных без его интерпретации. Если заданы преобразователи, они будут применены ВМЕСТО преобразования типа.Добавлено в версии 1.5.0: Добавлена поддержка defaultdict. В качестве входных данных можно указать defaultdict, где значение по умолчанию определяет тип данных столбцов, не перечисленных явно.
- dtype_backend:{“numpy_nullable”, “pyarrow”}, по умолчанию DataFrame с поддержкой NumPy
-
Какой dtype_backend использовать, например, должны ли в DataFrame быть массивы NumPy. Если задано “numpy_nullable”, для всех типов данных, имеющих реализацию с поддержкой пропусков, используются nullable-типы данных; если задано “pyarrow”, для всех типов данных используется pyarrow.
dtype_backend пока остаются экспериментальными.
Добавлено в версии 2.0.
- engine:{'c', 'python', 'pyarrow'}
-
Используемый движок разбора. Движки C и pyarrow работают быстрее, а движок Python на данный момент поддерживает больше возможностей. Многопоточность поддерживается только движком pyarrow.
Добавлено в версии 1.4.0: Движок «pyarrow» добавлен в качестве экспериментального; некоторые возможности им не поддерживаются или могут работать некорректно.
- converters:dict, по умолчанию None
-
Словарь функций для преобразования значений в определённых столбцах. Ключами могут быть целые числа или метки столбцов.
- true_values:list, по умолчанию None
-
Значения, которые следует считать
True. - false_values:list, по умолчанию None
-
Значения, которые следует считать
False. - skipinitialspace:boolean, по умолчанию False
-
Пропускать пробелы после разделителя.
- skiprows:list-like или integer, по умолчанию 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:int, по умолчанию 0
-
Число строк в конце файла, которые следует пропустить (не поддерживается при engine=’c’).
- nrows:int, по умолчанию None
-
Число строк файла, которые следует прочитать. Полезно для чтения больших файлов по частям.
- low_memory:boolean, по умолчанию True
-
Обрабатывать файл частями, что снижает потребление памяти при разборе, но может привести к определению разных типов данных. Чтобы гарантировать отсутствие смешанных типов, задайте
Falseили укажите тип с помощью параметраdtype. Учтите, что весь файл в любом случае считывается в одинDataFrame; чтобы вернуть данные частями, используйте параметрchunksizeилиiterator. (Допустимо только для парсера C.) - memory_map:boolean, по умолчанию False
-
Если для
filepath_or_bufferуказан путь к файлу, напрямую отобразить объект файла в память и обращаться к данным непосредственно оттуда. Использование этого параметра может повысить производительность, поскольку устраняет накладные расходы на операции ввода-вывода.
Обработка NA и пропущенных данных
- na_values:scalar, str, list-like или dict, по умолчанию None
-
Дополнительные строки, которые следует распознавать как NA/NaN. Если передан dict, задаются отдельные значения NA для каждого столбца. Список значений, интерпретируемых по умолчанию как NaN, см. ниже в разделе константы значений NA.
- keep_default_na:boolean, по умолчанию 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, при разборе используются только указанныеna_valuesзначения NaN.Если
keep_default_na—Falseиna_valuesне заданы, никакие строки не будут интерпретироваться как NaN.
Учтите, что если
na_filterпередан какFalse, параметрыkeep_default_naиna_valuesбудут проигнорированы. - na_filter:boolean, по умолчанию True
-
Обнаруживать маркеры пропущенных значений (пустые строки и значения из na_values). Если в данных нет значений NA, передача
na_filter=Falseможет повысить производительность при чтении большого файла. - verbose:boolean, по умолчанию False
-
Указывает число значений NA, помещённых в нечисловые столбцы.
- skip_blank_lines:boolean, по умолчанию True
-
Если задано
True, пустые строки пропускаются, а не интерпретируются как значения NaN.
Обработка даты и времени
- parse_dates:boolean, список int или имён, список списков или dict, по умолчанию False.
-
Если задано
True→ попытаться разобрать индекс.Если задано
[1, 2, 3]→ попытаться разобрать столбцы 1, 2, 3 как отдельные столбцы с датами.Если задано
[[1, 3]]→ объединить столбцы 1 и 3 и разобрать их как один столбец с датами.Если задано
{'foo': [1, 3]}→ разобрать столбцы 1, 3 как дату и назвать результат «foo».
Примечание
Для дат в формате ISO 8601 существует ускоренный способ обработки.
- infer_datetime_format:boolean, по умолчанию False
-
Если задано
Trueи для столбца включён parse_dates, попытаться определить формат даты и времени, чтобы ускорить обработку.Устарело с версии 2.0.0: Теперь строгая версия этого аргумента используется по умолчанию; его передача не влияет на результат.
- keep_date_col:boolean, по умолчанию False
-
Если задано
Trueи parse_dates указывает на объединение нескольких столбцов, сохранить исходные столбцы. - date_parser:function, по умолчанию None
-
Функция для преобразования последовательности строковых столбцов в массив объектов datetime. По умолчанию для преобразования используется
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:str или dict «столбец -> формат», по умолчанию None
-
Если используется вместе с
parse_dates, даты будут разбираться согласно этому формату. Для более сложных случаев считайте данные какobject, а затем при необходимости применитеto_datetime().Добавлено в версии 2.0.0.
- dayfirst:boolean, по умолчанию False
-
Даты в формате ДД/ММ, принятом в международных и европейских форматах.
- cache_dates:boolean, по умолчанию True
-
Если значение True, использовать кеш уникальных преобразованных дат для преобразования даты и времени. Может значительно ускорить разбор повторяющихся строк с датами, особенно строк со смещением часового пояса.
Итерация
- iterator:boolean, по умолчанию False
-
Вернуть объект
TextFileReaderдля итерации или получения фрагментов с помощьюget_chunk(). - chunksize:int, по умолчанию None
-
Вернуть объект
TextFileReaderдля итерации. См. ниже раздел итерация и обработка фрагментами.
Кавычки, сжатие и формат файла
- compression:{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict}, по умолчанию 'infer'
-
Для распаковки данных с диска во время чтения. Если задано ‘infer’, используется gzip, bz2, zip, xz или zstandard, когда
filepath_or_bufferявляется объектом типа path-like и заканчивается на ‘.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, по умолчанию None
-
Разделитель тысяч.
- decimal:str, по умолчанию '.'
-
Символ, который следует распознавать как десятичный разделитель. Например, для европейских данных используйте
','. - float_precision:string, по умолчанию None
-
Указывает, какой преобразователь должен использовать движок C для чисел с плавающей точкой. Доступны следующие варианты:
Noneдля обычного преобразователя,highдля преобразователя с высокой точностью иround_tripдля преобразователя с обратным преобразованием. - lineterminator:str (длина 1), по умолчанию None
-
Символ, используемый для разделения файла на строки. Допустим только для парсера C.
- quotechar:str (длина 1)
-
Символ, обозначающий начало и конец элемента в кавычках. Элементы в кавычках могут содержать разделитель, который в таком случае игнорируется.
- quoting:int или экземпляр csv.QUOTE_*, по умолчанию 0
-
Управляет заключением полей в кавычки согласно константам
csv.QUOTE_*. Используйте одно из значений:QUOTE_MINIMAL(0),QUOTE_ALL(1),QUOTE_NONNUMERIC(2) илиQUOTE_NONE(3). - doublequote:boolean, по умолчанию True
-
Если задано
quotechar, аquotingне равноQUOTE_NONE, указывает, следует ли интерпретировать два последовательных элементаquotecharвнутри поля как один элементquotechar. - escapechar:str (длина 1), по умолчанию None
-
Односимвольная строка, используемая для экранирования разделителя, когда заключение в кавычки задано как
QUOTE_NONE. - comment:str, по умолчанию None
-
Указывает, что оставшуюся часть строки не следует разбирать. Если символ комментария найден в начале строки, строка полностью игнорируется. Этот параметр должен содержать один символ. Как и пустые строки (если задано
skip_blank_lines=True), строки, полностью состоящие из комментариев, игнорируются параметромheader, но не параметромskiprows. Например, если заданоcomment='#', разбор ‘#empty\na,b,c\n1,2,3’ с помощьюheader=0приведёт к тому, что ‘a,b,c’ будет считаться заголовком. - encoding:str, по умолчанию None
-
Кодировка UTF для чтения и записи (например,
'utf-8'). Список стандартных кодировок Python. - dialect:str или экземпляр csv.Dialect, по умолчанию None
-
Если задан этот параметр, он переопределяет значения (как заданные по умолчанию, так и нет) следующих параметров:
delimiter,doublequote,escapechar,skipinitialspace,quotecharиquoting. При необходимости переопределения значений будет выдано предупреждение ParserWarning. Подробности см. в документацииcsv.Dialect.
Обработка ошибок
- on_bad_lines:(‘error’, ‘warn’, ‘skip’), по умолчанию ‘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. Если вам незнакомы эти понятия, ознакомьтесь с разделом здесь, чтобы узнать больше о типах данных, и с разделом здесь, чтобы узнать больше о преобразовании object в 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, используя спецификацию dict:
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» из выходных данных.
Работа с данными 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
Обработка дат
Указание столбцов с датами
Для упрощения работы с данными даты и времени 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. Для нестандартного разбора даты и времени используйте to_datetime() после pd.read_csv.
Примечание
У read_csv есть fast_path для разбора строк даты и времени в формате iso8601, например «2000-01-01T00:01:02+00:00» и его вариантов. Если данные можно сохранить в таком формате, загрузка будет значительно быстрее; наблюдалось ускорение примерно в 20 раз.
Устарело начиная с версии 2.2.0: Объединение столбцов с датами внутри read_csv устарело. Вместо этого используйте pd.to_datetime для соответствующих столбцов результата.
Функции разбора дат
Наконец, парсер позволяет указать пользовательскую date_format. С точки зрения производительности следует пробовать следующие способы разбора дат в указанном порядке:
Если формат известен, используйте
date_format, например:date_format="%d/%m/%Y"илиdate_format={column_name: "%d/%m/%Y"}.Если для разных столбцов используются разные форматы или требуется передать дополнительные параметры (например,
utc) вto_datetime, загрузите данные с типомobject, а затем используйтеto_datetime.
Разбор CSV-файла с разными часовыми поясами
pandas не может напрямую представлять столбец или индекс с разными часовыми поясами. Если CSV-файл содержит столбцы со значениями из разных часовых поясов, по умолчанию результатом будет столбец типа object со строками, даже при использовании parse_dates. Чтобы разобрать значения с разными часовыми поясами как столбец даты и времени, загрузите их с типом 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/December/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.
Рассмотрим несколько примеров:
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 ~/work/pandas/pandas/pandas/_libs/parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()
File ~/work/pandas/pandas/pandas/_libs/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:988, in ParserBase._validate_usecols_names(self, usecols, names)
986 missing = [c for c in usecols if c not in names]
987 if len(missing) > 0:
--> 988 raise ValueError(
989 f"Usecols do not match columns, columns expected but not found: "
990 f"{missing}"
991 )
993 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
Парсеры стараются «поступать правильно» и избегать хрупкого поведения. Вывод типов имеет большое значение. Если содержимое столбца можно привести к целочисленному типу без изменений, парсер сделает это. Все нечисловые столбцы будут иметь тип object, как и другие объекты 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
Обратите внимание: если указан аргумент header=None, парсер автоматически выбирает имена столбцов X.<номер столбца>. Кроме того, для идущих подряд столбцов можно указать только их ширину:
# 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 умеет определять файлы с разделителями (не обязательно запятыми), поскольку pandas использует класс 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 0x7f9452705a80>
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'), но при задании параметров, не поддерживаемых C, может перейти к Python.
В настоящее время движки C и pyarrow не поддерживают следующие параметры:
sep, если это не один символ (например, разделители-регулярные выражения)skipfootersep=Noneсdelim_whitespace=False
Указание любого из перечисленных выше параметров приведёт к ParserWarning, если явно не выбран движок Python с помощью engine='python'.
Движок pyarrow не поддерживает следующие параметры, не упомянутые в приведённом выше списке:
float_precisionchunksizecommentnrowsthousandsmemory_mapdialecton_bad_linesdelim_whitespacequotinglineterminatorconvertersdecimaliteratordayfirstinfer_datetime_formatverboseskipinitialspacelow_memory
Указание этих параметров с engine='pyarrow' вызовет ValueError.
Чтение/запись удалённых файлов
Для чтения или записи удалённых файлов во многих функциях ввода-вывода pandas можно передать URL. В следующем примере показано чтение 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_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 до 3lineterminator: последовательность символов, обозначающая конец строки (по умолчанию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, например объект StringIOcolumnsпо умолчанию None; столбцы для записиcol_spaceпо умолчанию None; минимальная ширина каждого столбца.na_repпо умолчаниюNaN; представление значения NAformattersпо умолчанию 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словарь вида {index -> [index]; columns -> [columns]; data -> [values]}
recordsсписок вида [{column -> value}; … ]
indexсловарь вида {index -> {column -> value}}
columnsсловарь вида {column -> {index -> value}}
valuesтолько массив значений
tableв соответствии со спецификацией JSON Table Schema
-
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, каждая запись будет записана в отдельной строке в формате 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}}'
Варианты orient
Существует несколько вариантов формата результирующего файла или строки 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 Table Schema, что позволяет сохранять метаданные, включая, помимо прочего, типы данных и имена индексов.
Примечание
Любой вариант orient, кодирующий данные в объект 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.jsontyp: тип восстанавливаемого объекта (series или frame); по умолчанию ‘frame’-
orient:- Series :
-
по умолчанию —
indexдопустимые значения: {
split,records,index}
- DataFrame
-
по умолчанию —
columnsдопустимые значения: {
split,records,index,columns,values,table}
Формат строки JSON
splitсловарь вида {index -> [index]; columns -> [columns]; data -> [values]}
recordsсписок вида [{column -> value} …]
indexсловарь вида {index -> {column -> value}}
columnsсловарь вида {column -> {index -> value}}
valuesтолько массив значений
tableв соответствии со спецификацией JSON Table Schema
dtype: если True, определять типы данных; если задан словарь «столбец — тип данных», использовать указанные типы; если заданоFalse, типы данных не определяются; по умолчанию True; применяется только к данным.convert_axes: логическое значение; попытаться преобразовать оси в соответствующие типы данных; по умолчаниюTrueconvert_dates: список столбцов, для которых нужно выполнить разбор дат; если заданоTrue, попытаться разобрать столбцы, похожие на даты; по умолчаниюTrue.keep_default_dates: логическое значение; по умолчаниюTrue. При разборе дат обрабатывать столбцы, которые по умолчанию считаются похожими на даты.precise_float: логическое значение; по умолчаниюFalse. Включает использование функции повышенной точности (strtod) при декодировании строк в значения double. По умолчанию (False) используется быстрая, но менее точная встроенная функциональность.date_unit: строка, единица измерения временной метки для определения при преобразовании дат. По умолчанию None. По умолчанию точность временной метки определяется автоматически; если это нежелательно, передайте одно из значений ‘s’, ‘ms’, ‘us’ или ‘ns’, чтобы задать точность временной метки в секундах, миллисекундах, микросекундах или наносекундах соответственно.lines: чтение файла, в котором каждая строка содержит один объект JSON.encoding: кодировка для декодирования байтов py3.chunksize: при использовании совместно сlines=Trueвозвращаетpandas.api.typing.JsonReader, который считывает поchunksizeстрок за одну итерацию.engine: либо"ujson"— встроенный парсер JSON, либо"pyarrow", который передаёт обработку функцииpyarrow.json.read_jsonиз pyarrow."pyarrow"доступен только при условииlines=True
Если JSON невозможно разобрать, парсер вызовет одно из исключений ValueError/TypeError/AssertionError.
Если при кодировании в JSON использовалось значение orient, отличное от значения по умолчанию, обязательно передайте здесь тот же параметр, чтобы декодирование дало корректные результаты. Обзор см. в разделе Варианты orient.
Преобразование данных
Значения по умолчанию для 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.при восстановлении столбцы bool будут преобразованы в
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.
Схема таблицы
Table Schema — это спецификация для описания табличных наборов данных в виде объекта JSON. JSON содержит информацию об именах полей, типах и других атрибутах. С помощью orient 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, если индекс (MultiIndex) уникален.
Второе поле, data, содержит сериализованные данные с ориентацией records. Индекс включается, а даты и время форматируются в соответствии с ISO 8601, как того требует спецификация Table Schema.
Полный список поддерживаемых типов приведён в спецификации Table Schema. В этой таблице показано соответствие типам pandas:
Тип pandas | Тип Table Schema |
|---|---|
int64 | integer |
float64 | number |
bool | boolean |
datetime64[ns] | datetime |
timedelta64[ns] | duration |
categorical | any |
object | str |
Несколько замечаний о создаваемой схеме таблицы:
Объект
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такое же и для MultiIndex, но в этом случае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'} -
Правила именования по умолчанию в общих чертах таковы:
Для series используется
object.name. Если он не задан, в качестве имени используетсяvaluesДля
DataFramesиспользуется строковое представление имени столбцаДля
Index(неMultiIndex) используетсяindex.name; если оно равно None, используетсяindex.Для
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 в созданную схему в соответствующий элемент fields добавляется дополнительный ключ extDtype. Этот ключ не входит в стандарт, но позволяет выполнять обратное преобразование JSON для расширенных типов (например, read_json(df.to_json(orient="table"), orient="table")).
Ключ extDtype содержит имя расширения. Если вы зарегистрировали ExtensionDtype надлежащим образом, pandas использует это имя для поиска в реестре и повторного преобразования сериализованных данных в пользовательский тип данных.
HTML
Чтение содержимого HTML
Предупреждение
Мы настоятельно рекомендуем прочитать приведённые ниже Особенности разбора HTML-таблиц, касающиеся проблем парсеров BeautifulSoup4/html5lib/lxml.
Функция read_html() верхнего уровня может принимать строку HTML, файл или URL и разбирать 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, поскольку большое количество функций, обращающихся к сети, замедляет сборку документации. Если вы заметили ошибку или неработающий пример, сообщите об этом на странице ошибок pandas на GitHub.
Чтение URL и поиск таблицы, содержащей определённый текст:
match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
Указание строки заголовка (по умолчанию для формирования индекса столбцов используются элементы <th> или <td>, расположенные внутри <thead>; если в <thead> содержится несколько строк, создаётся MultiIndex); если параметр указан, строка заголовка берётся из данных без разобранных элементов заголовка (элементов <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 позволяет назначить результирующей HTML-таблице классы CSS. Обратите внимание, что эти классы добавляются к существующему классу '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>&</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 [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 верхнего уровня read_html, связаны некоторые проблемы совместимости версий.
Проблемы с lxml
-
Преимущества
-
Недостатки
lxml не гарантирует результаты разбора, если только ему не передана строго корректная разметка.
Учитывая сказанное выше, мы позволяем вам, пользователям, использовать бэкенд lxml, но этот бэкенд использует 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() верхнего уровня может принимать строку XML, файл или URL и разбирать узлы и атрибуты в объект 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, содержащих журналы по биомедицине и наукам о жизни:
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 ElementTree в Python. Одно из её мощных средств — возможность выборочно или условно запрашивать узлы с помощью более выразительного 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-документ может содержать пространство имён по умолчанию без префикса. Если не назначить временный префикс, узлы не будут найдены и возникнет исключение 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-файле. С помощью процессора XSLT он позволяет преобразовывать исходные XML-документы в другие XML-документы, HTML и даже текст (CSV, JSON и т. д.).
Рассмотрим, например, несколько вложенную структуру поездок на чикагской надземной железной дороге «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() поддерживает разбор таких файлов с помощью iterparse из lxml и iterparse из etree. Эти эффективные с точки зрения использования памяти методы позволяют перебирать дерево XML и извлекать определённые элементы и атрибуты, не загружая всё дерево в память.
Добавлено в версии 1.5.0.
Чтобы использовать эту функцию, необходимо передать в read_xml путь к локальному 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>
Запись сочетания элементов и атрибутов:
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) с помощью модуля Python openpyxl. Файлы 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.
Примечание
Атрибут sheet_names объекта ExcelFile предоставляет доступ к списку листов.
Аргумент
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 для чтения всех листов. Листы можно указать по индексу или имени, используя соответственно целое число или строку.
Чтение MultiIndex
read_excel может читать индекс MultiIndex, если передать список столбцов в index_col, и столбец MultiIndex, если передать список строк в header. Если в index или columns сериализованы имена уровней, их тоже можно прочитать, указав строки или столбцы, соответствующие этим уровням.
Например, чтобы прочитать индекс MultiIndex без имен:
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
Если исходный файл содержит индекс MultiIndex и столбцы, списки, задающие каждый из них, следует передать в 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())
Разбор дат
Значения типа datetime обычно автоматически преобразуются в соответствующий тип данных при чтении файла Excel. Но если у вас есть столбец строк, которые похожи на даты (но фактически не отформатированы как даты в Excel), можно использовать ключевой аргумент parse_dates для преобразования этих строк в значения datetime:
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 двумя способами:
с помощью именованного аргумента
engineпо расширению имени файла (согласно значению по умолчанию, заданному в параметрах конфигурации)
По умолчанию pandas использует XlsxWriter для .xlsx и openpyxl для .xlsm. Если установлено несколько движков, движок по умолчанию можно задать, установив параметры конфигурации io.excel.xlsx.writer и io.excel.xls.writer. Если Xlsxwriter недоступен, pandas использует openpyxl для файлов .xlsx.
Чтобы указать нужный движок записи, можно передать именованный аргумент 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")
Стили и форматирование
Внешний вид листов Excel, созданных с помощью pandas, можно изменить, указав следующие параметры для метода to_excel объекта DataFrame.
float_format: строка формата для чисел с плавающей точкой (по умолчаниюNone).freeze_panes: кортеж из двух целых чисел, задающих нижнюю строку и крайний правый столбец, которые нужно закрепить. Оба параметра отсчитываются от единицы, поэтому (1, 1) закрепит первую строку и первый столбец (по умолчаниюNone).
Движок Xlsxwriter предоставляет множество возможностей для управления форматом листа Excel, созданного методом to_excel. Отличные примеры приведены в документации Xlsxwriter: https://xlsxwriter.readthedocs.io/working_with_pandas.html
Электронные таблицы OpenDocument
Методы ввода-вывода для файлов 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 в файлах и вместо них возвращает числа с плавающей точкой (если необходимо распознавать типы datetime, можно использовать calamine).
# 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
Как видно, мы получили те же данные, которые ранее записали в буфер обмена.
Примечание
Для использования этих методов в Linux может потребоваться установить xclip или xsel (с PyQt5, PyQt4 или qtpy).
Сериализация pickle
Все объекты 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 можно использовать для загрузки из файла любого сериализованного объекта 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
Предупреждение
Загрузка сериализованных данных из ненадежных источников может быть небезопасной.
Предупреждение
Обратная совместимость 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
Поддержка msgpack в pandas удалена в версии 1.0.0. Вместо нее рекомендуется использовать pickle.
Для передачи объектов pandas по сети также можно использовать формат сериализации Arrow IPC. Документацию по pyarrow см. здесь.
HDF5 (PyTables)
HDFStore — это объект, похожий на словарь, который читает и записывает 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
Закрытие Store и использование менеджера контекста:
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. К таким хранилищам нельзя добавлять данные после записи (хотя их можно просто удалить и перезаписать). Кроме того, по ним нельзя выполнять запросы: их необходимо извлекать целиком. Они также не поддерживают DataFrame с неуникальными именами столбцов. Хранилища формата 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:457, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
452 raise ValueError(
453 "key must be provided when HDF5 "
454 "file contains multiple datasets."
455 )
456 key = candidate_only_group._v_pathname
--> 457 return store.select(
458 key,
459 where=where,
460 start=start,
461 stop=stop,
462 columns=columns,
463 iterator=iterator,
464 chunksize=chunksize,
465 auto_close=auto_close,
466 )
467 except (ValueError, TypeError, LookupError):
468 if not isinstance(path_or_buf, HDFStore):
469 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:911, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
897 # create the iterator
898 it = TableIterator(
899 self,
900 s,
(...)
908 auto_close=auto_close,
909 )
--> 911 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2034, in TableIterator.get_result(self, coordinates)
2031 where = self.where
2033 # directly return the result
-> 2034 results = self.func(self.start, self.stop, where)
2035 self.close()
2036 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:895, in HDFStore.select.<locals>.func(_start, _stop, _where)
894 def func(_start, _stop, _where):
--> 895 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3295, in BlockManagerFixed.read(self, where, columns, start, stop)
3287 def read(
3288 self,
3289 where=None,
(...)
3293 ) -> DataFrame:
3294 # start, stop applied to rows, so 0th axis only
-> 3295 self.validate_read(columns, where)
3296 select_axis = self.obj_type()._get_block_manager_axis(0)
3298 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:2927, in GenericFixed.validate_read(self, columns, where)
2922 raise TypeError(
2923 "cannot pass a column specification when reading "
2924 "a Fixed format store. this store must be selected in its entirety"
2925 )
2926 if where is not None:
-> 2927 raise TypeError(
2928 "cannot pass a where specification when reading "
2929 "from a Fixed format store. this store must be selected in its entirety"
2930 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
Табличный формат
HDFStore поддерживает ещё один формат хранения на диске — формат PyTables, называемый форматом 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:618, in HDFStore.__getattr__(self, name)
616 """allow attribute access to get stores"""
617 try:
--> 618 return self.get(name)
619 except (KeyError, ClosedFileError):
620 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:818, in HDFStore.get(self, key)
816 if group is None:
817 raise KeyError(f"No object named {key} in the file")
--> 818 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1883, in HDFStore._read_group(self, group)
1882 def _read_group(self, group: Node):
-> 1883 s = self._create_storer(group)
1884 s.infer_axes()
1885 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1757, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1755 tt = "generic_table"
1756 else:
-> 1757 raise TypeError(
1758 "cannot create a storer if the object is not existing "
1759 "nor a value are passed"
1760 )
1761 else:
1762 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
Хранение типов данных
Хранение смешанных типов в таблице
Хранение данных разных типов поддерживается. Строки сохраняются с фиксированной шириной, равной максимальному размеру добавленного столбца. Последующая попытка добавить более длинные строки вызовет ValueError.
Передача min_itemsize={`values`: size} в качестве параметра при добавлении задаст большее минимальное значение для строковых столбцов. В настоящее время поддерживается хранение floats,
strings, ints, bools, datetime64. Для строковых столбцов передача nan_rep = 'nan' при добавлении изменит представление 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}
Хранение DataFrame с 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, если в выражении запроса есть ссылка на неизвестную переменную. Обычно это означает, что вы пытаетесь выбрать данные по столбцу, который не является data_column.
select вызовет SyntaxError, если выражение запроса недопустимо.
Запросы к timedelta64[ns]
Можно сохранять данные типа timedelta64[ns] и выполнять запросы к ним. Элементы задаются в формате <float>(<unit>), где число с плавающей точкой может быть знаковым (и дробным), а единица измерения для timedelta может быть D,s,ms,us,ns. Пример:
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 равны None, уровни автоматически становятся доступными через ключевое слово 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 несколько снижает производительность, поэтому пользователь должен сам выбрать нужные столбцы. Кроме того, после первой операции добавления или записи изменить столбцы данных (и индексируемые элементы) нельзя. (Разумеется, можно просто прочитать данные и создать новую таблицу!)
Итератор
В select и select_as_multiple можно передать iterator=True или chunksize=number_in_a_chunk, чтобы получить итератор по результатам. По умолчанию возвращается 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. Рассмотрим простой пример. Вы храните данные типа panel, даты находятся в 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 Это компрессор по умолчанию для
bloscblosc: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. Это означает, что следующие типы должны работать:
Тип | Представление отсутствующих значений |
|---|---|
с плавающей точкой : |
|
целое число : | |
логический | |
|
|
|
|
категориальный : см. раздел ниже | |
object : |
|
Столбцы unicode не поддерживаются, и их обработка ЗАВЕРШИТСЯ ОШИБКОЙ.
Категориальные данные
Можно записывать данные, содержащие типы category, в HDFStore. Запросы работают так же, как если бы это был массив объектов. Однако данные типа 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. Это повысит производительность чтения и записи.В таблицы можно записывать повторяющиеся строки, но при выборке они отфильтровываются (выбираются последние элементы; таким образом, таблица уникальна по парам major и minor).
Будет вызвано исключение
PerformanceWarning, если вы попытаетесь сохранить типы, которые PyTables будет сериализовать с помощью pickle (вместо хранения в виде встроенных типов). Дополнительную информацию и несколько решений см. здесь.
Feather
Feather обеспечивает двоичную столбцовую сериализацию фреймов данных. Формат разработан для эффективного чтения и записи фреймов данных и упрощения обмена данными между языками анализа данных.
Feather предназначен для точной сериализации и десериализации DataFrame и поддерживает все типы данных 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сохраняет расширенные типы данных, такие как nullable integer и string (требуется 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=int8, ordere...
i dictionary<values=string, indices=int8, ordere...
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, отклоняют файл, поскольку такого столбца нет в целевой таблице.
Чтобы не записывать индексы фрейма данных, передайте 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 не сохраняются при преобразовании фрейма данных в файлы 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.
Если драйвер ADBC недоступен или не поддерживает необходимые функции, установите SQLAlchemy вместе с библиотекой драйвера базы данных. Примеры таких драйверов: psycopg2 для PostgreSQL и pymysql для MySQL. Для SQLite он по умолчанию входит в стандартную библиотеку Python. Обзор поддерживаемых драйверов для каждого диалекта SQL можно найти в документации SQLAlchemy.
Если SQLAlchemy не установлен, вместо движка SQLAlchemy, подключения или строки URI можно использовать sqlite3.Connection.
Дополнительные стратегии описаны в некоторых примерах из сборника рецептов.
Основные функции:
| Читает таблицу базы данных SQL в DataFrame. |
| Читает результат SQL-запроса в DataFrame. |
| Читает результат SQL-запроса или таблицу базы данных в DataFrame. |
| Записывает записи из 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(), чтобы создать объект движка на основе URI базы данных. Движок нужно создать только один раз для каждой базы данных, к которой вы подключаетесь. Дополнительную информацию о create_engine() и формате URI см. в примерах ниже и в документации SQLAlchemy.
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)
Предупреждение
При открытии подключения к базе данных вы также отвечаете за его закрытие. Если оставить подключение открытым, это может привести к блокировке базы данных или другим сбоям.
Запись DataFrame
Предположим, что следующие данные находятся в 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
В некоторых базах данных при записи больших DataFrame могут возникать ошибки из-за превышения ограничений на размер пакета. Этого можно избежать, задав параметр 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 |
| 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 |
|
Сноски
Если вы хотите максимально сохранить типы базы данных на протяжении всего жизненного цикла 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 на основе их dtype. Если столбцы имеют dtype 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.
Примечание
Столбцы с dtype category будут преобразованы в плотное представление, которое получается при использовании np.asarray(categorical) (например, для строковых категорий это будет массив строк). Поэтому при повторном чтении таблицы базы данных категориальный тип не создаётся.
Типы данных даты и времени
При использовании ADBC или SQLAlchemy функция to_sql() может записывать данные даты и времени как с часовым поясом, так и без него. Однако итоговый формат данных в базе зависит от поддерживаемого типа данных даты и времени в используемой системе баз данных.
В следующей таблице перечислены поддерживаемые типы данных даты и времени для нескольких распространённых баз данных. В других диалектах баз данных могут использоваться иные типы данных даты и времени.
База данных | Типы даты и времени SQL | Поддержка часовых поясов |
|---|---|---|
SQLite |
| Нет |
MySQL |
| Нет |
PostgreSQL |
| Да |
При записи данных с часовым поясом в базы данных, которые не поддерживают часовые пояса, данные будут записаны как временные метки без часового пояса, соответствующие местному времени в этом часовом поясе.
Функция read_sql_table() также может считывать данные даты и времени как с часовым поясом, так и без него. При чтении типов TIMESTAMP WITH TIME ZONE pandas преобразует данные в UTC.
Способ вставки
Параметр method управляет используемой инструкцией SQL для вставки. Возможные значения:
None: используется стандартная инструкция SQLINSERT(по одной на строку).'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 определяет dtype столбцов по результатам запроса, а не по типам данных в физической схеме базы данных. Например, предположим, что userid — целочисленный столбец таблицы. Тогда, как и следовало ожидать, select userid ... вернёт серию с целочисленными значениями, а select cast(userid as text) ... — серию со значениями типа object (str). Соответственно, если результат запроса пуст, все полученные столбцы будут иметь значения типа object (поскольку этот тип наиболее общий). Если запрос иногда может возвращать пустой результат, после этого можно явно привести типы, чтобы обеспечить их корректность.
Также можно указать имя столбца в качестве индекса 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")
Выполнение запросов
В функции read_sql_query() можно выполнять запросы с помощью обычного SQL. В этом случае необходимо использовать вариант 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 поддерживают ограниченный набор типов данных; в файлах .dta можно хранить только строки длиной не более 244 символов, int8, int16, int32, float32 и float64. Кроме того, 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. Попытка записать файлы dta Stata со строками длиннее 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")
Извлечение из файла SPSS подмножества столбцов, содержащихся в usecols, без преобразования категориальных столбцов в pd.Categorical:
df = pd.read_spss(
"spss_data.sav",
usecols=["foo", "bar"],
convert_categoricals=False,
)
Дополнительные сведения о форматах файлов SAV и ZSAV доступны здесь.
Другие форматы файлов
pandas самостоятельно поддерживает ввод-вывод лишь для ограниченного набора форматов файлов, которые легко соответствуют его табличной модели данных. Для чтения и записи файлов других форматов в pandas и из pandas мы рекомендуем следующие пакеты, разработанные широким сообществом.
netCDF
xarray предоставляет структуры данных, вдохновлённые DataFrame pandas, для работы с многомерными наборами данных, уделяя особое внимание формату файлов netCDF и удобному преобразованию данных в 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–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/io.html
Комментарии и пустые строки
Игнорирование комментариев в строках и пустых строк
Если указан параметр
comment, строки, полностью состоящие из комментариев, будут игнорироваться. По умолчанию также игнорируются полностью пустые строки.Если
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использует номера строк файла (включая строки с комментариями и пустые строки):Если указаны оба параметра —
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