Как применить функцию к колонке в pandas
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это нужно
В pandas часто нужно применить функцию к колонке: конвертировать валюту (price × 90), категоризировать (age → young/middle/senior), извлечь часть строки, посчитать что-то не-вышеперечисленное. apply, map, векторизированные операции — три способа, каждый со своими trade-offs.
На собесе спрашивают: «чем отличается apply от map», «что быстрее». Junior использует apply везде. Middle знает, что map на Series быстрее, а vectorized операции (df['price'] * 90) в 100× быстрее apply. Senior понимает, что apply — antipattern на больших данных.
В статье:
mapна Series — простой lookupapplyна Series — функция к каждому элементуapplyна DataFrame — к строке или столбцу (axis)applymap(deprecated)- Vectorized операции — самый быстрый путь
- Performance comparison
1. Vectorized — первый выбор
Если можно vectorize — используйте:
df['price_rub'] = df['price_usd'] * 90
df['is_premium'] = df['total_spent'] > 100000
df['full_name'] = df['first_name'] + ' ' + df['last_name']
df['age_in_months'] = df['age'] * 12Numpy-операции работают в C, в 100-1000× быстрее apply.
2. map на Series — lookup
Через dict
category_map = {'A': 'Electronics', 'B': 'Clothing', 'C': 'Food'}
df['category_name'] = df['category_code'].map(category_map)Через функцию
df['price_tier'] = df['price'].map(lambda x: 'expensive' if x > 1000 else 'cheap')Через Series
df['mapped'] = df['key'].map(other_df.set_index('key')['value'])3. apply на Series
# простой вариант (как map)
df['price_log'] = df['price'].apply(np.log)
# сложная логика
def classify(row):
if row > 10000: return 'high'
elif row > 1000: return 'medium'
else: return 'low'
df['tier'] = df['price'].apply(classify)4. apply на DataFrame
axis=1 (по строке)
def compute_total(row):
return row['price'] * row['quantity'] + row['shipping']
df['total'] = df.apply(compute_total, axis=1)Медленно на больших DataFrame. Лучше vectorize:
df['total'] = df['price'] * df['quantity'] + df['shipping']axis=0 (по столбцу)
# статистика для каждой колонки
df.apply(lambda col: col.max() - col.min(), axis=0)5. applymap — устарел
До pandas 2.1 использовался для element-wise в DataFrame. Сейчас deprecated, используйте:
df.map(lambda x: str(x).upper()) # pandas 2.1+6. Performance comparison
На 1M строк:
| Способ | Время |
|---|---|
Vectorized (df['a'] * 2) |
10 ms |
df['a'].map(lambda x: x*2) |
300 ms |
df['a'].apply(lambda x: x*2) |
400 ms |
df.apply(lambda row: row['a']*2, axis=1) |
5-10 s |
Правило: vectorized → map → apply → apply(axis=1). Выбирайте первое возможное.
7. np.where для простых условий
# вместо apply
df['tier'] = np.where(df['price'] > 1000, 'expensive', 'cheap')
# для трёх вариантов
df['tier'] = np.select(
[df['price'] > 10000, df['price'] > 1000],
['high', 'medium'],
default='low'
)Быстрее apply.
8. pandas.cut для bins
df['age_group'] = pd.cut(df['age'],
bins=[0, 18, 35, 55, 100],
labels=['teen', 'young', 'middle', 'senior'])9. str accessor для строк
# не apply, а str
df['email_lower'] = df['email'].str.lower()
df['email_domain'] = df['email'].str.split('@').str[1]
df['phone_digits'] = df['phone'].str.replace(r'\D', '', regex=True)Быстрее apply(lambda x: x.lower()).
10. dt accessor для дат
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['weekday'] = df['date'].dt.weekday
df['is_weekend'] = df['date'].dt.weekday >= 5Частые ошибки
1. apply там, где можно vectorize
# плохо (медленно)
df['total'] = df.apply(lambda row: row['price'] * row['qty'], axis=1)
# хорошо
df['total'] = df['price'] * df['qty']2. apply на больших данных
На 10M строк apply может работать минуты. Используйте vectorized или numpy.
3. Результат apply не DataFrame
# если функция возвращает series — результат DataFrame
df['result'] = df['col'].apply(lambda x: pd.Series({'a': x, 'b': x*2}))
# может запутать4. Не копировать перед apply
SettingWithCopyWarning при присвоении после apply без .copy().
Когда apply оправдан
- Сложная логика, невозможно vectorize
- Маленькие данные (< 100K строк)
- Один раз — не в production pipeline
Связанные темы
FAQ
apply всегда медленный?
На axis=1 на больших — да. На Series — немного медленнее map.
np.vectorize помогает?
Не особо. Обёртка, но не настоящая векторизация.
parallel apply?
swifter / pandarallel — параллельный apply. Быстрее, но сложнее.
Numba?
Для heavy numeric — да. Превращает Python в compiled.
Тренируйте pandas — откройте тренажёр с 1500+ вопросами для собесов.