Миграция на pandas 3.0: как перейти с pandas 2 без боли

Пошаговое руководство по миграции на pandas 3.0: Copy-on-Write по умолчанию, новый StringDtype, выражения pd.col(), удалённые методы, изменения в датах и практический чек-лист перехода с примерами кода.

Введение

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:

  1. Замените все цепные присваивания (df["col"][mask] = value) на df.loc[mask, "col"] = value.
  2. Уберите лишние вызовы .copy(), которые вы добавляли для подавления SettingWithCopyWarning — теперь они не нужны.
  3. Если ваш код полагался на связь между DataFrame через общие данные (для экономии памяти) — пересмотрите архитектуру, в pandas 3.0 это невозможно.
  4. Проверьте код, который модифицировал данные «на месте» через представления — он будет работать некорректно.

Новый строковый тип данных 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")

Новые псевдонимы смещений. Старые однобуквенные псевдонимы удалены — это застаёт врасплох многих, потому что они были в ходу годами:

Старый псевдонимНовый псевдонимОписание
MMEКонец месяца
BMBMEКонец рабочего месяца
SMSMEКонец полумесяца
QQEКонец квартала
YYEКонец года
# Старый код (не работает в 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()0
  • GroupBy.prod()1
  • GroupBy.all()True
  • GroupBy.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 важно проверить совместимость с остальными библиотеками проекта. Минимальные версии зависимостей повысились:

ЗависимостьМинимальная версия
Python3.11
NumPy1.26.0
PyArrow13.0.0
SQLAlchemy2.0.36
matplotlib3.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:

  1. Создайте отдельное виртуальное окружение — не экспериментируйте в рабочей среде.
  2. Установите pandas 2.3 с флагами будущего поведения (future.infer_string = True, mode.copy_on_write = True).
  3. Запустите тесты — исправьте каждый DeprecationWarning и FutureWarning.
  4. Замените удалённые методы: applymap()map(), append()pd.concat(), Timestamp.utcnow()Timestamp.now("UTC").
  5. Замените цепные присваивания на .loc[] — все df["col"][mask] = value нужно переписать.
  6. Обновите проверки типов: dtype == "object"pd.api.types.is_string_dtype() для строковых столбцов.
  7. Обновите псевдонимы смещений: MME, QQE, YYE, BMBME.
  8. Проверьте работу с датами: учтите переход с наносекунд на микросекунды и замену pytz на zoneinfo.
  9. Установите PyArrow для лучшей производительности: pip install pyarrow.
  10. Обновитесь до 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.

Об авторе Editorial Team

Our team of expert writers and editors.