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.13

apply с 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').