В чём разница между apply(), map() и applymap() (в Pandas 2.x переименован в map())?
Series.map — поэлементно на одном столбце (словарь или func). DataFrame.apply(axis=1) — func получает строку-Series. DataFrame.map (ex-applymap) — func на каждую ячейку. apply(axis=1) самый медленный — предпочитайте векторизацию.
Три метода — три контракта
В Pandas 2.x существуют три похожих метода, которые часто путают:
Series.map(func_or_dict)— поэлементное преобразование одной Series; принимает функцию, словарь или другую Series.DataFrame.apply(func, axis=0|1)— применяет функцию к каждой колонке (axis=0) или к каждой строке (axis=1); func получает Series.DataFrame.map(func)— поэлементное преобразование каждой ячейки DataFrame; в Pandas <2.1 называлсяapplymap().
Когда что использовать
Series.map — замена значений через словарь или простая поэлементная функция на одном столбце. Быстрее apply, потому что не создаёт промежуточные объекты Series:
import pandas as pd
df = pd.DataFrame({
"city": ["Moscow", "Berlin", "NYC", "Berlin"],
"salary": [120_000, 95_000, 180_000, 88_000],
})
# map со словарём — O(1) lookup вместо Python-цикла
country_map = {"Moscow": "RU", "Berlin": "DE", "NYC": "US"}
df["country"] = df["city"].map(country_map) # NaN для неизвестных ключей
print(df)
DataFrame.apply с axis=1 — когда нужно комбинировать несколько колонок в одной строке:
# Нормализация зарплаты по медиане города
def normalize(row):
return row["salary"] / df.groupby("city")["salary"].transform("median")[row.name]
# Лучше через vectorized операции, но apply читаем для прототипа:
df["salary_norm"] = df.apply(
lambda row: row["salary"] / df.loc[df["city"] == row["city"], "salary"].median(),
axis=1,
)
print(df.head())
DataFrame.map (ex-applymap) — когда нужно применить одну функцию к каждой ячейке всего DataFrame, например форматирование или логирование:
numeric = df[["salary", "salary_norm"]]
# Округлить все числа до 2 знаков
rounded = numeric.map(lambda x: round(x, 2) if pd.notna(x) else x)
print(rounded)
Сравнение производительности
Правило: чем меньше Python-объектов создаётся на каждую ячейку, тем быстрее.
- Vectorized NumPy / встроенные методы Pandas — самые быстрые.
Series.map(dict)— быстро, hash lookup.Series.map(func)/DataFrame.map(func)— медленнее: Python-вызов на каждый элемент.DataFrame.apply(axis=1)— самый медленный: создаёт Series для каждой строки.
import numpy as np
import time
big = pd.DataFrame({"val": np.random.randn(1_000_000)})
t0 = time.perf_counter()
big["v2"] = big["val"].map(lambda x: x ** 2) # map на Series
print(f"map: {time.perf_counter() - t0:.3f}s")
t0 = time.perf_counter()
big["v3"] = big["val"] ** 2 # vectorized
print(f"vectorized: {time.perf_counter() - t0:.3f}s")
Переименование в Pandas 2.1
DataFrame.applymap() объявлен устаревшим в Pandas 2.1 и заменён на DataFrame.map(). Старый код с applymap выбросит FutureWarning. Переход тривиален — имя метода меняется, сигнатура идентична.
Подводные камни
- axis=0 по умолчанию в apply —
df.apply(func)применяет func к колонкам, не к строкам. Начинающие ожидают поэлементного поведения и получают неожиданный результат. - NaN при map со словарём — если ключ отсутствует в словаре,
Series.map(dict)возвращаетNaN, а не KeyError. Легко пропустить молчаливую потерю данных. - applymap не существует в Pandas ≥2.1 — код из Stack Overflow образца 2022 года упадёт с AttributeError или FutureWarning.
- apply(axis=1) на больших DataFrame — O(n) создание объектов Series; на миллионе строк в 10–50 раз медленнее vectorized эквивалента.
- Возврат разных типов из apply — если func возвращает скаляр для одних строк и список для других, Pandas может вернуть Series of lists вместо развёрнутого DataFrame.
- inplace в apply —
applyвсегда возвращает новый объект; попытка мутировать исходный DataFrame внутри func с CoW (Pandas 2.0+) не даст эффекта. - map на DataFrame vs Series —
df["col"].map()иdf.map()— разные методы разных классов; передача DataFrame туда, где ожидается Series, вызовет TypeError. - result_type в apply — параметр
result_type="expand"нужен, чтобы func, возвращающая tuple/list, разворачивалась в несколько колонок; без него получится Series of lists.
Common mistakes
- Объяснять
apply map applymapтолько синтаксисом без shape, dtype, состояния или режима выполнения. - Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
- Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.
What the interviewer is testing
- Может ли связать
apply map applymapс реальным контрактом входов и выходов. - Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
- Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.