Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Изменение формы и объединение таблиц Python: Pandas

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

  • concat()
  • join()
  • merge()

На данном уроке рассмотрим практические случаи, в которых они применяются.

Метод concat()

За основу возьмем данные о кликах на сайтах 4 магазинов за 28 дней.

df_clicks = pd.read_csv('./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

Разобьем исходные данные на два датафрейма за первую и вторую недели месяца. Это операция делается с использованием срезов.

df_clicks_first_week = df_clicks[:7]
df_clicks_second_week = df_clicks[7:14]
print(df_clicks_first_week)
print(df_clicks_second_week)
# =>   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
# 6    445.0  418.0  409.0  445.0
# 7    481.0  400.0  481.0  409.0
#
#      SHOP1  SHOP2  SHOP3  SHOP4
# day
# 8      NaN  267.0  333.0  344.0
# 9    300.0  278.0  300.0  311.0
# 10   289.0  311.0 -278.0  289.0
# 11   344.0  388.0  344.0  333.0
# 12   421.0  377.0  399.0  355.0
# 13   487.0  454.0 -443.0  487.0
# 14   531.0  432.0  531.0  443.0

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

df_weeks_concat = pd.concat([
    df_clicks_first_week,
    df_clicks_second_week
])
print(df_weeks_concat)
# =>   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
# 6    445.0  418.0  409.0  445.0
# 7    481.0  400.0  481.0  409.0
# 8      NaN  267.0  333.0  344.0
# 9    300.0  278.0  300.0  311.0
# 10   289.0  311.0 -278.0  289.0
# 11   344.0  388.0  344.0  333.0
# 12   421.0  377.0  399.0  355.0
# 13   487.0  454.0 -443.0  487.0
# 14   531.0  432.0  531.0  443.0

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

df_clicks_two_first = df_clicks[['SHOP1', 'SHOP2']]
df_clicks_two_last = df_clicks[['SHOP3', 'SHOP4']]
print(df_clicks_two_first.head())
print(df_clicks_two_last.head())
# =>   SHOP1  SHOP2
# day
# 1    319.0 -265.0
# 2    292.0  274.0
# 3    283.0  301.0
# 4    328.0  364.0
# 5    391.0  355.0
#
#      SHOP3  SHOP4
# day
# 1    319.0  328.0
# 2    292.0  301.0
# 3    274.0  283.0
# 4    328.0    NaN
# 5    373.0  337.0

Чтобы собрать их в единое целое, применяется метод concat(), но указывается направление объединения с помощью параметра axis:

  • 0 - объединение происходит по строкам
  • 1 - по столбцам
df_shop_concat = pd.concat([
    df_clicks_two_first,
    df_clicks_two_last
], axis=1)

print(df_shop_concat.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

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

print(pd.concat([
    df_clicks_two_first,
    df_clicks_two_last
]))
# =>   SHOP1  SHOP2  SHOP3  SHOP4
# day
# 1    319.0 -265.0    NaN    NaN
# 2    292.0  274.0    NaN    NaN
# 3    283.0  301.0    NaN    NaN
# 4    328.0  364.0    NaN    NaN
# 5    391.0  355.0    NaN    NaN

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

df_clicks_two_first = df_clicks[['SHOP1', 'SHOP2']][:5]
df_clicks_two_last = df_clicks[['SHOP3', 'SHOP4']][2:7]
print(df_clicks_two_first)
print(df_clicks_two_last)
# =>   SHOP1  SHOP2
# day
# 1    319.0 -265.0
# 2    292.0  274.0
# 3    283.0  301.0
# 4    328.0  364.0
# 5    391.0  355.0
#
#      SHOP3  SHOP4
# day
# 3    274.0  283.0
# 4    328.0    NaN
# 5    373.0  337.0
# 6    409.0  445.0
# 7    481.0  409.0

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

df_concat = pd.concat([
    df_clicks_two_first,
    df_clicks_two_last
],axis=1)
print(df_concat)
# =>   SHOP1  SHOP2  SHOP3  SHOP4
# day
# 1    319.0 -265.0    NaN    NaN
# 2    292.0  274.0    NaN    NaN
# 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
# 6      NaN    NaN  409.0  445.0
# 7      NaN    NaN  481.0  409.0

Метод join()

Метод concat() позволяет производить операции конкатенации по направлениям. Однако при работе с данными требуются более сложные объединения данных. Одним из методов, который поддерживает различные сценарии объединения данных по индексам, является метод join():

df_join_to_first = df_clicks_two_first.join(
    df_clicks_two_last
)
print(df_join_to_first)
# =>   SHOP1  SHOP2  SHOP3  SHOP4
# day
# 1    319.0 -265.0    NaN    NaN
# 2    292.0  274.0    NaN    NaN
# 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

Важно отметить, что join() не является методом Pandas, а применяется к датафрейму. Также важно, к какому датафрейму при объединении он применяется. Если поменять местами датафреймы в примере выше, то результат будет отличаться:

df_join_to_last = df_clicks_two_last.join(
    df_clicks_two_first
)
print(df_join_to_last)
# =>   SHOP3  SHOP4  SHOP1  SHOP2
# day
# 3    274.0  283.0  283.0  301.0
# 4    328.0    NaN  328.0  364.0
# 5    373.0  337.0  391.0  355.0
# 6    409.0  445.0    NaN    NaN
# 7    481.0  409.0    NaN    NaN

В примерах выше в результирующем датафрейме присутствуют только индексы датафрейма, к которому применялся данный метод. Такой способ объединения называется left join и применяется по умолчанию. Метод join() поддерживает различные сценарии объединения и включает такие случаи:

  • inner join — объединение по пересечению индексов
  • right join — внешнее объединение по всем индексам объединяемых датафреймов

Для их использования в примерах ниже указываются соответствующие значения параметра how:

print('left join:')
print(df_clicks_two_first.join(
    df_clicks_two_last,
    how='left'
))
print('right join:')
print(df_clicks_two_first.join(
    df_clicks_two_last,
    how='right'
))

print('inner join:')
print(df_clicks_two_first.join(
    df_clicks_two_last,
    how='inner'
))

print('outer join:')
print(df_clicks_two_first.join(
    df_clicks_two_last,
    how='outer'
))

# => left join:
#      SHOP1  SHOP2  SHOP3  SHOP4
# day
# 1    319.0 -265.0    NaN    NaN
# 2    292.0  274.0    NaN    NaN
# 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
#
# right join:
#      SHOP1  SHOP2  SHOP3  SHOP4
# day
# 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
# 6      NaN    NaN  409.0  445.0
# 7      NaN    NaN  481.0  409.0
#
# inner join:
#      SHOP1  SHOP2  SHOP3  SHOP4
# day
# 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
#
# outer join:
#      SHOP1  SHOP2  SHOP3  SHOP4
# day
# 1    319.0 -265.0    NaN    NaN
# 2    292.0  274.0    NaN    NaN
# 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
# 6      NaN    NaN  409.0  445.0
# 7      NaN    NaN  481.0  409.0

Метод merge()

Объединение данных можно производить не только по индексам, но и по столбцам значений двух датафреймов. Для этого не достаточно функционала метода join(), который может производить объединения по индексам датафреймов. Для таких случаев в Pandas используется метод merge().

Рассмотрим датасет с кликами, в котором дни месяца указаны в столбце day, а не в индексе строк:

df_clicks = df_clicks.reset_index()
print(df_clicks.head())
# => day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    1  319.0 -265.0  319.0  328.0
# 1    2  292.0  274.0  292.0  301.0
# 2    3  283.0  301.0  274.0  283.0
# 3    4  328.0  364.0  328.0    NaN
# 4    5  391.0  355.0  373.0  337.0

Будем решать задачу по объединению двух датасетов, содержащих пятидневные срезы по парам магазинов:

df_clicks_two_first = df_clicks[['day', 'SHOP1', 'SHOP2']][:5]
df_clicks_two_last = df_clicks[['day', 'SHOP3', 'SHOP4']][2:7]
print(df_clicks_two_first)
print(df_clicks_two_last)
# => day  SHOP1  SHOP2
# 0    1  319.0 -265.0
# 1    2  292.0  274.0
# 2    3  283.0  301.0
# 3    4  328.0  364.0
# 4    5  391.0  355.0
#
#    day  SHOP3  SHOP4
# 2    3  274.0  283.0
# 3    4  328.0    NaN
# 4    5  373.0  337.0
# 5    6  409.0  445.0
# 6    7  481.0  409.0

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

df_merged = pd.merge(
    df_clicks_two_first, df_clicks_two_last,
    left_on='day',
    right_on='day'
)
print(df_merged)
# => day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    3  283.0  301.0  274.0  283.0
# 1    4  328.0  364.0  328.0    NaN
# 2    5  391.0  355.0  373.0  337.0

Также как и в методе join() в методе merge() поддерживаются различные сценарии объединения данных:

print('inner merge:')
print(pd.merge(
    df_clicks_two_first, df_clicks_two_last,
    left_on='day',
    right_on='day',
    how='inner'
))

print('left merge:')
print(pd.merge(
    df_clicks_two_first, df_clicks_two_last,
    left_on='day',
    right_on='day',
    how='left'
))

print('right merge:')
print(pd.merge(
    df_clicks_two_first, df_clicks_two_last,
    left_on='day',
    right_on='day',
    how='right'
))

print('outer merge:')
print(pd.merge(
    df_clicks_two_first, df_clicks_two_last,
    left_on='day',
    right_on='day',
    how='outer'
))
# => inner merge:
#    day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    3  283.0  301.0  274.0  283.0
# 1    4  328.0  364.0  328.0    NaN
# 2    5  391.0  355.0  373.0  337.0
#
# left merge:
#    day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    1  319.0 -265.0    NaN    NaN
# 1    2  292.0  274.0    NaN    NaN
# 2    3  283.0  301.0  274.0  283.0
# 3    4  328.0  364.0  328.0    NaN
# 4    5  391.0  355.0  373.0  337.0
#
# right merge:
#    day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    3  283.0  301.0  274.0  283.0
# 1    4  328.0  364.0  328.0    NaN
# 2    5  391.0  355.0  373.0  337.0
# 3    6    NaN    NaN  409.0  445.0
# 4    7    NaN    NaN  481.0  409.0
#
# outer merge:
#    day  SHOP1  SHOP2  SHOP3  SHOP4
# 0    1  319.0 -265.0    NaN    NaN
# 1    2  292.0  274.0    NaN    NaN
# 2    3  283.0  301.0  274.0  283.0
# 3    4  328.0  364.0  328.0    NaN
# 4    5  391.0  355.0  373.0  337.0
# 5    6    NaN    NaN  409.0  445.0
# 6    7    NaN    NaN  481.0  409.0

Выводы

На данном уроке мы познакомились с различными методами Pandas для объединения табличных данных. Рассмотренные методы применяются по мере усложнения производимой операции:

  • concat() - данные объединяются по строкам или по столбцам с сохранением всех значений
  • join() - объединяюся датафреймы по индексам
  • merge() - объединяются датафреймы по наборам столбцов

Как мы увидели, методы join() и merge() поддерживают при этом различные сценарии объединений. Используя набор данных методов, аналитик может собирать довольно сложные таблицы по набору фрагментов данных.


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

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

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

Об обучении на Хекслете

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 5 025 ₽ в месяц
новый
Сбор, анализ и интерпретация данных
9 месяцев
с нуля
Старт 9 мая

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

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

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

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

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