Введение
Pandas позволяет разработчикам выбирать между различными типами массивов для представления данных датафрейма. Традиционно большинство датафреймов поддерживаются массивами NumPy
. Pandas 2.0 предоставила возможность использовать массивы PyArrow в качестве формата хранения данных. Кроме того, между этими массивами и датафреймом существует промежуточный слой Block
и BlockManager
. В статье мы рассмотрим, как этот слой управляет различными массивами. Главным образом, поинтересуемся, что скрывается за pd.DataFrame()
, а также постараемся ответить на все вопросы о внутреннем устройстве Pandas.
Пара слов о себе: вхожу в состав основной команды Pandas, которая, помимо всего прочего, занимается внутренними компонентами. В настоящее время являюсь сотрудником Coiled и работаю над Dask.
Структура данных Pandas
Как правило, датафрейм поддерживается каким-нибудь массивом, например NumPy
или Pandas ExtensionArray
. Эти массивы хранят данные датафрейма. pandas
добавляет промежуточный слой Block
и BlockManager
. Он управляет этими массивами, обеспечивая максимальную эффективность операций. Это одна из причин, почему в Pandas методы, работающие с несколькими столбцами, могут быть очень быстрыми. Далее более подробно рассмотрим упомянутые слои.
Массивы
Фактические данные датафрейма могут храниться в наборе массивов NumPy
или Pandas ExtensionArray
. Этот слой обычно направляет к базовой реализации, например использует NumPy API
при условии хранения данных в массивах NumPy
. Pandas хранит в них данные и вызывает свои методы без расширения интерфейса. Более подробная информация о pandas
ExtensionArray
предоставлена по ссылке.
Массивы NumPy
обычно являются двумерными и дают ряд преимуществ в производительности, о которых речь пойдет далее. На данный момент Pandas ExtensionArray
в основном представляют собой одномерные структуры данных, благодаря чему операции становятся предсказуемыми. Однако не обошлось и без недостатков: в ряде случаев страдает производительность.
ExtensionArray
допускает применение датафреймов, которые поддерживаются массивами PyArrow
и другими типами данных Pandas.
Block
Датафрейм обычно состоит из столбцов, представленных по крайней мере одним массивом. Как правило, имеется коллекция массивов, так как один массив может хранить только один определенный тип данных. Эти массивы хранят данные, но не владеют информацией о том, какие столбцы они представляют. Каждый массив из датафрейма обернут соответствующим блоком Block
. Block
добавляет дополнительную информацию в массивы, например расположение представленных им столбцов. Block
служит слоем вокруг фактических массивов с возможностью расширения вспомогательными методами, необходимыми для операций Pandas.
При выполнении фактической операции с датафреймом Block
гарантирует, что метод направляется в базовый массив. Например, при вызове astype
он убедится, что эта операция вызывается в массиве.
Данный слой не располагает информацией о других столбцах в датафрейме, являясь автономным объектом.
BlockManager
Как следует из названия, BlockManager
управляет всеми Block
, связанными с одним датафреймом. Он содержит сами Block
и информацию об осях датафрейма, например имена столбцов и метки Index
. И самое главное в том, что он направляет большинство операций к фактическим Block
:
df.replace(...)
BlockManager
гарантирует, что replace
выполняется для каждого Block
.
Понятие консолидированного датафрейма
Мы исходим из того, что датафреймы поддерживаются типами данных NumPy
, например их данные могут храниться в двумерных массивах.
При создании датафрейма Pandas гарантирует, что на каждый тип данных приходится только один Block
:
df = pd.DataFrame(
{
"a": [1, 2, 3],
"b": [1.5, 2.5, 3.5],
"c": [10, 11, 12],
"d": [10.5, 11.5, 12.5],
}
)
У этого датафрейма есть 4 столбца, представленные двумя массивами: один из них хранит целочисленный тип данных, а другой — числа с плавающей точкой. Это и есть консолидированный датафрейм.
Добавим новый столбец к этому датафрейму:
df["new"] = 100
У него такой же тип данных, как и у существующих столбцов "a"
и "c"
. Рассмотрим 2 возможных варианта дальнейших действий:
- Добавление нового столбца в существующий массив, содержащий целочисленные столбцы.
- Создание нового массива только для хранения нового столбца.
Первый вариант предусматривает добавление нового столбца в существующий массив. Для этого требуется скопировать данные, поскольку NumPy
не поддерживает эту операцию без копирования. В итоге добавление одного столбца оборачивается слишком большими затратами.
Второй вариант предполагает добавление третьего массива в коллекцию массивов. И все — больше никаких операций не требуется. Дешево и просто. Теперь у нас два Block
, которые хранят целочисленные данные. Такой датафрейм не является консолидированным.
Эти различия не имеют большого значения до тех пор, пока вы работаете только с одним столбцом. Но они скажутся на производительности операций, как только начнете взаимодействовать с несколькими столбцами. Так выполнение любой операции axis=1
приведет к транспонированию данных датафрейма. Транспонирование обычно сопровождается нулевым копированием, если выполняется для датафрейма, поддерживаемого одним массивом NumPy
. Но это правило перестает работать, если каждый столбец поддерживается разным массивом. В таком случае происходит снижение производительности.
Кроме того, требуется копия для получения всех целочисленных столбцов из датафрейма в виде массива NumPy
:
df[["a", "c", "new"]].to_numpy()
Так создается копия, поскольку результаты должны храниться в одном массиве NumPy
. Операция возвращает представление консолидированного датафрейма, что сопровождается минимальными затратами.
Предыдущие версии часто приводили к внутренней консолидации в случае с определенными методами, что, в свою очередь, оборачивалось непредсказуемым состоянием производительности. Такие методы, как reset_index
, вызывали совершенно ненужную консолидацию. В основном они были удалены в двух последних выпусках.
Подводя итоги, отметим, что консолидированный датафрейм лучше, чем неконсолидированный. Однако различия сильно зависят от типа выполняемых операций.
Заключение
Мы кратко рассмотрели внутреннее устройство датафрейма pandas
. Узнали, что такое Block
, BlockManager
, и как они управляют датафреймом.
Читайте также:
- Как получить данные в нужном формате с помощью Pandas
- Битва 4 инструментов визуализации данных на языке Python
- 6 способов оптимизировать рабочий процесс в Pandas
Читайте нас в Telegram, VK и Дзен
Перевод статьи Patrick Hoefler: Pandas Internals Explained