В работе с данными приходится собирать их из разных источников и объединять в единую структуру. В библиотеке 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 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
...
Важно отметить, что join() — это метод объекта DataFrame, который позволяет объединять два датафрейма по индексам. При этом результат зависит от того, к какому датафрейму применяется метод. Если поменять местами датафреймы в примере выше, результат будет отличаться:
df_join_to_last = df_clicks_two_last.join(
df_clicks_two_first
)
print(df_join_to_last)
SHOP3 SHOP4 SHOP1 SHOP2
day
1 319.0 328.0 319.0 -265.0
2 292.0 301.0 292.0 274.0
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 445.0 418.0
7 481.0 409.0 481.0 400.0
8 333.0 344.0 NaN 267.0
9 300.0 311.0 300.0 278.0
10 -278.0 289.0 289.0 311.0
...
В примерах выше результирующий датафрейм содержит все индексы того датафрейма, к которому применялся метод. Такой способ объединения соответствует left join и применяется по умолчанию. Метод join() поддерживает различные сценарии объединения, включая:
inner join— объединение по пересечению индексовleft join— включает все индексы левого датафреймаright join— включает все индексы правого датафреймаouter 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 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
right join:
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
...
inner join:
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
outer join:
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
...
| Тип join | Что включается | Пример поведения | Порядок колонок |
|---|---|---|---|
| left | Все индексы левого датафрейма; значения из правого там, где есть совпадение | df1.join(df2, how='left') — берём все строки df1, добавляем данные из df2, если индекс совпадает, иначе NaN |
Колонки левого df слева, колонки правого df справа |
| right | Все индексы правого датафрейма; значения из левого там, где есть совпадение | df1.join(df2, how='right') — берём все строки df2, добавляем данные из df1, если индекс совпадает, иначе NaN |
Колонки правого df слева, колонки левого df справа |
| inner | Только пересечение индексов df1 и df2 | df1.join(df2, how='inner') — берём только индексы, которые есть в обоих df |
Колонки объединяются: сначала df1, потом df2 |
| outer | Все индексы из обоих датафреймов | df1.join(df2, how='outer') — объединяет все индексы, там, где нет совпадений, ставит NaN |
Колонки объединяются: сначала df1, потом df2 |
Метод join() работает по индексам по умолчанию, но можно указывать конкретные колонки через параметр on. Если нужно объединять по колонкам с разными названиями, лучше использовать merge().
Метод 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
| Тип merge | Что включается | Пример использования | Особенности |
|---|---|---|---|
| inner | Только строки, где значения ключей совпадают в обоих датафреймах | pd.merge(df1, df2, on='key', how='inner') |
Результат содержит только общие ключи; аналог внутреннего join в SQL |
| left | Все строки левого датафрейма; правый добавляется там, где ключи совпадают | pd.merge(df1, df2, on='key', how='left') |
Если совпадения нет, в колонках правого датафрейма появятся NaN |
| right | Все строки правого датафрейма; левый добавляется там, где ключи совпадают | pd.merge(df1, df2, on='key', how='right') |
Если совпадения нет, в колонках левого датафрейма появятся NaN |
| outer | Все строки обоих датафреймов | pd.merge(df1, df2, on='key', how='outer') |
Там, где нет совпадения, ставятся NaN; аналог полного внешнего объединения |
| on / left_on / right_on | Определяет колонки для объединения | pd.merge(df1, df2, left_on='A', right_on='B') |
Используется, когда имена колонок для объединения различаются |
| suffixes | Добавляет суффиксы к повторяющимся колонкам | pd.merge(df1, df2, on='key', suffixes=('_left','_right')) |
Позволяет различать колонки с одинаковыми именами |
Метод merge() чаще используется для объединения по колонкам, а join() — по индексам. Можно объединять по нескольким колонкам одновременно, передав список: on=['key1','key2']. Метод merge() полностью аналогичен SQL JOIN по типу объединения (inner, left, right, outer).
Выводы
На данном уроке мы познакомились с различными методами Pandas для объединения табличных данных. Рассмотренные методы применяются по мере усложнения производимой операции:
concat()- данные объединяются по строкам или по столбцам с сохранением всех значенийjoin()- объединяюся датафреймы по индексамmerge()- объединяются датафреймы по наборам столбцов
Как мы увидели, методы join() и merge() поддерживают при этом различные сценарии объединений. Используя набор данных методов, аналитик может собирать довольно сложные таблицы по набору фрагментов данных.
Самостоятельная работа
Склонируйте репозиторий и используйте базовый набор data/Cite_clicks.csv. По нему отработайте техники concat(), join() и merge().
Подготовка
- Считайте таблицу кликов (
index_col=0) и выведите первые 10 строк. - Разбейте датафрейм на две недельные части (
df[:7],df[7:14]) и на пары столбцов ([['SHOP1','SHOP2']],[['SHOP3','SHOP4']]).
concat()
- Склейте недельные фрагменты по строкам (
pd.concat([...])) и убедитесь, что порядок сохранился. - Склейте пары столбцов по столбцам (
axis=1) и сравните с вариантом безaxis. - Повторите конкатенацию, если пары магазинов охватывают разные дни (например, первые пять дней vs дни 3–7) — зафиксируйте появление
NaN.
join()
- Выполните
df_first.join(df_last)иdf_last.join(df_first), чтобы увидеть зависимость от «левого» датафрейма. - Попробуйте
how='left' | 'right' | 'inner' | 'outer'и сравните индексы результата.
merge()
- Сбросьте индекс (
reset_index()), чтобы получить столбецday. - Сформируйте срезы по парам магазинов (например,
[['day','SHOP1','SHOP2']]и[['day','SHOP3','SHOP4']]) с перекрывающимися днями. - Объедините их через
pd.merge(..., left_on='day', right_on='day'), затем повторите дляhow='inner' | 'left' | 'right' | 'outer'.
Контрольный список
- Все примеры выполняются без ошибок, понятно, почему появляются
NaN. - Для каждого метода (
concat,join,merge) записаны выводы, в каких случаях он удобнее. - Понимаете разницу между объединением по индексам и по значениям столбцов.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.