Инструменты ввода-вывода (текст, CSV, HDF5, …)
API pandas для ввода-вывода — это набор функций верхнего уровня reader, к которым можно обратиться, как к pandas.read_csv(), которые обычно возвращают объект pandas. Соответствующие функции writer — это методы объектов, к которым обращаются, как к DataFrame.to_csv(). Ниже приведена таблица доступных readers и writers.
Тип формата | Описание данных | Чтение | Запись |
|---|
текст | |||
текст | Файл с фиксированной шириной | ||
текст | |||
текст | |||
текст | |||
текст | |||
текст | Буфер обмена | ||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные | |||
двоичные данные |
SQL | |||
SQL |
Здесь представлено сравнение производительности некоторых методов ввода-вывода.
Примечание
Для примеров, использующих класс StringIO, убедитесь, что вы импортировали его с помощью from io import StringIO для Python 3.
CSV & текстовые файлы
Функция по умолчанию для чтения текстовых файлов (также известных как плоские файлы) — это read_csv(). См. справочник для продвинутых стратегий.
Параметры парсинга
read_csv() принимает следующие распространённые аргументы:
Основные
- filepath_or_buffer:разные
-
Путь к файлу (строка, путь, или объект с методом
read()), URL (включая http, ftp и S3), или любой объект с методомread()(например, открытый файл илиStringIO). - sep:строка, по умолчанию ',' для read_csv(), \t для read_table()
-
Разделитель. Если sep —
None, C-движок не может автоматически определить разделитель, но Python-движок парсинга может, поэтому будет использоваться последний, который автоматически определит разделитель с помощью встроенного анализатора Python,csv.Sniffer. Кроме того, разделители длиной более 1 символа и отличные от'\s+'будут интерпретироваться как регулярные выражения и также будут форсировать использование Python-движка парсинга. Обратите внимание, что разделители-регулярные выражения могут игнорировать данные в кавычках. Пример регулярного выражения:'\\r\\t'. - delimiter:строка, по умолчанию None
-
Альтернативное имя аргумента для sep.
- delim_whitespace:булево, по умолчанию False
-
Указывает, будет ли использоваться пробел (например,
' 'или'\t') в качестве разделителя. Эквивалентно установкеsep='\s+'. Если этот параметр установлен вTrue, для параметраdelimiterничего не должно передаваться.
Расположение и имена столбцов и индексов
- header:int или список целых чисел, по умолчанию 'infer'
-
Номер(а) строки(ок) для использования в качестве имён столбцов и начала данных. По умолчанию имена столбцов определяются автоматически: если имена не переданы, поведение идентично
header=0, и имена столбцов определяются из первой строки файла; если имена столбцов явно переданы, поведение идентичноheader=None. Явно передайтеheader=0, чтобы заменить существующие имена.Значение header может быть списком целых чисел, определяющих расположение строк для создания Многоуровневого индекса по столбцам, например,
[0,1,3]. Промежуточные строки, которые не указаны, будут пропущены (например, 2 в этом примере пропущена). Обратите внимание, что этот параметр игнорирует прокомментированные строки и пустые строки, еслиskip_blank_lines=True, поэтому header=0 обозначает первую строку данных, а не первую строку файла. - names:последовательность, по умолчанию None
-
Список имён столбцов для использования. Если файл не содержит строки заголовка, то вы должны явно передать
header=None. Повторения в этом списке недопустимы. - index_col:int, str, последовательность int / str или False, необязательно, по умолчанию None
-
Столбец(ы) для использования в качестве меток строк
DataFrame, заданный в виде имени строки или индекса столбца. Если задана последовательность int / str, используется Многоуровневый индекс.Примечание
index_col=Falseможет быть использован для принудительного отказа pandas от использования первого столбца в качестве индекса, например, при наличии неправильно сформированного файла с разделителями в конце каждой строки.Значение по умолчанию
Noneуказывает pandas на попытку определения автоматически. Если количество полей в строке заголовка столбцов равно количеству полей в теле файла данных, то используется индекс по умолчанию. Если оно больше, то первые столбцы используются в качестве индекса, так что оставшееся количество полей в теле равно количеству полей в заголовке.Первая строка после заголовка используется для определения количества столбцов, которые будут включены в индекс. Если последующие строки содержат меньше столбцов, чем первая, они заполняются
NaN.Этого можно избежать с помощью
usecols. Это гарантирует, что столбцы берутся как есть, а хвостовые данные игнорируются. - usecols:последовательность или вызываемый объект, по умолчанию None
-
Возвращает подмножество столбцов. Если последовательность, все элементы должны быть либо позиционными (т. е. целочисленными индексами столбцов документа), либо строками, соответствующими именам столбцов, предоставленными пользователем в
namesили определёнными из строки(ок) заголовка документа. Еслиnamesзаданы, строки(ки) заголовка документа не учитываются. Например, допустимым параметром последовательностиusecolsбыло бы[0, 1, 2]или['foo', 'bar', 'baz'].Порядок элементов игнорируется, поэтому
usecols=[0, 1]равносильно[1, 0]. Для создания DataFrame изdataс сохранением порядка элементов используйтеpd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]для столбцов в порядке['foo', 'bar']илиpd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]для порядка['bar', 'foo'].Если это вызываемый объект, функция вызываемого объекта будет оцениваться по именам столбцов, возвращая имена, где вызываемый объект возвращает True:
In [1]: import pandas as pd In [2]: from io import StringIO In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [4]: pd.read_csv(StringIO(data)) Out[4]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"]) Out[5]: col1 col3 0 a 1 1 a 2 2 c 3
Использование этого параметра приводит к значительно более быстрому парсингу и меньшей потребности в памяти при использовании движка c. Движок Python сначала загружает данные, прежде чем решить, какие столбцы нужно удалить.
Общие параметры разбора
- dtype:Имя типа или словарь столбец -> тип, по умолчанию None
-
Тип данных для данных или столбцов. Например,
{'a': np.float64, 'b': np.int32, 'c': 'Int64'}Используйтеstrилиobjectвместе с соответствующими параметрамиna_values, чтобы сохранить и не интерпретировать dtype. Если указаны преобразователи, они будут применяться ВМЕСТО преобразования dtype.Добавлено в версии 1.5.0: Поддержка defaultdict была добавлена. Укажите defaultdict в качестве входных данных, где значение по умолчанию определяет тип данных столбцов, которые не указаны явно.
- dtype_backend:{“numpy_nullable”, “pyarrow”}, по умолчанию DataFrame с поддержкой NumPy
-
Какой dtype_backend использовать, например, должен ли DataFrame иметь массивы NumPy, nullable dtypes используются для всех типов данных, которые имеют реализацию nullable, когда установлен “numpy_nullable”, pyarrow используется для всех типов данных, если установлен “pyarrow”.
dtype_backends всё ещё находятся в стадии разработки.
Добавлено в версии 2.0.
- engine:{'c', 'python', 'pyarrow'}
-
Используемый движок парсера. Движки C и pyarrow быстрее, а движок python в настоящее время более функционален. Многопоточность в настоящее время поддерживается только движком pyarrow.
Добавлено в версии 1.4.0: Движок “pyarrow” был добавлен как экспериментальный движок, и некоторые функции не поддерживаются или могут не работать правильно с этим движком.
- converters:словарь, по умолчанию None
-
Словарь функций для преобразования значений в определённых столбцах. Ключи могут быть целыми числами или метками столбцов.
- true_values:список, по умолчанию None
-
Значения, которые следует рассматривать как
True. - false_values:список, по умолчанию None
-
Значения, которые следует рассматривать как
False. - skipinitialspace:булево значение, по умолчанию False
-
Пропустить пробелы после разделителя.
- skiprows:подобный списку или целое число, по умолчанию None
-
Номера строк для пропуска (с индексом 0) или количество строк для пропуска (int) в начале файла.
Если это вызываемая функция, вызываемая функция будет оцениваться по индексам строк, возвращая True, если строка должна быть пропущена, и False в противном случае:
In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [7]: pd.read_csv(StringIO(data)) Out[7]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0) Out[8]: col1 col2 col3 0 a b 2
- skipfooter:целое число, по умолчанию 0
-
Количество строк в нижней части файла, которые нужно пропустить (не поддерживается с engine=’c’).
- nrows:целое число, по умолчанию None
-
Количество строк файла для чтения. Полезно для чтения фрагментов больших файлов.
- low_memory:булево значение, по умолчанию True
-
Внутренне обрабатывать файл частями, что приводит к меньшему использованию памяти при разборе, но, возможно, к смешанному определению типов. Чтобы гарантировать отсутствие смешанных типов, установите
Falseили укажите тип с параметромdtype. Обратите внимание, что весь файл читается в единыйDataFrame, используйте параметрыchunksizeилиiterator, чтобы возвращать данные частями. (Только допустимо с парсером C) - memory_map:булево значение, по умолчанию False
-
Если для
filepath_or_bufferпредоставлен путь к файлу, отобразить объект файла непосредственно в памяти и получить доступ к данным напрямую из него. Использование этого параметра может повысить производительность, поскольку больше нет необходимости в операциях ввода-вывода.
Обработка значений NA и пропусков данных
- na_values:скаляр, str, список или словарь, по умолчанию None
-
Дополнительные строки, распознаваемые как NA/NaN. Если передан словарь, значения NA специфичны для каждого столбца. Смотрите значения NA const ниже для списка значений, интерпретируемых как NaN по умолчанию.
- keep_default_na:логический тип, по умолчанию True
-
Включать или не включать значения NaN по умолчанию при парсинге данных. В зависимости от того, передается ли
na_values, поведение следующее:Если
keep_default_naравенTrue, иna_valuesуказаны, тоna_valuesдобавляется к значениям NaN по умолчанию, используемым для парсинга.Если
keep_default_naравенTrue, иna_valuesне указаны, для парсинга используются только значения NaN по умолчанию.Если
keep_default_naравенFalse, иna_valuesуказаны, для парсинга используются только значения NaN, указанныеna_values.Если
keep_default_naравенFalse, иna_valuesне указаны, ни одна строка не будет распознаваться как NaN.
Обратите внимание, что если
na_filterпередается какFalse, параметрыkeep_default_naиna_valuesбудут проигнорированы. - na_filter:логический тип, по умолчанию True
-
Обнаружение маркеров пропущенных значений (пустые строки и значения na_values). В данных без значений NA передача
na_filter=Falseможет улучшить производительность чтения большого файла. - verbose:логический тип, по умолчанию False
-
Указывает количество значений NA, помещенных в нечисленные столбцы.
- skip_blank_lines:логический тип, по умолчанию True
-
Если
True, пропускайте пустые строки вместо интерпретации как NaN.
Обработка дат и времени
- parse_dates:логический тип или список целых чисел или имен или список списков или словарь, по умолчанию False.
-
Если
True-> попытаться разобрать индекс.Если
[1, 2, 3]-> попытаться разобрать столбцы 1, 2, 3 как отдельные столбцы дат.Если
[[1, 3]]-> объединить столбцы 1 и 3 и разобрать как один столбец дат.Если
{'foo': [1, 3]}-> разобрать столбцы 1, 3 как даты и назвать результат ‘foo’.
Примечание
Существует быстрый путь для дат в формате iso8601.
- infer_datetime_format:логический тип, по умолчанию False
-
Если
Trueи parse_dates включены для столбца, попробуйте определить формат даты и времени, чтобы ускорить обработку.Устарело начиная с версии 2.0.0: Жесткая версия этого аргумента теперь по умолчанию, передача его не оказывает никакого эффекта.
- keep_date_col:логический тип, по умолчанию False
-
Если
Trueи parse_dates определяют объединение нескольких столбцов, сохранить исходные столбцы. - date_parser:функция, по умолчанию None
-
Функция для преобразования последовательности столбцов строк в массив экземпляров дат и времени. По умолчанию используется
dateutil.parser.parserдля преобразования. pandas попытается вызвать date_parser тремя различными способами, переходя к следующему, если произойдет исключение: 1) Передать один или несколько массивов (как определено parse_dates) в качестве аргументов; 2) конкатенировать (по строкам) строковые значения из столбцов, определенных parse_dates, в один массив и передать его; и 3) вызвать date_parser один раз для каждой строки, используя одну или несколько строк (соответствующих столбцам, определенным parse_dates) в качестве аргументов.Устарело начиная с версии 2.0.0: Используйте
date_formatвместо этого или читайте какobject, а затем применяйтеto_datetime()по мере необходимости. - date_format:строка или словарь столбец -> формат, по умолчанию None
-
Если используется совместно с
parse_dates, даты будут анализироваться в соответствии с этим форматом. Для чего-либо более сложного, пожалуйста, прочитайте какobject, а затем применитеto_datetime()по мере необходимости.Новое в версии 2.0.0.
- dayfirst:логический тип, по умолчанию False
-
Даты формата ДД/ММ, международный и европейский формат.
- cache_dates:логический тип, по умолчанию True
-
Если True, используйте кэш уникальных, преобразованных дат для применения преобразования даты и времени. Может значительно ускорить парсинг дублирующихся строковых представлений дат, особенно с часовыми поясами.
Итерация
- iterator:boolean, default False
-
Возвращает объект
TextFileReaderдля итерации или получения фрагментов с помощьюget_chunk(). - chunksize:int, default None
-
Возвращает объект
TextFileReaderдля итерации. См. итерацию и фрагментацию ниже.
Правила цитирования, сжатие и формат файла
- compression:{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict}, default 'infer'
-
Для динамического распаковки данных на диске. Если ‘infer’, то используйте gzip, bz2, zip, xz или zstandard, если
filepath_or_bufferявляется путём, заканчивающимся на ‘.gz’, ‘.bz2’, ‘.zip’, ‘.xz’, ‘.zst’ соответственно, и не распаковывайте иначе. Если используется ‘zip’, ZIP-файл должен содержать только один файл данных для чтения. Установите значениеNoneдля отключения распаковки. Также может быть словарь с ключом'method', установленным в одно из значений {'zip','gzip','bz2','zstd'} и другие пары «ключ-значение» передаются вzipfile.ZipFile,gzip.GzipFile,bz2.BZ2Fileилиzstandard.ZstdDecompressor. В качестве примера, можно передать следующее для более быстрого сжатия и создания воспроизводимого gzip архива:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}.Изменено в версии 1.2.0: Предыдущие версии передавали записи словаря для ‘gzip’ в
gzip.open. - thousands:str, default None
-
Разделитель тысяч.
- decimal:str, default '.'
-
Символ, распознаваемый как десятичная точка. Например, используйте
','для европейских данных. - float_precision:string, default None
-
Определяет, какой преобразователь C-движок должен использовать для значений с плавающей точкой. Доступные варианты:
Noneдля обычного преобразователя,highдля преобразователя высокой точности иround_tripдля преобразователя обратного преобразования. - lineterminator:str (length 1), default None
-
Символ для разделения файла на строки. Действителен только с C-парсером.
- quotechar:str (length 1)
-
Символ, используемый для обозначения начала и конца цитируемого элемента. Цитируемые элементы могут содержать разделитель, и он будет проигнорирован.
- quoting:int or csv.QUOTE_* instance, default 0
-
Управление поведением цитирования полей в соответствии со значениями
csv.QUOTE_*. Используйте одно из значенийQUOTE_MINIMAL(0),QUOTE_ALL(1),QUOTE_NONNUMERIC(2) илиQUOTE_NONE(3). - doublequote:boolean, default True
-
Когда указано
quotecharиquotingнеQUOTE_NONE, указывает, интерпретировать ли два последовательных элементаquotecharвнутри поля как один элементquotechar. - escapechar:str (length 1), default None
-
Односимвольная строка, используемая для экранирования разделителя, когда цитирование равно
QUOTE_NONE. - comment:str, default None
-
Указывает, что остальная часть строки не должна анализироваться. Если он находится в начале строки, строка будет полностью проигнорирована. Этот параметр должен быть одиночным символом. Как и пустые строки (пока
skip_blank_lines=True), полностью прокомментированные строки игнорируются параметромheader, но неskiprows. Например, еслиcomment='#', парсинг ‘#empty\na,b,c\n1,2,3’ сheader=0даст в результате ‘a,b,c’, которая будет обработана как заголовок. - encoding:str, default None
-
Кодировка для использования для UTF при чтении/записи (например,
'utf-8'). Список стандартных кодировок Python. - dialect:str or csv.Dialect instance, default None
-
Если предоставлен, этот параметр переопределит значения (по умолчанию или нет) для следующих параметров:
delimiter,doublequote,escapechar,skipinitialspace,quotecharиquoting. Если необходимо переопределить значения, будет выведено предупреждение ParserWarning. См. документациюcsv.Dialectдля получения более подробной информации.
Обработка ошибок
- on_bad_lines:(‘error’, ‘warn’, ‘skip’), default ‘error’
-
Указывает, что делать при обнаружении плохой строки (строки с чрезмерным количеством полей). Допустимые значения:
‘error’, генерировать исключение ParserError при обнаружении плохой строки.
‘warn’, выводить предупреждение при обнаружении плохой строки и пропускать эту строку.
‘skip’, пропускать плохие строки без генерации исключений или предупреждений.
Добавлен в версии 1.3.0.
Указание типов данных столбцов
Вы можете указать тип данных для всего DataFrame или отдельных столбцов:
In [9]: import numpy as np
In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"
In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11
In [12]: df = pd.read_csv(StringIO(data), dtype=object)
In [13]: df
Out[13]:
a b c d
0 1 2 3 4
1 5 6 7 8
2 9 10 11 NaN
In [14]: df["a"][0]
Out[14]: '1'
In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})
In [16]: df.dtypes
Out[16]:
a int64
b object
c float64
d Int64
dtype: object
К счастью, pandas предлагает более одного способа гарантировать, что ваши столбцы содержат только один dtype. Если вы не знакомы с этими понятиями, вы можете узнать больше о типах данных здесь, а о преобразованиях типов в pandas здесь.
Например, вы можете использовать аргумент converters функции read_csv():
In [17]: data = "col_1\n1\n2\n'A'\n4.22"
In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})
In [19]: df
Out[19]:
col_1
0 1
1 2
2 'A'
3 4.22
In [20]: df["col_1"].apply(type).value_counts()
Out[20]:
col_1
<class 'str'> 4
Name: count, dtype: int64
Или вы можете использовать функцию to_numeric() для принудительного преобразования типов после чтения данных,
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
что преобразует все успешно прочитанные значения в числа с плавающей точкой, а некорректно прочитанные оставит как NaN.
В конечном счете, то, как вы будете обрабатывать чтение столбцов со смешанными типами данных, зависит от ваших конкретных потребностей. В приведенном выше примере, если вы хотите NaN аномалии в данных, то функция to_numeric() — вероятно, лучший вариант. Однако, если вы хотите, чтобы все данные были преобразованы, независимо от типа, то использование аргумента converters функции read_csv() определенно стоит попробовать.
Примечание
В некоторых случаях чтение некорректных данных со столбцами смешанных типов данных приведет к несогласованному набору данных. Если вы полагаетесь на pandas для определения типов данных ваших столбцов, движок для анализа будет определять типы данных для различных фрагментов данных, а не для всего набора данных сразу. В результате вы можете получить столбец(ы) со смешанными типами данных. Например,
In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))
In [26]: df = pd.DataFrame({"col_1": col_1})
In [27]: df.to_csv("foo.csv")
In [28]: mixed_df = pd.read_csv("foo.csv")
In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]:
col_1
<class 'int'> 737858
<class 'str'> 262144
Name: count, dtype: int64
In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')
приведет к тому, что mixed_df будет содержать тип данных int для некоторых фрагментов столбца и str для других из-за смешанных типов данных в прочитанных данных. Важно отметить, что весь столбец будет помечен типом dtype object, который используется для столбцов со смешанными типами данных.
Установление dtype_backend="numpy_nullable" приведет к применению nullable типов данных для каждого столбца.
In [31]: data = """a,b,c,d,e,f,g,h,i,j
....: 1,2.5,True,a,,,,,12-31-2019,
....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
....: """
....:
In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])
In [33]: df
Out[33]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 2019-12-31 <NA>
1 3 4.5 False b 6 7.5 True a 2019-12-31 <NA>
In [34]: df.dtypes
Out[34]:
a Int64
b Float64
c boolean
d string[python]
e Int64
f Float64
g boolean
h string[python]
i datetime64[ns]
j Int64
dtype: object
Определение типа данных категорий
Categorical столбцы могут быть распарсены непосредственно, указав dtype='category' или dtype=CategoricalDtype(categories, ordered).
In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"
In [36]: pd.read_csv(StringIO(data))
Out[36]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]:
col1 object
col2 object
col3 int64
dtype: object
In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]:
col1 category
col2 category
col3 category
dtype: object
Отдельные столбцы могут быть распарсены как Categorical с помощью спецификации в виде словаря:
In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]:
col1 category
col2 object
col3 int64
dtype: object
Указание dtype='category' приведет к неупорядоченному Categorical, чьи categories — это уникальные значения, наблюдаемые в данных. Для большего контроля над категориями и порядком создайте CategoricalDtype заранее и передайте его для dtype данного столбца.
In [40]: from pandas.api.types import CategoricalDtype
In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)
In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]:
col1 category
col2 object
col3 int64
dtype: object
При использовании dtype=CategoricalDtype, «неожиданные» значения за пределами dtype.categories обрабатываются как пропущенные значения.
In [43]: dtype = CategoricalDtype(["a", "b", "d"]) # No 'c'
In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]:
0 a
1 a
2 NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']
Это соответствует поведению Categorical.set_categories().
Примечание
С dtype='category' полученные категории всегда будут распарсены как строки (тип данных object). Если категории числовые, их можно преобразовать с помощью функции to_numeric(), или, как уместно, с помощью другого преобразователя, например, to_datetime().
Когда dtype — это CategoricalDtype с однородными categories (все числовые, все даты и т. д.), преобразование выполняется автоматически.
In [45]: df = pd.read_csv(StringIO(data), dtype="category")
In [46]: df.dtypes
Out[46]:
col1 category
col2 category
col3 category
dtype: object
In [47]: df["col3"]
Out[47]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
Наименование и использование столбцов
Обработка названий столбцов
Файл может или не может содержать строку заголовка. pandas предполагает, что первая строка должна использоваться в качестве названий столбцов:
In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [53]: pd.read_csv(StringIO(data))
Out[53]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
Указав аргумент names в сочетании с header, можно указать другие имена для использования и указать, следует ли удалять строку заголовка (если она есть):
In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]:
foo bar baz
0 1 2 3
1 4 5 6
2 7 8 9
In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]:
foo bar baz
0 a b c
1 1 2 3
2 4 5 6
3 7 8 9
Если заголовок находится в строке, отличной от первой, передайте номер строки в header. Это пропустит предшествующие строки:
In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"
In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
Примечание
По умолчанию имена столбцов вычисляются: если имена не переданы, поведение идентично header=0, и имена столбцов вычисляются из первой непустой строки файла. Если имена столбцов явно переданы, поведение идентично header=None.
Обработка дублирующихся имён
Если файл или заголовок содержат дублирующиеся имена, pandas по умолчанию будет различать их, чтобы предотвратить перезапись данных:
In [59]: data = "a,b,a\n0,1,2\n3,4,5"
In [60]: pd.read_csv(StringIO(data))
Out[60]:
a b a.1
0 0 1 2
1 3 4 5
Больше нет дублирующихся данных, поскольку дублирующиеся столбцы «X», …, «X» становятся «X», «X.1», …, «X.N».
Фильтрация столбцов (usecols)
Аргумент usecols позволяет вам выбрать любой подмножество столбцов в файле, используя имена столбцов, номера позиций или вызываемую функцию:
In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"
In [62]: pd.read_csv(StringIO(data))
Out[62]:
a b c d
0 1 2 3 foo
1 4 5 6 bar
2 7 8 9 baz
In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]:
b d
0 2 foo
1 5 bar
2 8 baz
In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]:
a c d
0 1 3 foo
1 4 6 bar
2 7 9 baz
In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]:
a c
0 1 3
1 4 6
2 7 9
Аргумент usecols также можно использовать для указания столбцов, которые не следует использовать в конечном результате:
In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]:
b d
0 2 foo
1 5 bar
2 8 baz
В этом случае вызываемая функция указывает, что мы исключаем столбцы «a» и «c» из результата.
Работа с данными Unicode
Аргумент encoding следует использовать для закодированных данных Unicode, что приведет к декодированию байтовых строк в Unicode в результате:
In [86]: from io import BytesIO
In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"
In [88]: data = data.decode("utf8").encode("latin-1")
In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")
In [90]: df
Out[90]:
word length
0 Träumen 7
1 Grüße 5
In [91]: df["word"][1]
Out[91]: 'Grüße'
Некоторые форматы, которые кодируют все символы как несколько байтов, например, UTF-16, не будут правильно анализироваться без указания кодировки. Полный список стандартных кодировок Python.
Индексные столбцы и заключительные разделители
Если в файле есть один или больше столбцов данных, чем количество имен столбцов, первый столбец будет использоваться в качестве имён строк DataFrame:
In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [93]: pd.read_csv(StringIO(data))
Out[93]:
a b c
4 apple bat 5.7
8 orange cow 10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]:
a b c
index
4 apple bat 5.7
8 orange cow 10.0
Обычно этого поведения можно достичь, используя параметр index_col.
Есть некоторые исключительные случаи, когда файл подготовлен с разделителями в конце каждой строки данных, что вводит в заблуждение парсер. Чтобы явно отключить вывод индексного столбца и отбросить последний столбец, передайте index_col=False:
In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [98]: pd.read_csv(StringIO(data))
Out[98]:
a b c
4 apple bat NaN
8 orange cow NaN
In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]:
a b c
0 4 apple bat
1 8 orange cow
Если анализируется подмножество данных с помощью параметра usecols, то спецификация index_col основана на этом подмножестве, а не на исходных данных.
In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]:
b c
4 bat NaN
8 cow NaN
In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]:
b c
4 bat NaN
8 cow NaN
Обработка дат
Указание столбцов дат
Для лучшей работы с данными типа datetime, read_csv() использует ключевые аргументы parse_dates и date_format, что позволяет пользователям указывать различные столбцы и форматы дат/времени для преобразования входных текстовых данных в объекты datetime.
Простейший случай — просто передать parse_dates=True:
In [104]: with open("foo.csv", mode="w") as f:
.....: f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
.....:
# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)
In [106]: df
Out[106]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)
Часто требуется хранить данные даты и времени отдельно или хранить различные поля дат отдельно. Ключевой аргумент parse_dates можно использовать для указания комбинации столбцов для извлечения дат и/или времени.
Вы можете указать список списков столбцов для parse_dates, результирующие столбцы дат будут добавлены в начало вывода (чтобы не повлиять на существующий порядок столбцов), а новые имена столбцов будут являться конкатенацией имён компонентных столбцов:
In [108]: data = (
.....: "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
.....: "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
.....: "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
.....: "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
.....: "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
.....: "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
.....: )
.....:
In [109]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])
In [111]: df
Out[111]:
1_2 1_3 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
По умолчанию парсер удаляет компонентные столбцы дат, но вы можете сохранить их с помощью ключевого аргумента keep_date_col:
In [112]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
.....: )
.....:
In [113]: df
Out[113]:
1_2 1_3 0 ... 2 3 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD ... 19:00:00 18:56:00 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD ... 20:00:00 19:56:00 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD ... 21:00:00 20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD ... 21:00:00 21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD ... 22:00:00 21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD ... 23:00:00 22:56:00 -0.59
[6 rows x 7 columns]
Обратите внимание, что если вы хотите объединить несколько столбцов в один столбец дат, необходимо использовать вложенный список. Другими словами, parse_dates=[1, 2] указывает, что второй и третий столбцы должны быть обработаны как отдельные столбцы дат, в то время как parse_dates=[[1, 2]] означает, что два столбца должны быть обработаны в один столбец.
Вы также можете использовать словарь для указания пользовательских имён столбцов:
In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)
In [116]: df
Out[116]:
nominal actual 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
Важно помнить, что если несколько текстовых столбцов должны быть обработаны в один столбец дат, то новый столбец добавляется в начало данных. Указание index_col основано на этом новом наборе столбцов, а не на исходных столбцах данных:
In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [118]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=date_spec, index_col=0
.....: ) # index is the nominal column
.....:
In [119]: df
Out[119]:
actual 0 4
nominal
1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
Примечание
Если столбец или индекс содержит необрабатываемую дату, весь столбец или индекс будут возвращены неизменёнными как тип данных object. Для нестандартного разбора datetime используйте to_datetime() после pd.read_csv.
Примечание
read_csv имеет быстрый путь для разбора строк datetime в формате iso8601, например “2000-01-01T00:01:02+00:00” и аналогичные вариации. Если вы сможете организовать хранение datetime в этом формате, время загрузки значительно ускорится, наблюдалось примерно в ~20 раз.
Устарело начиная с версии 2.2.0: Объединение столбцов дат внутри read_csv устарело. Используйте pd.to_datetime для соответствующих столбцов результатов вместо этого.
Функции разбора дат
Наконец, парсер позволяет указать пользовательскую date_format. С точки зрения производительности, вы должны попробовать эти методы разбора дат в таком порядке:
Если вы знаете формат, используйте
date_format, например:date_format="%d/%m/%Y"илиdate_format={column_name: "%d/%m/%Y"}.Если у вас разные форматы для разных столбцов или вы хотите передать дополнительные параметры (например,
utc) вto_datetime, то вы должны считать свои данные какobjectтип данных, а затем использоватьto_datetime.
Разбор CSV с смешанными часовыми поясами
pandas не может напрямую представить столбец или индекс со смешанными часовыми поясами. Если ваш CSV-файл содержит столбцы со смешанными часовыми поясами, результат по умолчанию будет столбцом object-dtype со строками, даже с parse_dates. Чтобы обработать значения со смешанными часовыми поясами как столбец datetime, считайте их как object тип данных, а затем вызовите to_datetime() с utc=True.
In [120]: content = """\
.....: a
.....: 2000-01-01T00:00:00+05:00
.....: 2000-01-01T00:00:00+06:00"""
.....:
In [121]: df = pd.read_csv(StringIO(content))
In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [123]: df["a"]
Out[123]:
0 1999-12-31 19:00:00+00:00
1 1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]
Определение формата даты и времени
Вот несколько примеров строк с датой и временем, которые можно угадать (все представляют 30 декабря 2011 года в 00:00:00):
“20111230”
“2011/12/30”
“20111230 00:00:00”
“12/30/2011 00:00:00”
“30/Dec/2011 00:00:00”
“30/Декабря/2011 00:00:00”
Обратите внимание, что определение формата чувствительно к dayfirst. С dayfirst=True он предположит, что “01/12/2011” — это 1 декабря. С dayfirst=False (по умолчанию) он предположит, что “01/12/2011” — это 12 января.
Если вы пытаетесь разобрать столбец строк с датами, pandas попытается угадать формат из первого элемента, отличного от NaN, а затем разобрать оставшуюся часть столбца с этим форматом. Если pandas не сможет угадать формат (например, если ваша первая строка — '01 December US/Pacific 2000'), будет выведено предупреждение, и каждая строка будет разобрана индивидуально с помощью dateutil.parser.parse. Наиболее безопасный способ разбора дат — явно задать format=.
In [124]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [125]: df
Out[125]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
В случае, если в одном столбце смешаны форматы дат и времени, вы можете передать format='mixed'
In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")
In [127]: df = pd.read_csv(data)
In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')
In [129]: df
Out[129]:
date
0 2000-01-12
1 2000-01-13
или, если все ваши форматы дат и времени являются ISO8601 (возможно, не идентично отформатированными):
In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")
In [131]: df = pd.read_csv(data)
In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')
In [133]: df
Out[133]:
date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00
Международные форматы дат
Хотя форматы дат в США имеют тенденцию быть MM/DD/YYYY, во многих международных форматах используется DD/MM/YYYY. Для удобства предоставляется ключевое слово dayfirst:
In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"
In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [136]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
Запись CSV в объекты бинарных файлов
Новое в версии 1.2.0.
df.to_csv(..., mode="wb") позволяет записывать CSV в объект файла, открытого в двоичном режиме. В большинстве случаев нет необходимости указывать mode, так как Pandas автоматически определит, открыт ли объект файла в текстовом или двоичном режиме.
In [139]: import io
In [140]: data = pd.DataFrame([0, 1, 2])
In [141]: buffer = io.BytesIO()
In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")
Указание метода для преобразования чисел с плавающей точкой
Параметр float_precision может быть указан для использования определенного преобразователя чисел с плавающей точкой во время разбора с помощью C движка. Варианты — обычный преобразователь, преобразователь высокой точности и преобразователь обратного преобразования (который гарантирует обратное преобразование значений после записи в файл). Например:
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"
In [144]: data = "a,b,c\n1,2,{0}".format(val)
In [145]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision=None,
.....: )["c"][0] - float(val)
.....: )
.....:
Out[145]: 5.551115123125783e-17
In [146]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision="high",
.....: )["c"][0] - float(val)
.....: )
.....:
Out[146]: 5.551115123125783e-17
In [147]: abs(
.....: pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
.....: - float(val)
.....: )
.....:
Out[147]: 0.0
Разделители тысяч
Для больших чисел, которые были записаны с разделителями тысяч, вы можете установить ключевое слово thousands в строку длиной 1, чтобы целые числа были обработаны правильно:
По умолчанию числа с разделителями тысяч будут обработаны как строки:
In [148]: data = (
.....: "ID|level|category\n"
.....: "Patient1|123,000|x\n"
.....: "Patient2|23,000|y\n"
.....: "Patient3|1,234,018|z"
.....: )
.....:
In [149]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [150]: df = pd.read_csv("tmp.csv", sep="|")
In [151]: df
Out[151]:
ID level category
0 Patient1 123,000 x
1 Patient2 23,000 y
2 Patient3 1,234,018 z
In [152]: df.level.dtype
Out[152]: dtype('O')
Ключевое слово thousands позволяет корректно обрабатывать целые числа:
In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")
In [154]: df
Out[154]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
In [155]: df.level.dtype
Out[155]: dtype('int64')
Значения NA
Для управления значениями, которые обрабатываются как пропущенные значения (обозначенные NaN), укажите строку в na_values. Если вы укажете список строк, то все значения в нём будут считаться пропущенными значениями. Если вы укажете число (целое число, например float, как 5.0 или вещественное число, как integer, как 5), соответствующие эквивалентные значения также будут означать пропущенное значение (в этом случае эффективно [5.0, 5] распознаются как NaN).
Чтобы полностью переопределить значения по умолчанию, которые распознаются как пропущенные, укажите keep_default_na=False.
Рассмотрим несколько примеров:
pd.read_csv("path_to_file.csv", na_values=[5])
В примере выше 5 и 5.0 будут распознаваться как NaN, в дополнение к значениям по умолчанию. Строка сначала интерпретируется как числовое 5, а затем как NaN.
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])
Выше только пустое поле будет распознаваться как NaN.
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])
Выше и NA, и 0 как строки будут NaN.
pd.read_csv("path_to_file.csv", na_values=["Nope"])
Значения по умолчанию, помимо строки "Nope" распознаются как NaN.
Бесконечность
inf подобные значения будут распарсованы как np.inf (положительная бесконечность), а -inf как -np.inf (отрицательная бесконечность). Это игнорирует регистр значения, означая, что Inf также будет распарсовано как np.inf.
Булевы значения
Общие значения True, False, TRUE и FALSE распознаются как булевы. Иногда может потребоваться распознать другие значения как булевы. Для этого используйте опции true_values и false_values следующим образом:
In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"
In [157]: print(data)
a,b,c
1,Yes,2
3,No,4
In [158]: pd.read_csv(StringIO(data))
Out[158]:
a b c
0 1 Yes 2
1 3 No 4
In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]:
a b c
0 1 True 2
1 3 False 4
Обработка «плохих» строк
В некоторых файлах могут встречаться строки с неправильным форматом, содержащие слишком мало или слишком много полей. Строки с недостаточным количеством полей будут заполнены значениями NA в хвостовых полях. Строки с избыточным количеством полей по умолчанию вызовут ошибку:
In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
623 return parser
625 with parser:
--> 626 return parser.read(nrows)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
1916 nrows = validate_integer("nrows", nrows)
1917 try:
1918 # error: "ParserBase" has no attribute "read"
1919 (
1920 index,
1921 columns,
1922 col_dict,
-> 1923 ) = self._engine.read( # type: ignore[attr-defined]
1924 nrows
1925 )
1926 except Exception:
1927 self.close()
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
232 try:
233 if self.low_memory:
--> 234 chunks = self._reader.read_low_memory(nrows)
235 # destructive to chunks
236 data = _concatenate_chunks(chunks)
File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()
File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()
File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()
File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()
File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()
ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
Можно выбрать пропуск плохих строк:
In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]:
a b c
0 1 2 3
1 8 9 10
Новая функция в версии 1.4.0.
Или передать вызываемую функцию для обработки плохой строки, если engine="python". Плохая строка будет списком строк, разделённых sep:
In [164]: external_list = []
In [165]: def bad_lines_func(line):
.....: external_list.append(line)
.....: return line[-3:]
.....:
In [166]: external_list
Out[166]: []
Примечание
Вызываемая функция будет обрабатывать только строки с избыточным количеством полей. Плохие строки, вызванные другими ошибками, будут пропущены без сообщений об ошибках.
In [167]: bad_lines_func = lambda line: print(line)
In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'
In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'
In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]:
name type
0 name a a is of type a
В этом случае строка не была обработана, так как «плохая строка» вызвана символом экранирования.
Также можно использовать параметр usecols для удаления лишних данных столбцов, которые появляются в некоторых строках, но не в других:
In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
617 _validate_names(kwds.get("names", None))
619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
622 if chunksize or iterator:
623 return parser
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
1617 self.options["has_index_names"] = kwds["has_index_names"]
1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
1895 raise ValueError(msg)
1897 try:
-> 1898 return mapping[engine](f, **self.options)
1899 except Exception:
1900 if self.handles is not None:
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
152 # error: Cannot determine type of 'names'
153 if len(self.names) < len(usecols): # type: ignore[has-type]
154 # error: Cannot determine type of 'names'
--> 155 self._validate_usecols_names(
156 usecols,
157 self.names, # type: ignore[has-type]
158 )
160 # error: Cannot determine type of 'names'
161 self._validate_parse_dates_presence(self.names) # type: ignore[has-type]
File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
977 missing = [c for c in usecols if c not in names]
978 if len(missing) > 0:
--> 979 raise ValueError(
980 f"Usecols do not match columns, columns expected but not found: "
981 f"{missing}"
982 )
984 return usecols
ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]
В случае необходимости сохранить все данные, включая строки с избыточным количеством полей, можно указать достаточное количество names. Это гарантирует, что строки с недостаточным количеством полей будут заполнены NaN.
In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]:
a b c d
0 name type NaN NaN
1 name a a is of type a NaN NaN
2 name b b is of type b" NaN NaN
Диалект
Ключевое слово dialect обеспечивает большую гибкость в указании формата файла. По умолчанию используется диалект Excel, но можно указать либо имя диалекта, либо экземпляр csv.Dialect.
Предположим, у вас есть данные с незакрытыми кавычками:
In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"
In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f
По умолчанию read_csv использует диалект Excel и рассматривает двойную кавычку как символ кавычек, что приводит к ошибке при обнаружении новой строки перед закрывающей двойной кавычкой.
Можно обойти эту проблему, используя dialect:
In [175]: import csv
In [176]: dia = csv.excel()
In [177]: dia.quoting = csv.QUOTE_NONE
In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]:
label1 label2 label3
index1 "a c e
index2 b d f
Все параметры диалекта можно указать отдельно в качестве ключевых аргументов:
In [179]: data = "a,b,c~1,2,3~4,5,6"
In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]:
a b c
0 1 2 3
1 4 5 6
Ещё одним распространённым параметром диалекта является skipinitialspace для пропуска пробелов после разделителя:
In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"
In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6
In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]:
a b c
0 1 2 3
1 4 5 6
Парсеры прилагают все усилия, чтобы «сделать всё правильно» и не быть хрупкими. Вывод типов — очень важная деталь. Если столбец может быть приведен к целочисленному типу без изменения содержимого, парсер это сделает. Любые нечисловые столбцы будут передаваться как тип «объект» как и в других объектах pandas.
Кавычки и символы экранирования
Кавычки (и другие символы экранирования) во вложенных полях могут обрабатываться различными способами. Один из способов — использовать обратные слэши; для правильной обработки этих данных необходимо указать параметр escapechar:
In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'
In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5
In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]:
a b
0 hello, "Bob", nice to see you 5
Файлы с фиксированной шириной столбцов
В то время как read_csv() считывает данные с разделителями, функция read_fwf() работает с файлами данных, у которых известна и фиксирована ширина столбцов. Параметры функции для read_fwf в основном такие же, как и для read_csv, с двумя дополнительными параметрами и другим использованием параметра delimiter:
colspecs: Список пар (кортежей), задающих пределы фиксированных по ширине полей каждой строки как полуоткрытых интервалов (т.е. [от, до[ ). Строковое значение ‘infer’ может быть использовано для инструкции парсеру попытаться определить спецификации столбцов из первых 100 строк данных. По умолчанию, если не указано иное, происходит вычисление.widths: Список ширины полей, которые могут быть использованы вместо ‘colspecs’, если интервалы являются непрерывными.delimiter: Символы, которые следует рассматривать как символы-заполнители в файле с фиксированной шириной. Могут быть использованы для указания символа-заполнителя полей, если он не является пробелами (например, ‘~’).
Рассмотрим типичный файл данных с фиксированной шириной:
In [187]: data1 = (
.....: "id8141 360.242940 149.910199 11950.7\n"
.....: "id1594 444.953632 166.985655 11788.4\n"
.....: "id1849 364.136849 183.628767 11806.2\n"
.....: "id1230 413.836124 184.375703 11916.8\n"
.....: "id1948 502.953953 173.237159 12468.3"
.....: )
.....:
In [188]: with open("bar.csv", "w") as f:
.....: f.write(data1)
.....:
Для того, чтобы проанализировать этот файл в DataFrame, нам просто нужно предоставить спецификации столбцов функции read_fwf вместе с именем файла:
# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)
In [191]: df
Out[191]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
Обратите внимание, как парсер автоматически выбирает имена столбцов X.<номер столбца>, когда указан аргумент header=None. В качестве альтернативы, вы можете указать только ширину столбцов для непрерывных столбцов:
# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]
In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)
In [194]: df
Out[194]:
0 1 2 3
0 id8141 360.242940 149.910199 11950.7
1 id1594 444.953632 166.985655 11788.4
2 id1849 364.136849 183.628767 11806.2
3 id1230 413.836124 184.375703 11916.8
4 id1948 502.953953 173.237159 12468.3
Парсер будет обрабатывать дополнительные пробелы вокруг столбцов, поэтому наличие дополнительных разделителей между столбцами в файле допустимо.
По умолчанию read_fwf попытается определить colspecs файла, используя первые 100 строк файла. Это возможно только в тех случаях, когда столбцы выровнены и правильно разделены указанным delimiter (по умолчанию разделитель — пробел).
In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)
In [196]: df
Out[196]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
read_fwf поддерживает параметр dtype для указания типов анализируемых столбцов, отличных от вычисленных.
In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]:
1 float64
2 float64
3 float64
dtype: object
In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]:
0 object
1 float64
2 object
3 float64
dtype: object
Индексы
Файлы с «неявным» столбцом индекса
Рассмотрим файл с на одной записью меньше в заголовке, чем количество столбцов данных:
In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"
In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5
In [201]: with open("foo.csv", "w") as f:
.....: f.write(data)
.....:
В этом специальном случае, read_csv предполагает, что первый столбец должен использоваться в качестве индекса DataFrame:
In [202]: pd.read_csv("foo.csv")
Out[202]:
A B C
20090101 a 1 2
20090102 b 3 4
20090103 c 4 5
Обратите внимание, что даты не были автоматически проанализированы. В этом случае вам нужно сделать так же, как и раньше:
In [203]: df = pd.read_csv("foo.csv", parse_dates=True)
In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)
Чтение индекса с MultiIndex
Предположим, у вас есть данные, индексированные по двум столбцам:
In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'
In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
In [207]: with open("mindex_ex.csv", mode="w") as f:
.....: f.write(data)
.....:
Аргумент index_col для read_csv может принимать список номеров столбцов, чтобы преобразовать несколько столбцов в MultiIndex для индекса возвращаемого объекта:
In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])
In [209]: df
Out[209]:
zit xit
year indiv
1977 A 1.2 0.6
B 1.5 0.5
In [210]: df.loc[1977]
Out[210]:
zit xit
indiv
A 1.2 0.6
B 1.5 0.5
Чтение столбцов с MultiIndex
Указав список расположений строк для аргумента header, вы можете считать MultiIndex для столбцов. Указание несмежных строк пропустит промежуточные строки.
In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))
In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))
In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)
In [214]: df.to_csv("mi.csv")
In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0
In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]:
c 1 2
d a b
a Unnamed: 2_level_2 Unnamed: 3_level_2
1 1.0 1.0
2 b 1.0 1.0
3 c 1.0 1.0
4 d 1.0 1.0
read_csv также может интерпретировать более распространенный формат индексов с несколькими столбцами.
In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"
In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12
In [219]: with open("mi2.csv", "w") as fh:
.....: fh.write(data)
.....:
In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]:
a b c
q r s t u v
one 1 2 3 4 5 6
two 7 8 9 10 11 12
Примечание
Если index_col не указан (например, у вас нет индекса или вы его создали с помощью df.to_csv(..., index=False)), то какие-либо names в индексе столбцов будут утрачены.
Автоматическое определение разделителя
read_csv может определять файлы с разделителями (не обязательно с запятыми), используя класс csv.Sniffer из модуля csv. Для этого необходимо указать sep=None.
In [221]: df = pd.DataFrame(np.random.randn(10, 4))
In [222]: df.to_csv("tmp2.csv", sep=":", index=False)
In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]:
0 1 2 3
0 0.469112 -0.282863 -1.509059 -1.135632
1 1.212112 -0.173215 0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929 1.071804
3 0.721555 -0.706771 -1.039575 0.271860
4 -0.424972 0.567020 0.276232 -1.087401
5 -0.673690 0.113648 -1.478427 0.524988
6 0.404705 0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312 0.844885
8 1.075770 -0.109050 1.643563 -1.469388
9 0.357021 -0.674600 -1.776904 -0.968914
Чтение нескольких файлов для создания одного DataFrame
Для объединения нескольких файлов лучше использовать concat(). Пример см. в справочнике.
Итерация по файлам по частям
Предположим, вам нужно итерироваться по файлу (возможно, очень большому) лениво, а не загружать весь файл в память, например, так:
In [224]: df = pd.DataFrame(np.random.randn(10, 4))
In [225]: df.to_csv("tmp.csv", index=False)
In [226]: table = pd.read_csv("tmp.csv")
In [227]: table
Out[227]:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
Указав chunksize для read_csv, значением возврата будет итерируемый объект типа TextFileReader:
In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
.....: print(reader)
.....: for chunk in reader:
.....: print(chunk)
.....:
<pandas.io.parsers.readers.TextFileReader object at 0x7ff2e5421db0>
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
0 1 2 3
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
0 1 2 3
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
Изменено в версии 1.2: read_csv/json/sas возвращает менеджер контекста при итерации по файлу.
Указание iterator=True также вернёт TextFileReader объект:
In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
.....: print(reader.get_chunk(5))
.....:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
Указание движка анализатора
Pandas поддерживает три движка: C, Python и экспериментальный pyarrow (требует пакет pyarrow). В целом, pyarrow-движок быстрее при больших объёмах данных и по скорости сопоставим с C-движком в большинстве других случаев. Python-движок, как правило, медленнее, чем pyarrow и C-движки в большинстве случаев. Тем не менее, pyarrow-движок значительно менее устойчив, чем C-движок, у которого отсутствуют некоторые функции по сравнению с Python-движком.
Возможно, pandas использует C-анализатор (указан как engine='c'), но может перейти к Python, если указаны не поддерживаемые C-опции.
В настоящее время не поддерживаются опции C- и pyarrow-движков:
sep, отличные от одного символа (например, разделители regex)skipfootersep=Noneсdelim_whitespace=False
Указание любой из вышеперечисленных опций приведёт к ParserWarning, если не выбран Python-движок явно, используя engine='python'.
Опции, которые не поддерживаются pyarrow-движком и не покрываются списком выше:
float_precisionchunksizecommentnrowsthousandsmemory_mapdialecton_bad_linesdelim_whitespacequotinglineterminatorconvertersdecimaliteratordayfirstinfer_datetime_formatverboseskipinitialspacelow_memory
Указание этих опций с engine='pyarrow' вызовет ValueError.
Чтение/запись удаленных файлов
Вы можете передать URL для чтения или записи удаленных файлов во многие функции ввода-вывода pandas — следующий пример демонстрирует чтение файла CSV:
df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")
Новое в версии 1.3.0.
Заголовок по умолчанию можно отправить вместе с HTTP(s)-запросами, передав словарь соответствий ключей и значений заголовков в качестве аргумента storage_options, как показано ниже:
headers = {"User-Agent": "pandas"}
df = pd.read_csv(
"https://download.bls.gov/pub/time.series/cu/cu.item",
sep="\t",
storage_options=headers
)
Все URL, которые не являются локальными файлами или HTTP(s), обрабатываются с помощью fsspec, если он установлен, и его различными реализациями файловых систем (включая Amazon S3, Google Cloud, SSH, FTP, webHDFS...). Некоторые из этих реализаций потребуют установки дополнительных пакетов, например, для URL S3 требуется библиотека s3fs:
df = pd.read_json("s3://pandas-test/adatafile.json")
При работе с удаленными системами хранения вам может потребоваться дополнительная настройка с переменными окружения или конфигурационными файлами в особых местах. Например, чтобы получить доступ к данным в вашем ведре S3, вам необходимо определить учетные данные одним из способов, перечисленных в документации S3Fs. То же самое касается нескольких систем хранения, и вы должны следовать ссылкам в fsimpl1 для реализаций, встроенных в fsspec, и fsimpl2 для тех, которые не включены в основное распределение fsspec.
Вы также можете передавать параметры непосредственно драйверу бэкенда. Поскольку fsspec не использует переменную окружения AWS_S3_HOST, мы можем напрямую определить словарь, содержащий endpoint_url, и передать объект в параметр storage:
storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)
Дополнительные примеры конфигураций и документации можно найти в документации S3Fs.
Если у вас нет учетных данных S3, вы по-прежнему можете получить доступ к общедоступным данным, указав анонимное подключение, например
Новое в версии 1.2.0.
pd.read_csv(
"s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
"-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"anon": True},
)
fsspec также позволяет использовать сложные URL-адреса для доступа к данным в сжатых архивах, локальному кешированию файлов и многим другим функциям. Чтобы кэшировать вышеприведенный пример локально, вы измените вызов на
pd.read_csv(
"simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
"SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"s3": {"anon": True}},
)
где мы указываем, что параметр «anon» предназначен для части «s3» реализации, а не для реализации кэширования. Обратите внимание, что кэширование выполняется в временной папке только на время сеанса, но вы также можете указать постоянное хранилище.
Выгрузка данных
Запись в формате CSV
Объекты Series и DataFrame имеют метод экземпляра to_csv, который позволяет сохранить содержимое объекта в виде файла с разделителями запятых. Функция принимает несколько аргументов. Только первый является обязательным.
path_or_buf: Строковый путь к файлу для записи или объект файла. Если объект файла, он должен быть открыт с помощьюnewline=''sep: Разделитель полей для выходного файла (по умолчанию “,”)na_rep: Строковое представление пропущенного значения (по умолчанию ‘’)float_format: Строковый формат для чисел с плавающей точкойcolumns: Столбцы для записи (по умолчанию None)header: Нужно ли записывать имена столбцов (по умолчанию True)index: Нужно ли записывать имена строк (индексы) (по умолчанию True)index_label: Метки столбцов для столбцов индекса, если необходимо. Если None (по умолчанию), иheaderиindexравны True, используются имена индекса. (Последовательность должна быть указана, еслиDataFrameиспользует MultiIndex).mode: Режим записи Python, по умолчанию ‘w’encoding: Строка, представляющая кодировку, которую следует использовать, если содержимое не ASCII, для версий Python до 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словарь, похожий на {индекс -> [индекс], столбцы -> [столбцы], данные -> [значения]}
recordsсписок, похожий на [{столбец -> значение}, … , {столбец -> значение}]
indexсловарь, похожий на {индекс -> {столбец -> значение}}
columnsсловарь, похожий на {столбец -> {индекс -> значение}}
valuesпросто массив значений
tableсоответствует схеме таблицы JSON
-
date_format: строка, тип преобразования даты, ‘epoch’ для временной метки, ‘iso’ для ISO8601.double_precision: количество десятичных знаков для кодирования чисел с плавающей точкой, по умолчанию 10.force_ascii: принудительно кодировать строку в ASCII, по умолчанию True.date_unit: единица измерения времени для кодирования, управляет точностью временных меток и ISO8601. Одно из значений ‘s’, ‘ms’, ‘us’ или ‘ns’ для секунд, миллисекунд, микросекунд и наносекунд соответственно. По умолчанию ‘ms’.default_handler: обработчик, вызываемый, если объект не может быть преобразован в подходящий для JSON формат. Принимает один аргумент, который является объектом для преобразования, и возвращает сериализуемый объект.lines: еслиrecordsorient, тогда каждый элемент записи будет записан в отдельной строке как json.mode: строка, режим записи при записи в путь. ‘w’ для записи, ‘a’ для добавления. По умолчанию ‘w’
Обратите внимание, что NaN, NaT и None будут преобразованы в null, а datetime объекты будут преобразованы на основе параметров date_format и date_unit.
In [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [231]: json = dfj.to_json()
In [232]: json
Out[232]: '{"A":{"0":-0.1213062281,"1":0.6957746499,"2":0.9597255933,"3":-0.6199759194,"4":-0.7323393705},"B":{"0":-0.0978826728,"1":0.3417343559,"2":-1.1103361029,"3":0.1497483186,"4":0.6877383895}}'
Параметры ориентирования
Существует ряд различных параметров для формата результирующего файла/строки JSON. Рассмотрим следующие DataFrame и Series:
In [233]: dfjo = pd.DataFrame(
.....: dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
.....: columns=list("ABC"),
.....: index=list("xyz"),
.....: )
.....:
In [234]: dfjo
Out[234]:
A B C
x 1 4 7
y 2 5 8
z 3 6 9
In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")
In [236]: sjo
Out[236]:
x 15
y 16
z 17
Name: D, dtype: int64
Ориентированный на столбцы (по умолчанию для DataFrame) сериализует данные как вложенные объекты JSON, с метками столбцов, выступающими в качестве основного индекса:
In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'
# Not available for Series
Ориентированный на индексы (по умолчанию для Series) аналогичен ориентированному на столбцы, но метки индекса теперь являются основными:
In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'
In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'
Ориентированный на записи сериализует данные в массив JSON записей «столбец —> значение», метки индекса не включаются. Это полезно для передачи DataFrame данных в библиотеки построения графиков, например, в библиотеку JavaScript d3.js:
In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'
In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'
Ориентированный на значения — это базовый параметр, который сериализует данные во вложенные массивы JSON только значений, метки столбцов и индексов не включаются:
In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'
# Not available for Series
Ориентированный на разделы сериализует данные в объект JSON, содержащий отдельные записи для значений, индекса и столбцов. Имя также включено для Series:
In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'
In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'
Ориентированный на таблицы сериализует данные в JSON Схема таблицы, позволяя сохранить метаданные, включая, но не ограничиваясь, типы данных и именами индексов.
Примечание
Любой параметр ориентирования, который кодируется в объект JSON, не сохранит порядок меток индекса и столбцов при сериализации туда и обратно. Если вы хотите сохранить порядок меток, используйте параметр split, так как он использует упорядоченные контейнеры.
Обработка дат
Запись в формате даты ISO:
In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [246]: dfd["date"] = pd.Timestamp("20130101")
In [247]: dfd = dfd.sort_index(axis=1, ascending=False)
In [248]: json = dfd.to_json(date_format="iso")
In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Запись в формате даты ISO с микросекундами:
In [250]: json = dfd.to_json(date_format="iso", date_unit="us")
In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Маркеры времени эпохи, в секундах:
In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")
In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Запись в файл с индексом даты и столбцом даты:
In [254]: dfj2 = dfj.copy()
In [255]: dfj2["date"] = pd.Timestamp("20130101")
In [256]: dfj2["ints"] = list(range(5))
In [257]: dfj2["bools"] = True
In [258]: dfj2.index = pd.date_range("20130101", periods=5)
In [259]: dfj2.to_json("test.json")
In [260]: with open("test.json") as fh:
.....: print(fh.read())
.....:
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}
Поведение по умолчанию
Если сериализатор JSON не может обработать содержимое контейнера напрямую, он будет действовать следующим образом:
если тип данных не поддерживается (например,
np.complex_), тоdefault_handler, если задано, будет вызываться для каждого значения, в противном случае генерируется исключение.-
если объект не поддерживается, он попытается сделать следующее:
проверить, определен ли для объекта метод
toDict, и вызвать его. МетодtoDictдолжен возвращатьdict, который затем будет сериализован в JSON.вызвать
default_handler, если он был предоставлен.преобразовать объект в
dict, пройдя по его содержимому. Однако это часто приводит к ошибкеOverflowErrorили к неожиданным результатам.
В общем случае, лучшим подходом для объектов или типов данных, которые не поддерживаются, является предоставление default_handler. Например:
>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json() # raises
RuntimeError: Unhandled numpy dtype 15
можно обработать, указав простой default_handler:
In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'
Чтение JSON
Чтение строки JSON в объект pandas может принимать несколько параметров. Парсер попытается разобрать DataFrame, если typ не задано или задано None. Чтобы явно принудительно выполнить Series парсинг, передайте typ=series
filepath_or_buffer: ВАЛИДНАЯ строка JSON или дескриптор файла / StringIO. Строка может быть URL. Допустимые схемы URL включают http, ftp, S3 и file. Для URL файлов ожидается хост. Например, локальный файл может быть file://localhost/path/to/table.jsontyp: тип объекта для извлечения (ряд или таблица), по умолчанию ‘frame’-
orient:- Ряд :
-
по умолчанию
indexразрешённые значения {
split,records,index}
- Таблица
-
по умолчанию
columnsразрешённые значения {
split,records,index,columns,values,table}
Формат строки JSON
splitпохожий на словарь {индекс -> [индекс], столбцы -> [столбцы], данные -> [значения]}
recordsсписок, похожий на [{столбец -> значение}, …, {столбец -> значение}]
indexпохожий на словарь {индекс -> {столбец -> значение}}
columnsпохожий на словарь {столбец -> {индекс -> значение}}
valuesтолько массив значений
tableсоблюдение схемы табличной схемы
dtype: если True, вывести типы, если словарь столбец-тип, использовать их, еслиFalse, то не выводить типы, по умолчанию True, применяется только к данным.convert_axes: boolean, попробовать преобразовать оси в правильные типы, по умолчаниюTrueconvert_dates: список столбцов для парсинга дат; ЕслиTrue, то попытаться разобрать столбцы, похожие на дату, по умолчаниюTrue.keep_default_dates: boolean, по умолчаниюTrue. При парсинге дат разобрать столбцы, похожие на дату, по умолчанию.precise_float: boolean, по умолчаниюFalse. Включить использование функции с большей точностью (strtod) при декодировании строк в двойные значения. По умолчанию (False) используется быстрая, но менее точная встроенная функциональность.date_unit: строка, единица измерения временной метки для определения преобразования дат. По умолчанию None. По умолчанию точность временной метки будет определяться, если этого не требуется, передайте одно из ‘s’, ‘ms’, ‘us’ или ‘ns’ для принудительного установления точности временной метки соответственно к секундам, миллисекундам, микросекундам или наносекундам.lines: считывает файл как один объект JSON на строку.encoding: кодировка для декодирования py3 байтов.chunksize: при использовании в сочетании сlines=True, возвращает итератор, который считываетpandas.api.typing.JsonReaderстрок за итерацию.engine: Либо"ujson", встроенный парсер JSON, либо"pyarrow", который перенаправляет на pyarrow’spyarrow.json.read_json."pyarrow"доступен только приlines=True
Парсер поднимет одно из ValueError/TypeError/AssertionError, если JSON не может быть проанализирован.
Если был использован не по умолчанию orient при кодировании в JSON, убедитесь, что тот же параметр передан здесь, чтобы декодирование дало осмысленные результаты. См. Параметры ориентации для обзора.
Преобразование данных
По умолчанию convert_axes=True, dtype=True и convert_dates=True будут пытаться разобрать оси и все данные в соответствующие типы, включая даты. Если вам нужно переопределить определённые типы данных, передайте словарь в dtype. convert_axes следует устанавливать в False только если вам нужно сохранить числовые значения в виде строк (например, ‘1’, ‘2’) в осях.
Примечание
Значения больших целых чисел могут быть преобразованы в даты, если convert_dates=True, и данные и/или имена столбцов имеют «похожий на дату» вид. Точный порог зависит от date_unit. «Похожий на дату» означает, что имя столбца соответствует одному из следующих критериев:
он оканчивается на
'_at'он оканчивается на
'_time'он начинается с
'timestamp'он является
'modified'он является
'date'
Предупреждение
При чтении данных JSON автоматическое приведение к типам данных имеет некоторые особенности:
индекс может быть воссоздан в другом порядке по сравнению с сериализацией, то есть порядок возвращаемых данных не гарантируется как одинаковый с порядком до сериализации
столбец, содержавший данные типа
float, будет преобразован вinteger, если это можно сделать безопасно, например, столбец типа1.столбцы булевых значений будут преобразованы в
integerпри восстановлении
Поэтому иногда вам может потребоваться указать конкретные типы данных через параметр dtype.
Чтение из JSON-строки:
In [262]: from io import StringIO
In [263]: pd.read_json(StringIO(json))
Out[263]:
date B A
0 1 0.403310 0.176444
1 1 0.301624 -0.154951
2 1 -1.369849 -2.179861
3 1 1.462696 -0.954208
4 1 -0.826591 -1.743161
Чтение из файла:
In [264]: pd.read_json("test.json")
Out[264]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356 0 True
2013-01-02 0.695775 0.341734 1356 1 True
2013-01-03 0.959726 -1.110336 1356 2 True
2013-01-04 -0.619976 0.149748 1356 3 True
2013-01-05 -0.732339 0.687738 1356 4 True
Не преобразовывать данные (но преобразовывать оси и даты):
In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]:
A object
B object
date object
ints object
bools object
dtype: object
Указать типы данных для преобразования:
In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]:
A float32
B float64
date int64
ints int64
bools int8
dtype: object
Сохранить строковые индексы:
In [267]: from io import StringIO
In [268]: si = pd.DataFrame(
.....: np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
.....: )
.....:
In [269]: si
Out[269]:
0 1 2 3
0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0
In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')
In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')
In [272]: json = si.to_json()
In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)
In [274]: sij
Out[274]:
0 1 2 3
0 0 0 0 0
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')
In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')
Даты, записанные в наносекундах, необходимо читать обратно в наносекундах:
In [277]: from io import StringIO
In [278]: json = dfj2.to_json(date_unit="ns")
# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")
In [280]: dfju
Out[280]:
A B date ints bools
1356998400000000000 -0.121306 -0.097883 1356998400 0 True
1357084800000000000 0.695775 0.341734 1356998400 1 True
1357171200000000000 0.959726 -1.110336 1356998400 2 True
1357257600000000000 -0.619976 0.149748 1356998400 3 True
1357344000000000000 -0.732339 0.687738 1356998400 4 True
# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))
In [282]: dfju
Out[282]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 2013-01-01 0 True
2013-01-02 0.695775 0.341734 2013-01-01 1 True
2013-01-03 0.959726 -1.110336 2013-01-01 2 True
2013-01-04 -0.619976 0.149748 2013-01-01 3 True
2013-01-05 -0.732339 0.687738 2013-01-01 4 True
# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")
In [284]: dfju
Out[284]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356998400 0 True
2013-01-02 0.695775 0.341734 1356998400 1 True
2013-01-03 0.959726 -1.110336 1356998400 2 True
2013-01-04 -0.619976 0.149748 1356998400 3 True
2013-01-05 -0.732339 0.687738 1356998400 4 True
Установив аргумент dtype_backend, можно управлять типом данных по умолчанию, используемым для результирующей DataFrame.
In [285]: data = (
.....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
.....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
.....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
.....: )
.....:
In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")
In [287]: df
Out[287]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 12-31-2019 None
1 3 4.5 False b 6 7.5 True a 12-31-2019 None
In [288]: df.dtypes
Out[288]:
a int64[pyarrow]
b double[pyarrow]
c bool[pyarrow]
d string[pyarrow]
e int64[pyarrow]
f double[pyarrow]
g bool[pyarrow]
h string[pyarrow]
i string[pyarrow]
j null[pyarrow]
dtype: object
Нормализация
pandas предоставляет вспомогательную функцию для преобразования словаря или списка словарей в плоскую таблицу.
In [289]: data = [
.....: {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
.....: {"name": {"given": "Mark", "family": "Regner"}},
.....: {"id": 2, "name": "Faye Raker"},
.....: ]
.....:
In [290]: pd.json_normalize(data)
Out[290]:
id name.first name.last name.given name.family name
0 1.0 Coleen Volk NaN NaN NaN
1 NaN NaN NaN Mark Regner NaN
2 2.0 NaN NaN NaN NaN Faye Raker
In [291]: data = [
.....: {
.....: "state": "Florida",
.....: "shortname": "FL",
.....: "info": {"governor": "Rick Scott"},
.....: "county": [
.....: {"name": "Dade", "population": 12345},
.....: {"name": "Broward", "population": 40000},
.....: {"name": "Palm Beach", "population": 60000},
.....: ],
.....: },
.....: {
.....: "state": "Ohio",
.....: "shortname": "OH",
.....: "info": {"governor": "John Kasich"},
.....: "county": [
.....: {"name": "Summit", "population": 1234},
.....: {"name": "Cuyahoga", "population": 1337},
.....: ],
.....: },
.....: ]
.....:
In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]:
name population state shortname info.governor
0 Dade 12345 Florida FL Rick Scott
1 Broward 40000 Florida FL Rick Scott
2 Palm Beach 60000 Florida FL Rick Scott
3 Summit 1234 Ohio OH John Kasich
4 Cuyahoga 1337 Ohio OH John Kasich
Параметр max_level обеспечивает больший контроль над уровнем, до которого должна производиться нормализация. При max_level=1 следующий фрагмент кода выполняет нормализацию до первого уровня вложенности переданного словаря.
In [293]: data = [
.....: {
.....: "CreatedBy": {"Name": "User001"},
.....: "Lookup": {
.....: "TextField": "Some text",
.....: "UserField": {"Id": "ID001", "Name": "Name001"},
.....: },
.....: "Image": {"a": "b"},
.....: }
.....: ]
.....:
In [294]: pd.json_normalize(data, max_level=1)
Out[294]:
CreatedBy.Name Lookup.TextField Lookup.UserField Image.a
0 User001 Some text {'Id': 'ID001', 'Name': 'Name001'} b
JSON, разделенный по строкам
pandas может читать и записывать файлы JSON, разделенные по строкам, которые часто используются в конвейерах обработки данных с Hadoop или Spark.
Для файлов JSON, разделенных по строкам, pandas также может вернуть итератор, который читает по chunksize строкам за раз. Это может быть полезно для больших файлов или для чтения из потока.
In [295]: from io import StringIO
In [296]: jsonl = """
.....: {"a": 1, "b": 2}
.....: {"a": 3, "b": 4}
.....: """
.....:
In [297]: df = pd.read_json(StringIO(jsonl), lines=True)
In [298]: df
Out[298]:
a b
0 1 2
1 3 4
In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'
# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
.....: reader
.....: for chunk in reader:
.....: print(chunk)
.....:
Empty DataFrame
Columns: []
Index: []
a b
0 1 2
a b
1 3 4
Файлы JSON, разделенные по строкам, также можно читать с помощью pyarrow-читателя, указав engine="pyarrow".
In [301]: from io import BytesIO
In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")
In [303]: df
Out[303]:
a b
0 1 2
1 3 4
Новое в версии 2.0.0.
Схема таблицы
Схема таблицы — это спецификация для описания табличных наборов данных как объекта JSON. JSON содержит информацию о названиях полей, типах и других атрибутах. Вы можете использовать ориентацию table для построения строки JSON с двумя полями, schema и data.
In [304]: df = pd.DataFrame(
.....: {
.....: "A": [1, 2, 3],
.....: "B": ["a", "b", "c"],
.....: "C": pd.date_range("2016-01-01", freq="d", periods=3),
.....: },
.....: index=pd.Index(range(3), name="idx"),
.....: )
.....:
In [305]: df
Out[305]:
A B C
idx
0 1 a 2016-01-01
1 2 b 2016-01-02
2 3 c 2016-01-03
In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'
Поле schema содержит ключ fields, который сам содержит список пар «имя столбца — тип», включая Index или MultiIndex (см. ниже список типов). Поле schema также содержит поле primaryKey, если (мульти)индекс уникален.
Второе поле, data, содержит сериализованные данные с ориентацией records. Индекс включён, а все даты и время отформатированы в формате ISO 8601, как требуется спецификацией Схемы таблицы.
Полный список поддерживаемых типов описан в спецификации Схемы таблицы. Эта таблица показывает сопоставление с типами pandas:
Тип pandas | Тип схемы таблицы |
|---|---|
int64 | целое число |
float64 | число |
bool | булево |
datetime64[ns] | дата и время |
timedelta64[ns] | продолжительность |
categorical | любой |
object | строка |
Несколько замечаний по сгенерированной схеме таблицы:
Объект
schemaсодержит полеpandas_version. Оно содержит версию диалекта схемы pandas и будет увеличиваться при каждой ревизии.-
Все даты преобразуются в UTC при сериализации. Даже значения с таймзоной без таймзоны, которые обрабатываются как UTC с смещением 0.
In [307]: from pandas.io.json import build_table_schema In [308]: s = pd.Series(pd.date_range("2016", periods=4)) In [309]: build_table_schema(s) Out[309]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'} -
Даты и время с таймзоной (до сериализации) включают дополнительное поле
tzс именем таймзоны (например,'US/Central').In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central")) In [311]: build_table_schema(s_tz) Out[311]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'} -
Периоды преобразуются в метки времени до сериализации, и поэтому имеют такое же поведение преобразования в UTC. Кроме того, периоды будут содержать дополнительное поле
freqс частотой периода, например,'A-DEC'.In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4)) In [313]: build_table_schema(s_per) Out[313]: {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'} -
Категориальные значения используют тип
anyи ограничениеenum, перечисляющее набор возможных значений. Кроме того, включено полеordered:In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"])) In [315]: build_table_schema(s_cat) Out[315]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'any', 'constraints': {'enum': ['a', 'b']}, 'ordered': False}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'} -
Поле
primaryKey, содержащее массив меток, включено если индекс уникален:In [316]: s_dupe = pd.Series([1, 2], index=[1, 1]) In [317]: build_table_schema(s_dupe) Out[317]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'pandas_version': '1.4.0'} -
Поведение
primaryKeyаналогично с MultiIndexes, но в этом случаеprimaryKey— это массив:In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)])) In [319]: build_table_schema(s_multi) Out[319]: {'fields': [{'name': 'level_0', 'type': 'string'}, {'name': 'level_1', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': FrozenList(['level_0', 'level_1']), 'pandas_version': '1.4.0'} -
Именование по умолчанию примерно соответствует следующим правилам:
Для рядов используется
object.name. Если он отсутствует, то используется имяvaluesДля
DataFramesиспользуется строковое представление имени столбцаДля
Index(неMultiIndex) используетсяindex.name, со значением по умолчаниюindex, если оно None.Для
MultiIndexиспользуетсяmi.names. Если у какого-либо уровня нет имени, то используетсяlevel_<i>.
read_json также принимает orient='table' в качестве аргумента. Это позволяет сохранять метаданные, такие как типы данных и имена индексов, с возможностью обратного преобразования.
In [320]: df = pd.DataFrame(
.....: {
.....: "foo": [1, 2, 3, 4],
.....: "bar": ["a", "b", "c", "d"],
.....: "baz": pd.date_range("2018-01-01", freq="d", periods=4),
.....: "qux": pd.Categorical(["a", "b", "c", "c"]),
.....: },
.....: index=pd.Index(range(4), name="idx"),
.....: )
.....:
In [321]: df
Out[321]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [322]: df.dtypes
Out[322]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
In [323]: df.to_json("test.json", orient="table")
In [324]: new_df = pd.read_json("test.json", orient="table")
In [325]: new_df
Out[325]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [326]: new_df.dtypes
Out[326]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
Обратите внимание, что строка «index» в качестве имени Index не поддерживает обратное преобразование, также как и любые имена, начинающиеся с 'level_' в MultiIndex. Они используются по умолчанию в DataFrame.to_json() для обозначения пропущенных значений, и последующее чтение не может различить намерение.
In [327]: df.index.name = "index"
In [328]: df.to_json("test.json", orient="table")
In [329]: new_df = pd.read_json("test.json", orient="table")
In [330]: print(new_df.index.name)
None
При использовании orient='table' вместе с определёнными пользователем ExtensionArray, сгенерированная схема будет содержать дополнительный ключ extDtype в соответствующем элементе fields. Этот дополнительный ключ не является стандартным, но позволяет выполнять обратные преобразования JSON для типов расширений (например, read_json(df.to_json(orient="table"), orient="table")).
Ключ extDtype содержит имя расширения, если вы правильно зарегистрировали ExtensionDtype, pandas будет использовать это имя для поиска в реестре и повторного преобразования сериализованных данных в ваш пользовательский тип данных.
HTML
Чтение содержимого HTML
Предупреждение
Мы настоятельно рекомендуем прочитать раздел Особенности разбора HTML-таблиц ниже, касающийся проблем с парсерами BeautifulSoup4/html5lib/lxml.
Функция верхнего уровня read_html() может принимать строку/файл/URL HTML и будет парсить HTML-таблицы в список pandas DataFrames. Давайте рассмотрим несколько примеров.
Примечание
read_html возвращает list объектов DataFrame, даже если в содержимом HTML содержится только одна таблица.
Чтение URL без опций:
In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[ Bank NameBank CityCity StateSt ... Acquiring InstitutionAI Closing DateClosing FundFund
0 Almena State Bank Almena KS ... Equity Bank October 23, 2020 10538
1 First City Bank of Florida Fort Walton Beach FL ... United Fidelity Bank, fsb October 16, 2020 10537
2 The First State Bank Barboursville WV ... MVB Bank, Inc. April 3, 2020 10536
3 Ericson State Bank Ericson NE ... Farmers and Merchants Bank February 14, 2020 10535
4 City National Bank of New Jersey Newark NJ ... Industrial Bank November 1, 2019 10534
.. ... ... ... ... ... ... ...
558 Superior Bank, FSB Hinsdale IL ... Superior Federal, FSB July 27, 2001 6004
559 Malta National Bank Malta OH ... North Valley Bank May 3, 2001 4648
560 First Alliance Bank & Trust Co. Manchester NH ... Southern New Hampshire Bank & Trust February 2, 2001 4647
561 National State Bank of Metropolis Metropolis IL ... Banterra Bank of Marion December 14, 2000 4646
562 Bank of Honolulu Honolulu HI ... Bank of the Orient October 13, 2000 4645
[563 rows x 7 columns]]
Примечание
Данные с указанного выше URL меняются каждую понедельник, поэтому полученные данные могут немного отличаться.
Чтение URL с передачей заголовков вместе с HTTP-запросом:
In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 Accept-Encoding: identity
1 Host: www.sump.org
2 User-Agent: Python-urllib/3.8
3 Connection: close]
In [324]: headers = {
In [325]: 'User-Agent':'Mozilla Firefox v14.0',
In [326]: 'Accept':'application/json',
In [327]: 'Connection':'keep-alive',
In [328]: 'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 User-Agent: Mozilla Firefox v14.0
1 AcceptEncoding: gzip, deflate, br
2 Accept: application/json
3 Connection: keep-alive
4 Auth: Bearer 2*/f3+fe68df*4]
Примечание
Мы видим выше, что переданные нами заголовки отражаются в HTTP-запросе.
Чтение содержимого файла из указанного выше URL и передача его read_html в виде строки:
In [331]: html_str = """
.....: <table>
.....: <tr>
.....: <th>A</th>
.....: <th colspan="1">B</th>
.....: <th rowspan="1">C</th>
.....: </tr>
.....: <tr>
.....: <td>a</td>
.....: <td>b</td>
.....: <td>c</td>
.....: </tr>
.....: </table>
.....: """
.....:
In [332]: with open("tmp.html", "w") as f:
.....: f.write(html_str)
.....:
In [333]: df = pd.read_html("tmp.html")
In [334]: df[0]
Out[334]:
A B C
0 a b c
Вы также можете передать экземпляр StringIO, если хотите:
In [335]: dfs = pd.read_html(StringIO(html_str))
In [336]: dfs[0]
Out[336]:
A B C
0 a b c
Примечание
Следующие примеры не выполняются IPython-интерпретатором из-за того, что наличие большого количества функций, осуществляющих доступ к сети, замедляет сборку документации. Если вы обнаружите ошибку или пример, который не выполняется, пожалуйста, не стесняйтесь сообщать об этом на странице проблем GitHub pandas pandas GitHub issues page.
Чтение URL и поиск таблицы, содержащей определенный текст:
match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
Укажите строку заголовка (по умолчанию <th> или <td> элементы, расположенные внутри <thead>, используются для формирования индекса столбцов, если в <thead> содержатся несколько строк, создается многоуровневый индекс); если указано, строка заголовка берется из данных за вычетом проанализированных элементов заголовка (<th> элементов).
dfs = pd.read_html(url, header=0)
Укажите столбец индекса:
dfs = pd.read_html(url, index_col=0)
Укажите количество строк, которые нужно пропустить:
dfs = pd.read_html(url, skiprows=0)
Укажите количество строк, которые нужно пропустить, используя список (range также работает):
dfs = pd.read_html(url, skiprows=range(2))
Укажите атрибут HTML:
dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0])) # Should be True
Укажите значения, которые должны быть преобразованы в NaN:
dfs = pd.read_html(url, na_values=["No Acquirer"])
Укажите, нужно ли сохранить стандартный набор значений NaN:
dfs = pd.read_html(url, keep_default_na=False)
Укажите преобразователи для столбцов. Это полезно для числовых текстовых данных, имеющих ведущие нули. По умолчанию столбцы, являющиеся числовыми, преобразуются в числовые типы, а ведущие нули теряются. Чтобы этого избежать, мы можем преобразовать эти столбцы в строки.
url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
url_mcc,
match="Telekom Albania",
header=0,
converters={"MNC": str},
)
Используйте некоторую комбинацию вышеперечисленного:
dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)
Чтение выходных данных pandas to_html (с некоторой потерей точности чисел с плавающей запятой):
df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)
lxml бэкенд будет генерировать ошибку при неудачном разборе, если это единственный парсер, который вы предоставили. Если у вас есть только один парсер, вы можете указать только строку, но считается хорошей практикой передавать список с одной строкой, если, например, функция ожидает последовательность строк. Вы можете использовать:
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])
Или вы можете передать flavor='lxml' без списка:
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")
Однако, если у вас установлены bs4 и html5lib и вы передаете None или ['lxml',
'bs4'], то разбор, скорее всего, будет успешным. Обратите внимание, что как только разбор будет успешным, функция вернёт значение.
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])
Ссылки можно извлечь из ячеек вместе с текстом, используя extract_links="all".
In [337]: html_table = """
.....: <table>
.....: <tr>
.....: <th>GitHub</th>
.....: </tr>
.....: <tr>
.....: <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
.....: </tr>
.....: </table>
.....: """
.....:
In [338]: df = pd.read_html(
.....: StringIO(html_table),
.....: extract_links="all"
.....: )[0]
.....:
In [339]: df
Out[339]:
(GitHub, None)
0 (pandas, https://github.com/pandas-dev/pandas)
In [340]: df[("GitHub", None)]
Out[340]:
0 (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object
In [341]: df[("GitHub", None)].str[1]
Out[341]:
0 https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object
Новое в версии 1.5.0.
Запись в файлы HTML
DataFrame объекты имеют метод to_html, который отображает содержимое DataFrame в виде HTML-таблицы. Аргументы функции такие же, как и в методе to_string, описанном выше.
Примечание
Для краткости здесь показаны не все возможные параметры для DataFrame.to_html. Полный список параметров см. в DataFrame.to_html().
Примечание
В среде, поддерживающей отображение HTML, такой как Jupyter Notebook, display(HTML(...))` отобразит исходный HTML в данной среде.
In [342]: from IPython.display import display, HTML
In [343]: df = pd.DataFrame(np.random.randn(2, 2))
In [344]: df
Out[344]:
0 1
0 -0.345352 1.314232
1 0.690579 0.995761
In [345]: html = df.to_html()
In [346]: print(html) # raw html
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [347]: display(HTML(html))
<IPython.core.display.HTML object>
Аргумент columns ограничит отображаемые столбцы:
In [348]: html = df.to_html(columns=[0])
In [349]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
</tr>
</tbody>
</table>
In [350]: display(HTML(html))
<IPython.core.display.HTML object>
float_format принимает вызываемый объект Python для управления точностью значений с плавающей запятой:
In [351]: html = df.to_html(float_format="{0:.10f}".format)
In [352]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.3453521949</td>
<td>1.3142323796</td>
</tr>
<tr>
<th>1</th>
<td>0.6905793352</td>
<td>0.9957609037</td>
</tr>
</tbody>
</table>
In [353]: display(HTML(html))
<IPython.core.display.HTML object>
bold_rows сделает метки строк по умолчанию жирными, но вы можете отключить это:
In [354]: html = df.to_html(bold_rows=False)
In [355]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<td>1</td>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [356]: display(HTML(html))
<IPython.core.display.HTML object>
Аргумент classes предоставляет возможность задавать CSS-классы полученной HTML-таблице. Обратите внимание, что эти классы добавляются к существующему классу 'dataframe'.
In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
Аргумент render_links предоставляет возможность добавлять гиперссылки в ячейки, содержащие URL-адреса.
In [358]: url_df = pd.DataFrame(
.....: {
.....: "name": ["Python", "pandas"],
.....: "url": ["https://www.python.org/", "https://pandas.pydata.org"],
.....: }
.....: )
.....:
In [359]: html = url_df.to_html(render_links=True)
In [360]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>name</th>
<th>url</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Python</td>
<td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
</tr>
<tr>
<th>1</th>
<td>pandas</td>
<td><a href="https://pandas.pydata.org" target="_blank">https://pandas.pydata.org</a></td>
</tr>
</tbody>
</table>
In [361]: display(HTML(html))
<IPython.core.display.HTML object>
Наконец, аргумент escape позволяет контролировать, будут ли символы “<”, “>” и “&” экранироваться в результирующем HTML (по умолчанию это True). Чтобы получить HTML без экранированных символов, передайте escape=False
In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})
Экранированные:
In [363]: html = df.to_html()
In [364]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</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 io верхнего уровня read_html.
Проблемы с lxml
-
Преимущества
-
Недостатки
lxml не гарантирует результаты разбора, если ему не предоставлен строго валидный разметка.
Учитывая вышесказанное, мы позволили вам, пользователю, использовать backend lxml, но этот backend будет использовать html5lib, если lxml не сможет выполнить разбор.
Поэтому настоятельно рекомендуется установить и BeautifulSoup4, и html5lib, чтобы получить валидный результат (при условии, что все остальное валидно), даже если lxml не удастся.
Проблемы с BeautifulSoup4 с использованием lxml в качестве бэкенда
Вышеупомянутые проблемы также актуальны, поскольку BeautifulSoup4 по сути является просто оболочкой вокруг бэкенда парсера.
Проблемы с BeautifulSoup4 с использованием html5lib в качестве бэкенда
-
Преимущества
html5lib намного более либерален, чем lxml, и, следовательно, гораздо лучше справляется с реальной разметкой, а не, например, просто отбрасывает элемент без уведомления.
html5lib автоматически генерирует валидную разметку HTML5 из невалидной. Это очень важно для разбора HTML-таблиц, поскольку гарантирует валидный документ. Однако это не означает, что он «правильный», так как процесс исправления разметки не имеет единого определения.
html5lib — чистый Python и не требует дополнительных шагов сборки помимо собственной установки.
-
Недостатки
Самый большой недостаток использования html5lib — это его медлительность. Однако следует учитывать тот факт, что многие таблицы в сети недостаточно велики, чтобы время работы алгоритма разбора имело значение. Скорее всего, узким местом будет процесс чтения исходного текста из URL через веб, то есть ввод-вывод (IO). Для очень больших таблиц это может быть не так.
LaTeX
Новое в версии 1.3.0.
В настоящее время нет методов чтения из LaTeX, только методы вывода.
Запись в файлы LaTeX
Примечание
Объекты DataFrame и Styler в настоящее время имеют метод to_latex. Рекомендуется использовать метод Styler.to_latex() вместо DataFrame.to_latex() из-за большей гибкости условного форматирования в первом случае и возможной будущей устарелости второго.
Ознакомьтесь с документацией Styler.to_latex, где приведены примеры условного форматирования и объяснено действие его ключевых аргументов.
Для простого применения достаточно следующего шаблона.
In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])
In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
& c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}
Для форматирования значений перед выводом используйте цепочку метода Styler.format.
In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
& c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}
XML
Чтение XML
Новое в версии 1.3.0.
Функция верхнего уровня read_xml() может принимать строку/файл/URL XML и будет парсить узлы и атрибуты в pandas DataFrame.
Примечание
Поскольку нет стандартной структуры XML, где типы дизайна могут варьироваться по-разному, read_xml лучше всего работает с более плоскими и неглубокими версиями. Если документ XML глубоко вложен, используйте функцию stylesheet для преобразования XML в более плоскую версию.
Давайте рассмотрим несколько примеров.
Чтение строки XML:
In [372]: from io import StringIO
In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
.....: <bookstore>
.....: <book category="cooking">
.....: <title lang="en">Everyday Italian</title>
.....: <author>Giada De Laurentiis</author>
.....: <year>2005</year>
.....: <price>30.00</price>
.....: </book>
.....: <book category="children">
.....: <title lang="en">Harry Potter</title>
.....: <author>J K. Rowling</author>
.....: <year>2005</year>
.....: <price>29.99</price>
.....: </book>
.....: <book category="web">
.....: <title lang="en">Learning XML</title>
.....: <author>Erik T. Ray</author>
.....: <year>2003</year>
.....: <price>39.95</price>
.....: </book>
.....: </bookstore>"""
.....:
In [374]: df = pd.read_xml(StringIO(xml))
In [375]: df
Out[375]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
Чтение URL без опций:
In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")
In [377]: df
Out[377]:
category title author year price cover
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00 None
1 children Harry Potter J K. Rowling 2005 29.99 None
2 web XQuery Kick Start Vaidyanathan Nagarajan 2003 49.99 None
3 web Learning XML Erik T. Ray 2003 39.95 paperback
Чтение содержимого файла “books.xml” и передача его read_xml как строки:
In [378]: file_path = "books.xml"
In [379]: with open(file_path, "w") as f:
.....: f.write(xml)
.....:
In [380]: with open(file_path, "r") as f:
.....: df = pd.read_xml(StringIO(f.read()))
.....:
In [381]: df
Out[381]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
Чтение содержимого “books.xml” как экземпляра StringIO или BytesIO и передача его read_xml:
In [382]: with open(file_path, "r") as f:
.....: sio = StringIO(f.read())
.....:
In [383]: df = pd.read_xml(sio)
In [384]: df
Out[384]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
In [385]: with open(file_path, "rb") as f:
.....: bio = BytesIO(f.read())
.....:
In [386]: df = pd.read_xml(bio)
In [387]: df
Out[387]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
Даже чтение XML из ведер AWS S3, таких как NIH NCBI PMC Article Datasets, предоставляющие биомедицинские и биологические научные журналы:
In [388]: df = pd.read_xml(
.....: "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
.....: xpath=".//journal-meta",
.....: )
.....:
In [389]: df
Out[389]:
journal-id journal-title issn publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN
С lxml по умолчанию как parser, вы получаете доступ к полному функциональному XML-библиотеке, которая расширяет API Python's ElementTree. Одним из мощных инструментов является возможность выборочного или условного запроса узлов с более выразительным XPath:
In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")
In [391]: df
Out[391]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
Укажите только элементы или только атрибуты для парсинга:
In [392]: df = pd.read_xml(file_path, elems_only=True)
In [393]: df
Out[393]:
title author year price
0 Everyday Italian Giada De Laurentiis 2005 30.00
1 Harry Potter J K. Rowling 2005 29.99
2 Learning XML Erik T. Ray 2003 39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)
In [395]: df
Out[395]:
category
0 cooking
1 children
2 web
XML-документы могут иметь пространства имен с префиксами и пространствами имен по умолчанию без префиксов, оба из которых обозначаются специальным атрибутом xmlns. Для парсинга узла в контексте пространства имен, xpath должен ссылаться на префикс.
Например, ниже XML содержит пространство имен с префиксом, doc, и URI в https://example.com. Для парсинга doc:row узлов, необходимо использовать namespaces.
In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <doc:data xmlns:doc="https://example.com">
.....: <doc:row>
.....: <doc:shape>square</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides>4.0</doc:sides>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>circle</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides/>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>triangle</doc:shape>
.....: <doc:degrees>180</doc:degrees>
.....: <doc:sides>3.0</doc:sides>
.....: </doc:row>
.....: </doc:data>"""
.....:
In [397]: df = pd.read_xml(StringIO(xml),
.....: xpath="//doc:row",
.....: namespaces={"doc": "https://example.com"})
.....:
In [398]: df
Out[398]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
Аналогично, XML-документ может иметь пространство имен по умолчанию без префикса. Если не присвоить временное имя для корректного URI, не будет возвращено никаких узлов и будет возбуждено исключение ValueError. Но присвоение любого временного имени для корректного URI позволяет парсить узлы.
In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <data xmlns="https://example.com">
.....: <row>
.....: <shape>square</shape>
.....: <degrees>360</degrees>
.....: <sides>4.0</sides>
.....: </row>
.....: <row>
.....: <shape>circle</shape>
.....: <degrees>360</degrees>
.....: <sides/>
.....: </row>
.....: <row>
.....: <shape>triangle</shape>
.....: <degrees>180</degrees>
.....: <sides>3.0</sides>
.....: </row>
.....: </data>"""
.....:
In [400]: df = pd.read_xml(StringIO(xml),
.....: xpath="//pandas:row",
.....: namespaces={"pandas": "https://example.com"})
.....:
In [401]: df
Out[401]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
Однако, если XPath не ссылается на имена узлов, такие как значение по умолчанию, /*, то namespaces не требуется.
Примечание
Поскольку xpath определяет родителя контента, подлежащего парсингу, парсятся только непосредственные потомки, которые включают дочерние узлы или текущие атрибуты. Поэтому read_xml не будет парсить текст внуков или других потомков, и не будет парсить атрибуты потомков. Чтобы получить контент на более низком уровне, нужно настроить xpath на более низкий уровень. Например,
In [402]: xml = """
.....: <data>
.....: <row>
.....: <shape sides="4">square</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="0">circle</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="3">triangle</shape>
.....: <degrees>180</degrees>
.....: </row>
.....: </data>"""
.....:
In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")
In [404]: df
Out[404]:
shape degrees
0 square 360
1 circle 360
2 triangle 180
показывает, что атрибут sides элемента shape не был пропарсен должным образом, поскольку этот атрибут находится в потомке элемента row, а не в элементе row. Другими словами, атрибут sides является потомком внука элемента row. Однако, xpath нацелен на элемент row, что охватывает только его дочерние элементы и атрибуты.
С lxml в качестве парсера, вы можете сплющить вложенные XML-документы с помощью скрипта XSLT, который также может быть строками/файлами/URL. В качестве справки, XSLT — это язык специального назначения, написанный в специальном XML-файле, который может преобразовывать исходные XML-документы в другие XML, HTML, даже текст (CSV, JSON и т. д.) с помощью процессора XSLT.
Например, рассмотрим эту несколько вложенную структуру поездок по "L" Чикаго, где станции и поездки содержат данные в своих разделах. С помощью XSLT ниже, lxml может преобразовать исходный вложенный документ в более плоский вывод (как показано ниже для демонстрации) для более простого парсинга в DataFrame:
In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station id="40850" name="Library"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="41700" name="Washington/Wabash"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="40380" name="Clark/Lake"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: </response>"""
.....:
In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/response">
.....: <xsl:copy>
.....: <xsl:apply-templates select="row"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <xsl:copy>
.....: <station_id><xsl:value-of select="station/@id"/></station_id>
.....: <station_name><xsl:value-of select="station/@name"/></station_name>
.....: <xsl:copy-of select="month|rides/*"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station_id>40850</station_id>
.....: <station_name>Library</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>41700</station_id>
.....: <station_name>Washington/Wabash</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>40380</station_id>
.....: <station_name>Clark/Lake</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </row>
.....: </response>"""
.....:
In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)
In [409]: df
Out[409]:
station_id station_name ... avg_saturday_rides avg_sunday_holiday_rides
0 40850 Library ... 534.0 417.2
1 41700 Washington/Wabash ... 1909.8 1438.6
2 40380 Clark/Lake ... 1657.0 1453.8
[3 rows x 6 columns]
Для очень больших XML-файлов, размер которых может достигать сотен мегабайт или гигабайт, pandas.read_xml() поддерживает разбор таких больших файлов с использованием lxml’s iterparse и etree’s iterparse, которые являются эффективными с точки зрения памяти методами для итерации по XML-дереву и извлечения определенных элементов и атрибутов без загрузки всего дерева в память.
Введено в версии 1.5.0.
Для использования этой функции необходимо передать физический путь к файлу XML в read_xml и использовать аргумент iterparse. Файлы не должны быть сжаты или указывать на онлайн-источники, а должны храниться на локальном диске. Также iterparse должен быть словарем, где ключ — это повторяющиеся узлы в документе (которые становятся строками), а значение — список любых элементов или атрибутов, являющихся потомками (т.е. дочерними, внучатыми) повторяющегося узла. Поскольку в этом методе XPath не используется, потомки не обязательно должны иметь одинаковые отношения друг с другом. Ниже показан пример чтения данных из последнего архива данных статьи Википедии (размером более 12 ГБ+).
In [1]: df = pd.read_xml(
... "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
... iterparse = {"page": ["title", "ns", "id"]}
... )
... df
Out[2]:
title ns id
0 Gettysburg Address 0 21450
1 Main Page 0 42950
2 Declaration by United Nations 0 8435
3 Constitution of the United States of America 0 8435
4 Declaration of Independence (Israel) 0 17858
... ... ... ...
3578760 Page:Black cat 1897 07 v2 n10.pdf/17 104 219649
3578761 Page:Black cat 1897 07 v2 n10.pdf/43 104 219649
3578762 Page:Black cat 1897 07 v2 n10.pdf/44 104 219649
3578763 The History of Tom Jones, a Foundling/Book IX 0 12084291
3578764 Page:Shakespeare of Stratford (1926) Yale.djvu/91 104 21450
[3578765 rows x 3 columns]
Запись XML
Введено в версии 1.3.0.
Объекты DataFrame имеют метод экземпляра to_xml, который отображает содержимое DataFrame в виде XML-документа.
Примечание
Этот метод не поддерживает специальные свойства XML, включая DTD, CData, XSD схемы, инструкции обработки, комментарии и другие. Поддерживаются только пространства имен на уровне корня. Однако stylesheet позволяет изменять дизайн после начального вывода.
Давайте рассмотрим несколько примеров.
Запись XML без опций:
In [410]: geom_df = pd.DataFrame(
.....: {
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
Запись XML с новым корнем и именем строки:
In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<objects>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</objects>
<objects>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</objects>
<objects>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</objects>
</geometry>
Запись XML, ориентированная на атрибуты:
In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
<row index="0" shape="square" degrees="360" sides="4.0"/>
<row index="1" shape="circle" degrees="360"/>
<row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>
Запись смешанного XML с элементами и атрибутами:
In [414]: print(
.....: geom_df.to_xml(
.....: index=False,
.....: attr_cols=['shape'],
.....: elem_cols=['degrees', 'sides'])
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<data>
<row shape="square">
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row shape="circle">
<degrees>360</degrees>
<sides/>
</row>
<row shape="triangle">
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
Любой DataFrames с иерархическими столбцами будет сплюснут для имен XML-элементов, уровни которых разделены символом подчеркивания:
In [415]: ext_geom_df = pd.DataFrame(
.....: {
.....: "type": ["polygon", "other", "polygon"],
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
.....: columns='type',
.....: values=['degrees', 'sides'],
.....: aggfunc='sum')
.....:
In [417]: pvt_df
Out[417]:
degrees sides
type other polygon other polygon
shape
circle 360.0 NaN 0.0 NaN
square NaN 360.0 NaN 4.0
triangle NaN 180.0 NaN 3.0
In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<shape>circle</shape>
<degrees_other>360.0</degrees_other>
<degrees_polygon/>
<sides_other>0.0</sides_other>
<sides_polygon/>
</row>
<row>
<shape>square</shape>
<degrees_other/>
<degrees_polygon>360.0</degrees_polygon>
<sides_other/>
<sides_polygon>4.0</sides_polygon>
</row>
<row>
<shape>triangle</shape>
<degrees_other/>
<degrees_polygon>180.0</degrees_polygon>
<sides_other/>
<sides_polygon>3.0</sides_polygon>
</row>
</data>
Запись XML с пространством имен по умолчанию:
In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data xmlns="https://example.com">
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
Запись XML с префиксом пространства имен:
In [420]: print(
.....: geom_df.to_xml(namespaces={"doc": "https://example.com"},
.....: prefix="doc")
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<doc:data xmlns:doc="https://example.com">
<doc:row>
<doc:index>0</doc:index>
<doc:shape>square</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides>4.0</doc:sides>
</doc:row>
<doc:row>
<doc:index>1</doc:index>
<doc:shape>circle</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides/>
</doc:row>
<doc:row>
<doc:index>2</doc:index>
<doc:shape>triangle</doc:shape>
<doc:degrees>180</doc:degrees>
<doc:sides>3.0</doc:sides>
</doc:row>
</doc:data>
Запись XML без объявления или красивой печати:
In [421]: print(
.....: geom_df.to_xml(xml_declaration=False,
.....: pretty_print=False)
.....: )
.....:
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>
Запись XML и преобразование со стилизованным листом:
In [422]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/data">
.....: <geometry>
.....: <xsl:apply-templates select="row"/>
.....: </geometry>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <object index="{index}">
.....: <xsl:if test="shape!='circle'">
.....: <xsl:attribute name="type">polygon</xsl:attribute>
.....: </xsl:if>
.....: <xsl:copy-of select="shape"/>
.....: <property>
.....: <xsl:copy-of select="degrees|sides"/>
.....: </property>
.....: </object>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
<object index="0" type="polygon">
<shape>square</shape>
<property>
<degrees>360</degrees>
<sides>4.0</sides>
</property>
</object>
<object index="1">
<shape>circle</shape>
<property>
<degrees>360</degrees>
<sides/>
</property>
</object>
<object index="2" type="polygon">
<shape>triangle</shape>
<property>
<degrees>180</degrees>
<sides>3.0</sides>
</property>
</object>
</geometry>
Заключительные замечания по XML
Все XML-документы соответствуют спецификациям W3C. Как парсеры
etree, так иlxmlне смогут разобрать документ разметки, который не является правильно сформированным или не соответствует правилам синтаксиса XML. Следует помнить, что HTML не является XML-документом, если не соответствует спецификациям XHTML. Однако другие популярные типы разметки, включая KML, XAML, RSS, MusicML, MathML, соответствуют схемам XML.По этой причине, если ваше приложение создает XML перед операциями pandas, используйте соответствующие библиотеки DOM, такие как
etreeиlxmlдля построения необходимого документа, а не с помощью конкатенации строк или регулярных выражений. Всегда помните, что XML — это специальный текстовый файл с правилами разметки.В случае с очень большими XML-файлами (несколько сотен мегабайт до гигабайт), XPath и XSLT могут стать ресурсоёмкими операциями. Убедитесь, что у вас достаточно оперативной памяти для чтения и записи больших XML-файлов (примерно в 5 раз больше, чем размер текста).
Поскольку XSLT — это язык программирования, используйте его с осторожностью, так как такие скрипты могут представлять собой риск безопасности в вашей среде и могут запускать большие или бесконечные рекурсивные операции. Всегда тестируйте скрипты на небольших фрагментах перед полным запуском.
Парсер etree поддерживает все функции как
read_xml, так иto_xml, за исключением сложного XPath и любого XSLT. Хотя ограниченный по функциям,etreeпо-прежнему является надёжным и способным парсером и строителем дерева. Его производительность может быть немного ниже, чем уlxmlдля больших файлов, но это практически незаметно для файлов небольшого и среднего размера.
Файлы Excel
Метод read_excel() может читать файлы Excel 2007+ (.xlsx) используя модуль openpyxl Python. Файлы Excel 2003 (.xls) можно читать, используя xlrd. Бинарные файлы Excel (.xlsb) можно читать, используя pyxlsb. Все форматы можно читать, используя движок calamine. Метод экземпляра to_excel() используется для сохранения DataFrame в Excel. В целом, семантика аналогична работе с данными csv. См. раздел справочник для некоторых продвинутых стратегий.
Примечание
При engine=None будет использоваться следующий алгоритм определения движка:
Если
path_or_buffer— это формат OpenDocument (.odf, .ods, .odt), то будет использован odf.В противном случае, если
path_or_buffer— это формат xls, то будет использованxlrd.В противном случае, если
path_or_buffer— это формат xlsb, то будет использованpyxlsb.В противном случае будет использован
openpyxl.
Чтение файлов Excel
В самом простом случае, read_excel принимает путь к файлу Excel, и sheet_name, указывающий, какой лист нужно проанализировать.
При использовании параметра engine_kwargs, pandas передаст эти аргументы в движок. Для этого важно знать, какую функцию pandas использует внутри.
Для движка openpyxl pandas использует
openpyxl.load_workbook()для чтения (.xlsx) и (.xlsm) файлов.Для движка xlrd pandas использует
xlrd.open_workbook()для чтения (.xls) файлов.Для движка pyxlsb pandas использует
pyxlsb.open_workbook()для чтения (.xlsb) файлов.Для движка odf pandas использует
odf.opendocument.load()для чтения (.ods) файлов.Для движка calamine pandas использует
python_calamine.load_workbook()для чтения (.xlsx), (.xlsm), (.xls), (.xlsb), (.ods) файлов.
# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")
ExcelFile класс
Для работы с несколькими листами из одного файла можно использовать класс ExcelFile, который может быть обернут вокруг файла и передан в read_excel. Это даст выигрыш в производительности при чтении нескольких листов, так как файл будет загружен в память только один раз.
xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")
Класс ExcelFile также может использоваться как менеджер контекста.
with pd.ExcelFile("path_to_file.xls") as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
Свойство sheet_names сгенерирует список имён листов в файле.
Основной случай использования класса ExcelFile — это парсинг нескольких листов с разными параметрами:
data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)
Обратите внимание, что если параметры парсинга одинаковы для всех листов, то список имён листов можно просто передать в read_excel без потери производительности.
# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])
# equivalent using the read_excel function
data = pd.read_excel(
"path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)
ExcelFile также может быть вызван с объектом xlrd.book.Book в качестве параметра. Это позволяет пользователю контролировать, как считывается файл Excel. Например, листы можно загружать по требованию, вызывая xlrd.open_workbook() с on_demand=True.
import xlrd
xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
Указание листов
Примечание
Второй аргумент — это sheet_name, а не ExcelFile.sheet_names.
Примечание
Атрибут ExcelFile sheet_names предоставляет доступ к списку листов.
Аргументы
sheet_nameпозволяют указать лист или листы для чтения.Значение по умолчанию для
sheet_name— 0, что означает чтение первого листа.Передайте строку, чтобы сослаться на имя конкретного листа в книге.
Передайте целое число, чтобы сослаться на индекс листа. Индексы следуют соглашениям Python, начиная с 0.
Передайте список строк или целых чисел, чтобы получить словарь указанных листов.
Передайте
None, чтобы получить словарь всех доступных листов.
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])
Использование индекса листа:
# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])
Использование всех значений по умолчанию:
# Returns a DataFrame
pd.read_excel("path_to_file.xls")
Использование None для получения всех листов:
# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)
Использование списка для получения нескольких листов:
# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])
read_excel может читать более одного листа, установив sheet_name либо на список имён листов, либо на список позиций листов, или None для чтения всех листов. Листы можно указывать по индексу листа или имени листа, используя целое число или строку соответственно.
Чтение многоуровневого индекса
Можно прочитать многоуровневый индекс, передав список столбцов в index_col и список строк в header. Если имена уровней индекса или столбцов сериализованы, они будут также считаны, если указаны соответствующие строки/столбцы.
Например, чтобы считать многоуровневый индекс без имён:
In [424]: df = pd.DataFrame(
.....: {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
.....: index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
.....: )
.....:
In [425]: df.to_excel("path_to_file.xlsx")
In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [427]: df
Out[427]:
a b
a c 1 5
d 2 6
b c 3 7
d 4 8
Если имена уровней индекса присутствуют, они также будут обработаны, используя те же параметры.
In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])
In [429]: df.to_excel("path_to_file.xlsx")
In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [431]: df
Out[431]:
a b
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
Если исходный файл содержит многоуровневые индексы и столбцы, необходимо передать списки, определяющие каждый уровень в index_col и header:
In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])
In [433]: df.to_excel("path_to_file.xlsx")
In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])
In [435]: df
Out[435]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
Пропущенные значения в столбцах, указанных в index_col, будут заполнены вперёд, чтобы обеспечить обратимость с to_excel для merged_cells=True. Чтобы избежать заполнения вперёд пропущенных значений, используйте set_index после чтения данных вместо index_col.
Парсинг определённых столбцов
Часто пользователи вставляют столбцы для временных вычислений в Excel, и вам может не потребоваться считывать эти столбцы. read_excel принимает ключевое слово usecols, позволяющее указать подмножество столбцов для парсинга.
Можно указать набор столбцов и диапазонов Excel через запятую в виде строки:
pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")
Если usecols — список целых чисел, предполагается, что это индексы столбцов файла для парсинга.
pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])
Порядок элементов игнорируется, поэтому usecols=[0, 1] эквивалентно [1, 0].
Если usecols — список строк, предполагается, что каждая строка соответствует имени столбца, предоставленному пользователем в names или полученному из заголовков документа. Эти строки определяют, какие столбцы будут обработаны:
pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])
Порядок элементов игнорируется, поэтому usecols=['baz', 'joe'] эквивалентно ['joe', 'baz'].
Если usecols — вызываемый объект, функция будет вызвана для имён столбцов, возвращая имена, для которых функция вернула значение True.
pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())
Парсинг дат
Значения типа даты/времени обычно автоматически преобразуются в соответствующий тип данных при чтении файла Excel. Но если у вас есть столбец строк, которые выглядят как даты (но фактически не отформатированы как даты в Excel), вы можете использовать ключевое слово parse_dates для преобразования этих строк в даты:
pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])
Преобразователи ячеек
Возможно преобразовывать содержимое ячеек Excel с помощью параметра converters. Например, для преобразования столбца в булевы значения:
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})
Этот параметр обрабатывает пропущенные значения и рассматривает исключения в преобразователях как пропущенные данные. Преобразования применяются к каждой ячейке по отдельности, а не к столбцу в целом, поэтому тип массива не гарантируется. Например, столбец целых чисел с пропущенными значениями не может быть преобразован в массив с целым типом данных, потому что NaN — это строго тип float. Можно вручную маскировать пропущенные данные, чтобы восстановить целочисленный тип:
def cfun(x):
return int(x) if x else -1
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})
Указание типов данных
В качестве альтернативы преобразователям, тип для целого столбца можно указать с помощью ключевого слова dtype, которое принимает словарь, сопоставляющий имена столбцов с типами. Для интерпретации данных без определения типа используйте тип str или object.
pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})
Запись файлов Excel
Запись файлов Excel в диск
Чтобы записать объект DataFrame в лист файла Excel, можно использовать метод to_excel. Аргументы в основном такие же, как и у to_csv, описанных выше, первым аргументом является имя файла Excel, а необязательным вторым аргументом — имя листа, в который следует записать DataFrame. Например:
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
Файлы с расширением .xlsx будут записаны с использованием xlsxwriter (если доступно) или openpyxl.
DataFrame будет записано таким образом, чтобы имитировать вывод REPL. index_label будет размещено во второй строке вместо первой. Вы можете разместить его в первой строке, установив параметр merge_cells в to_excel() на значение False:
df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)
Чтобы записать отдельные DataFrames в отдельные листы одного файла Excel, можно передать параметр ExcelWriter.
with pd.ExcelWriter("path_to_file.xlsx") as writer:
df1.to_excel(writer, sheet_name="Sheet1")
df2.to_excel(writer, sheet_name="Sheet2")
При использовании параметра engine_kwargs pandas передаст эти аргументы движку. Для этого важно знать, какую функцию pandas использует внутри.
Для движка openpyxl pandas использует
openpyxl.Workbook()для создания нового листа иopenpyxl.load_workbook()для добавления данных в существующий лист. Движок openpyxl записывает в файлы (.xlsx) и (.xlsm).Для движка xlsxwriter pandas использует
xlsxwriter.Workbook()для записи в файлы (.xlsx).Для движка odf pandas использует
odf.opendocument.OpenDocumentSpreadsheet()для записи в файлы (.ods).
Запись файлов Excel в память
pandas поддерживает запись файлов Excel в объекты типа буфера, такие как StringIO или BytesIO, используя ExcelWriter.
from io import BytesIO
bio = BytesIO()
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")
# Save the workbook
writer.save()
# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read()
Примечание
engine является необязательным, но рекомендуется. Установка движка определяет версию создаваемого файла книги. Установка engine='xlrd' создаст файл книги формата Excel 2003 (xls). Использование 'openpyxl' или 'xlsxwriter' создаст файл книги формата Excel 2007 (xlsx). Если параметр опущен, создается файл книги формата Excel 2007.
Движки записи файлов Excel
pandas выбирает движок для записи файлов Excel двумя способами:
ключевой аргумент
engineрасширение имени файла (через значение по умолчанию, указанное в параметрах конфигурации)
По умолчанию pandas использует XlsxWriter для .xlsx, openpyxl для .xlsm. Если у вас установлено несколько движков, можно установить движок по умолчанию, настроив параметры конфигурации io.excel.xlsx.writer и io.excel.xls.writer. pandas будет использовать openpyxl для файлов .xlsx, если Xlsxwriter недоступен.
Чтобы указать, какой движок вы хотите использовать, можно передать ключевой аргумент engine в to_excel и в ExcelWriter. Встроенные движки:
openpyxl: требуется версия 2.4 или вышеxlsxwriter
# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")
# Or via pandas configuration.
from pandas import options # noqa: E402
options.io.excel.xlsx.writer = "xlsxwriter"
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
Стиль и форматирование
Вид и ощущения создаваемых с помощью pandas листов Excel можно изменить, используя следующие параметры в методе DataFrame’s to_excel.
float_format: Строка форматирования для чисел с плавающей точкой (по умолчаниюNone).freeze_panes: Кортеж из двух целых чисел, представляющих последнюю строку и последний столбец для заморозки. Каждый из этих параметров базируется на единице, так что (1, 1) заморозит первую строку и первый столбец (по умолчаниюNone).
Использование движка Xlsxwriter предоставляет множество вариантов управления форматом листа Excel, созданного с помощью метода to_excel. Отличные примеры можно найти в документации Xlsxwriter здесь: https://xlsxwriter.readthedocs.io/working_with_pandas.html
OpenDocument электронные таблицы
Методы io для файлов Excel также поддерживают чтение и запись электронных таблиц OpenDocument с использованием модуля odfpy. Семантика и возможности чтения и записи электронных таблиц OpenDocument соответствуют возможностям работы с файлами Excel с использованием engine='odf'. Необходимо установить необязательную зависимость «odfpy».
Метод read_excel() может читать электронные таблицы OpenDocument
# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")
Аналогично, метод to_excel() может записывать электронные таблицы OpenDocument
# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")
Бинарные файлы Excel (.xlsb)
Метод read_excel() также может читать бинарные файлы Excel с использованием модуля pyxlsb. Семантика и возможности чтения бинарных файлов Excel в основном соответствуют возможностям работы с файлами Excel с использованием engine='pyxlsb'. pyxlsb не распознает типы данных datetime в файлах и вернёт числа с плавающей точкой вместо этого (вы можете использовать calamine, если вам нужно распознавать типы datetime).
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")
Примечание
В настоящее время pandas поддерживает только чтение бинарных файлов Excel. Запись не реализована.
Calamine (файлы Excel и ODS)
Метод read_excel() может читать файлы Excel (.xlsx, .xlsm, .xls, .xlsb) и электронные таблицы OpenDocument (.ods) с использованием модуля python-calamine. Этот модуль представляет собой обвязку для библиотеки Rust calamine и в большинстве случаев быстрее других движков. Необходимо установить необязательную зависимость «python-calamine».
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")
Буфер обмена
Удобный способ получения данных — использование метода read_clipboard(), который получает содержимое буфера обмена и передает его методу read_csv. Например, вы можете скопировать следующий текст в буфер обмена (CTRL-C на многих операционных системах):
A B C
x 1 4 p
y 2 5 q
z 3 6 r
И затем импортировать данные непосредственно в DataFrame, вызвав:
>>> clipdf = pd.read_clipboard()
>>> clipdf
A B C
x 1 4 p
y 2 5 q
z 3 6 r
Метод to_clipboard можно использовать для записи содержимого DataFrame в буфер обмена. После чего вы можете вставить содержимое буфера обмена в другие приложения (CTRL-V на многих операционных системах). Ниже показано, как записать DataFrame в буфер обмена и прочитать его обратно.
>>> df = pd.DataFrame(
... {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )
>>> df
A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
A B C
x 1 4 p
y 2 5 q
z 3 6 r
Мы видим, что получили тот же контент, который ранее записали в буфер обмена.
Примечание
Возможно, вам потребуется установить xclip или xsel (с PyQt5, PyQt4 или qtpy) на Linux, чтобы использовать эти методы.
Загрузка/сохранение
Все объекты pandas оснащены методами to_pickle, которые используют модуль Python cPickle для сохранения структур данных на диск в формате pickle.
In [436]: df
Out[436]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
In [437]: df.to_pickle("foo.pkl")
Функция read_pickle в пространстве имён pandas может использоваться для загрузки любого закодированного в формате pickle объекта pandas (или любого другого закодированного объекта) из файла:
In [438]: pd.read_pickle("foo.pkl")
Out[438]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
Предупреждение
Загрузка данных, закодированных в формате pickle, полученных из ненадежных источников, может быть небезопасной.
Предупреждение
read_pickle() гарантирует обратную совместимость только с несколькими предыдущими версиями.
Сжатые файлы pickle
read_pickle(), DataFrame.to_pickle() и Series.to_pickle() могут читать и записывать сжатые файлы pickle. Типы сжатия gzip, bz2, xz, zstd поддерживаются для чтения и записи. Формат файла zip поддерживает только чтение и должен содержать только один файл данных для чтения.
Тип сжатия может быть явным параметром или выводиться из расширения файла. Если ‘infer’, используется gzip, bz2, zip, xz, zstd, если имя файла заканчивается на '.gz', '.bz2', '.zip', '.xz' или '.zst', соответственно.
Параметр сжатия также может быть словарем dict для передачи параметров протоколу сжатия. Он должен содержать ключ 'method', установленный в имя протокола сжатия, которое должно быть одним из {'zip', 'gzip', 'bz2', 'xz', 'zstd'}. Все остальные пары «ключ-значение» передаются в подлежащую библиотеку сжатия.
In [439]: df = pd.DataFrame(
.....: {
.....: "A": np.random.randn(1000),
.....: "B": "foo",
.....: "C": pd.date_range("20130101", periods=1000, freq="s"),
.....: }
.....: )
.....:
In [440]: df
Out[440]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
Использование явного типа сжатия:
In [441]: df.to_pickle("data.pkl.compress", compression="gzip")
In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")
In [443]: rt
Out[443]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
Вывод типа сжатия из расширения:
In [444]: df.to_pickle("data.pkl.xz", compression="infer")
In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")
In [446]: rt
Out[446]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
По умолчанию используется ‘infer’:
In [447]: df.to_pickle("data.pkl.gz")
In [448]: rt = pd.read_pickle("data.pkl.gz")
In [449]: rt
Out[449]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
In [450]: df["A"].to_pickle("s1.pkl.bz2")
In [451]: rt = pd.read_pickle("s1.pkl.bz2")
In [452]: rt
Out[452]:
0 -0.317441
1 -1.236269
2 0.896171
3 -0.487602
4 -0.082240
...
995 -0.171092
996 1.786173
997 -0.575189
998 0.820750
999 -1.256530
Name: A, Length: 1000, dtype: float64
Передача параметров протоколу сжатия для ускорения процесса сжатия:
In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})
msgpack
Поддержка pandas для msgpack была удалена в версии 1.0.0. Рекомендуется использовать pickle вместо этого.
В качестве альтернативы можно использовать формат сериализации Arrow IPC для передачи по сети объектов pandas. Документацию по pyarrow см. здесь.
HDF5 (PyTables)
Это похожий на словарь объект, который читает и записывает pandas с использованием высокопроизводительного формата HDF5 с помощью отличной библиотеки PyTables. Смотрите пособие для некоторых продвинутых стратегий
Предупреждение
pandas использует PyTables для чтения и записи файлов HDF5, что позволяет сериализовать данные типа object с помощью pickle. Загрузка данных с использованием pickle, полученных из ненадежных источников, может быть небезопасной.
См.: https://docs.python.org/3/library/pickle.html для получения дополнительной информации.
In [454]: store = pd.HDFStore("store.h5")
In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
Объекты могут быть записаны в файл, как если бы вы добавляли пары ключ-значение в словарь:
In [456]: index = pd.date_range("1/1/2000", periods=8)
In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
# store.put('s', s) is an equivalent method
In [459]: store["s"] = s
In [460]: store["df"] = df
In [461]: store
Out[461]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
В текущей или последующей сессии Python вы можете получить сохранённые объекты:
# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
Удаление объекта, указанного ключом:
# store.remove('df') is an equivalent method
In [464]: del store["df"]
In [465]: store
Out[465]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
Закрытие хранилища и использование менеджера контекста:
In [466]: store.close()
In [467]: store
Out[467]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
In [468]: store.is_open
Out[468]: False
# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
.....: store.keys()
.....:
API чтения/записи
HDFStore поддерживает API верхнего уровня, используя read_hdf для чтения и to_hdf для записи, аналогично тому, как работают read_csv и to_csv.
In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})
In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)
In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]:
A B
3 3 3
4 4 4
HDFStore по умолчанию не будет удалять строки, все значения в которых отсутствуют. Это поведение можно изменить, установив dropna=True.
In [473]: df_with_missing = pd.DataFrame(
.....: {
.....: "col1": [0, np.nan, 2],
.....: "col2": [1, np.nan, np.nan],
.....: }
.....: )
.....:
In [474]: df_with_missing
Out[474]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")
In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [477]: df_with_missing.to_hdf(
.....: "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
.....: )
.....:
In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]:
col1 col2
0 0.0 1.0
2 2.0 NaN
Фиксированный формат
В приведенных выше примерах показано хранение с использованием put, которое записывает HDF5 в PyTables в фиксированном формате массива, называемом форматом fixed. Эти типы хранилищ не могут быть добавлены после записи (хотя вы можете их просто удалить и переписать). Они также не поддерживают запросы; они должны быть получены целиком. Они также не поддерживают DataFrames с не уникальными именами столбцов. Форматы fixed предлагают очень быструю запись и немного более быстрое чтение, чем хранилища table. Этот формат задаётся по умолчанию при использовании put или to_hdf или format='fixed' или format='f'.
Предупреждение
Формат fixed вызовет исключение TypeError, если вы попытаетесь получить данные с помощью where:
In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")
In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")
File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
447 raise ValueError(
448 "key must be provided when HDF5 "
449 "file contains multiple datasets."
450 )
451 key = candidate_only_group._v_pathname
--> 452 return store.select(
453 key,
454 where=where,
455 start=start,
456 stop=stop,
457 columns=columns,
458 iterator=iterator,
459 chunksize=chunksize,
460 auto_close=auto_close,
461 )
462 except (ValueError, TypeError, LookupError):
463 if not isinstance(path_or_buf, HDFStore):
464 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
892 # create the iterator
893 it = TableIterator(
894 self,
895 s,
(...)
903 auto_close=auto_close,
904 )
--> 906 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
2026 where = self.where
2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
2030 self.close()
2031 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
889 def func(_start, _stop, _where):
--> 890 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
3270 def read(
3271 self,
3272 where=None,
(...)
3276 ) -> DataFrame:
3277 # start, stop applied to rows, so 0th axis only
-> 3278 self.validate_read(columns, where)
3279 select_axis = self.obj_type()._get_block_manager_axis(0)
3281 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
2917 raise TypeError(
2918 "cannot pass a column specification when reading "
2919 "a Fixed format store. this store must be selected in its entirety"
2920 )
2921 if where is not None:
-> 2922 raise TypeError(
2923 "cannot pass a where specification when reading "
2924 "from a Fixed format store. this store must be selected in its entirety"
2925 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
Формат таблицы
HDFStore поддерживает другой формат на диске, формат table. Концептуально table похож на DataFrame, с строками и столбцами. К table можно добавлять данные в той же или другой сессиях. Кроме того, поддерживаются операции удаления и запросов. Этот формат задаётся с помощью format='table' или format='t' для append или put или to_hdf.
Этот формат также можно задать в качестве опции pd.set_option('io.hdf.default_format','table'), чтобы по умолчанию put/append/to_hdf хранил данные в формате table.
In [481]: store = pd.HDFStore("store.h5")
In [482]: df1 = df[0:4]
In [483]: df2 = df[4:]
# append data (creates a table automatically)
In [484]: store.append("df", df1)
In [485]: store.append("df", df2)
In [486]: store
Out[486]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# select the entire object
In [487]: store.select("df")
Out[487]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'
Примечание
Вы также можете создать table, передав format='table' или format='t' в операцию put.
Иерархические ключи
Ключи к хранилищу могут быть указаны как строка. Они могут быть в формате иерархического имени пути (например, foo/bar/bah), что создаст иерархию подхранилищ (или Groups в терминологии PyTables). Ключи могут быть указаны без ведущего ‘/’ и всегда являются абсолютными (например, ‘foo’ относится к ‘/foo’). Операции удаления могут удалить всё в подхранилище и ниже, поэтому будьте осторожны.
In [489]: store.put("foo/bar/bah", df)
In [490]: store.append("food/orange", df)
In [491]: store.append("food/apple", df)
In [492]: store
Out[492]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']
# remove all nodes under this level
In [494]: store.remove("food")
In [495]: store
Out[495]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
Вы можете пройтись по иерархии групп, используя метод walk, который вернёт кортеж для каждого ключа группы вместе с относительными ключами его содержимого.
In [496]: for (path, subgroups, subkeys) in store.walk():
.....: for subgroup in subgroups:
.....: print("GROUP: {}/{}".format(path, subgroup))
.....: for subkey in subkeys:
.....: key = "/".join([path, subkey])
.....: print("KEY: {}".format(key))
.....: print(store.get(key))
.....:
GROUP: /foo
KEY: /df
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
Предупреждение
Иерархические ключи не могут быть получены как точечный (атрибутный) доступ, как описано выше для элементов, хранящихся под узлом корня.
In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah
File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
611 """allow attribute access to get stores"""
612 try:
--> 613 return self.get(name)
614 except (KeyError, ClosedFileError):
615 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
811 if group is None:
812 raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
1877 def _read_group(self, group: Node):
-> 1878 s = self._create_storer(group)
1879 s.infer_axes()
1880 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1750 tt = "generic_table"
1751 else:
-> 1752 raise TypeError(
1753 "cannot create a storer if the object is not existing "
1754 "nor a value are passed"
1755 )
1756 else:
1757 if isinstance(value, Series):
TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]:
/foo/bar/bah (Group) ''
children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]
Вместо этого используйте явные ключи на основе строк:
In [499]: store["foo/bar/bah"]
Out[499]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
Хранение типов
Хранение смешанных типов в таблице
Поддерживается хранение данных со смешанным типом dtype. Строки хранятся как шириной фиксированной длины, используя максимальный размер добавляемого столбца. Попытки добавить более длинные строки приведут к ошибке ValueError.
Передача min_itemsize={`values`: size} в качестве параметра append установит больший минимум для столбцов строк. Хранение floats,
strings, ints, bools, datetime64 в настоящее время поддерживается. Для столбцов строк передача nan_rep = 'nan' в append изменит представление nan по умолчанию на диске (что преобразуется в/из np.nan), по умолчанию это nan.
In [500]: df_mixed = pd.DataFrame(
.....: {
.....: "A": np.random.randn(8),
.....: "B": np.random.randn(8),
.....: "C": np.array(np.random.randn(8), dtype="float32"),
.....: "string": "string",
.....: "int": 1,
.....: "bool": True,
.....: "datetime64": pd.Timestamp("20010102"),
.....: },
.....: index=list(range(8)),
.....: )
.....:
In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan
In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})
In [503]: df_mixed1 = store.select("df_mixed")
In [504]: df_mixed1
Out[504]:
A B C ... int bool datetime64
0 0.013747 -1.166078 -1.292080 ... 1 True 1970-01-01 00:00:00.978393600
1 -0.712009 0.247572 1.526911 ... 1 True 1970-01-01 00:00:00.978393600
2 -0.645096 1.687406 0.288504 ... 1 True 1970-01-01 00:00:00.978393600
3 NaN NaN 0.097771 ... 1 True NaT
4 NaN NaN 1.536408 ... 1 True NaT
5 -0.023202 0.043702 0.926790 ... 1 True 1970-01-01 00:00:00.978393600
6 2.359782 0.088224 -0.676448 ... 1 True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724 ... 1 True 1970-01-01 00:00:00.978393600
[8 rows x 7 columns]
In [505]: df_mixed1.dtypes.value_counts()
Out[505]:
float64 2
float32 1
object 1
int64 1
bool 1
datetime64[ns] 1
Name: count, dtype: int64
# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]:
/df_mixed/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
"values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
"values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
"values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
"values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
"values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
byteorder := 'little'
chunkshape := (689,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
Хранение DataFrames с MultiIndex
Хранение DataFrames с MultiIndex в виде таблиц очень похоже на хранение/выбор из однородного индекса DataFrames.
In [507]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: names=["foo", "bar"],
.....: )
.....:
In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [509]: df_mi
Out[509]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
In [510]: store.append("df_mi", df_mi)
In [511]: store.select("df_mi")
Out[511]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]:
A B C
foo bar
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
Примечание
Ключевое слово index зарезервировано и не может использоваться в качестве имени уровня.
Запросы
Запрос таблицы
select и delete операции имеют необязательный критерий, который можно указать, чтобы выбрать/удалить только подмножество данных. Это позволяет иметь очень большую таблицу на диске и извлекать только часть данных.
Запрос задаётся с помощью класса Term внутри, как булево выражение.
indexиcolumnsявляются поддерживаемыми индексаторами дляDataFrames.Если
data_columnsуказаны, они могут использоваться в качестве дополнительных индексаторов.Имя уровня в MultiIndex, с именем по умолчанию
level_0,level_1, … если не указано.
Допустимые операторы сравнения:
=, ==, !=, >, >=, <, <=
Допустимые булевы выражения комбинируются с:
|: или&: и(и): для группировки
Эти правила похожи на то, как булевы выражения используются в pandas для индексации.
Примечание
=будет автоматически расширено до оператора сравнения==~— это оператор не, но может использоваться только в очень ограниченных случаяхЕсли передан список/кортеж выражений, они будут объединены через
&
Вот допустимые выражения:
'index >= date'"columns = ['A', 'D']""columns in ['A', 'D']"'columns = A''columns == A'"~(columns = ['A', 'B'])"'index > df.index[3] & string = "bar"''(index > df.index[3] & index <= df.index[6]) | string = "bar"'"ts >= Timestamp('2012-02-01')""major_axis>=20130101"
indexers находятся слева от подвыражения:
columns, major_axis, ts
Правая часть подвыражения (после оператора сравнения) может быть:
функциями, которые будут вычислены, например,
Timestamp('2012-02-01')строками, например,
"bar"типами данных, подобными дате, например,
20130101или"20130101"списками, например,
"['A', 'B']"переменными, определёнными в локальном пространстве имён, например,
date
Примечание
Передача строки в запрос путём её интерполяции в выражение запроса не рекомендуется. Просто присвойте интересующую строку переменной и используйте эту переменную в выражении. Например, сделайте так
string = "HolyMoly'"
store.select("df", "index == string")
вместо этого
string = "HolyMoly'"
store.select('df', f'index == {string}')
Последнее не будет работать и вызовет SyntaxError. Обратите внимание, что в переменной string есть одинарная кавычка, за которой следует двойная кавычка.
Если вам необходимо выполнить интерполяцию, используйте спецификатор формата '%r'
store.select("df", "index == %r" % string)
который процитирует string.
Вот некоторые примеры:
In [513]: dfq = pd.DataFrame(
.....: np.random.randn(10, 4),
.....: columns=list("ABCD"),
.....: index=pd.date_range("20130101", periods=10),
.....: )
.....:
In [514]: store.append("dfq", dfq, format="table", data_columns=True)
Используйте булевы выражения с вычислением функций в строке.
In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]:
A B
2013-01-05 -0.830545 -0.457071
2013-01-06 0.431186 1.049421
2013-01-07 0.617509 -0.811230
2013-01-08 0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10 0.361428 0.887304
Используйте ссылку на столбец в строке.
In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]:
A B C D
2013-01-02 0.658179 0.362814 -0.917897 0.010165
2013-01-03 0.905122 1.848731 -1.184241 0.932053
2013-01-05 -0.830545 -0.457071 1.565581 1.148032
2013-01-06 0.431186 1.049421 0.383309 0.595013
2013-01-07 0.617509 -0.811230 -2.088563 -1.393500
2013-01-08 0.947422 -0.671233 -0.847097 -1.187785
2013-01-10 0.361428 0.887304 0.266457 -0.399641
Ключевое слово columns может быть предоставлено для выбора списка столбцов, которые будут возвращены, это эквивалентно передаче 'columns=list_of_columns_to_filter':
In [517]: store.select("df", "columns=['A', 'B']")
Out[517]:
A B
2000-01-01 0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03 0.816983 1.965656
2000-01-04 0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06 1.103675 1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977 0.465222
start и stop параметры могут быть указаны для ограничения общего пространства поиска. Они относятся к общему количеству строк в таблице.
Примечание
select вызовет ValueError, если в выражении запроса есть ссылка на неизвестную переменную. Обычно это означает, что вы пытаетесь выбрать столбец, который не является столбцом данных.
select вызовет SyntaxError, если выражение запроса неверно.
Запрос timedelta64[ns]
Вы можете хранить и запрашивать с помощью типа timedelta64[ns]. Условия могут быть указаны в формате: <float>(<unit>), где число с плавающей точкой может быть со знаком (и дробным), а единица может быть D,s,ms,us,ns для timedelta. Вот пример:
In [518]: from datetime import timedelta
In [519]: dftd = pd.DataFrame(
.....: {
.....: "A": pd.Timestamp("20130101"),
.....: "B": [
.....: pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
.....: for i in range(10)
.....: ],
.....: }
.....: )
.....:
In [520]: dftd["C"] = dftd["A"] - dftd["B"]
In [521]: dftd
Out[521]:
A B C
0 2013-01-01 2013-01-01 00:00:10 -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10 -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10 -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10 -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10 -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10 -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10 -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10 -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10 -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50
In [522]: store.append("dftd", dftd, data_columns=True)
In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]:
A B C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10 -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10 -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10 -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10 -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10 -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50
Запрос MultiIndex
Выбор из MultiIndex может быть осуществлен с помощью имени уровня.
In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])
In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]:
A B C
foo bar
baz two -1.646063 -0.695847 -0.429156
Если имена уровней MultiIndex, уровни автоматически становятся доступными через ключевое слово level_n с n уровня, из которого вы хотите выбрать MultiIndex.
In [526]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: )
.....:
In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [528]: df_mi_2
Out[528]:
A B C
foo one -0.219582 1.186860 -1.437189
two 0.053768 1.872644 -1.469813
three -0.564201 0.876341 0.407749
bar one -0.232583 0.179812 0.922152
two -1.820952 -0.641360 2.133239
baz two -0.941248 -0.136307 -1.271305
three -0.099774 -0.061438 -0.845172
qux one 0.465793 0.756995 -0.541690
two -0.802241 0.877657 -2.553831
three 0.094899 -2.319519 0.293601
In [529]: store.append("df_mi_2", df_mi_2)
# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]:
A B C
foo two 0.053768 1.872644 -1.469813
Индексирование
Вы можете создать/изменить индекс для таблицы с помощью create_table_index после того, как данные уже находятся в таблице (после операции append/put). Создание индекса таблицы настоятельно рекомендуется. Это значительно ускорит ваши запросы, когда вы используете select с индексируемым измерением в качестве where.
Примечание
Индексы автоматически создаются для индексируемых и любых указанных вами столбцов данных. Это поведение можно отключить, передав index=False в append.
# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index
In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')
# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")
In [534]: i = store.root.df.table.cols.index.index
In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')
Часто при добавлении больших объемов данных в хранилище полезно отключить создание индекса для каждого добавления, а затем пересоздать его в конце.
In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [538]: st = pd.HDFStore("appends.h5", mode="w")
In [539]: st.append("df", df_1, data_columns=["B"], index=False)
In [540]: st.append("df", df_2, data_columns=["B"], index=False)
In [541]: st.get_storer("df").table
Out[541]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
Затем создайте индекс после завершения добавления.
In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")
In [543]: st.get_storer("df").table
Out[543]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
autoindex := True
colindexes := {
"B": Index(9, fullshuffle, zlib(1)).is_csi=True}
In [544]: st.close()
См. здесь, как создать полностью отсортированный индекс (CSI) в существующем хранилище.
Запрос по столбцам данных
Вы можете назначить (и индексировать) определенные столбцы, для которых вы хотите выполнять запросы (кроме столбцов indexable, которые вы всегда можете запросить). Например, предположим, что вы хотите выполнить эту распространенную операцию в файле и вернуть только фрейм, который соответствует этому запросу. Вы можете указать data_columns = True, чтобы заставить все столбцы быть data_columns.
In [545]: df_dc = df.copy()
In [546]: df_dc["string"] = "foo"
In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan
In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"
In [549]: df_dc["string2"] = "cool"
In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0
In [551]: df_dc
Out[551]:
A B C string string2
2000-01-01 0.858644 -0.851236 1.058006 foo cool
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-04 0.712795 -0.062433 0.736755 foo cool
2000-01-05 -0.298721 -1.988045 1.475308 NaN cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-07 -0.729161 -0.142928 -1.063038 foo cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
In [553]: store.select("df_dc", where="B > 0")
Out[553]:
A B C string string2
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]:
/df_dc/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2),
"C": Float64Col(shape=(), dflt=0.0, pos=3),
"string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
"string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
byteorder := 'little'
chunkshape := (1680,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}
Существует некоторое снижение производительности при преобразовании большого количества столбцов в data columns, поэтому пользователю предоставляется возможность назначить их. Кроме того, вы не можете изменить столбцы данных (ни индексируемые) после первой операции добавления/записи (Конечно, вы можете просто считать данные и создать новую таблицу!).
Итератор
Вы можете передать iterator=True или chunksize=number_in_a_chunk в select и select_as_multiple, чтобы вернуть итератор по результатам. По умолчанию возвращается 50 000 строк в блоке.
In [557]: for df in store.select("df", chunksize=3):
.....: print(df)
.....:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
A B C
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
A B C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
Примечание
Вы также можете использовать итератор с read_hdf, который откроет, а затем автоматически закроет хранилище по завершении итерации.
for df in pd.read_hdf("store.h5", "df", chunksize=3):
print(df)
Обратите внимание, что ключевое слово chunksize относится к строкам источника. Таким образом, если вы выполняете запрос, chunksize разделит общее количество строк в таблице и примененный запрос, возвращая итератор по потенциально разным по размеру блокам.
Вот рецепт для генерации запроса и его использования для создания блоков возврата равного размера.
In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})
In [559]: dfeq
Out[559]:
number
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
In [560]: store.append("dfeq", dfeq, data_columns=["number"])
In [561]: def chunks(l, n):
.....: return [l[i: i + n] for i in range(0, len(l), n)]
.....:
In [562]: evens = [2, 4, 6, 8, 10]
In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")
In [564]: for c in chunks(coordinates, 2):
.....: print(store.select("dfeq", where=c))
.....:
number
1 2
3 4
number
5 6
7 8
number
9 10
Расширенные запросы
Выбор одного столбца
Для извлечения одного индексируемого или данных столбца используйте метод select_column. Это позволит, например, очень быстро получить индекс. Эти методы возвращают Series результата, индексированного по номеру строки. В настоящее время они не поддерживают селектор where.
In [565]: store.select_column("df_dc", "index")
Out[565]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
3 2000-01-04
4 2000-01-05
5 2000-01-06
6 2000-01-07
7 2000-01-08
Name: index, dtype: datetime64[ns]
In [566]: store.select_column("df_dc", "string")
Out[566]:
0 foo
1 foo
2 foo
3 foo
4 NaN
5 NaN
6 foo
7 bar
Name: string, dtype: object
Выбор координат
Иногда вам нужно получить координаты (т.е. местоположения индексов) вашего запроса. Это возвращает Index результирующих местоположений. Эти координаты также могут быть переданы в последующие операции where.
In [567]: df_coord = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [568]: store.append("df_coord", df_coord)
In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")
In [570]: c
Out[570]:
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
...
990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
dtype='int64', length=268)
In [571]: store.select("df_coord", where=c)
Out[571]:
0 1
2002-01-02 0.007717 1.168386
2002-01-03 0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208 1.260503
... ... ...
2002-09-22 -1.139583 0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24 0.059018 1.775482
2002-09-25 1.242255 -0.055457
2002-09-26 0.410317 2.194489
[268 rows x 2 columns]
Выбор с помощью маски where
Иногда ваш запрос может включать создание списка строк для выбора. Обычно этот mask будет результирующей index операции индексирования. Этот пример выбирает месяцы в datetimeindex, которые равны 5.
In [572]: df_mask = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [573]: store.append("df_mask", df_mask)
In [574]: c = store.select_column("df_mask", "index")
In [575]: where = c[pd.DatetimeIndex(c).month == 5].index
In [576]: store.select("df_mask", where=where)
Out[576]:
0 1
2000-05-01 1.479511 0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03 0.900321 0.049695
2000-05-04 0.614266 -1.077151
2000-05-05 0.233881 0.493246
... ... ...
2002-05-27 0.294122 0.457407
2002-05-28 -1.102535 1.215650
2002-05-29 -0.432911 0.753606
2002-05-30 -1.105212 2.311877
2002-05-31 2.567296 2.610691
[93 rows x 2 columns]
Объект Storer
Если вы хотите проверить сохранённый объект, получите его через get_storer. Вы можете использовать это программно, например, для получения количества строк в объекте.
In [577]: store.get_storer("df_dc").nrows
Out[577]: 8
Запросы к нескольким таблицам
Методы append_to_multiple и select_as_multiple могут выполнять добавление/выбор из нескольких таблиц одновременно. Идея состоит в том, чтобы иметь одну таблицу (назовём её таблицей-селектором), по которой вы индексируете большинство/все столбцы и выполняете свои запросы. Другие таблицы — это таблицы данных с индексом, соответствующим индексу таблицы-селектора. Затем вы можете выполнить очень быстрый запрос к таблице-селектору, получив при этом много данных. Этот метод аналогичен наличию очень широкой таблицы, но обеспечивает более эффективные запросы.
Метод append_to_multiple разбивает заданный единственный DataFrame на несколько таблиц в соответствии с d, словарем, который сопоставляет имена таблиц со списком «столбцов», которые вы хотите в этой таблице. Если None используется вместо списка, в этой таблице будут оставшиеся не указанные столбцы из заданного DataFrame. Аргумент selector определяет, какая таблица является таблицей-селектором (из которой вы можете выполнять запросы). Аргумент dropna удалит строки из входного DataFrame, чтобы обеспечить синхронизацию таблиц. Это означает, что если строка для одной из таблиц, в которые производится запись, полностью np.nan, эта строка будет удалена из всех таблиц.
Если dropna имеет значение False, ПОЛЬЗОВАТЕЛЬ НЕСУЩ ЗАВИСИМОСТЬ ОТ СИНХРОНИЗАЦИИ ТАБЛИЦ. Помните, что строки, полностью np.Nan, не записываются в HDFStore. Если вы выберете вызвать dropna=False, некоторые таблицы могут иметь больше строк, чем другие, и поэтому select_as_multiple может не сработать или вернёт неожиданные результаты.
In [578]: df_mt = pd.DataFrame(
.....: np.random.randn(8, 6),
.....: index=pd.date_range("1/1/2000", periods=8),
.....: columns=["A", "B", "C", "D", "E", "F"],
.....: )
.....:
In [579]: df_mt["foo"] = "bar"
In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan
# you can also create the tables individually
In [581]: store.append_to_multiple(
.....: {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
.....: )
.....:
In [582]: store
Out[582]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# individual tables were created
In [583]: store.select("df1_mt")
Out[583]:
A B
2000-01-01 0.162291 -0.430489
2000-01-02 NaN NaN
2000-01-03 0.429207 -1.099274
2000-01-04 1.869081 -1.466039
2000-01-05 0.092130 -1.726280
2000-01-06 0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342 0.557402
In [584]: store.select("df2_mt")
Out[584]:
C D E F foo
2000-01-01 -2.502042 0.668149 0.460708 1.834518 bar
2000-01-02 0.130441 -0.608465 0.439872 0.506364 bar
2000-01-03 -1.069546 1.236277 0.116634 -1.772519 bar
2000-01-04 0.137462 0.313939 0.748471 -0.943009 bar
2000-01-05 0.836517 2.049798 0.562167 0.189952 bar
2000-01-06 1.112750 -0.151596 1.503311 0.939470 bar
2000-01-07 -0.294348 0.335844 -0.794159 1.495614 bar
2000-01-08 0.860312 -0.538674 -0.541986 -1.759606 bar
# as a multiple
In [585]: store.select_as_multiple(
.....: ["df1_mt", "df2_mt"],
.....: where=["A>0", "B>0"],
.....: selector="df1_mt",
.....: )
.....:
Out[585]:
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []
Удаление из таблицы
Вы можете удалить из таблицы выборочно, указав where. При удалении строк важно понимать, что PyTables удаляет строки, удаляя их, а затем перемещая последующие данные. Таким образом, удаление может быть очень дорогостоящей операцией в зависимости от ориентации ваших данных. Для достижения оптимальной производительности желательно, чтобы удаляемый размер был первым в indexables.
Данные упорядочены (на диске) с точки зрения indexables. Вот простой пример. Вы храните данные типа "панель", с датами в major_axis и идентификаторами в minor_axis. Данные затем чередуются следующим образом:
-
- date_1
-
id_1
id_2
.
id_n
-
- date_2
-
id_1
.
id_n
Должно быть ясно, что операция удаления по major_axis будет относительно быстрой, так как один блок удаляется, а затем перемещаются последующие данные. С другой стороны, операция удаления по minor_axis будет очень дорогостоящей. В этом случае было бы практически быстрее переписать таблицу, используя where, который выбирает все, кроме отсутствующих данных.
Предупреждение
Обратите внимание, что HDF5 НЕ ВОЗВРАЩАЕТ ПАМЯТЬ в файлах h5 автоматически. Таким образом, многократное удаление (или удаление узлов) и повторное добавление БУДЕТ ВЕСТИ К УВЕЛИЧЕНИЮ РАЗМЕРА ФАЙЛА.
Для переупаковки и очистки файла используйте ptrepack.
Примечания и замечания
Сжатие
PyTables позволяет сжимать хранимые данные. Это относится ко всем видам хранилищ, а не только к таблицам. Для управления сжатием используются два параметра: complevel и complib.
complevelуказывает, как и насколько сильно сжимать данные.complevel=0иcomplevel=Noneотключают сжатие, а0<complevel<10включает сжатие.-
complibуказывает, какую библиотеку сжатия использовать. Если ничего не указано, используется библиотека по умолчаниюzlib. Библиотека сжатия обычно оптимизирована для достижения высокой степени сжатия или скорости, а результаты зависят от типа данных. Выбор типа сжатия зависит от ваших конкретных потребностей и данных. Список поддерживаемых библиотек сжатия:zlib: Библиотека сжатия по умолчанию. Классическая библиотека сжатия, достигающая высокой степени сжатия, но несколько медленная.
lzo: Быстрое сжатие и распаковку.
bzip2: Высокая степень сжатия.
-
blosc: Быстрое сжатие и распаковку.
Поддержка альтернативных сжимающих алгоритмов blosc:
blosc:blosclz Это сжимающий алгоритм по умолчанию для
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 базовым типом данных. Это означает, что следующие типы данных работают:
Тип | Представляет пропущенные значения |
|---|---|
floating : |
|
integer : | |
boolean | |
|
|
|
|
categorical : см. раздел ниже | |
object : |
|
Столбцы unicode не поддерживаются и БУДУТ ВЫЗЫВАТЬ ОШИБКУ.
Данные категорий
Вы можете записывать данные, содержащие category типы данных в HDFStore. Запросы работают так же, как если бы это был массив object. Однако данные с типом category хранятся более эффективно.
In [586]: dfcat = pd.DataFrame(
.....: {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
.....: )
.....:
In [587]: dfcat
Out[587]:
A B
0 a -1.520478
1 a -1.069391
2 b -0.551981
3 b 0.452407
4 c 0.409257
5 d 0.301911
6 b -0.640843
7 a -2.253022
In [588]: dfcat.dtypes
Out[588]:
A category
B float64
dtype: object
In [589]: cstore = pd.HDFStore("cats.h5", mode="w")
In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])
In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")
In [592]: result
Out[592]:
A B
2 b -0.551981
3 b 0.452407
4 c 0.409257
6 b -0.640843
In [593]: result.dtypes
Out[593]:
A category
B float64
dtype: object
Столбцы строк
min_itemsize
Внутренняя реализация HDFStore использует фиксированную ширину столбца (itemsize) для столбцов строк. Размер строки столбца рассчитывается как максимальная длина данных (для данного столбца), переданных в HDFStore, при первом добавлении. При последующих добавлениях, может быть добавлена строка для столбца, больше, чем может вместить столбец, будет поднято исключение (иначе у вас может быть беззвучное усечение этих столбцов, что приведет к потере информации). В будущем мы можем это ослабить и разрешить пользователю задать усечение.
Передайте min_itemsize при первом создании таблицы, чтобы заранее указать минимальную длину определенного столбца строк. min_itemsize может быть целым числом или словарем, сопоставляющим имя столбца с целым числом. Вы можете передать values в качестве ключа, чтобы разрешить всем индексируемым или данным_столбцам иметь этот min_itemsize.
Передача словаря min_itemsize приведет к автоматическому созданию всех переданных столбцов как данных_столбцов.
Примечание
Если вы не передаете никаких data_columns, то min_itemsize будет максимальной длиной любой переданной строки.
In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))
In [595]: dfs
Out[595]:
A B
0 foo bar
1 foo bar
2 foo bar
3 foo bar
4 foo bar
# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)
In [597]: store.get_storer("dfs").table
Out[597]:
/dfs/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
byteorder := 'little'
chunkshape := (963,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})
In [599]: store.get_storer("dfs2").table
Out[599]:
/dfs2/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
"A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
byteorder := 'little'
chunkshape := (1598,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"A": Index(6, mediumshuffle, zlib(1)).is_csi=False}
nan_rep
Столбцы строк будут сериализовать np.nan (пропущенное значение) с представлением строки nan_rep. По умолчанию это строковое значение nan. Вы можете случайно преобразовать фактическое nan значение в пропущенное значение.
In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})
In [601]: dfss
Out[601]:
A
0 foo
1 bar
2 nan
In [602]: store.append("dfss", dfss)
In [603]: store.select("dfss")
Out[603]:
A
0 foo
1 bar
2 NaN
# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")
In [605]: store.select("dfss2")
Out[605]:
A
0 foo
1 bar
2 nan
Производительность
Формат
tablesимеет штраф производительности при записи по сравнению с хранилищамиfixed. Преимущество заключается в возможности добавления/удаления и запроса (возможно, очень больших объемов данных). Время записи, как правило, больше по сравнению с обычными хранилищами. Время запроса может быть очень быстрым, особенно по индексированной оси.Вы можете передать
chunksize=<int>вappend, указав размер куска записи (по умолчанию 50000). Это значительно снизит использование памяти при записи.Вы можете передать
expectedrows=<int>в первыйappend, чтобы установить ОБЩЕЕ количество строк, которыеPyTablesожидает. Это позволит оптимизировать производительность чтения/записи.В таблицы можно записать дубликаты строк, но они будут отфильтрованы при выборе (выбираются последние элементы; таким образом, таблица уникальна по парам «главный-второстепенный»).
Будет поднято исключение
PerformanceWarning, если вы пытаетесь сохранить типы, которые будут сериализованы PyTables (а не сохранены как собственные типы). См. здесь для получения дополнительной информации и некоторых решений.
Feather
Feather предоставляет двоичную столбцовую сериализацию для фреймов данных. Она разработана для повышения эффективности чтения и записи фреймов данных и упрощения обмена данными между языками анализа данных.
Feather разработан для точной сериализации и десериализации DataFrames, поддерживая все типы данных pandas, включая расширенные типы, такие как категориальные и datetime с часовыми поясами.
Некоторые особенности:
Формат НЕ запишет
IndexилиMultiIndexдляDataFrameи выдаст ошибку, если предоставлен не по умолчанию. Вы можете.reset_index()для сохранения индекса или.reset_index(drop=True)для его игнорирования.Не поддерживаются дублирующие имена столбцов и имена столбцов, не являющиеся строками.
Фактические объекты Python в столбцах типа object не поддерживаются. При попытке сериализации они вызовут полезное сообщение об ошибке.
См. Полную документацию.
In [606]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.Categorical(list("abc")),
.....: "g": pd.date_range("20130101", periods=3),
.....: "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "i": pd.date_range("20130101", periods=3, freq="ns"),
.....: }
.....: )
.....:
In [607]: df
Out[607]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
In [608]: df.dtypes
Out[608]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
Запись в файл feather.
In [609]: df.to_feather("example.feather")
Чтение из файла feather.
In [610]: result = pd.read_feather("example.feather")
In [611]: result
Out[611]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
# we preserve dtypes
In [612]: result.dtypes
Out[612]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
Parquet
Apache Parquet предоставляет двоичное колоночное разбиение данных для кадров данных. Он разработан для повышения эффективности чтения и записи кадров данных, а также для облегчения совместного использования данных между языками анализа данных. Parquet может использовать различные методы сжатия, чтобы максимально уменьшить размер файла, сохраняя при этом хорошую производительность чтения.
Parquet разработан для точного сериализации и десериализации DataFrame, поддерживая все типы данных pandas, включая расширенные типы данных, такие как datetime с часовым поясом.
Несколько замечаний.
Дублирование имен столбцов и имена столбцов, не являющиеся строками, не поддерживаются.
pyarrowдвижок всегда записывает индекс в выходные данные, ноfastparquetзаписывает только индексы, отличные от значения по умолчанию. Этот дополнительный столбец может создавать проблемы для потребителей, не являющихся pandas, которые не ожидают его. Вы можете принудительно включить или исключить индексы с помощью аргументаindex, независимо от базового движка.Имена уровней индекса, если указаны, должны быть строками.
В
pyarrowдвижке типы данных категорий для типов данных, не являющихся строками, могут быть сериализованы в parquet, но десериализуются как их примитивный тип данных.pyarrowдвижок сохраняет флагorderedтипов данных категорий со строковыми типами.fastparquetне сохраняет флагordered.Неподдерживаемые типы включают
Intervalи фактические типы Python-объектов. Эти попытки сериализации приведут к полезному сообщению об ошибке.Periodтип поддерживается с pyarrow >= 0.16.0.pyarrowдвижок сохраняет расширенные типы данных, такие как нулевой целочисленный и строковый тип данных (требуется pyarrow >= 0.16.0 и требуются расширенные типы для реализации необходимых протоколов, см. документацию по расширенным типам).
Вы можете указать engine для направления сериализации. Это может быть одно из pyarrow, или fastparquet, или auto. Если движок НЕ указан, то проверяется опция pd.options.io.parquet.engine; если она также auto, то используется pyarrow, а в качестве последнего варианта используется fastparquet.
См. документацию для pyarrow и fastparquet.
Примечание
Эти движки очень похожи и должны читать/записывать практически идентичные файлы формата parquet. pyarrow>=8.0.0 поддерживает данные timedelta, fastparquet>=0.1.4 поддерживает даты и время с учетом часового пояса. Эти библиотеки отличаются наличием различных зависимостей (fastparquet с использованием numba, а pyarrow использует библиотеку c).
In [613]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.date_range("20130101", periods=3),
.....: "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "h": pd.Categorical(list("abc")),
.....: "i": pd.Categorical(list("abc"), ordered=True),
.....: }
.....: )
.....:
In [614]: df
Out[614]:
a b c d e f g h i
0 a 1 3 4.0 True 2013-01-01 2013-01-01 00:00:00-05:00 a a
1 b 2 4 5.0 False 2013-01-02 2013-01-02 00:00:00-05:00 b b
2 c 3 5 6.0 True 2013-01-03 2013-01-03 00:00:00-05:00 c c
In [615]: df.dtypes
Out[615]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
Запись в файл parquet.
In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")
In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")
Чтение из файла parquet.
In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")
In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")
In [620]: result.dtypes
Out[620]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
Установив аргумент dtype_backend, вы можете контролировать значения по умолчанию типов данных, используемых для результирующего DataFrame.
In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")
In [622]: result.dtypes
Out[622]:
a string[pyarrow]
b int64[pyarrow]
c uint8[pyarrow]
d double[pyarrow]
e bool[pyarrow]
f timestamp[ns][pyarrow]
g timestamp[ns, tz=US/Eastern][pyarrow]
h dictionary<values=string, indices=int32, order...
i dictionary<values=string, indices=int32, order...
dtype: object
Примечание
Обратите внимание, что это не поддерживается для fastparquet.
Чтение только определенных столбцов файла parquet.
In [623]: result = pd.read_parquet(
.....: "example_fp.parquet",
.....: engine="fastparquet",
.....: columns=["a", "b"],
.....: )
.....:
In [624]: result = pd.read_parquet(
.....: "example_pa.parquet",
.....: engine="pyarrow",
.....: columns=["a", "b"],
.....: )
.....:
In [625]: result.dtypes
Out[625]:
a object
b int64
dtype: object
Обработка индексов
Сериализация DataFrame в parquet может включать неявный индекс в качестве одного или нескольких столбцов в выходном файле. Таким образом, этот код:
In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [627]: df.to_parquet("test.parquet", engine="pyarrow")
создаёт файл parquet с тремя столбцами, если вы используете pyarrow для сериализации: a, b и __index_level_0__. Если вы используете fastparquet, индекс может или не может быть записан в файл.
Этот неожиданный дополнительный столбец приводит к тому, что некоторые базы данных, такие как Amazon Redshift, отклоняют файл, потому что этот столбец отсутствует в целевой таблице.
Если вы хотите исключить индексы DataFrame при записи, передайте index=False в to_parquet():
In [628]: df.to_parquet("test.parquet", index=False)
Это создаёт файл parquet только с двумя ожидаемыми столбцами, a и b. Если у вашего DataFrame есть пользовательский индекс, вы его не получите при загрузке этого файла в DataFrame.
Передача index=True всегда запишет индекс, даже если это не является стандартным поведением для используемого движка.
Разбиение файлов Parquet
Parquet поддерживает разбиение данных на основе значений одного или нескольких столбцов.
In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})
In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)
path указывает родительскую директорию, в которую будут сохранены данные. partition_cols — это имена столбцов, по которым будет происходить разбиение набора данных. Столбцы разбиваются в порядке их указания. Разбиения на основе партиций определяются уникальными значениями в столбцах партиций. Приведённый пример создаёт разбиение набора данных, которое может выглядеть следующим образом:
test
├── a=0
│ ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│ └── ...
└── a=1
├── e6ab24a4f45147b49b54a662f0c412a3.parquet
└── ...
ORC
Аналогично формату parquet, формат ORC — это двоичная колоночная сериализация для фреймов данных. Он разработан для повышения эффективности чтения фреймов данных. pandas предоставляет как читатель, так и записыватель для формата ORC, read_orc() и to_orc(). Это требует библиотеки pyarrow.
Предупреждение
Сильно рекомендуется устанавливать pyarrow с помощью conda из-за некоторых проблем, возникающих с pyarrow.
to_orc()требует pyarrow>=7.0.0.read_orc()иto_orc()пока не поддерживаются в Windows, вы можете найти подходящие среды на странице установки дополнительных зависимостей.Для поддерживаемых типов данных обратитесь к поддерживаемым функциям ORC в Arrow.
В настоящее время часовые пояса в столбцах datetime не сохраняются при преобразовании DataFrame в файлы ORC.
In [631]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(4.0, 7.0, dtype="float64"),
.....: "d": [True, False, True],
.....: "e": pd.date_range("20130101", periods=3),
.....: }
.....: )
.....:
In [632]: df
Out[632]:
a b c d e
0 a 1 4.0 True 2013-01-01
1 b 2 5.0 False 2013-01-02
2 c 3 6.0 True 2013-01-03
In [633]: df.dtypes
Out[633]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
Запись в файл orc.
In [634]: df.to_orc("example_pa.orc", engine="pyarrow")
Чтение из файла orc.
In [635]: result = pd.read_orc("example_pa.orc")
In [636]: result.dtypes
Out[636]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
Чтение только определённых столбцов из файла orc.
In [637]: result = pd.read_orc(
.....: "example_pa.orc",
.....: columns=["a", "b"],
.....: )
.....:
In [638]: result.dtypes
Out[638]:
a object
b int64
dtype: object
SQL-запросы
Модуль pandas.io.sql предоставляет набор обёртки для запросов, чтобы облегчить получение данных и снизить зависимость от API конкретной базы данных.
В случае возможности пользователи могут сначала выбрать драйверы Apache Arrow ADBC. Эти драйверы должны обеспечить наилучшую производительность, обработку значений NULL и определение типов.
В версии 2.2.0: Добавлена нативная поддержка драйверов ADBC
Полный список драйверов ADBC и их состояние разработки можно найти в документации ADBC Driver Implementation Status.
Если драйвер ADBC недоступен или имеет недостающую функциональность, пользователи должны установить SQLAlchemy вместе с библиотекой драйвера их базы данных. Примерами таких драйверов являются psycopg2 для PostgreSQL или pymysql для MySQL. Для SQLite это включено в стандартную библиотеку Python по умолчанию. Обзор поддерживаемых драйверов для каждого диалекта SQL можно найти в документации SQLAlchemy.
Если SQLAlchemy не установлен, вы можете использовать sqlite3.Connection вместо объекта SQLAlchemy engine, connection или строки URI.
См. также некоторые примеры из справочника по рецептам для некоторых продвинутых стратегий.
Основные функции:
| Чтение таблицы 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() для создания объекта engine из URI базы данных. Вам нужно создать engine только один раз на каждую базу данных, к которой вы подключаетесь. Дополнительную информацию о create_engine() и формате URI см. в примерах ниже и в документации SQLAlchemy documentation
In [639]: from sqlalchemy import create_engine
# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")
Если вы хотите управлять своими подключениями, вы можете передать один из них вместо этого. В примере ниже открывается подключение к базе данных с помощью менеджера контекста Python, который автоматически закрывает подключение после завершения блока. Дополнительную информацию о том, как обрабатывается подключение к базе данных, см. в документации SQLAlchemy.
with engine.connect() as conn, conn.begin():
data = pd.read_sql_table("data", conn)
Предупреждение
При открытии подключения к базе данных вы также несете ответственность за его закрытие. Последствия оставления подключения открытым могут включать блокировку базы данных или другие нарушения работы.
Запись DataFrames
Предполагая, что следующие данные находятся в DataFrame data, мы можем вставить их в базу данных с помощью to_sql().
id | Дата | Col_1 | Col_2 | Col_3 |
|---|---|---|---|---|
26 | 2012-10-18 | X | 25.7 | True |
42 | 2012-10-19 | Y | -12.4 | False |
63 | 2012-10-20 | Z | 5.73 | True |
In [641]: import datetime
In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]
In [643]: d = [
.....: (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
.....: (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
.....: (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
.....: ]
.....:
In [644]: data = pd.DataFrame(d, columns=c)
In [645]: data
Out[645]:
id Date Col_1 Col_2 Col_3
0 26 2010-10-18 X 27.50 True
1 42 2010-10-19 Y -12.50 False
2 63 2010-10-20 Z 5.73 True
In [646]: data.to_sql("data", con=engine)
Out[646]: 3
В некоторых базах данных запись больших DataFrames может привести к ошибкам из-за превышения ограничений размера пакетов. Этого можно избежать, установив параметр chunksize при вызове to_sql. Например, следующее записывает data в базу данных порциями по 1000 строк за раз:
In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3
Типы данных SQL
Обеспечение согласованного управления типами данных в базах данных SQL сложно. Не каждая база данных SQL предлагает одинаковые типы, и даже когда они это делают, реализация заданного типа может различаться таким образом, что оказывает тонкое влияние на то, как типы могут быть сохранены.
Для наилучшего сохранения типов базы данных пользователям рекомендуется использовать драйверы ADBC, когда они доступны. Система типов Arrow предлагает более широкий спектр типов, которые более точно соответствуют типам баз данных, чем историческая система типов pandas/NumPy. Для иллюстрации, вот этот (неполный) список типов, доступных в разных базах данных и бэкендах pandas:
numpy/pandas | arrow | postgres | sqlite |
|---|---|---|---|
int16/Int16 | int16 | SMALLINT | INTEGER |
int32/Int32 | int32 | INTEGER | INTEGER |
int64/Int64 | int64 | BIGINT | INTEGER |
float32 | float32 | REAL | REAL |
float64 | float64 | DOUBLE PRECISION | REAL |
object | string | TEXT | TEXT |
bool |
| 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 на основе типа данных данных. Когда у вас есть столбцы типа object, pandas будет пытаться определить тип данных.
Вы всегда можете переопределить значения по умолчанию, указав желаемый тип SQL для любого из столбцов с помощью аргумента dtype. Этот аргумент требует словарь, сопоставляющий имена столбцов со SQLAlchemy-типами (или строками для режима обратной совместимости sqlite3). Например, чтобы использовать тип sqlalchemy String вместо стандартного типа Text для строковых столбцов:
In [648]: from sqlalchemy.types import String
In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3
Примечание
Из-за ограниченной поддержки timedelta в разных вариантах баз данных, столбцы с типом timedelta64 будут записываться как целые значения в виде наносекунд в базу данных, и будет выведено предупреждение. Единственное исключение из этого правила — использование драйвера ADBC PostgreSQL, в этом случае timedelta будет записан в базу данных как INTERVAL.
Примечание
Столбцы типа category будут преобразованы в плотное представление, как вы получите с помощью np.asarray(categorical) (например, для категорий строк это даёт массив строк). Из-за этого при повторном чтении таблицы базы данных категориальная переменная не создаётся.
Типы данных DateTime
Используя ADBC или SQLAlchemy, to_sql() способен записывать данные типа DateTime, которые являются либо локальными, либо с учётом часового пояса. Однако, конечные данные, хранящиеся в базе данных, в конечном счёте зависят от поддерживаемого типа данных для данных DateTime используемой системы базы данных.
В следующей таблице перечислены поддерживаемые типы данных для данных DateTime для некоторых распространённых баз данных. Другие диалекты баз данных могут иметь различные типы данных для данных DateTime.
База данных | Типы данных SQL DateTime | Поддержка часовых поясов |
|---|---|---|
SQLite |
| Нет |
MySQL |
| Нет |
PostgreSQL |
| Да |
При записи данных с учётом часового пояса в базы данных, не поддерживающие часовые пояса, данные будут записаны как временные метки без учёта часового пояса, которые находятся в местном времени относительно часового пояса.
read_sql_table() также способен читать данные типа DateTime, которые являются либо с учётом часового пояса, либо без него. При чтении типов TIMESTAMP WITH TIME ZONE, pandas преобразует данные в UTC.
Метод вставки
Параметр method управляет используемой SQL-командой вставки. Возможные значения:
None: Использует стандартную SQL-командуINSERT(одна на строку).'multi': Передаёт несколько значений в одной командеINSERT. Она использует специальный синтаксис SQL, не поддерживаемый всеми бэкендами. Это обычно обеспечивает лучшую производительность для аналитических баз данных, таких как Presto и Redshift, но имеет худшую производительность для традиционных SQL-бэкендов, если таблица содержит много столбцов. Для получения дополнительной информации см. документацию SQLAlchemy документацию.вызываемый объект с сигнатурой
(pd_table, conn, keys, data_iter): Это можно использовать для реализации более производительного метода вставки, основанного на специфических функциях диалекта бэкенда.
Пример вызываемого объекта, использующего PostgreSQL команду COPY:
# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO
def psql_insert_copy(table, conn, keys, data_iter):
"""
Execute SQL statement inserting data
Parameters
----------
table : pandas.io.sql.SQLTable
conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
keys : list of str
Column names
data_iter : Iterable that iterates the values to be inserted
"""
# gets a DBAPI connection that can provide a cursor
dbapi_conn = conn.connection
with dbapi_conn.cursor() as cur:
s_buf = StringIO()
writer = csv.writer(s_buf)
writer.writerows(data_iter)
s_buf.seek(0)
columns = ', '.join(['"{}"'.format(k) for k in keys])
if table.schema:
table_name = '{}.{}'.format(table.schema, table.name)
else:
table_name = table.name
sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
table_name, columns)
cur.copy_expert(sql=sql, file=s_buf)
Чтение таблиц
read_sql_table() будет читать таблицу базы данных, учитывая имя таблицы и, необязательно, подмножество столбцов для чтения.
Примечание
Для использования read_sql_table(), необходимо установить драйвер ADBC или необязательную зависимость SQLAlchemy.
In [650]: pd.read_sql_table("data", engine)
Out[650]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
Примечание
Драйверы ADBC будут напрямую отображать типы базы данных на типы Arrow. Для других драйверов обратите внимание, что pandas определяет типы столбцов (dtypes) из выходных данных запроса, а не из схемы физической базы данных. Например, предположим, что userid — это столбец целого типа в таблице. Тогда, интуитивно, select userid ... вернёт серию целых чисел, а select cast(userid as text) ... вернёт серию с типом object (строка). Соответственно, если вывод запроса пуст, то все результирующие столбцы будут возвращены как серии с типом object (поскольку это самый общий случай). Если вы ожидаете, что ваш запрос иногда будет генерировать пустой результат, вы можете явно привести типы данных после, чтобы гарантировать целостность dtypes.
Вы также можете указать имя столбца в качестве DataFrame индекса и указать подмножество столбцов для чтения.
In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]:
index Date Col_1 Col_2 Col_3
id
26 0 2010-10-18 X 27.50 True
42 1 2010-10-19 Y -12.50 False
63 2 2010-10-20 Z 5.73 True
In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]:
Col_1 Col_2
0 X 27.50
1 Y -12.50
2 Z 5.73
И вы можете явно форсировать разбор столбцов как дат:
In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
При необходимости вы можете явно указать строку формата или словарь аргументов для передачи в pandas.to_datetime():
pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
"data",
engine,
parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)
Вы можете проверить, существует ли таблица, используя has_table()
Поддержка схем
Чтение из различных схем и запись в них поддерживаются через ключевое слово schema в функциях read_sql_table() и to_sql(). Однако следует отметить, что это зависит от типа базы данных (в sqlite нет схем). Например:
df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")
Запросы
Вы можете выполнять запросы с помощью простого SQL в функции read_sql_query(). В этом случае вы должны использовать соответствующий вариант SQL для вашей базы данных. При использовании SQLAlchemy вы также можете передать конструкции языка выражений SQLAlchemy, которые независимы от базы данных.
In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.50 1
1 1 42 2010-10-19 00:00:00.000000 Y -12.50 0
2 2 63 2010-10-20 00:00:00.000000 Z 5.73 1
Конечно, вы можете указать более «сложный» запрос.
In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]:
id Col_1 Col_2
0 42 Y -12.5
Функция read_sql_query() поддерживает аргумент chunksize. Указав его, вы получите итератор по частям результата запроса:
In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))
In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
.....: print(chunk)
.....:
a b c
0 -0.395347 -0.822726 -0.363777
1 1.676124 -0.908102 -1.391346
2 -1.094269 0.278380 1.205899
3 1.503443 0.932171 -0.709459
4 -0.645944 -1.351389 0.132023
a b c
0 0.210427 0.192202 0.661949
1 1.690629 -1.046044 0.618697
2 -0.013863 1.314289 1.951611
3 -1.485026 0.304662 1.194757
4 -0.446717 0.528496 -0.657575
a b c
0 -0.876654 0.336252 0.172668
1 0.337684 -0.411202 -0.828394
2 -0.244413 1.094948 0.087183
3 1.125934 -1.480095 1.205944
4 -0.451849 0.452214 -2.208192
a b c
0 -2.061019 0.044184 -0.017118
1 1.248959 -0.675595 -1.908296
2 -0.125934 1.491974 0.648726
3 0.391214 0.438609 1.634248
4 1.208707 -1.535740 1.620399
Примеры подключения к движку
Для подключения с помощью SQLAlchemy вы используете функцию create_engine() для создания объекта движка из URI базы данных. Вам нужно создать движок только один раз для каждой базы данных, к которой вы подключаетесь.
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")
engine = create_engine("oracle://scott:tiger@127.0.0.1:1521/sidname")
engine = create_engine("mssql+pyodbc://mydsn")
# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")
# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")
Для получения дополнительной информации см. примеры в документации SQLAlchemy документации
Расширенные запросы SQLAlchemy
Вы можете использовать конструкции SQLAlchemy для описания вашего запроса.
Используйте sqlalchemy.text() для указания параметров запроса нейтральным для бэкенда способом
In [659]: import sqlalchemy as sa
In [660]: pd.read_sql(
.....: sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
.....: )
.....:
Out[660]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.5 1
Если у вас есть описание базы данных в SQLAlchemy, вы можете выразить условия WHERE с помощью выражений SQLAlchemy
In [661]: metadata = sa.MetaData()
In [662]: data_table = sa.Table(
.....: "data",
.....: metadata,
.....: sa.Column("index", sa.Integer),
.....: sa.Column("Date", sa.DateTime),
.....: sa.Column("Col_1", sa.String),
.....: sa.Column("Col_2", sa.Float),
.....: sa.Column("Col_3", sa.Boolean),
.....: )
.....:
In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []
Вы можете комбинировать выражения SQLAlchemy с параметрами, передаваемыми в read_sql(), используя sqlalchemy.bindparam()
In [664]: import datetime as dt
In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))
In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]:
index Date Col_1 Col_2 Col_3
0 1 2010-10-19 Y -12.50 False
1 2 2010-10-20 Z 5.73 True
Поддержка Sqlite по умолчанию
Использование sqlite поддерживается без использования SQLAlchemy. Этот режим требует адаптер Python для работы с базами данных, соответствующий Python DB-API.
Вы можете создать подключения следующим образом:
import sqlite3
con = sqlite3.connect(":memory:")
А затем выполнить следующие запросы:
data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)
Google BigQuery
Пакет pandas-gbq предоставляет функциональность для чтения/записи из Google BigQuery.
pandas интегрируется с этим внешним пакетом. Если pandas-gbq установлен, вы можете использовать методы pandas pd.read_gbq и DataFrame.to_gbq, которые вызовут соответствующие функции из pandas-gbq.
Полную документацию можно найти здесь.
Формат Stata
Запись в формате Stata
Метод DataFrame.to_stata() запишет DataFrame в файл .dta. Версия формата этого файла всегда 115 (Stata 12).
In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [668]: df.to_stata("stata.dta")
Файлы данных Stata имеют ограниченную поддержку типов данных; только строки длиной 244 символа или меньше, int8, int16, int32, float32 и float64 могут быть сохранены в файлах .dta. Кроме того, Stata резервирует определённые значения для представления пропущенных данных. Экспорт значения, отличного от пропущенного, выходящего за пределы разрешённого диапазона в Stata для определённого типа данных, приведёт к повторному типу переменной на следующий больший размер. Например, значения int8 ограничены в Stata значениями от -127 до 100, поэтому переменные со значениями выше 100 вызовут преобразование к типу int16. Пропущенные значения nan в типах данных с плавающей запятой сохраняются как основной тип пропущенных данных (. в Stata).
Примечание
Экспорт пропущенных значений для целочисленных типов данных невозможен.
Модуль Stata для записи корректно обрабатывает другие типы данных, включая int64, bool, uint8, uint16, uint32, преобразуя их к наименьшему поддерживаемому типу, который может представлять данные. Например, данные с типом uint8 будут преобразованы к типу int8, если все значения меньше 100 (верхняя граница для значений, не являющихся пропущенными, int8 в Stata), или, если значения выходят за этот диапазон, переменная преобразуется к типу int16.
Предупреждение
Преобразование из int64 в float64 может привести к потере точности, если значения int64 больше, чем 2**53.
Предупреждение
StataWriter и DataFrame.to_stata() поддерживают только строки с фиксированной шириной, содержащие до 244 символов — ограничение, наложенное форматом файла dta версии 115. Попытка записать файлы Stata dta со строками длиной более 244 символов вызывает исключение ValueError.
Чтение из формата Stata
Функция верхнего уровня read_stata будет читать файл dta и возвращать либо DataFrame, либо pandas.api.typing.StataReader, которые могут использоваться для постепенного чтения файла.
In [669]: pd.read_stata("stata.dta")
Out[669]:
index A B
0 0 -0.165614 0.490482
1 1 -0.637829 0.067091
2 2 -0.242577 1.348038
3 3 0.647699 -0.644937
4 4 0.625771 0.918376
5 5 0.401781 -1.488919
6 6 -0.981845 -0.046882
7 7 -0.306796 0.877025
8 8 -0.336606 0.624747
9 9 -1.582600 0.806340
Указание chunksize приводит к экземпляру pandas.api.typing.StataReader, который может использоваться для чтения chunksize строк файла за раз. Объект StataReader может использоваться как итератор.
In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
.....: for df in reader:
.....: print(df.shape)
.....:
(3, 3)
(3, 3)
(3, 3)
(1, 3)
Для более точного управления используйте iterator=True и укажите chunksize при каждом вызове read().
In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
.....: chunk1 = reader.read(5)
.....: chunk2 = reader.read(5)
.....:
В настоящее время index извлекается как столбец.
Параметр convert_categoricals указывает, следует ли читать метки значений и использовать их для создания переменной Categorical из них. Метки значений также могут быть получены функцией value_labels, которая требует вызова read() перед использованием.
Параметр convert_missing указывает, следует ли сохранять представления пропущенных значений в Stata. Если False (по умолчанию), пропущенные значения представлены как np.nan. Если True, пропущенные значения представлены с помощью объектов StataMissingValue, и столбцы, содержащие пропущенные значения, будут иметь тип данных object.
Примечание
read_stata() и StataReader поддерживают форматы .dta 113-115 (Stata 10-12), 117 (Stata 13) и 118 (Stata 14).
Примечание
Указание preserve_dtypes=False приведет к повышению точности до стандартных типов данных pandas: int64 для всех целочисленных типов и float64 для данных с плавающей точкой. По умолчанию типы данных Stata сохраняются при импорте.
Примечание
Все объекты StataReader, созданные функцией read_stata() (при использовании iterator=True или chunksize) или созданные вручную, должны использоваться как управляющие контекстом (например, оператор with). Хотя метод close() доступен, его использование не поддерживается. Он не входит в общедоступный API и будет удален в будущих версиях без предупреждения.
Данные категорий
Данные Categorical могут быть экспортированы в файлы данных Stata в виде данных с метками значений. Экспортируемые данные состоят из кодов базовых категорий как целочисленных значений данных и категорий как меток значений. В Stata нет явного эквивалента Categorical, и информация о том, является ли переменная упорядоченной, теряется при экспорте.
Предупреждение
Stata поддерживает только строковые метки значений, поэтому при экспорте данных используется str по категориям. Экспорт переменных Categorical с категориями, отличными от строк, генерирует предупреждение и может привести к потере информации, если представления str категорий не уникальны.
Данные с метками аналогичным образом могут быть импортированы из файлов данных Stata как переменные Categorical с использованием ключевого аргумента convert_categoricals (True по умолчанию). Ключевой аргумент order_categoricals (True по умолчанию) определяет, являются ли импортированные переменные Categorical упорядоченными.
Примечание
При импорте категориальных данных значения переменных в файле данных Stata не сохраняются, поскольку переменные Categorical всегда используют целочисленные типы данных между -1 и n-1, где n — количество категорий. Если исходные значения в файле данных Stata требуются, их можно импортировать, установив convert_categoricals=False, что импортирует исходные данные (но не метки переменных). Исходные значения могут быть сопоставлены с импортированными категориальными данными, так как существует простое соответствие между исходными значениями данных Stata и кодами категорий импортированных категориальных переменных: пропущенным значениям присваивается код -1, наименьшему исходному значению присваивается 0, второму наименьшему — 1 и так далее, пока наибольшему исходному значению не будет присвоен код n-1.
Примечание
Stata поддерживает частично помеченные ряды. Эти ряды имеют метки значений для некоторых, но не для всех значений данных. Импорт частично помеченного ряда создаст Categorical со строковыми категориями для помеченных значений и числовыми категориями для значений без метки.
Форматы SAS
Функция верхнего уровня read_sas() может читать (но не записывать) файлы формата SAS XPORT (.xpt) и SAS7BDAT (.sas7bdat).
Файлы SAS содержат только два типа значений: текстовые ASCII и значения с плавающей запятой (обычно 8 байт, но иногда усеченные). Для файлов xport автоматического преобразования типов к целым числам, датам или категориальным значениям не происходит. Для файлов SAS7BDAT коды формата могут позволить автоматически преобразовать переменные даты в даты. По умолчанию весь файл читается и возвращается как DataFrame.
Укажите chunksize или используйте iterator=True, чтобы получить объекты-читатели (XportReader или SAS7BDATReader) для поэтапного чтения файла. Объекты-читатели также имеют атрибуты, которые содержат дополнительную информацию о файле и его переменных.
Чтение файла SAS7BDAT:
df = pd.read_sas("sas_data.sas7bdat")
Получение итератора и чтение файла XPORT 100 000 строк за раз:
def do_something(chunk):
pass
with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
for chunk in rdr:
do_something(chunk)
Спецификация формата файлов xport доступна на сайте SAS.
Официальной документации по формату SAS7BDAT нет.
Форматы SPSS
Функция верхнего уровня read_spss() может читать (но не записывать) файлы формата SPSS SAV (.sav) и ZSAV (.zsav).
Файлы SPSS содержат имена столбцов. По умолчанию весь файл читается, категориальные столбцы преобразуются в pd.Categorical, и возвращается DataFrame со всеми столбцами.
Укажите параметр usecols, чтобы получить подмножество столбцов. Укажите convert_categoricals=False, чтобы избежать преобразования категориальных столбцов в pd.Categorical.
Чтение файла SPSS:
df = pd.read_spss("spss_data.sav")
Извлечение подмножества столбцов, содержащихся в usecols из файла SPSS и избегание преобразования категориальных столбцов в pd.Categorical:
df = pd.read_spss(
"spss_data.sav",
usecols=["foo", "bar"],
convert_categoricals=False,
)
Дополнительную информацию о форматах файлов SAV и ZSAV можно найти здесь.
Другие форматы файлов
Сам pandas поддерживает только ввод/вывод с ограниченным набором форматов файлов, которые четко отображаются в его табличной модели данных. Для чтения и записи других форматов файлов в pandas и из pandas мы рекомендуем эти пакеты из более широкого сообщества.
netCDF
xarray предоставляет структуры данных, вдохновленные pandas DataFrame для работы с многомерными наборами данных, с акцентом на формате файлов netCDF и легким преобразованием в pandas и из pandas.
Учет производительности
Это неформальное сравнение различных методов ввода/вывода, использующее pandas 0.24.2. Временные интервалы зависят от компьютера, и небольшие различия следует игнорировать.
In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})
In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A 1000000 non-null float64
B 1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB
Ниже будут использоваться следующие тестовые функции для сравнения производительности нескольких методов ввода/вывода:
import numpy as np
import os
sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
def test_sql_write(df):
if os.path.exists("test.sql"):
os.remove("test.sql")
sql_db = sqlite3.connect("test.sql")
df.to_sql(name="test_table", con=sql_db)
sql_db.close()
def test_sql_read():
sql_db = sqlite3.connect("test.sql")
pd.read_sql_query("select * from test_table", sql_db)
sql_db.close()
def test_hdf_fixed_write(df):
df.to_hdf("test_fixed.hdf", key="test", mode="w")
def test_hdf_fixed_read():
pd.read_hdf("test_fixed.hdf", "test")
def test_hdf_fixed_write_compress(df):
df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")
def test_hdf_fixed_read_compress():
pd.read_hdf("test_fixed_compress.hdf", "test")
def test_hdf_table_write(df):
df.to_hdf("test_table.hdf", key="test", mode="w", format="table")
def test_hdf_table_read():
pd.read_hdf("test_table.hdf", "test")
def test_hdf_table_write_compress(df):
df.to_hdf(
"test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
)
def test_hdf_table_read_compress():
pd.read_hdf("test_table_compress.hdf", "test")
def test_csv_write(df):
df.to_csv("test.csv", mode="w")
def test_csv_read():
pd.read_csv("test.csv", index_col=0)
def test_feather_write(df):
df.to_feather("test.feather")
def test_feather_read():
pd.read_feather("test.feather")
def test_pickle_write(df):
df.to_pickle("test.pkl")
def test_pickle_read():
pd.read_pickle("test.pkl")
def test_pickle_write_compress(df):
df.to_pickle("test.pkl.compress", compression="xz")
def test_pickle_read_compress():
pd.read_pickle("test.pkl.compress", compression="xz")
def test_parquet_write(df):
df.to_parquet("test.parquet")
def test_parquet_read():
pd.read_parquet("test.parquet")
При записи три самых быстрых функции — это test_feather_write, test_hdf_fixed_write и test_hdf_fixed_write_compress.
In [4]: %timeit test_sql_write(df)
3.29 s ± 43.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit test_hdf_fixed_write(df)
19.4 ms ± 560 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: %timeit test_hdf_fixed_write_compress(df)
19.6 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [7]: %timeit test_hdf_table_write(df)
449 ms ± 5.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [8]: %timeit test_hdf_table_write_compress(df)
448 ms ± 11.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [9]: %timeit test_csv_write(df)
3.66 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [10]: %timeit test_feather_write(df)
9.75 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [11]: %timeit test_pickle_write(df)
30.1 ms ± 229 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [12]: %timeit test_pickle_write_compress(df)
4.29 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [13]: %timeit test_parquet_write(df)
67.6 ms ± 706 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
При чтении три самые быстрые функции — это test_feather_read, test_pickle_read и test_hdf_fixed_read.
In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Файлы test.pkl.compress, test.parquet и test.feather занимают наименьшее место на диске (в байтах).
29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983 Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144 Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf
© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/io.html
Комментарии и пустые строки
Игнорирование комментариев и пустых строк
Если параметр
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