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.