Введение
21 января 2026 года вышла версия pandas 3.0.0 — и это, пожалуй, самое масштабное обновление библиотеки за последние годы. Разработчики потратили несколько лет на переработку внутренней архитектуры, и результат получился впечатляющим.
Три вещи, которые определяют этот релиз:
- Copy-on-Write (CoW) теперь включён по умолчанию — операции индексирования возвращают копию, а не представление (view). Тот самый класс багов с непредсказуемым поведением представлений? Его больше нет.
- Новый строковый тип StringDtype заменяет старый
objectдля текстовых данных. Это и экономия памяти, и интеграция с Apache Arrow. - Выражения pd.col() — декларативный синтаксис для ссылок на столбцы DataFrame. Наконец-то можно обойтись без лямбда-функций в простых трансформациях.
Если честно, на русском языке до сих пор трудно найти нормальное руководство по миграции на pandas 3.0. Мы постарались это исправить: разберём каждое ломающее изменение, покажем конкретные примеры кода и предложим пошаговый план перехода.
Рекомендуемая стратегия миграции: сначала обновитесь до pandas 2.3, включите флаги будущего поведения, исправьте все предупреждения — и только потом переходите на pandas 3.0. Так вы поймаете проблемы совместимости ещё до перехода на новую мажорную версию.
Кстати, 17 февраля 2026 года вышел pandas 3.0.1 — патч-релиз с исправлениями регрессий из 3.0.0. Именно его и рекомендуется ставить.
Минимальные требования: Python 3.11+ и NumPy 1.26.0+. Если у вас более старые версии — сначала обновите их.
Подготовка к миграции
Миграция на pandas 3.0 — процесс, который лучше выполнять поэтапно. Резкий прыжок с pandas 2.x на 3.0 почти гарантированно приведёт к куче одновременных ошибок, в которых разобраться будет непросто.
Вот пятишаговый план, который мы рекомендуем:
Шаг 1. Проверьте текущую версию pandas
import pandas as pd
print(pd.__version__)
# Например: 2.2.1
Шаг 2. Обновитесь до pandas 2.3
В версии 2.3 есть все deprecation warnings для функций, которые удалены в 3.0. По сути, это ваш главный инструмент для обнаружения проблем совместимости.
# pip
pip install pandas==2.3.*
# conda
conda install pandas=2.3
Шаг 3. Включите флаги будущего поведения
В pandas 2.3 есть два ключевых флага, которые эмулируют поведение 3.0:
import pandas as pd
# Включить новый строковый тип (как в pandas 3.0)
pd.options.future.infer_string = True
# Включить Copy-on-Write (как в pandas 3.0)
pd.options.mode.copy_on_write = True
Добавьте эти строки в начало вашего кода или в файл конфигурации — пусть они работают для всех операций.
Шаг 4. Запустите тесты и разберитесь с предупреждениями
Прогоните весь набор тестов с включёнными флагами. Каждое DeprecationWarning и FutureWarning — это код, который сломается в pandas 3.0. Чтобы точно ничего не пропустить:
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
warnings.filterwarnings('always', category=FutureWarning)
Шаг 5. Обновитесь до pandas 3.0
# pip (с рекомендуемой поддержкой PyArrow)
pip install "pandas[pyarrow]>=3.0"
# conda
conda install pandas=3.0
# Только pandas без PyArrow (не рекомендуется)
pip install pandas>=3.0
Настоятельно советуем ставить pandas вместе с PyArrow: pip install pandas[pyarrow]. PyArrow даёт оптимальную работу нового строкового типа и заметно улучшает производительность I/O операций.
Copy-on-Write: главное изменение в pandas 3.0
Copy-on-Write (CoW) — без преувеличения, самое значительное архитектурное изменение в pandas 3.0. Идея проста: любая операция индексирования или метод, возвращающий DataFrame или Series, теперь ведёт себя так, как будто возвращается полная копия данных. Изменение производного объекта никогда не затронет исходный.
Проблема, которую решает CoW. В pandas 2.x при индексировании DataFrame результат мог быть либо view, либо копией — и это зависело от внутренней структуры данных. Отсюда и печально известный SettingWithCopyWarning:
# Старый pandas 2.x: непредсказуемое поведение
df2 = df[df["value"] > 10]
df2["status"] = "high" # SettingWithCopyWarning!
# Изменился ли df? Зависит от внутренней реализации...
В pandas 3.0 этой проблемы просто нет. df2 всегда ведёт себя как независимая копия:
# pandas 3.0: предсказуемое поведение
df2 = df[df["value"] > 10]
df2["status"] = "high" # Никаких предупреждений, df НЕ изменён
Цепное присваивание больше не работает. Это одно из главных последствий CoW — chained assignment теперь гарантированно не модифицирует исходный DataFrame:
# Это больше НЕ модифицирует df в pandas 3.0:
df["score"][df["grade"] == "C"] = 0
# Изменяется только временный объект, df остаётся прежним!
# Правильный подход — используйте .loc:
df.loc[df["grade"] == "C", "score"] = 0
А что с производительностью? Тут всё умнее, чем кажется. Несмотря на то что концептуально каждая операция возвращает копию, реального копирования при каждой операции не происходит. pandas 3.0 использует ленивое копирование: сначала создаётся view, и только при попытке модификации выполняется настоящее копирование. Для read-only операций производительность не страдает.
Под капотом работает система BlockValuesRefs со слабыми ссылками. Каждый блок данных хранит счётчик ссылок, и копирование происходит только для изменяемого блока, а не для всего DataFrame.
Что удалено:
SettingWithCopyWarningполностью удалён — он больше не нужен.- Опция
mode.copy_on_writeобъявлена устаревшей (CoW всегда включён). - Метод
DataFrame.copy(deep=False)теперь работает идентичноDataFrame.copy(deep=True).
Практический чек-лист для адаптации к CoW:
- Замените все цепные присваивания (
df["col"][mask] = value) наdf.loc[mask, "col"] = value. - Уберите лишние вызовы
.copy(), которые вы добавляли для подавленияSettingWithCopyWarning— теперь они не нужны. - Если ваш код полагался на связь между DataFrame через общие данные (для экономии памяти) — пересмотрите архитектуру, в pandas 3.0 это невозможно.
- Проверьте код, который модифицировал данные «на месте» через представления — он будет работать некорректно.
Новый строковый тип данных StringDtype
В pandas 2.x строковые данные по умолчанию жили в столбцах с типом object. Если вы когда-нибудь задумывались, почему это неэффективно — тип object хранит массив указателей на Python-объекты, каждый из которых является отдельной строкой в куче Python. Результат: раздутое потребление памяти и медленные строковые операции.
В pandas 3.0 строки по умолчанию используют новый тип str, основанный на StringDtype с бэкендом PyArrow (если он установлен):
# pandas 2.x
import pandas as pd
ser = pd.Series(["hello", "world"])
print(ser.dtype) # object
# pandas 3.0
import pandas as pd
ser = pd.Series(["hello", "world"])
print(ser.dtype) # str
Экономия памяти может достигать 50% для текстовых столбцов. На больших датасетах с категориальными строками, URL-адресами или текстовыми описаниями разница будет ощутимой.
Интеграция с экосистемой. Новый строковый тип построен на Apache Arrow, что даёт zero-copy обмен данными с Polars, DuckDB, Vaex и другими инструментами, поддерживающими Arrow PyCapsule Protocol. Передача данных между библиотеками становится радикально быстрее:
import pandas as pd
import polars as pl
# Создаём DataFrame в pandas 3.0
pdf = pd.DataFrame({"name": ["Алиса", "Борис", "Виктор"]})
# Передаём в Polars с нулевым копированием (через Arrow PyCapsule)
plf = pl.from_pandas(pdf)
Ломающее изменение: если у вас где-то есть проверка dtype == "object" для текстовых столбцов — она сломается. Вот как это исправить:
# Старый код (сломается в pandas 3.0):
if df["name"].dtype == "object":
# обработка строк...
# Новый код (работает в обеих версиях):
if pd.api.types.is_string_dtype(df["name"]):
# обработка строк...
# Или более строгая проверка:
if isinstance(df["name"].dtype, pd.StringDtype):
# обработка строк...
Важно: хранение "pyarrow_numpy" для StringDtype объявлено устаревшим. Если вы явно использовали pd.StringDtype(storage="pyarrow_numpy"), переходите на pd.StringDtype(storage="pyarrow") или просто используйте тип по умолчанию.
Новый синтаксис pd.col()
Вот это, на мой взгляд, одна из самых приятных новинок pandas 3.0. Функция pd.col() позволяет ссылаться на столбцы DataFrame по имени и строить выражения без лямбда-функций. Код становится заметно чище:
# Старый подход (лямбда-функции):
df.assign(total=lambda x: x["price"] * x["quantity"])
# Новый подход (pd.col):
df.assign(total=pd.col("price") * pd.col("quantity"))
Выражения pd.col() поддерживают все стандартные арифметические операторы (+, -, *, /, //, **, %), операторы сравнения и большинство методов Series:
import pandas as pd
df = pd.DataFrame({
"city": ["Москва", "Санкт-Петербург", "Новосибирск"],
"population": [13100000, 5600000, 1600000],
"area_km2": [2561, 1439, 505]
})
# Вычисление плотности населения
result = df.assign(
density=pd.col("population") / pd.col("area_km2"),
city_upper=pd.col("city").str.upper()
)
print(result)
Поддерживаемые методы: .str.upper(), .str.lower(), .str.len(), .sum(), .mean() и другие методы Series. Можно строить довольно сложные выражения декларативно.
Ограничение: пока что pd.col() нельзя использовать внутри groupby. Поддержка группировок запланирована на будущие релизы. Но уже сейчас в типичных сценариях с assign и query — это очень удобно.
Изменения в работе с датами и временем
Тут тоже есть на что обратить внимание. pandas 3.0 меняет разрешение по умолчанию, поведение смещений и бэкенд временных зон.
Микросекунды вместо наносекунд. В pandas 2.x все временные метки хранились с наносекундным разрешением (datetime64[ns]). В pandas 3.0 разрешение по умолчанию — микросекунды (datetime64[us]):
# pandas 2.x
pd.to_datetime(["2026-03-08"]).dtype # datetime64[ns]
# pandas 3.0
pd.to_datetime(["2026-03-08"]).dtype # datetime64[us]
Внимание: если ваш код конвертирует datetime в целые числа (например, для хранения), значения будут в 1000 раз меньше — теперь это микросекунды, а не наносекунды. Обязательно проверьте все операции .astype(int) с временными столбцами.
pd.offsets.Day — теперь календарный день. В pandas 3.0 pd.offsets.Day представляет именно календарный день, а не 24-часовой интервал. Разница важна при переходе на летнее/зимнее время:
import pandas as pd
# При переходе на летнее время день может быть не 24 часа
ts = pd.Timestamp("2026-03-29 00:00", tz="Europe/Moscow")
# Календарный день (pandas 3.0 по умолчанию)
result = ts + pd.offsets.Day(1)
print(result) # 2026-03-30 00:00:00+03:00
# Если нужен именно 24-часовой интервал:
result = ts + pd.Timedelta(hours=24)
zoneinfo вместо pytz. pandas 3.0 перешёл на модуль zoneinfo из стандартной библиотеки Python. Если ваш код проверяет тип временной зоны через isinstance(tz, pytz.BaseTzInfo), его нужно обновить:
# pandas 3.0 использует zoneinfo
ts = pd.Timestamp(2026, 3, 8).tz_localize("Europe/Moscow")
print(type(ts.tz)) # <class 'zoneinfo.ZoneInfo'>
# Старый формат pytz больше не возвращается:
# type(ts.tz) != pytz.timezone (как было в pandas 2.x)
Удалённые методы и функции
pandas 3.0 окончательно удаляет методы, которые были deprecated в предыдущих версиях. Вот полная таблица замен (сохраните себе — пригодится):
| Удалённый метод | Замена |
|---|---|
DataFrame.applymap() | DataFrame.map() |
DataFrame.append() | pd.concat() |
Series.ravel() | np.asarray(series) |
Styler.applymap() | Styler.map() |
Styler.applymap_index() | Styler.map_index() |
Index.format() | Index.astype(str) |
Series.__int__ / __float__ | int(ser.iloc[0]) / float(ser.iloc[0]) |
Timestamp.utcnow() | Timestamp.now("UTC") |
Timestamp.utcfromtimestamp() | Timestamp.fromtimestamp(ts, "UTC") |
DataFrameGroupby.corrwith() | Удалён (устарел) |
Вот примеры миграции для самых частых случаев:
import pandas as pd
import numpy as np
df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
# applymap → map
# Старый код: df.applymap(lambda x: x * 2)
df.map(lambda x: x * 2)
# append → concat
# Старый код: df.append({"a": 4, "b": 7}, ignore_index=True)
pd.concat([df, pd.DataFrame([{"a": 4, "b": 7}])], ignore_index=True)
# Timestamp.utcnow → Timestamp.now("UTC")
# Старый код: pd.Timestamp.utcnow()
pd.Timestamp.now("UTC")
Новые псевдонимы смещений. Старые однобуквенные псевдонимы удалены — это застаёт врасплох многих, потому что они были в ходу годами:
| Старый псевдоним | Новый псевдоним | Описание |
|---|---|---|
M | ME | Конец месяца |
BM | BME | Конец рабочего месяца |
SM | SME | Конец полумесяца |
Q | QE | Конец квартала |
Y | YE | Конец года |
# Старый код (не работает в pandas 3.0):
# ts = pd.date_range("2026-01-01", periods=12, freq="M")
# Новый код:
ts = pd.date_range("2026-01-01", periods=12, freq="ME")
print(ts)
# Аналогично для ресемплинга:
# df.resample("Q").sum() # Старый код
df.resample("QE").sum() # Новый код
Изменения в поведении методов
Помимо удалённых методов, pandas 3.0 меняет поведение ряда существующих. Эти изменения менее очевидны, но могут привести к неприятным сюрпризам, если их проигнорировать.
Методы inplace теперь возвращают self. Методы replace(), fillna(), ffill(), bfill(), interpolate(), where(), mask() и clip() с параметром inplace=True теперь возвращают self вместо None:
# В pandas 2.x это возвращало None:
result = df.fillna(0, inplace=True) # result is None
# В pandas 3.0 возвращается сам DataFrame:
result = df.fillna(0, inplace=True) # result is df
# Но лучше избегать inplace и писать:
df = df.fillna(0)
concat() с sort=False для DatetimeIndex. Наконец-то параметр sort=False в pd.concat() по-настоящему работает для DatetimeIndex. В pandas 2.x DatetimeIndex сортировался всегда, независимо от параметра. Мелочь, а приятно.
GroupBy: значения для ненаблюдаемых групп. При группировке по категориальным данным агрегирующие функции теперь возвращают предсказуемые значения для ненаблюдаемых категорий:
GroupBy.sum()→0GroupBy.prod()→1GroupBy.all()→TrueGroupBy.any()→False
NaN в nullable-типах. Значения NaN в столбцах с nullable-типами (Int64, Float64, boolean) теперь единообразно трактуются как NA. Больше никакой несогласованности между разными nullable-типами.
Позиционные аргументы в статистических методах. Передача позиционных аргументов в all(), min(), max(), sum(), prod(), mean(), median() объявлена устаревшей. Используйте именованные аргументы:
# Устаревший синтаксис:
# df.sum(0) # позиционный аргумент
# Правильный синтаксис:
df.sum(axis=0)
Совместимость с экосистемой
При обновлении до pandas 3.0 важно проверить совместимость с остальными библиотеками проекта. Минимальные версии зависимостей повысились:
| Зависимость | Минимальная версия |
|---|---|
| Python | 3.11 |
| NumPy | 1.26.0 |
| PyArrow | 13.0.0 |
| SQLAlchemy | 2.0.36 |
| matplotlib | 3.9.3 |
scikit-learn. Свежие версии scikit-learn в целом работают с pandas 3.0, но будьте внимательны: то, что раньше было object, теперь str. Некоторые трансформеры могут обрабатывать эти типы по-разному, так что прогоните тесты.
Другие библиотеки. Перед обновлением проверьте совместимость всех зависимостей. Большинство популярных библиотек уже выпустили обновления, но кто-то может отставать. Простая проверка: pip check — покажет конфликты зависимостей.
Совет из практики: всегда тестируйте миграцию в отдельном виртуальном окружении. Не трогайте рабочее окружение, пока все тесты не пройдут в изолированной среде:
# Создаём изолированное окружение для тестирования
python -m venv pandas3_test
source pandas3_test/bin/activate # Linux/macOS
# pandas3_test\Scripts\activate # Windows
pip install "pandas[pyarrow]>=3.0"
pip install -r requirements.txt
pytest
Практический чек-лист миграции
Итак, собираем всё вместе. Вот сводный план для перехода с pandas 2.x на pandas 3.0:
- Создайте отдельное виртуальное окружение — не экспериментируйте в рабочей среде.
- Установите pandas 2.3 с флагами будущего поведения (
future.infer_string = True,mode.copy_on_write = True). - Запустите тесты — исправьте каждый
DeprecationWarningиFutureWarning. - Замените удалённые методы:
applymap()→map(),append()→pd.concat(),Timestamp.utcnow()→Timestamp.now("UTC"). - Замените цепные присваивания на
.loc[]— всеdf["col"][mask] = valueнужно переписать. - Обновите проверки типов:
dtype == "object"→pd.api.types.is_string_dtype()для строковых столбцов. - Обновите псевдонимы смещений:
M→ME,Q→QE,Y→YE,BM→BME. - Проверьте работу с датами: учтите переход с наносекунд на микросекунды и замену pytz на
zoneinfo. - Установите PyArrow для лучшей производительности:
pip install pyarrow. - Обновитесь до pandas 3.0 и прогоните тесты ещё раз.
Часто задаваемые вопросы
1. Можно ли отключить Copy-on-Write в pandas 3.0?
Нет. CoW — единственный режим работы pandas 3.0. Опция mode.copy_on_write существует для обратной совместимости, но ничего не делает. Если ваш код критически зависит от мутации через представления — его придётся переписать.
2. Нужно ли устанавливать PyArrow для pandas 3.0?
Формально нет, но на практике — очень рекомендуется. Без PyArrow строковые столбцы будут использовать менее эффективную внутреннюю реализацию. С PyArrow вы получаете оптимальное хранение строк, zero-copy обмен данными с Arrow-совместимыми библиотеками и лучшую производительность I/O.
3. Совместим ли pandas 3.0 с Python 3.10?
Нет. Минимальная версия — Python 3.11. Если у вас 3.10 или старше — сначала обновите Python и проверьте совместимость всех зависимостей.
4. Как проверить, что мой код готов к pandas 3.0?
Лучший способ — использовать pandas 2.3 с включёнными флагами: pd.options.future.infer_string = True и pd.options.mode.copy_on_write = True. Если все тесты проходят без deprecation warnings — переход должен быть безболезненным.
5. Что делать, если зависимость не поддерживает pandas 3.0?
Несколько вариантов: (а) зафиксируйте версию pandas в requirements.txt (pandas<3.0) и подождите; (б) используйте отдельное виртуальное окружение для несовместимых компонентов; (в) создайте issue в репозитории библиотеки; (г) поищите альтернативную библиотеку с поддержкой pandas 3.0.