apply() для столбцов и строк
Используйте DataFrame.apply() с axis=0 и axis=1, чтобы выполнять пользовательскую функцию над каждым столбцом или каждой строкой.
«apply() для столбцов и строк» — бесплатный урок Pandas & NumPy Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Pandas & NumPy Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Pandas & NumPy Academy содержит 4 уроков всего.
Когда использовать apply()
DataFrame.apply() применяет пользовательскую функцию к каждому столбцу (axis=0) или каждой строке (axis=1). Это крайнее средство — оно медленнее встроенных векторизованных операций, — но оно необходимо, когда вычисление невозможно выразить с помощью арифметики NumPy, логической индексации или встроенных функций агрегирования. Используйте его для сложной условной логики, специального разбора строк или многоэтапных вычислений, выполняемых за один раз для отдельной строки или столбца.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'price': [10.5, 25.0, 8.3, 100.0],
'quantity': [3, 1, 5, 2],
'tax_rate': [0.05, 0.10, 0.05, 0.15]
})
print(df)apply() для столбцов (axis=0)
При использовании axis=0 (значение по умолчанию) функция apply() передаёт каждый столбец в виде Series. Функция получает по одному столбцу за раз и должна вернуть скалярное значение (для агрегирования) или Series (для преобразования). Это удобно для единообразного применения пользовательского шага нормализации или очистки ко всем числовым столбцам одним вызовом.
# Custom range normalisation (0 to 1) for each column
def min_max_scale(col):
return (col - col.min()) / (col.max() - col.min())
df_scaled = df[['price', 'quantity']].apply(min_max_scale, axis=0)
print(df_scaled)apply() для строк (axis=1)
При использовании axis=1 функция apply() передаёт каждую строку в виде Series. Индекс строки содержит имена столбцов, поэтому значения можно получать по имени. Это идеально подходит для вычислений на уровне строки, зависящих от нескольких столбцов, например для расчёта общей выручки после налогообложения, когда для каждой строки задана собственная налоговая ставка.
def revenue_after_tax(row):
subtotal = row['price'] * row['quantity']
return subtotal * (1 + row['tax_rate'])
df['revenue_after_tax'] = df.apply(revenue_after_tax, axis=1)
print(df)Возврат нескольких значений с помощью apply()
Функция, переданная в apply(axis=1), может вернуть pd.Series с именованными элементами, которые Pandas преобразует в несколько новых столбцов. Это более удобно, чем дважды вызывать apply() для создания двух новых столбцов. Также функция может вернуть dict, который Pandas аналогичным образом преобразует в столбцы.
def compute_totals(row):
subtotal = row['price'] * row['quantity']
tax = subtotal * row['tax_rate']
return pd.Series({'subtotal': subtotal, 'tax': tax, 'total': subtotal + tax})
result = df.apply(compute_totals, axis=1)
print(result)apply() для пользовательского агрегирования столбцов
Используйте apply(func, axis=0), чтобы вычислить пользовательскую статистику для каждого столбца и вернуть сводную Series. Например, можно одним вызовом вычислить коэффициент вариации (стандартное отклонение, делённое на среднее) для каждого числового столбца. Результат представляет собой Series с индексом из имён столбцов и удобен для быстрой диагностики отдельных столбцов.
def coeff_of_variation(col):
return col.std() / col.mean() if col.mean() != 0 else np.nan
cv = df[['price', 'quantity']].apply(coeff_of_variation, axis=0)
print('Coefficient of variation per column:')
print(cv)Сравнение apply() с векторизованными альтернативами
Перед использованием apply() всегда проверяйте, существует ли векторизованная альтернатива. Для расчёта выручки после налогообложения выражение df['price'] * df['quantity'] * (1 + df['tax_rate']) выполняет ту же задачу, что и вариант с apply(axis=1), но работает в 10–100 раз быстрее, поскольку использует циклы NumPy на уровне C. Используйте apply() только в случаях, когда векторизованная логика была бы слишком сложной для чтения.
import time
start = time.time()
df['vectorised'] = df['price'] * df['quantity'] * (1 + df['tax_rate'])
print('Vectorised:', time.time() - start, 's')
start = time.time()
df['apply_result'] = df.apply(revenue_after_tax, axis=1)
print('apply():', time.time() - start, 's')apply() с лямбда-функцией
Для коротких преобразований в одну строку передавайте в apply() лямбда-функцию lambda напрямую, вместо определения именованной функции. Лямбда-функции удобны для простых операций, но их следует избегать в сложной логике, где именованную функцию с комментариями проще понимать и тестировать. Используйте лямбда-функции умеренно: их нельзя легко тестировать отдельно или профилировать по имени.
# Clip revenue between 0 and 200, then round to nearest 10
df['revenue_clean'] = df['revenue_after_tax'].apply(lambda x: round(min(max(x, 0), 200) / 10) * 10)
print(df[['revenue_after_tax', 'revenue_clean']])Передача дополнительных аргументов в apply()
Передавайте дополнительные аргументы функции с помощью кортежа args или именованных аргументов в apply(func, args=(val,), axis=1). Это позволяет не использовать глобальные переменные внутри функции и делает её повторно используемой с разными значениями параметров. В Python 3.8 и более поздних версиях также можно использовать functools.partial, чтобы создать частично применённую версию функции.
def discount_price(row, discount_pct, threshold):
if row['quantity'] >= threshold:
return row['price'] * (1 - discount_pct)
return row['price']
df['discounted_price'] = df.apply(
discount_price, axis=1,
args=(0.1, 3) # 10% discount for quantity >= 3
)
print(df[['price', 'quantity', 'discounted_price']])apply() для преобразования типов
Если столбец содержит значения разных типов — целые числа, числа с плавающей точкой и строки, — astype() вызывает ошибку. Используйте apply(pd.to_numeric, errors='coerce'), чтобы попытаться преобразовать значения построчно и вернуть NaN для тех, которые преобразовать невозможно. Это безопасный подход для столбцов, которые должны быть числовыми, но иногда содержат нечисловые записи из-за ошибок ввода данных.
messy = pd.Series(['10', '20.5', 'N/A', '100', '--'])
clean = messy.apply(pd.to_numeric, errors='coerce')
print(clean)Производительность: apply() и np.vectorize
Когда функцию для скалярных значений необходимо применить поэлементно, np.vectorize(func)(array) обычно работает быстрее, чем Series.apply(func), поскольку vectorize из NumPy выполняет диспетчеризацию через C, избегая накладных расходов на вызовы функций Python. Однако np.vectorize всё равно медленнее настоящей широковещательной обработки — этот инструмент полезен только тогда, когда ни одно выражение с широковещательной обработкой не позволяет выразить логику.
def classify_size(price):
if price < 15:
return 'small'
elif price < 50:
return 'medium'
return 'large'
# np.vectorize approach
classify_v = np.vectorize(classify_size)
df['size_class'] = classify_v(df['price'].values)
print(df[['price', 'size_class']])Когда apply() — правильный выбор
Используйте apply, когда: (1) логика требует одновременного ветвления по значениям нескольких столбцов; (2) функция вызывает внешний API или базу данных для каждой строки (и последовательной обработки избежать нельзя); (3) функция разбирает сложную строку, например хранящийся в ячейке блок JSON; или (4) функция возвращает объект переменной длины, например список. Во всех остальных случаях предпочитайте векторизованные операции ради скорости и удобства чтения.
import json
# Parse a JSON metadata column row by row
df['metadata'] = ['{"color": "red"}', '{"color": "blue"}', '{}', '{"color": "green"}']
df['color'] = df['metadata'].apply(lambda s: json.loads(s).get('color', 'unknown'))
print(df[['metadata', 'color']])Быстрая проверка
Проверьте, насколько хорошо Вы усвоили концепции анализа данных из этого урока.
Итоги урока
В этом уроке Вы научились: использовать apply(axis=0) для вычисления пользовательской статистики на уровне столбцов, использовать apply(axis=1) для вычислений на уровне строк с несколькими входными столбцами, а также понимать, когда для повышения производительности следует предпочесть векторизованные альтернативы. Далее мы рассмотрим использование apply() с GroupBy для сложных агрегирований на уровне групп.
Изучай Python с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «apply() для столбцов и строк» бесплатный?
Да — полный текст урока «apply() для столбцов и строк» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Pandas & NumPy Academy, подпишись на CoddyKit PRO. Курс Pandas & NumPy Academy содержит 4 уроков всего.
Чему я научусь в уроке «apply() для столбцов и строк»?
Используйте DataFrame.apply() с axis=0 и axis=1, чтобы выполнять пользовательскую функцию над каждым столбцом или каждой строкой. Ты практикуешь Pandas & NumPy Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Pandas & NumPy Academy?
Предыдущий опыт не требуется. Pandas & NumPy Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «apply() для столбцов и строк»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Pandas & NumPy Academy?
Да. Каждый урок Pandas & NumPy Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- apply() для столбцов и строк
- apply() с GroupBy
- map() и applymap() для поэлементных операций
- Цепочки методов с pipe()