5 методов работы с двумя столбцами в Pandas: сравнение и оптимизация

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Аналитики данных и разработчики, работающие с Pandas в Python
  • Студенты и специалисты, желающие повысить свои навыки в обработке данных
  • Профессионалы, стремящиеся оптимизировать производительность кодов для обработки больших объемов информации

    Работа с данными в Python — это искусство, в котором библиотека Pandas играет роль основного холста. Одна из частых задач, с которой сталкиваются аналитики и разработчики — применение функций к двум столбцам DataFrame. Как рассчитать новый столбец на основе значений из двух других? Как это сделать максимально эффективно, не погрязнув в циклах и неоптимальном коде? В этой статье я раскрою 5 проверенных методов, которые позволят вам элегантно решать подобные задачи в Pandas, значительно ускоряя обработку данных. 📊🐼

Хотите освоить Python и научиться профессионально работать с данными? Обучение Python-разработке от Skypro — это практический курс, где вы изучите не только основы языка, но и профессиональную работу с библиотеками для анализа данных, включая Pandas. Вы научитесь писать оптимальный код для обработки больших объемов информации и реализовывать сложные алгоритмы анализа, которые пригодятся в реальных проектах.

Сравнение 5 методов работы с двумя столбцами в Pandas

Pandas предоставляет несколько способов применения функций к двум столбцам DataFrame. Каждый метод имеет свои преимущества и недостатки, которые важно понимать для выбора оптимального решения в конкретной ситуации.

Давайте представим, что у нас есть таблица продаж с двумя столбцами: quantity (количество проданных единиц) и price (цена за единицу). Нам нужно вычислить общую стоимость для каждой строки, умножив количество на цену.

Вот пять основных методов, которые мы подробно рассмотрим:

  1. Векторизованные операции — прямые арифметические действия между столбцами
  2. Метод apply() с лямбда-функциями
  3. Использование zip() в сочетании с map()
  4. Метод numpy.vectorize() для создания векторизованных функций
  5. Использование eval() для выражений

Давайте начнем с создания тестового DataFrame для наших примеров:

Python
Скопировать код
import pandas as pd
import numpy as np

# Создаем тестовый DataFrame
df = pd.DataFrame({
'quantity': [5, 10, 15, 20, 25],
'price': [1\.5, 2.5, 3.5, 4.5, 5.5]
})

Теперь сравним эти методы по нескольким ключевым параметрам:

Метод Скорость выполнения Читаемость кода Гибкость Сложность использования
Векторизованные операции Очень высокая Отличная Низкая Низкая
apply() + lambda Средняя Хорошая Высокая Средняя
zip() + map() Средняя Средняя Высокая Средняя
numpy.vectorize() Высокая Средняя Высокая Высокая
eval() Высокая Хорошая Средняя Низкая

Александр Петров, Senior Data Analyst Когда я только начинал работать с Pandas, я попытался обработать набор данных из 5 миллионов строк, используя циклы Python для вычисления значений между столбцами. Скрипт работал несколько часов, и это казалось нормальным, пока коллега не показал мне, как решить ту же задачу с помощью векторизованных операций. Разница была ошеломляющей — вместо часов код выполнялся за секунды. Это стало моим первым важным уроком: в Pandas почти всегда есть более эффективный способ, чем тот, что приходит на ум изначально.

Пошаговый план для смены профессии

Векторизованные операции в DataFrame: максимальная скорость

Векторизованные операции — самый быстрый и читаемый способ работы с двумя столбцами в Pandas. Они позволяют выполнять арифметические операции непосредственно между столбцами DataFrame без использования явных циклов.

Основное преимущество этого подхода — высокая производительность, поскольку операции выполняются на низком уровне с использованием оптимизированного кода NumPy.

Python
Скопировать код
# Умножаем количество на цену, используя векторизованную операцию
df['total'] = df['quantity'] * df['price']
print(df)

Этот код выполняется очень быстро и интуитивно понятен даже для начинающих. Pandas автоматически согласовывает индексы и выполняет умножение поэлементно.

Важно отметить, что векторизованные операции поддерживают все основные арифметические действия:

  • Сложение: df['col1'] + df['col2']
  • Вычитание: df['col1'] – df['col2']
  • Умножение: df['col1'] * df['col2']
  • Деление: df['col1'] / df['col2']
  • Возведение в степень: df['col1'] ** df['col2']
  • Целочисленное деление: df['col1'] // df['col2']
  • Остаток от деления: df['col1'] % df['col2']

Для более сложных вычислений можно комбинировать операции:

Python
Скопировать код
# Вычисляем скидку 10% от общей стоимости
df['discount'] = df['quantity'] * df['price'] * 0.1

# Вычисляем итоговую сумму с учетом скидки
df['final_price'] = df['quantity'] * df['price'] – df['discount']

Также векторизованные операции прекрасно работают с математическими функциями из библиотеки NumPy:

Python
Скопировать код
# Логарифм отношения цены к количеству
df['log_ratio'] = np.log(df['price'] / df['quantity'])

# Округляем до ближайшего целого
df['rounded_total'] = np.round(df['quantity'] * df['price'])

Несмотря на все преимущества, у векторизованных операций есть ограничения. Они хорошо работают с простыми арифметическими операциями, но могут стать неудобными, когда требуется реализовать сложную логику. В таких случаях стоит обратиться к другим методам.

Метод apply() и lambda-функции для обработки данных

Метод apply() — это гибкий инструмент для обработки данных в Pandas, который позволяет применять пользовательские функции к строкам или столбцам DataFrame. В сочетании с lambda-функциями он становится мощным средством для обработки нескольких столбцов одновременно.

Существует два основных способа использования apply() для работы с двумя столбцами:

  1. Применение apply() к отдельной строке (axis=1)
  2. Использование apply() с серией, полученной из двух столбцов

Рассмотрим первый подход:

Python
Скопировать код
# Использование apply() с axis=1 для доступа к строкам
df['total'] = df.apply(lambda row: row['quantity'] * row['price'], axis=1)

В этом примере apply() вызывается для всего DataFrame, параметр axis=1 указывает, что функция должна применяться к каждой строке, а не к столбцам. Lambda-функция принимает строку DataFrame как аргумент и возвращает произведение значений из столбцов quantity и price.

Для более сложных вычислений можно использовать именованную функцию:

Python
Скопировать код
# Определяем функцию для расчета итоговой стоимости с учетом скидки
def calculate_total_with_discount(row):
base_price = row['quantity'] * row['price']
discount_percent = 5 if row['quantity'] > 10 else 0
discount = base_price * discount_percent / 100
return base_price – discount

# Применяем функцию к каждой строке
df['total_with_discount'] = df.apply(calculate_total_with_discount, axis=1)

Второй подход с использованием серии:

Python
Скопировать код
# Создаем серию из двух столбцов
quantity_price = pd.Series(zip(df['quantity'], df['price']))

# Применяем функцию к серии
df['total'] = quantity_price.apply(lambda x: x[0] * x[1])

Этот метод может быть немного эффективнее, чем применение apply() к всему DataFrame, особенно если DataFrame содержит много столбцов.

Марина Соколова, Data Scientist Несколько лет назад я работала над проектом анализа финансовых транзакций, где нужно было классифицировать каждую транзацию по сложным правилам, зависящим от нескольких столбцов. Первоначально я использовала векторизованные операции, но код становился все более запутанным и трудным для поддержки. Переход на метод apply() с четко определенной функцией классификации решил проблему. Хотя производительность немного снизилась, код стал намного понятнее и легче в поддержке. Баланс между читаемостью и производительностью всегда был для меня ключевым фактором при выборе подхода к обработке данных.

Вот сравнение производительности разных вариантов использования apply():

Метод Производительность Читаемость Удобство отладки
apply() с axis=1 и лямбда Средняя Хорошая Средняя
apply() с именованной функцией Средняя Отличная Отличное
apply() с серией Выше средней Средняя Средняя

Когда следует использовать apply() вместо векторизованных операций? Основные случаи:

  • Логика вычислений включает условные выражения
  • Необходимо вызвать внешние функции или методы
  • Требуется доступ к нескольким столбцам с разными типами данных
  • Необходимо выполнить операции, которые сложно выразить через векторизацию

Важно помнить, что метод apply() всегда медленнее векторизованных операций, поэтому его стоит использовать только когда вам нужна дополнительная гибкость. 🔄

Использование функции zip и map в Pandas DataFrame

Функции zip() и map() предоставляют еще один элегантный способ обработки двух столбцов в Pandas. Этот подход особенно полезен, когда вам нужно попарно применить функцию к элементам столбцов, сохраняя при этом достаточную производительность.

Основная идея состоит в использовании zip() для объединения значений из двух столбцов в пары, а затем применении функции к этим парам с помощью map().

Рассмотрим базовый пример:

Python
Скопировать код
# Умножаем quantity на price с использованием zip и map
df['total'] = list(map(lambda x: x[0] * x[1], zip(df['quantity'], df['price'])))

Что происходит в этом коде:

  1. zip(df['quantity'], df['price']) создает итератор, генерирующий пары значений из обоих столбцов
  2. Функция lambda x: x[0] * x[1] умножает первый элемент пары на второй
  3. map() применяет эту функцию к каждой паре из итератора
  4. list() преобразует результат map в список, который затем присваивается новому столбцу

Для более сложных операций можно использовать именованные функции:

Python
Скопировать код
def calculate_discount(qty, price):
total = qty * price
# Скидка 10% для покупок на сумму более 50
return total * 0.9 if total > 50 else total

df['discounted_total'] = list(map(calculate_discount, df['quantity'], df['price']))

Заметьте, что в этом примере мы напрямую передаем столбцы в map(), что упрощает код.

Комбинация zip() и map() особенно эффективна, когда нужно работать с более чем двумя столбцами:

Python
Скопировать код
# Добавим столбец с налогом
df['tax_rate'] = [0\.05, 0.07, 0.06, 0.08, 0.05]

# Рассчитаем итоговую сумму с учетом налога
def calculate_total_with_tax(qty, price, tax_rate):
return qty * price * (1 + tax_rate)

df['final_total'] = list(map(calculate_total_with_tax, 
df['quantity'], 
df['price'], 
df['tax_rate']))

Этот метод имеет несколько преимуществ:

  • Более высокая производительность по сравнению с apply() для многих задач
  • Позволяет легко работать с произвольным количеством столбцов
  • Дает доступ к стандартным функциям Python (не ограничен возможностями Pandas)
  • Требует минимум дополнительного кода для реализации

Однако у него есть и недостатки:

  • Может быть менее читабельным, особенно для сложных операций
  • Требует явного преобразования результата в список
  • Не позволяет напрямую обращаться к индексам строк

При выборе между zip()+map() и другими методами, руководствуйтесь следующими принципами:

  • Используйте этот подход, когда вам нужна производительность выше, чем у apply(), но требуется гибкость, недоступная в векторизованных операциях
  • Особенно эффективен для случаев, когда логика обработки зависит от значений в нескольких столбцах
  • Хорошо подходит для работы с небольшими и средними DataFrame

Оптимизация производительности при вычислениях в Pandas

Когда речь идет о работе с большими объемами данных, производительность становится критически важным фактором. Выбор правильного метода обработки двух столбцов может значительно повлиять на время выполнения вашего кода.

Давайте рассмотрим несколько стратегий оптимизации производительности при работе с двумя столбцами в Pandas:

1. Выбор правильного метода для конкретной задачи

Проведем сравнительный анализ производительности различных методов на DataFrame с миллионом строк:

Python
Скопировать код
import pandas as pd
import numpy as np
import time

# Создаем большой DataFrame для тестирования
n = 1000000
df_large = pd.DataFrame({
'a': np.random.rand(n),
'b': np.random.rand(n)
})

# Тестирование векторизованных операций
start = time.time()
df_large['result1'] = df_large['a'] * df_large['b']
vector_time = time.time() – start

# Тестирование apply + lambda
start = time.time()
df_large['result2'] = df_large.apply(lambda x: x['a'] * x['b'], axis=1)
apply_time = time.time() – start

# Тестирование zip + map
start = time.time()
df_large['result3'] = list(map(lambda x: x[0] * x[1], zip(df_large['a'], df_large['b'])))
zip_map_time = time.time() – start

# Тестирование eval
start = time.time()
df_large['result4'] = df_large.eval('a * b')
eval_time = time.time() – start

print(f"Векторизация: {vector_time:.4f} сек")
print(f"Apply + lambda: {apply_time:.4f} сек")
print(f"Zip + map: {zip_map_time:.4f} сек")
print(f"Eval: {eval_time:.4f} сек")

Результаты такого бенчмарка обычно показывают, что векторизованные операции и eval() значительно быстрее, чем apply() или zip()+map(). Но это справедливо только для простых операций.

Основные рекомендации по выбору метода:

  • Для простых арифметических операций: всегда используйте векторизованные операции
  • Для сложной логики с условиями: apply() или np.where()
  • Для обработки нескольких столбцов с одинаковой функцией: zip()+map()
  • Для длинных цепочек операций: eval()

2. Использование метода eval() для сложных выражений

Метод eval() в Pandas позволяет эффективно выполнять строковые выражения без создания промежуточных объектов DataFrame:

Python
Скопировать код
# Использование eval() для цепочки операций
df['complex_result'] = df.eval('quantity * price * (1 – discount_rate) + tax')

Преимущества eval():

  • Высокая производительность для сложных выражений
  • Уменьшение использования памяти за счет отсутствия промежуточных объектов
  • Читаемый синтаксис для арифметических операций

3. Использование numpy.where() как альтернатива условным конструкциям

Для условной логики между столбцами, np.where() часто работает быстрее, чем apply():

Python
Скопировать код
# Используем np.where для условной логики
df['discount'] = np.where(
df['quantity'] > 10,
df['quantity'] * df['price'] * 0.1, # если quantity > 10
0 # иначе
)

Для более сложной логики можно комбинировать несколько условий:

Python
Скопировать код
# Многоуровневая скидка
df['tier_discount'] = np.where(
df['quantity'] > 20, df['price'] * 0.2, # скидка 20% для quantity > 20
np.where(
df['quantity'] > 10, df['price'] * 0.1, # скидка 10% для quantity > 10
np.where(
df['quantity'] > 5, df['price'] * 0.05, # скидка 5% для quantity > 5
0 # нет скидки
)
)
)

4. Предварительная фильтрация данных

Если операция применяется только к части данных, сначала отфильтруйте DataFrame:

Python
Скопировать код
# Применяем функцию только к строкам, соответствующим условию
mask = df['quantity'] > 10
df.loc[mask, 'bonus'] = df.loc[mask, 'quantity'] * df.loc[mask, 'price'] * 0.05

5. Использование numba для ускорения пользовательских функций

Библиотека numba позволяет компилировать Python-функции в эффективный машинный код:

Python
Скопировать код
from numba import jit

@jit(nopython=True)
def fast_calculation(quantity, price):
return quantity * price * (1 – 0.05 if quantity > 10 else 0)

# Применение numba-оптимизированной функции
result = np.empty(len(df))
for i in range(len(df)):
result[i] = fast_calculation(df.iloc[i]['quantity'], df.iloc[i]['price'])

df['fast_result'] = result

Однако стоит помнить, что накладные расходы на компиляцию могут превышать выигрыш для небольших DataFrame.

При работе с двумя столбцами в Pandas, выбор метода напрямую влияет на производительность, читаемость и масштабируемость вашего кода. Векторизованные операции должны быть вашим первым выбором для простых вычислений благодаря непревзойденной скорости. Для сложной логики используйте apply() с четко структурированными функциями или комбинацию zip() и map(). Постоянно тестируйте производительность на реальных данных и не бойтесь экспериментировать с разными подходами. Помните, что оптимальный метод сегодня может стать узким местом завтра, когда ваши данные вырастут на порядок.

Загрузка...