apply vs map vs applymap в Pandas — в чём разница

Коротко

В pandas есть три метода для применения функций к данным: map, apply и applymap. map работает поэлементно с Series, apply — по строкам или столбцам, applymap — поэлементно ко всему DataFrame. На собеседованиях аналитиков просят объяснить разницу и выбрать подходящий метод для задачи.

В pandas 2.1+ метод applymap переименован в DataFrame.map(). Старое название работает, но выдаёт FutureWarning. В новом коде используйте DataFrame.map().

map — поэлементно для Series

Series.map() применяет функцию, словарь или Series к каждому элементу столбца. Работает только с Series — к целому DataFrame не применить.

import pandas as pd

df = pd.DataFrame({
    'name': ['Анна', 'Борис', 'Вика'],
    'department': ['аналитика', 'разработка', 'аналитика'],
    'salary': [90000, 150000, 110000]
})

# Функция к каждому элементу
df['salary_k'] = df['salary'].map(lambda x: f"{x // 1000}k")
# 0     90k
# 1    150k
# 2    110k

# Словарь — удобно для маппинга категорий
dept_map = {'аналитика': 'AN', 'разработка': 'DEV'}
df['dept_code'] = df['department'].map(dept_map)

Если ключа нет в словаре, map вернёт NaN. Это полезно — сразу видно неожиданные значения в данных.

apply — строки, столбцы, гибкость

apply() есть и у Series, и у DataFrame. Для Series работает поэлементно. Для DataFrame — применяет функцию к строкам (axis=1) или столбцам (axis=0).

df = pd.DataFrame({
    'name': ['Анна', 'Борис', 'Вика'],
    'sql_score': [85, 92, 78],
    'python_score': [90, 88, 95]
})

# По столбцам (axis=0) — функция получает Series-столбец
df[['sql_score', 'python_score']].apply(lambda col: col.max() - col.min())
# sql_score       14
# python_score     7

# По строкам (axis=1) — функция получает строку как Series
def evaluate(row):
    avg = (row['sql_score'] + row['python_score']) / 2
    return 'сильный' if avg >= 88 else 'средний'

df['level'] = df.apply(evaluate, axis=1)

apply с axis=1 — самый медленный вариант в pandas. Для каждой строки создаётся Series, вызывается Python-функция. На миллионе строк это заметно.

applymap — поэлементно для DataFrame

applymap() применяет функцию к каждому элементу DataFrame. Удобно, когда нужно трансформировать все ячейки одинаково.

df = pd.DataFrame({
    'q1': [1200.567, 980.123, 1500.789],
    'q2': [1100.456, 1050.321, 1400.654]
})

# Округление всех значений
df_rounded = df.applymap(lambda x: round(x, 1))

# В pandas 2.1+ используйте DataFrame.map()
df_rounded = df.map(lambda x: round(x, 1))

На практике applymap используется редко — обычно эффективнее работать постолбцово или использовать векторные операции.

Таблица отличий

map apply applymap
Работает с Series Series и DataFrame DataFrame
Применяется к Элементам Элементам, строкам, столбцам Элементам
Принимает dict Да Нет Нет
axis Нет 0 (столбцы) / 1 (строки) Нет
Статус Актуален Актуален Deprecated с pandas 2.1
Замена DataFrame.map()

Когда что использовать

map — маппинг значений по словарю, простые поэлементные преобразования одного столбца. Замена категорий, форматирование.

apply — логика зависит от нескольких столбцов, агрегация по строкам/столбцам, сложные условия. Хорошо работает с groupby.

applymap / DataFrame.map() — одинаковое преобразование всех ячеек DataFrame. Форматирование, округление, type casting.

Векторные альтернативы — быстрее apply

Прежде чем писать apply, подумайте: можно ли решить задачу без него? Векторные операции в 10-100 раз быстрее.

# Плохо — apply на каждую строку
df['bonus'] = df.apply(lambda row: row['salary'] * 0.15 if row['rating'] >= 4 else 0, axis=1)

# Хорошо — np.where, векторная операция
import numpy as np
df['bonus'] = np.where(df['rating'] >= 4, df['salary'] * 0.15, 0)

# Плохо — applymap для округления
df[['q1', 'q2']].applymap(lambda x: round(x, 2))

# Хорошо — встроенный метод
df[['q1', 'q2']].round(2)

Альтернативы: np.where, np.select, pd.cut, арифметика с Series, строковые методы .str. Подробнее — в шпаргалке по pandas.

Частые ошибки

apply вместо векторных операций. df['col'].apply(lambda x: x * 2) — медленнее, чем df['col'] * 2. Если функция — простая арифметика или условие, apply не нужен.

applymap в новом коде. С pandas 2.1 метод deprecated. Используйте DataFrame.map(), иначе получите предупреждение, а позже — ошибку.

map для DataFrame. DataFrame.map(func) в pandas 2.1+ — это бывший applymap, а не Series.map(). Путаница с именами — частый источник багов.

Вопросы с собеседований

Чем отличаются map, apply и applymap? — map работает поэлементно только с Series, принимает функцию или словарь. apply работает с Series и DataFrame, может обрабатывать строки или столбцы. applymap (deprecated, теперь DataFrame.map) — поэлементно для DataFrame.

Что произошло с applymap в новых версиях pandas? — В pandas 2.1 applymap переименован в DataFrame.map(). Старое имя выдаёт FutureWarning. Это сделали для единообразия — теперь map есть и у Series, и у DataFrame.

Когда apply оправдан, а когда лучше векторизация? — apply оправдан, когда логика зависит от нескольких столбцов и не выражается через np.where/np.select. Для простой арифметики, условий и строковых операций — всегда векторизация.

Как ускорить apply с axis=1? — Заменить на np.where, np.select или np.vectorize. Если логика слишком сложная — попробовать itertuples() (быстрее apply) или Cython. Но сначала проверить, можно ли переписать через векторные операции.

Можно ли передать словарь в apply? — Нет, apply принимает только функцию. Для маппинга по словарю используйте map или replace.

Потренировать Python-вопросы на практике можно в тренажёре Карьерника. А больше примеров вопросов — на отдельной странице.

Попробовать тренажёр в Telegram — вопросы по pandas, SQL и аналитике. Бесплатно.

FAQ

Чем DataFrame.map() отличается от Series.map()?

Series.map() принимает функцию, словарь или Series и работает поэлементно с одним столбцом. DataFrame.map() (бывший applymap) принимает только функцию и применяет её к каждому элементу всего DataFrame. Несмотря на одинаковое имя, это разные методы с разным поведением.

Что быстрее — map или apply для Series?

Для простых операций map немного быстрее, потому что у apply больше overhead. Но оба медленнее векторных операций. Если задача решается через df['col'] * 2 — используйте это.

Как мигрировать с applymap на DataFrame.map()?

Просто замените df.applymap(func) на df.map(func). Сигнатура и поведение идентичны. Единственное — убедитесь, что версия pandas >= 2.1.

Можно ли использовать apply после groupby?

Да, и это мощный паттерн. df.groupby('col').apply(func) вызывает функцию для каждой группы как отдельного DataFrame. Подробнее — в гайде по groupby. Также полезно разобрать разницу между map и apply для Series.