2025, Dec 27 06:01

Почему не стоит мутировать строки в Pandas DataFrame.apply

Почему мутации в Pandas DataFrame.apply не поддерживаются, чем это грозит и как корректно вычитать среднее по столбцу через векторизованные операции.

Использование Pandas DataFrame.apply с пользовательской функцией, которая изменяет свой аргумент, — частая ошибка новичков. На учебных примерах или небольших данных такой код может выглядеть рабочим, но официальная документация прямо предупреждает: изменять объекты внутри apply «не поддерживается». Что именно означает «не поддерживается» на практике и как надежно получить тот же результат?

Пример, из-за которого возникает вопрос

Сценарий простой: посчитать среднее числового столбца и вычесть его из каждой строки с помощью apply. Пример ниже отражает эту идею, но с другими именами переменных, чтобы сосредоточиться на механике, а не на конкретном туториале:

mean_pts = ratings_df.points.mean()
ratings_df.points.map(lambda v: v - mean_pts)

def center_points(row_obj):
    row_obj.points = row_obj.points - mean_pts
    return row_obj

ratings_df.apply(center_points, axis='columns')

Функция center_points изменяет переданную ей строку и возвращает её же. На беглом запуске результат выглядит корректным, что и порождает вопрос: раз работает, почему в документации сказано, что изменять нельзя?

Что здесь означает «не поддерживается»

«Не поддерживается в DataFrame.apply()» означает, что Pandas не гарантирует результат, если ваша функция меняет объект, переданный в apply. Если вы получите неожиданное или неверное поведение, такая непоследовательность не считается багом Pandas. И наоборот, даже если сегодня всё совпадает с ожиданиями, будущие версии не обязаны сохранять это поведение. Никаких встроенных ограничений, которые помешают вам мутировать объект внутри apply, нет — это валидный Python, — но Pandas не обещает устойчивой семантики в таком случае.

Рекомендуемый подход

Безопасная модель мышления для DataFrame.apply — создавать новый объект на основе старого, а не менять строки на месте. Если вы всё же используете apply, относитесь к нему как к преобразованию, которое возвращает новый DataFrame или Series:

df_out = ratings_df.apply(some_func, axis='columns')

Для арифметики по столбцам предпочтительнее встроенные операции Pandas или NumPy. Они векторизованы, обычно быстрее и проще. В нашем случае вычитание среднего из столбца — это прямое столбцовое действие. apply вовсе не нужен.

Решение без мутаций в apply

Того же эффекта — вычесть среднее из столбца points — можно добиться напрямую и эффективно:

mean_pts = ratings_df.points.mean()
ratings_df.points -= mean_pts

Этот подход использует векторизованную арифметику для модификации столбца и соответствует рекомендуемой работе со столбцами DataFrame.

Почему это важно

Опора на мутацию внутри apply привязывает ваш код к поведению, которое Pandas не гарантирует. Это делает конвейеры хрупкими и порождает тонкие несоответствия, которые трудно отлавливать — особенно между версиями. Векторизованные операции или использование apply как чистого преобразования дают более ясные намерения, лучшую производительность и большую стабильность со временем.

Выводы

Если нужно скорректировать значения в столбце, выбирайте прямое, векторизованное присваивание, а не изменение строк внутри apply. Когда всё‑таки прибегаете к apply, проектируйте функцию так, чтобы она возвращала новое значение или новую строку, а не меняла аргумент на месте. Так преобразования остаются предсказуемыми и совместимыми с будущими версиями.