Как применить функцию к колонке в 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 — простой lookup
  • apply на 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'] * 12

Numpy-операции работают в 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+ вопросами для собесов.