map vs apply в Pandas — в чём разница и когда что использовать
Коротко
map работает только с Series и применяет функцию поэлементно. apply работает и с Series, и с DataFrame — может применять функцию к строкам, столбцам или элементам. map быстрее для простых преобразований, apply гибче для сложной логики.
Что такое map
Series.map() принимает функцию, словарь или Series и применяет преобразование к каждому элементу. Работает только с Series (отдельным столбцом), не с DataFrame.
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вика'],
'city': ['Москва', 'Питер', 'Казань'],
'salary': [80000, 120000, 95000]
})
# Функция к каждому элементу
df['salary_k'] = df['salary'].map(lambda x: f"{x // 1000}k")
# 0 80k
# 1 120k
# 2 95k
# Словарь для маппинга значений
city_map = {'Москва': 'MSK', 'Питер': 'SPB', 'Казань': 'KZN'}
df['city_code'] = df['city'].map(city_map)
# 0 MSK
# 1 SPB
# 2 KZNЕсли значение не найдено в словаре, map вернёт NaN — это удобно для обнаружения неожиданных данных.
Что такое apply
apply() есть и у Series, и у DataFrame. Для Series — работает поэлементно (как map). Для DataFrame — применяет функцию к каждой строке (axis=1) или столбцу (axis=0).
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вика'],
'math': [85, 92, 78],
'python': [90, 88, 95]
})
# apply к столбцу (axis=0, по умолчанию) — функция получает Series
df_stats = df[['math', 'python']].apply(lambda col: col.max() - col.min())
# math 14
# python 7
# apply к строкам (axis=1) — функция получает строку как Series
df['total'] = df[['math', 'python']].apply(lambda row: row.sum(), axis=1)
# 0 175
# 1 180
# 2 173
# Сложная логика по строке
def grade(row):
avg = (row['math'] + row['python']) / 2
if avg >= 90:
return 'A'
elif avg >= 80:
return 'B'
return 'C'
df['grade'] = df.apply(grade, axis=1)
# 0 B
# 1 A
# 2 BКлючевые отличия
| map | apply | |
|---|---|---|
| Работает с | Только Series | Series и DataFrame |
| Принимает | Функцию, dict, Series | Функцию |
| Применяется к | Элементам | Элементам, строкам или столбцам |
| Маппинг через dict | Да | Нет |
| Производительность | Быстрее для простых операций | Медленнее из-за гибкости |
| Возврат NaN при отсутствии ключа | Да (для dict) | Нет (будет ошибка) |
Когда использовать map
- Замена значений по словарю (категории, коды, переименование)
- Простые поэлементные преобразования одного столбца
- Форматирование значений (строки, округление)
- Когда нужен NaN для отсутствующих ключей
# Маппинг категорий
status_map = {0: 'inactive', 1: 'active', 2: 'banned'}
df['status_label'] = df['status'].map(status_map)
# Быстрое преобразование типа
df['price_str'] = df['price'].map('{:.2f} ₽'.format)Когда использовать apply
- Логика зависит от нескольких столбцов одной строки
- Агрегация по строкам или столбцам DataFrame
- Сложные условия, которые не выразить одной lambda
- Применение функции к группам (после groupby)
# Логика зависит от нескольких столбцов
df['bonus'] = df.apply(
lambda row: row['salary'] * 0.2 if row['rating'] >= 4 else row['salary'] * 0.1,
axis=1
)
# После groupby
df.groupby('department')['salary'].apply(lambda x: x.nlargest(3).mean())Типичная ошибка
Использовать apply там, где можно обойтись векторными операциями:
# Медленно — apply вызывает Python-функцию для каждой строки
df['tax'] = df['salary'].apply(lambda x: x * 0.13)
# Быстро — векторная операция через NumPy
df['tax'] = df['salary'] * 0.13apply с axis=1 — одна из самых медленных операций в Pandas. На 1 млн строк разница с векторной операцией может быть 100x. Всегда проверяйте, можно ли решить задачу без apply.
Вопросы с собеседований
— В чём разница между map и apply в Pandas? — map работает только с Series поэлементно и принимает словарь или функцию. apply работает с Series и DataFrame, может применять функцию к строкам или столбцам.
— Что быстрее — map или apply? — Для поэлементных операций на Series map немного быстрее. Но оба медленнее векторных операций. Если можно заменить на df['col'] * 2 — лучше так.
— Как заменить значения по словарю в Pandas?
— Через map: df['col'].map(my_dict). Альтернатива — df['col'].replace(my_dict), но replace не поставит NaN для отсутствующих ключей.
— Когда apply лучше, чем map? — Когда нужно работать с несколькими столбцами одновременно (axis=1) или агрегировать столбцы DataFrame (axis=0). map не может этого.
— Есть ли альтернативы apply для ускорения?
— Векторные операции NumPy, np.where для условий, np.select для множественных условий, pd.cut для бинаризации. Для совсем тяжёлых случаев — Cython или df.itertuples().
FAQ
А что насчёт applymap?
applymap (в новых версиях Pandas переименован в map для DataFrame) применяет функцию к каждому элементу DataFrame. Используется редко — обычно эффективнее работать постолбцово.
Можно ли использовать apply с groupby?
Да, это один из самых мощных паттернов. df.groupby('col').apply(func) вызывает func для каждой группы как отдельного DataFrame. Полезно для сложных агрегаций, которые не выразить стандартными agg-функциями.
Почему apply с axis=1 такой медленный?
Потому что для каждой строки Pandas создаёт Series из значений строки, вызывает Python-функцию, собирает результат. Это полностью обходит механизм векторизации NumPy. На большом DataFrame разница катастрофическая.
map вернул NaN — что делать?
Значит в словаре нет ключа для этого значения. Добавьте недостающий ключ в словарь или используйте fillna() после map для значения по умолчанию: df['col'].map(my_dict).fillna('unknown').