Зарегистрируйтесь, чтобы продолжить обучение

Применение функций к столбцам и строкам таблицы Python: Pandas

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

В этом уроке мы познакомимся с методами, которые позволяют уйти от поэлементных операций и применять сценарии обработки ко всему набору данных.

Преобразования данных методом apply()

Метод apply() — это инструмент для преобразования объекта DataFrame. аpply() можно применять как к одному столбцу, так и к нескольким.

Разберем каждую ситуацию подробнее.

apply() для преобразования одного столбца

Рассмотрим пример на данных о кликах на сайте с четырьмя магазинами. Допустим, произошла техническая ошибка, и по первому магазину количество кликов завышено на 50 штук. Исправим это с помощью apply(). Подгрузим данные:

import pandas as pd

df_clicks = pd.read_json('./data/Cite_clicks.csv', index_col=0)
print(df_clicks.head())
# =>   SHOP1    SHOP2   SHOP3   SHOP4
# day
#  1    319.0   -265.0  319.0   328.0
#  2    292.0   274.0   292.0   301.0
#  3    283.0   301.0   274.0   283.0
#  4    328.0   364.0   328.0   NaN
#  5    391.0   355.0   373.0   337.0

Уменьшим значения продаж для первого магазина на 50 пунктов:

df_clicks['SHOP1'] = df_clicks['SHOP1'].apply(lambda x: x - 50)
print(df_clicks.head())
# =>   SHOP1    SHOP2   SHOP3   SHOP4
# day
# 1    269.0    -265.0  319.0   328.0
# 2    242.0    274.0   292.0   301.0
# 3    233.0    301.0   274.0   283.0
# 4    278.0    364.0   328.0   NaN
# 5    341.0    355.0   373.0   337.0

В аргументах apply() находится лямбда-функция языка Python. У нее нет названия, и она используется только в конкретном участке кода. В нашем случае лямбда-функция принимает на вход параметр x и возвращает x - 50. Каждое значение в столбце — это вход указанной функции. Новый столбец формируется из ее выходных значений.

Теперь представим, что появилась дополнительная информация. Количество кликов у магазина 2 завышено на 50 штук, если кликов не более 200, и на 100, если кликов более 200. Попробуем исправить и эту ошибку:

def correct_clicks(x):
    if x < 200:
        return x - 50
    else:
        return x - 100

df_clicks['SHOP2'] = df_clicks['SHOP2'].apply(correct_clicks)
print(df_clicks.head())
# =>    SHOP1   SHOP2   SHOP3   SHOP4
# day
#  1    269.0   -315.0  319.0   328.0
#  2    242.0   174.0   292.0   301.0
#  3    233.0   201.0   274.0   283.0
#  4    278.0   264.0   328.0   NaN
#  5    341.0   255.0   373.0   337.0

Здесь вместо лямбда-функции в аргументах метода apply() отдельная функция correct_clicks(). Она принимает на вход каждый элемент столбца и выполняет над ним описанное выше преобразование.

Теперь мы узнаем, что у второго столбца порог 200 кликов — это порог между разницей в 50 или 100, у третьего этот порог равен 150 и у четвертого — 250. Выполним параметризацию функции correct_clicks():

def correct_clicks(x, threshold):
    if x < threshold:
        return x - 50
    else:
        return x - 100

df_clicks['SHOP3'] = df_clicks['SHOP3'].apply(lambda x: correct_clicks(x, 150))
df_clicks['SHOP4'] = df_clicks['SHOP4'].apply(lambda x: correct_clicks(x, 250))

print(df_clicks.head())
# =>   SHOP1    SHOP2   SHOP3   SHOP4
# day
#  1    269.0   -315.0  169.0   328.0
#  2    242.0   174.0   142.0   301.0
#  3    233.0   201.0   124.0   283.0
#  4    278.0   264.0   178.0   NaN
#  5    341.0   255.0   173.0   337.0

В случае параметризации необходимо использовать correct_clicks() в теле лямда-функции.

apply() для преобразования нескольких столбцов

Предположим, что нам необходимо найти наибольшее число кликов за каждый день среди магазинов 1 и 3. Сформируем новый столбец MAX_SHOP1_SHOP3:

df_clicks['SHOP1'] = df_clicks['SHOP1'].fillna(df_clicks['SHOP1'].mean())
df_clicks['SHOP3'] = df_clicks['SHOP3'].fillna(df_clicks['SHOP3'].mean())

df_clicks['MAX_SHOP1_SHOP3'] = df_clicks.apply(lambda x: max(x['SHOP1'], x['SHOP3']), axis=1)
print(df_clicks.head())
# =>   SHOP1    SHOP2   SHOP3   SHOP4   MAX_SHOP1_SHOP3
# day
#  1    269.0   -315.0  169.0   328.0    269.0
#  2    242.0   174.0   142.0   301.0    242.0
#  3    233.0   201.0   124.0   283.0    233.0
#  4    278.0   264.0   178.0   NaN      278.0
#  5    341.0   255.0   173.0   337.0    341.0

В первых двух строчках мы заполнили пропуски в данных средними значениями с помощью метода fillna(). Это нужно, чтобы не было ошибок при вызове функции max(). Далее apply() применяется ко всему DataFrame. Параметр axis задает направление обхода набора данных:

  • 0 задает обход по столбцам
  • 1 задает обход по строкам

Мы использовали значение 1, так в качестве аргумента x лямбда-функции выступают строки набора данных. Так как мы ищем максимум среди 1 и 3 магазинов, то обращаемся к соответствующим значениям по ключам магазинов: SHOP1 и SHOP3.

Теперь учтем следующую информацию: если максимум окажется больше, чем 200, то мы должны заполнить столбец числом -1. Это будет говорить о некоторой технической ошибке:

def correct_max(x, threshold):
    if max(x['SHOP1'], x['SHOP3']) > threshold:
        return -1
    return max(x['SHOP1'], x['SHOP3'])

df_clicks['MAX_SHOP1_SHOP3_CORRECT'] = df_clicks.apply(lambda x: correct_max(x, 200), axis=1)
print(df_clicks.head())
# =>    SHOP1   SHOP2   SHOP3   SHOP4   MAX_SHOP1_SHOP3 MAX_SHOP1_SHOP3_CORRECT
# day
#  1    269.0   -315.0  169.0   328.0     269.0                -1.0
#  2    242.0   174.0   142.0   301.0     242.0                -1.0
#  3    233.0   201.0   124.0   283.0     233.0                -1.0
#  4    278.0   264.0   178.0   NaN       278.0                -1.0
#  5    341.0   255.0   173.0   337.0     341.0                -1.0

Векторизованные функции

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

%time df_clicks['SUM'] =  df_clicks.apply(lambda x: x['SHOP1'] + x['SHOP2'], axis=1)
# => CPU times: user 1.11 ms, sys: 57 µs, total: 1.17 ms
# Wall time: 1.15 ms
%time df_clicks['SUM'] =  df_clicks['SHOP1'] + df_clicks['SHOP2']
# => CPU times: user 541 µs, sys: 16 µs, total: 557 µs
# Wall time: 526 µs

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

Векторные арифметические операции

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

df_clicks['MULTIPLE'] =  df_clicks['SHOP1'] * df_clicks['SHOP2']
print(df_clicks[['SHOP1', 'SHOP2', 'MULTIPLE']].head())
# =>    SHOP1   SHOP2   MULTIPLE
# day
#  1    319.0   -265.0  -84535.0
#  2    292.0   274.0   80008.0
#  3    283.0   301.0   85183.0
#  4    328.0   364.0   119392.0
#  5    391.0   355.0   138805.0
df_clicks['MINUS'] = df_clicks['SHOP1'] - 100
print(df_clicks[['SHOP1', 'MINUS']].head())
# =>   SHOP1    MINUS
# day
#  1    319.0   219.0
#  2    292.0   192.0
#  3    283.0   183.0
#  4    328.0   228.0
#  5    391.0   291.0

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

Изменение типов данных

Тип столбцов важен для DataFrame. От него зависит корректность проведения тех или иных операций. Иногда его приходится изменять.

Предположим, что нам в каких-то целях необходимо поработать с числовым столбцом, как со строковыми данными. Это можно легко сделать с помощью метода astype():

print(df_clicks[['SHOP1', 'SHOP2']].info())
# => <class 'pandas.core.frame.DataFrame'>
# Int64Index: 28 entries, 1 to 28
# Data columns (total 2 columns):
#  #   Column  Non-Null Count  Dtype
# ---  ------  --------------  -----
#  0   SHOP1   28 non-null     float64
#  1   SHOP2   27 non-null     float64
# dtypes: float64(2)
# memory usage: 1.7 KB

Преобразуем тип для SHOP1:

df_clicks['SHOP1'] = df_clicks['SHOP1'].astype(str)
print(df_clicks[['SHOP1', 'SHOP2']].info())
# => <class 'pandas.core.frame.DataFrame'>
# Int64Index: 28 entries, 1 to 28
# Data columns (total 2 columns):
#  #   Column  Non-Null Count  Dtype
# ---  ------  --------------  -----
#  0   SHOP1   28 non-null     object
#  1   SHOP2   27 non-null     float64
# dtypes: float64(1), object(1)
# memory usage: 1.7+ KB

Метод astype() ожидает на вход тип объекта, к которому необходимо привести элементы столбца.

Замена элементов согласно словарю

Существуют преобразования данных в столбцах DataFrame, которые требуют заменить одно значение на другое. Если подстановку значений можно задать с помощью словаря, то достаточно воспользоваться методом map().

Допустим, 150 кликов и более — это хорошо, а меньше — это плохо. Добавим столбец с категорией значений 0 и 1 согласно данному условию:

df_clicks['GOOD_OR_BAD'] = df_clicks['SHOP2'].apply(lambda x: 0 if x < 150 else 1)
print(df_clicks[['SHOP2', 'GOOD_OR_BAD']].head())
# =>    SHOP2   GOOD_OR_BAD
# day
#  1    -315.0    0
#  2     174.0    1
#  3     201.0    1
#  4     264.0    1
#  5     255.0    1

Создадим словарь, согласно которому будет выполняться замена:

map_dict = {0:'BAD', 1:'GOOD'}

Заменим значения столбца GOOD_OR_BAD:

df_clicks['GOOD_OR_BAD'] = df_clicks['GOOD_OR_BAD'].map(map_dict)
print(df_clicks[['SHOP2','GOOD_OR_BAD']].head())
# =>   SHOP2    GOOD_OR_BAD
# day
#  1    -315.0    BAD
#  2     174.0    GOOD
#  3     201.0    GOOD
#  4     264.0    GOOD
#  5     255.0    GOOD

Выводы

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

  • Использование метода apply()
  • Применение векторизованных функций

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
новый
Google таблицы, SQL, Python, Superset, Tableau, Pandas, визуализация данных, Anaconda, Jupyter Notebook, A/B-тесты, ROI
9 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу