apply vs map vs applymap в Pandas: шпаргалка

Зачем знать разницу

Один из классических вопросов на собесе аналитика с Python. Методы похожие, но работают по-разному — и знать это нужно.

Краткая шпаргалка

Метод На чём работает Что принимает Что возвращает
Series.map() Series dict, Series, функция Series
Series.apply() Series функция Series
DataFrame.apply() DataFrame функция Series или DataFrame
DataFrame.applymap() DataFrame функция DataFrame (elemwise)

Series.map()

Маппинг значений по словарю, Series или функции.

С dict

s = pd.Series(['a', 'b', 'c', 'a'])
mapping = {'a': 1, 'b': 2, 'c': 3}
s.map(mapping)
# [1, 2, 3, 1]

С Series (lookup-таблица)

lookup = pd.Series({'a': 10, 'b': 20, 'c': 30})
s.map(lookup)
# [10, 20, 30, 10]

С функцией

s = pd.Series([1, 2, 3])
s.map(lambda x: x * 2)
# [2, 4, 6]

Особенность: если ключ не найден — NaN. Используйте fillna или param na_action.

Series.apply()

Применяет функцию к каждому элементу. Похоже на map с функцией, но поддерживает аргументы.

s = pd.Series([1, 2, 3])
s.apply(lambda x: x ** 2)
# [1, 4, 9]

# С аргументами
s.apply(lambda x, n: x ** n, n=3)
# [1, 8, 27]

Возвращает Series или множественные значения

s.apply(lambda x: pd.Series([x, x**2], index=['orig', 'sq']))
# DataFrame с колонками orig и sq

Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.

DataFrame.apply()

Применяет функцию к строке или столбцу (не к ячейке!).

По столбцам (по умолчанию axis=0)

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

df.apply(sum)
# a    6
# b   15

По строкам (axis=1)

df.apply(sum, axis=1)
# 0    5
# 1    7
# 2    9

С произвольной функцией

df.apply(lambda row: row['a'] + row['b'], axis=1)

DataFrame.applymap()

Применяется к каждой ячейке. Deprecated в новых версиях — используйте map:

# Старое
df.applymap(lambda x: x * 2)

# Новое (pandas 2.1+)
df.map(lambda x: x * 2)

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

map для Series

  • Маппинг категории → код: s.map({'a': 1, 'b': 2}).
  • Простая трансформация: s.map(str.lower).

apply для Series

  • Сложная функция с аргументами: s.apply(my_func, arg=5).
  • Функция возвращает несколько значений: s.apply(lambda x: pd.Series([x, -x])).

apply для DataFrame

  • Функции по строкам/столбцам: sum, max, custom aggregations.
  • axis=1 — построчно, axis=0 — по столбцам (реже).

applymap / map для DataFrame

  • Функция для каждой ячейки: форматирование, преобразование типов.

Производительность

Медленнее векторизации, но приемлемо:

  • applymap / .map на DataFrame — самое медленное.
  • apply(axis=1) — медленно, цикл по строкам.
  • apply(axis=0) — быстрее.
  • .map — быстрее всех этих.

Ещё быстрее — NumPy-операции:

# ❌ Медленно
df['sum'] = df.apply(lambda row: row['a'] + row['b'], axis=1)

# ✅ Быстро — векторизация
df['sum'] = df['a'] + df['b']

На собесе часто спрашивают: «как ускорить этот apply?» Ответ — векторизация через + - * / или np.where.

Типичные задачи

1. Категория → число

df['category_code'] = df['category'].map({'A': 1, 'B': 2, 'C': 3})

2. Длина строки в столбце

df['name_len'] = df['name'].apply(len)
# или
df['name_len'] = df['name'].str.len()  # быстрее

3. Сумма по строке

df['total'] = df[['a', 'b', 'c']].sum(axis=1)
# НЕ через apply — это векторизовано

4. Условная метка

df['label'] = df['amount'].apply(lambda x: 'high' if x > 1000 else 'low')
# Лучше через np.where:
df['label'] = np.where(df['amount'] > 1000, 'high', 'low')

5. Сложная трансформация row-wise

def classify(row):
    if row['age'] < 18:
        return 'child'
    elif row['income'] < 30000:
        return 'low'
    else:
        return 'high'

df['class'] = df.apply(classify, axis=1)

Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.

10 задач с собесов

1. Маппинг категории в лейбл

df['label'] = df['cat'].map({'a': 'Cat A', 'b': 'Cat B'})

2. Нижний регистр для строк

df['name'].map(str.lower)
# Быстрее: df['name'].str.lower()

3. Применить условие row-wise

df.apply(lambda row: row['x'] if row['y'] > 0 else -row['x'], axis=1)

4. Подсчёт символов в ячейках DataFrame

df.map(len)  # pandas 2.1+
# Или df.applymap(len) в старых версиях

5. Разбить колонку «ФИО» на три

df[['fam', 'name', 'otch']] = df['fio'].str.split(' ', expand=True)
# Через apply — медленнее

6. Применить функцию к группам

df.groupby('cat').apply(lambda g: g.sort_values('x').head(1))

7. Безопасный map с fillna

df['code'].map(mapping).fillna(-1)

8. Применить функцию с аргументами

df['x'].apply(lambda x, n: x * n, n=10)

9. map по Series из другой таблицы

user_to_city = users.set_index('user_id')['city']
orders['city'] = orders['user_id'].map(user_to_city)
# Быстрее, чем merge для simple lookup

10. apply против векторизации

# Медленно
df['x2'] = df['x'].apply(lambda x: x ** 2)

# Быстро
df['x2'] = df['x'] ** 2

Как тренироваться

Разницу учить на практике: попробуйте 3-4 задачи через каждый метод, почувствуйте, где какой подходит.

Совет: на собесе, если показывают код с apply и спрашивают «как улучшить» — почти всегда ответ «векторизация». Это знак технической зрелости.

Читайте также

FAQ

Чем apply отличается от map?

map работает только с Series, принимает dict/Series/функция, не поддерживает kwargs. apply — универсальный, с kwargs, работает на Series и DataFrame.

Почему applymap deprecated?

В pandas 2.1+ DataFrame.map заменяет applymap. Функциональность та же, имя согласовано с Series.map.

Как ускорить медленный apply?

  1. Векторизовать (обычно + * или np.where).
  2. Использовать .str методы для строк.
  3. Если через функцию — попробовать numba или cython.

Когда apply всё-таки уместен?

Когда логика сложная, не выражается через векторные операции. Например, классификация с 5 условиями или parsing сложной строки.