Обзор Ibis: единый Python API для данных описывает библиотеку, которая даёт один интерфейс к более чем 20 backend. Выражение строится на Python, вычисление выполняет выбранная система, а результат возвращается через execute или to_pandas. Ниже — устройство выражений, режимы работы, учебный запрос и границы переносимости.
Задача Ibis
Ibis решает задачу разрыва между кодом анализа и местом, где лежат данные. На главной странице проекта библиотека описана как открытая dataframe-библиотека, работающая с любой системой данных, с одним API более чем для 20 backend. По умолчанию используется встроенный DuckDB, также доступны Polars и DataFusion. Тот же код переносится на удалённую систему сменой одной строки подключения.
Практический смысл: аналитик пишет выражения на Python, а тяжёлую часть выполняет backend. В документации проекта прямо сказано, что Ibis разделяет dataframe API и исполнение на backend. Большинство backend поддерживают диалект SQL, в который Ibis компилирует выражения через SQLGlot. Это объясняет, почему один и тот же код работает поверх разных движков.
Второй элемент задачи — совмещение Python и SQL. На главной странице проекта показано, что SQL-строку можно передать в выражение через t.sql(...), а затем продолжить обработку методами dataframe. Такой подход закрывает разрыв между инженерной работой с SQL и аналитической работой на Python.
Третий элемент — единая точка входа к разным системам. В руководстве по конфигурации сказано, что ibis.options.default_backend управляет backend для выражений, которые возвращают функции верхнего уровня: ibis.memtable, ibis.read_csv, ibis.read_parquet. По умолчанию это экземпляр DuckDB. Сменить его можно через ibis.set_backend, если зависимости нужного backend установлены.
Что это даёт на практике. Команда может разрабатывать запрос локально на DuckDB, а затем направить его на удалённую систему, изменив строку подключения. Документация проекта описывает это как итерацию локально и развёртывание удалённо. Ограничение здесь в том, что смена backend сохраняет API, а поддержка конкретных функций зависит от выбранной системы.
Отдельно стоит установка. В руководстве предупреждают: пакет ibis-framework отличается от пакета ibis в PyPI, и они не могут сосуществовать в одном окружении, так как оба импортируются как ibis. Для локального старта рекомендуют backend DuckDB и установку вида pip install 'ibis-framework[duckdb,examples]'.
Expression и backend
Выражение в Ibis — это объект, описывающий преобразование таблицы. В руководстве показано: после подключения к базе и получения таблицы penguins = con.table("penguins") на экран выводится схема с типами колонок. Это и есть выражение: описание того, что нужно сделать, без немедленного результата.
Backend — система, которая это выражение исполнит. На главной странице проекта приведены примеры подключения: ibis.connect("duckdb://"), ibis.connect("polars://"), ibis.connect("datafusion://"), ibis.connect("pyspark://"). После подключения одинаковые вызовы con.read_parquet("penguins.parquet") и t.head(3) дают одинаковый результат в виде таблицы с колонками species, island, bill_length_mm и другими.
Компиляция в SQL — ключевой механизм. Документация проекта показывает, что для SQL-backend можно посмотреть сгенерированный запрос через ibis.to_sql(grouped). Для выражения с группировкой и подсчётом Ibis формирует запрос с подзапросом, COUNT(*) и GROUP BY по номерам колонок, а внешний слой добавляет ORDER BY. Это позволяет проверить, что именно уйдёт в базу.
Смешивание SQL и Python описано там же. Строку t.sql("SELECT species, island, COUNT(*) AS count FROM penguins GROUP BY species, island") можно продолжить методом .order_by("count"). SQL задаёт агрегацию, а Python API дополняет выражение сортировкой; вычисление обоих шагов выполняет backend. В отзыве на главной странице проекта пользователь Daniel Kim отмечает, что использует SQL для тяжёлых агрегаций и переключается на dataframe-подобный API для динамических преобразований.
Практический вывод для выбора backend. Один API покрывает разные системы, а конкретные функции, типы и семантика зависят от движка. Перед переносом стоит сверить, какие операции поддерживает целевая система. Компиляция в SQL через ibis.to_sql даёт проверяемый артефакт для такой сверки.
Lazy и interactive
Ibis по умолчанию ленив. В руководстве сказано: вместо данных при печати выражения видна схема таблицы. Чтобы посмотреть строки, вызывают head и затем to_pandas. Вызов penguins.head() без to_pandas показывает представление выражения: r0 := DatabaseTable: penguins и Limit[r0, n=5].
Материализация происходит на вызове to_pandas. В руководстве прямо сказано: to_pandas берёт существующее ленивое выражение и вычисляет его. Запрос исполняет backend, в примере — DuckDB. Только при вызове to_pandas Ibis забирает результаты и преобразует их в DataFrame. Важное уточнение из того же текста: Ibis возвращает результат как pandas DataFrame, но не использует pandas для вычислений.
Interactive-режим меняет поведение при печати. В руководстве по конфигурации сказано: Ibis из коробки находится в deferred-режиме, а установка ibis.options.interactive = True заставляет выражения исполняться сразу при выводе в консоль. В туториале уточняется: interactive частично исполняет запросы, чтобы дать предпросмотр результата, и возвращает до 10 строк. Это тот же вызов to_pandas с лимитом 10 строк.
Разница между режимами важна для отладки. В deferred-режиме печать безопасна: она показывает план выражения. В interactive-режиме печать запускает вычисление на backend. Для больших таблиц это означает реальную нагрузку на движок при каждом выводе.
Есть отдельный лимит на извлечение строк. В руководстве по конфигурации сказано: если у выражения нет лимита через API limit, применяется значение по умолчанию, чтобы не забрать слишком много данных. Текущее значение — 10000 строк, его меняет опция ibis.options.sql.default_limit. Установка None вернёт все строки запроса на клиент.
Для логирования есть опция ibis.options.verbose. По умолчанию информация идёт в sys.stdout, а функцию вывода можно заменить через ibis.options.verbose_log. Это помогает увидеть, какие запросы реально уходят в backend.
Учебный запрос и эталон
Ниже — учебный пример на условных данных. Он показывает логическое выражение и ожидаемую таблицу. Скорость Ibis здесь не измерялась. Возьмём четыре строки: регион и сумма. Строки: (RU, 40), (EU, 70), (EU, −5), (RU, 100). Выражение: отфильтровать строки с суммой больше нуля, сгруппировать по региону и сложить суммы.
Ожидаемый результат: EU даёт 70, RU даёт 140. Строка (EU, −5) отсеивается фильтром. Без order_by порядок строк в результате не гарантирован, поэтому эталон задаёт значения без фиксированного порядка.
import ibis
rows = [
{"region": "RU", "amount": 40},
{"region": "EU", "amount": 70},
{"region": "EU", "amount": -5},
{"region": "RU", "amount": 100},
]
t = ibis.memtable(rows)
expr = (
t.filter(t.amount > 0)
.group_by("region")
.aggregate(total=t.amount.sum())
)
print(expr)
Печать expr покажет план выражения. Чтобы получить таблицу, добавьте .order_by("region") и вызовите .execute() или .to_pandas(). Ожидаемые пары: EU — 70, RU — 140.
Проверка эталона идёт по шагам. Сначала сверяются типы колонок: region — строка, amount — целое. Затем проверяется обработка null: если в данных есть пропуск, сумма и фильтр ведут себя по правилам backend. Затем сверяется результат: две строки с ожидаемыми значениями. Затем смотрится скомпилированный SQL через ibis.to_sql(expr) и оценивается объём извлекаемых данных.
Этот порядок важен при смене backend. Одинаковый fixture из четырёх строк прогоняется на двух системах, и результаты сравниваются. Расхождение в типах, null или значениях означает, что перенос требует переработки. Такой пример показывает логику выражения и границу backend. Производительность библиотеки требует отдельного измерения.
Получение результата
Результат в Ibis появляется в момент материализации. В руководстве сказано: to_pandas вычисляет ленивое выражение, запрос исполняет backend, и только затем Ibis забирает строки и преобразует их в DataFrame. Метод execute делает то же самое и возвращает результат в подходящем для backend виде.
Для учебного примера вызов выглядит так:
result = expr.order_by("region").execute()
print(result)
На выходе — таблица с двумя строками. Значения: EU — 70, RU — 140. Порядок задан явно через order_by.
Практическое правило для больших таблиц: агрегировать на стороне backend. Если выражение сначала материализует миллионы строк, а потом считает сумму в Python, нагрузка ложится на клиент. Если агрегация описана в выражении, её выполняет backend, и на клиент уходит только итог. В руководстве по конфигурации это косвенно подтверждается лимитом по умолчанию: он защищает от извлечения слишком большого объёма.
Проверка результата включает несколько пунктов. Сверьте число строк с ожидаемым. Сверьте значения агрегатов с эталоном. Проверьте, что фильтр отсёк нужные строки. Посмотрите ibis.to_sql(expr) и проверьте присутствие агрегации в SQL.
Отдельно про interactive-режим. Если он включён, печать выражения уже запускает вычисление с лимитом 10 строк. Для отладки это удобно, для больших таблиц — дополнительная нагрузка. Перед прогоном на реальных данных стоит выключить interactive или понимать, что каждый вывод запускает запрос.
Переносимость и ограничения
Переносимость в Ibis опирается на один API поверх разных backend. На главной странице проекта сказано: тот же dataframe API работает более чем с 20 backend, а переход с локальной разработки на удалённую систему делается сменой одной строки кода. Пример из документации: разработка на DuckDB, развёртывание на BigQuery.
Ограничения начинаются там, где заканчивается общий API. Поддержка функций зависит от выбранного backend. Если выражение использует Python UDF, а целевая система его не поддерживает, перенос блокируется до переработки выражения. Это учебный пример границы; тест конкретных систем не проводился.
Карточка переноса помогает проверить совместимость. Порядок такой:
| Шаг | Что проверяется | Признак проблемы |
|---|---|---|
| 1 | Одинаковый fixture на backendA и backendB | Разные входные данные |
| 2 | Типы колонок после чтения | Расхождение типов |
| 3 | Обработка null в фильтре и агрегате | Разные значения |
| 4 | Итоговый результат выражения | Несовпадение с эталоном |
| 5 | SQL через ibis.to_sql |
Ошибка компиляции |
| 6 | Объём извлекаемых данных | Слишком много строк на клиент |
Если на шаге 2 или 3 обнаружено расхождение, перенос требует переработки выражения. Если на шаге 5 компиляция падает, разберите сообщение ошибки и сверьте схему и поддержку операций у целевого backend. Если на шаге 6 объём велик, агрегацию стоит перенести в выражение.
Сравнение режимов работы:
| Режим | Что происходит при печати | Когда использовать |
|---|---|---|
| Deferred (по умолчанию) | Показывается план выражения | Разработка и отладка логики |
| Interactive | Запрос исполняется, до 10 строк | Быстрый просмотр данных |
execute / to_pandas |
Полное вычисление на backend | Получение итогового результата |
Ещё одно ограничение связано с лимитом строк. По умолчанию применяется 10000 строк, если выражение не задаёт limit. Установка None вернёт все строки на клиент. Для больших таблиц это риск: сначала стоит агрегировать данные на стороне backend, а затем забирать итог.
Установка тоже влияет на переносимость. Пакет ibis-framework отличается от пакета ibis в PyPI, и они не могут сосуществовать в одном окружении. Для каждого backend нужны свои зависимости. В руководстве рекомендуют начинать с DuckDB как полностью функционального локального варианта.
Частые вопросы
Чем Ibis отличается от pandas?
Ibis описывает выражение, а вычисление выполняет backend. В руководстве сказано, что Ibis возвращает результат как pandas DataFrame, но не использует pandas для вычислений. Запрос исполняет выбранная система, в примере — DuckDB. Это позволяет работать с данными, которые не помещаются в память клиента, если агрегация описана в выражении.
Что такое backend в Ibis?
Backend — система, которая исполняет выражение. На главной странице проекта перечислены DuckDB, Polars, DataFusion, PySpark и другие, всего более 20. Подключение идёт через ibis.connect с указанием схемы, например duckdb:// или polars://. Поддержка конкретных функций зависит от выбранного backend.
Как получить результат выражения?
Нужно вызвать execute или to_pandas. В руководстве сказано, что to_pandas берёт ленивое выражение и вычисляет его, а запрос исполняет backend. Только при этом вызове Ibis забирает строки и преобразует их в DataFrame. В deferred-режиме печать выражения без этих вызовов показывает план; при включённом interactive она запускает вычисление.
Зачем нужен interactive-режим?
Interactive-режим исполняет выражение при печати и возвращает предпросмотр до 10 строк. В руководстве по конфигурации сказано, что по умолчанию Ibis работает в deferred-режиме и показывает внутренние детали выражения. Interactive удобен для быстрого просмотра данных, но запускает вычисление при каждом выводе.
Что проверять при переносе на другой backend?
Сначала прогнать одинаковый fixture на двух системах. Затем сверить типы колонок, обработку null и итоговый результат. Затем посмотреть SQL через ibis.to_sql и оценить объём извлекаемых данных. Если выражение использует Python UDF, а целевая система его не поддерживает, перенос блокируется до переработки.
