Мощные инструменты Pandas: объединяем данные с merge, join, concat
Для кого эта статья:
- Аналитики данных, стремящиеся улучшить навыки работы с библиотекой Pandas.
- Студенты и начинающие специалисты в области анализа данных.
Профессионалы, желающие получить знания о методах объединения данных и избегать распространённых ошибок.
Манипулирование данными без качественных инструментов для их объединения — всё равно что собирать мозаику в боксёрских перчатках. Вы можете потратить часы на попытки соединить разрозненные датафреймы вручную или изобретать сложные алгоритмы. А можете потратить 15 минут на изучение трёх ключевых методов Pandas —
merge,joinиconcat— и получить чистый, структурированный датафрейм без лишних усилий. В этом руководстве я покажу, как превратить хаос данных в стройную систему, избегая распространённых ошибок, которые делают даже опытные аналитики. 🐼
Хотите не просто узнать о слиянии данных, а освоить всю экосистему анализа информации? Профессия аналитик данных от Skypro даст вам не только глубокое понимание Pandas, но и весь стек инструментов для превращения сырых данных в ценные инсайты. От базовых операций до продвинутых техник визуализации — вы пройдёте путь от новичка до востребованного специалиста под руководством практикующих аналитиков.
Объединение датафреймов в Pandas: базовые концепции
Прежде чем погрузиться в детали конкретных методов объединения, разберём фундаментальные концепции, которые лежат в основе работы с множественными источниками данных в Pandas.
В мире обработки данных часто возникает необходимость комбинировать информацию из различных источников. Библиотека Pandas предлагает три основных метода для решения этой задачи:
- merge — объединение по ключевым столбцам, аналогично SQL-join
- concat — "склеивание" датафреймов по строкам или столбцам
- join — специализированный метод для объединения на основе индексов
Каждый из этих методов имеет свою специфику, сильные стороны и оптимальные сценарии применения. Чтобы наглядно продемонстрировать различия, давайте создадим простые датафреймы для примеров:
import pandas as pd
# Данные о сотрудниках
employees = pd.DataFrame({
'employee_id': [1, 2, 3, 4, 5],
'name': ['Анна', 'Борис', 'Виктор', 'Галина', 'Дмитрий'],
'department_id': [101, 102, 101, 103, 102]
})
# Данные об отделах
departments = pd.DataFrame({
'department_id': [101, 102, 103, 104],
'department_name': ['Маркетинг', 'Продажи', 'IT', 'Финансы']
})
# Данные о зарплатах
salaries = pd.DataFrame({
'employee_id': [1, 2, 3, 5, 6],
'salary': [75000, 85000, 65000, 95000, 60000]
})
Ключевая концепция при объединении данных — понимание типа связи между таблицами:
| Тип связи | Описание | Пример в наших данных |
|---|---|---|
| Один к одному (1:1) | Каждой записи в первой таблице соответствует одна запись во второй | Сотрудник и его зарплата |
| Один ко многим (1:N) | Одной записи в первой таблице могут соответствовать несколько записей во второй | Отдел и сотрудники в нём |
| Многие ко многим (M:N) | Несколько записей в первой таблице связаны с несколькими записями во второй | Сотрудники и проекты (если бы у нас была таблица проектов) |
Ещё одна важная концепция — направление объединения:
- Вертикальное объединение — добавление строк (увеличение количества наблюдений)
- Горизонтальное объединение — добавление столбцов (увеличение количества атрибутов)
Максим Петров, Senior Data Analyst
Как-то раз я столкнулся с "безобидной" задачей — объединить два датафрейма с данными о продажах за разные периоды. Сделал простой
pd.concat(dfs)и отправил отчёт руководству. Через час меня вызвали на ковёр — цифры в отчёте были абсурдны. Оказалось, что в новом периоде изменилась структура ID товаров, и при прямом объединении одинаковые товары теперь считались разными.После этого я всегда следую правилу: перед любым объединением датафреймов проверяю уникальность и согласованность ключей обоих наборов данных. Один простой вызов
df1['key'].value_counts()иdf2['key'].value_counts()экономит часы разбирательств и защищает от ошибок.
Прежде чем выполнять любое объединение, необходимо ответить на несколько ключевых вопросов:
- Какие столбцы будут использоваться как ключи для объединения?
- Нужно ли сохранять все строки из обеих таблиц или только совпадающие?
- Как обрабатывать дубликаты в ключевых столбцах?
- Какие столбцы должны присутствовать в итоговом датафрейме?
Теперь, вооружившись базовыми концепциями, перейдём к подробному рассмотрению каждого метода, начиная с самого мощного и гибкого — pd.merge.

Мастерство pd.merge: варианты объединения таблиц
Метод pd.merge — это швейцарский нож для объединения данных в Pandas. Вдохновлённый SQL-операциями JOIN, он позволяет объединять датафреймы на основе общих столбцов или индексов с высокой гибкостью. 🛠️
Базовый синтаксис выглядит следующим образом:
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=False,
suffixes=('_x', '_y'), indicator=False, validate=None)
Ключевой параметр how определяет тип объединения. Он принимает следующие значения:
- inner — возвращает только строки, где значения ключа присутствуют в обоих датафреймах (аналог INNER JOIN в SQL)
- outer — возвращает все строки из обоих датафреймов (аналог FULL OUTER JOIN)
- left — возвращает все строки из левого датафрейма и соответствующие из правого (LEFT JOIN)
- right — возвращает все строки из правого датафрейма и соответствующие из левого (RIGHT JOIN)
Давайте рассмотрим примеры различных типов объединения с нашими датафреймами:
# Inner join – только сотрудники с известными отделами
employees_with_dept = pd.merge(
employees,
departments,
on='department_id',
how='inner'
)
# Left join – все сотрудники, даже если нет информации об их отделе
all_employees_with_dept = pd.merge(
employees,
departments,
on='department_id',
how='left'
)
# Right join – все отделы, даже пустые
all_depts_with_employees = pd.merge(
employees,
departments,
on='department_id',
how='right'
)
# Outer join – все сотрудники и все отделы
complete_org_data = pd.merge(
employees,
departments,
on='department_id',
how='outer'
)
Когда ключевые столбцы имеют разные имена, используйте параметры left_on и right_on:
# Объединение по разным именам столбцов
pd.merge(
employees,
salaries,
left_on='employee_id',
right_on='employee_id',
how='inner'
)
Мощная функция merge способна обрабатывать сложные сценарии объединения:
| Сценарий | Решение с pd.merge | Комментарий |
|---|---|---|
| Объединение по нескольким столбцам | pd.merge(df1, df2, on=['key1', 'key2']) | Только строки, где совпадают значения обоих ключей |
| Объединение с индексом | pd.merge(df1, df2, left_index=True, right_on='key') | Индекс df1 соединяется со столбцом 'key' в df2 |
| Проверка корректности связи | pd.merge(df1, df2, validate='1:1') | Выдаст ошибку, если отношение не "один к одному" |
| Отслеживание источника строк | pd.merge(df1, df2, indicator=True) | Добавляет столбец '_merge' с информацией о происхождении строки |
Параметр validate особенно полезен для предотвращения ошибок целостности данных. Он принимает следующие значения:
- '1:1' — каждое значение ключа уникально в обоих датафреймах
- '1:m' — каждое значение ключа уникально в левом датафрейме
- 'm:1' — каждое значение ключа уникально в правом датафрейме
- 'm:m' — значения ключа могут повторяться в обоих датафреймах
При использовании pd.merge важно помнить о нескольких потенциальных подводных камнях:
- Проблема с дубликатами столбцов решается параметром suffixes (по умолчанию 'x' и 'y')
- NaN-значения в ключевых столбцах не считаются совпадающими
- При объединении больших датафреймов merge может потреблять значительный объём памяти
Для оптимизации производительности при работе с большими наборами данных:
- Предварительно отфильтруйте ненужные строки и столбцы перед объединением
- Используйте индексы для ускорения операций объединения
- Применяйте категориальные типы данных для столбцов с повторяющимися значениями
pd.concat в действии: вертикальное и горизонтальное слияние
В отличие от merge, который специализируется на реляционном соединении данных, метод pd.concat предназначен для простого "склеивания" датафреймов — либо по строкам (вертикально), либо по столбцам (горизонтально). Если merge — это хирургическая операция, то concat — это скорее склеивание листов скотчем. 📏
Базовый синтаксис pd.concat выглядит так:
pd.concat(objs, axis=0, join='outer', ignore_index=False,
keys=None, levels=None, names=None, verify_integrity=False,
sort=False, copy=True)
Главный параметр здесь — axis, который определяет направление объединения:
- axis=0 (по умолчанию) — вертикальное объединение (добавление строк)
- axis=1 — горизонтальное объединение (добавление столбцов)
Создадим примеры датафреймов для демонстрации:
# Данные о продажах за первый квартал
sales_q1 = pd.DataFrame({
'date': ['2023-01-15', '2023-02-20', '2023-03-10'],
'product': ['A', 'B', 'A'],
'units': [100, 150, 120]
})
# Данные о продажах за второй квартал
sales_q2 = pd.DataFrame({
'date': ['2023-04-05', '2023-05-12', '2023-06-25'],
'product': ['B', 'A', 'C'],
'units': [80, 200, 95]
})
# Данные о ценах продуктов
prices = pd.DataFrame({
'product': ['A', 'B', 'C', 'D'],
'price': [10, 15, 20, 25]
})
Вертикальное объединение (добавление строк) — самый распространённый сценарий для concat:
# Объединение данных о продажах за два квартала
all_sales = pd.concat([sales_q1, sales_q2])
print(all_sales)
По умолчанию индексы сохраняются, что может привести к дублированию. Чтобы сбросить индексы, используйте параметр ignore_index:
# Объединение с сбросом индексов
all_sales = pd.concat([sales_q1, sales_q2], ignore_index=True)
print(all_sales)
Для отслеживания источника данных можно использовать параметр keys:
# Объединение с многоуровневым индексом для отслеживания источника
all_sales_tracked = pd.concat([sales_q1, sales_q2], keys=['Q1', 'Q2'])
print(all_sales_tracked)
# Выбор только данных за первый квартал
q1_data = all_sales_tracked.loc['Q1']
Горизонтальное объединение (добавление столбцов) также легко реализуется с помощью concat:
# Создаём датафрейм с дополнительной информацией
product_info = pd.DataFrame({
'product': ['A', 'B', 'C', 'D'],
'category': ['Electronics', 'Furniture', 'Clothing', 'Books']
})
# Горизонтальное объединение
product_details = pd.concat([prices, product_info], axis=1)
Однако в этом случае concat просто размещает датафреймы рядом, не учитывая связи между данными. В результате мы получим:
"""
product price product category
0 A 10 A Electronics
1 B 15 B Furniture
2 C 20 C Clothing
3 D 25 D Books
"""
Заметьте, что столбец 'product' дублируется. В данном случае объединение произошло по позиции, а не по значениям.
Анна Соколова, Data Engineer
В начале своей карьеры я работала над проектом, где нужно было объединить логи из нескольких источников. Наша система ежедневно собирала файлы с однотипной структурой, и задача казалась простой — использовать concat и готово.
Проблемы начались, когда отчёты показали странный скачок в метриках. После долгого расследования выяснилось, что в одном из источников незаметно изменили порядок столбцов, а concat просто "склеил" их позиционно, без сопоставления имён. Данные выглядели валидными, но были полностью бессмысленными!
С тех пор я всегда использую два приёма: во-первых, перед concat явно указываю список столбцов для каждого датафрейма; во-вторых, добавляю проверку на консистентность данных после объединения. Эти простые шаги спасли от множества головных болей.
Для корректного объединения по значениям в этой ситуации лучше использовать merge. Однако concat имеет параметр join, который контролирует, как обрабатывать столбцы при горизонтальном объединении:
- join='outer' (по умолчанию) — включает все столбцы, заполняя отсутствующие значения NaN
- join='inner' — включает только столбцы, присутствующие во всех объединяемых объектах
# Датафреймы с разным набором столбцов
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'B': [5, 6], 'C': [7, 8]})
# Объединение с сохранением всех столбцов
pd.concat([df1, df2], ignore_index=True) # Столбцы: A, B, C с NaN-значениями
# Объединение с сохранением только общих столбцов
pd.concat([df1, df2], ignore_index=True, join='inner') # Только столбец B
Важно помнить о следующих параметрах при использовании concat:
| Параметр | Значение по умолчанию | Для чего используется |
|---|---|---|
ignore_index | False | Сброс исходных индексов и создание нового последовательного индекса |
join | 'outer' | Определяет, как обрабатывать несовпадающие столбцы |
keys | None | Создаёт иерархический индекс для отслеживания источника каждой строки |
verify_integrity | False | Проверяет наличие дубликатов в результирующем индексе |
sort | False | Сортировка столбцов в лексикографическом порядке |
Типичные сценарии использования concat:
- Объединение временных рядов данных (например, логов за разные периоды)
- Сборка разрозненных частей одного большого датасета
- Добавление вычисляемых столбцов к существующему датафрейму
- Реализация операции "union all" из SQL
Метод concat также может работать с Series и смешанными объектами Pandas, что делает его очень гибким инструментом для манипуляций с данными.
Эффективное использование pd.join для связывания данных
Метод pd.join — это специализированный вариант объединения датафреймов, оптимизированный для работы с индексами. Фактически, это своего рода обёртка вокруг метода merge, ориентированная на индексное объединение. 🔍
Базовый синтаксис join выглядит следующим образом:
DataFrame.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)
В отличие от merge, который вызывается как функция Pandas, join вызывается как метод датафрейма. Также join по умолчанию использует тип объединения 'left' вместо 'inner'.
Давайте создадим примеры, чтобы продемонстрировать особенности join:
# Установим индексы для наших датафреймов
employees_indexed = employees.set_index('employee_id')
departments_indexed = departments.set_index('department_id')
salaries_indexed = salaries.set_index('employee_id')
# Выведем для примера
print(employees_indexed.head())
print(salaries_indexed.head())
Теперь самый простой случай — объединение двух датафреймов по их индексам:
# Объединение сотрудников и их зарплат по индексу employee_id
employees_with_salaries = employees_indexed.join(salaries_indexed)
print(employees_with_salaries)
Метод join особенно удобен в случаях, когда вам нужно последовательно присоединить несколько датафреймов к основному:
# Создадим дополнительные данные
performance = pd.DataFrame({
'employee_id': [1, 2, 3, 4],
'rating': ['Excellent', 'Good', 'Good', 'Excellent']
}).set_index('employee_id')
attendance = pd.DataFrame({
'employee_id': [1, 2, 3, 5],
'days_present': [22, 20, 21, 19]
}).set_index('employee_id')
# Последовательное объединение нескольких датафреймов
complete_employee_data = employees_indexed.join([salaries_indexed, performance, attendance])
Для объединения по разным индексам используется параметр on:
# Приведём пример с использованием другого столбца для объединения
# Сначала вернём датафрейм сотрудников с обычными столбцами
employees_normal = employees_indexed.reset_index()
# Теперь создадим объединение по столбцу department_id с датафреймом отделов
result = employees_normal.join(
departments_indexed,
on='department_id'
)
Метод join имеет ряд ограничений по сравнению с merge:
- Поддерживает только одностороннее объединение по ключу (левый датафрейм по столбцу, правый по индексу)
- Не позволяет объединять по разным названиям столбцов (как lefton и righton в merge)
- Не имеет расширенных функций, таких как indicator или validate
Когда стоит использовать join вместо merge:
- Когда вы работаете с данными, где естественным ключом является индекс
- При последовательном присоединении нескольких датафреймов к основному
- Когда вам нужен более компактный и читаемый код для простых случаев объединения
- Когда объединение по индексу более производительно для вашего конкретного случая
Подводные камни при использовании join:
- Неявное поведение с дубликатами индексов может привести к неожиданным результатам
- По умолчанию используется left join, что может привести к потере данных, если вы ожидаете inner join
- При работе с большими данными индексное объединение может быть медленнее, чем хешированное объединение с merge
Типичный паттерн эффективного использования join в рабочем процессе:
# 1. Загрузить и подготовить данные с логичными индексами
df1 = pd.read_csv('data1.csv').set_index('id')
df2 = pd.read_csv('data2.csv').set_index('id')
df3 = pd.read_csv('data3.csv').set_index('id')
# 2. Создать базовый датафрейм с основными данными
base_df = df1[['name', 'email', 'department']]
# 3. Последовательно обогащать дополнительной информацией
enriched_df = base_df.join(
df2[['salary', 'hire_date']],
how='left'
).join(
df3[['performance', 'manager']],
how='left'
)
# 4. При необходимости сбросить индекс в обычный столбец
final_df = enriched_df.reset_index()
Помните, что выбор между merge и join часто сводится к стилю кода и удобству использования. В большинстве случаев они взаимозаменяемы, но join обычно предпочтительнее, когда вы работаете с данными, где индексы уже настроены как естественные ключи для объединения.
Сравнение методов объединения данных: что выбрать и когда
При выборе метода объединения данных в Pandas решающее значение имеет понимание специфики вашей задачи и особенностей каждого метода. Неправильный выбор может привести к ухудшению производительности, неожиданным результатам или излишне сложному коду. 🤔
Давайте сравним все три метода объединения по ключевым характеристикам:
| Характеристика | pd.merge | pd.concat | DataFrame.join |
|---|---|---|---|
| Основное назначение | Реляционное объединение по значениям | Склеивание наборов данных | Объединение по индексу |
| Тип объединения по умолчанию | inner | outer (для столбцов) | left |
| Направление объединения | По горизонтали (столбцы) | По горизонтали или вертикали | По горизонтали (столбцы) |
| Объединение нескольких датафреймов | Последовательно (два за раз) | Одновременно (список) | Одновременно (список) |
| Гибкость в ключах объединения | Высокая | Низкая (по позиции) | Средняя |
| Производительность при больших данных | Средняя | Высокая | Средняя |
| Удобство использования | Требует явного указания параметров | Простой и интуитивно понятный | Компактный синтаксис |
Вот рекомендации по выбору метода для типичных сценариев:
Используйте
pd.merge, когда:- Нужно объединить таблицы по значениям одного или нескольких столбцов
- Требуется высокая степень контроля над процессом объединения
- Необходимы дополнительные функции, такие как indicator или validate
- Работаете в парадигме SQL-подобных операций
Используйте
pd.concat, когда:- Нужно "склеить" несколько датафреймов с одинаковой или похожей структурой
- Объединяете данные по строкам (вертикально)
- Формируете панельные данные или временные ряды из фрагментов
- Нужна максимальная скорость на больших наборах данных
Используйте
DataFrame.join, когда:- Данные уже имеют логичный индекс для объединения
- Нужен более краткий и читаемый синтаксис
- Последовательно присоединяете несколько датафреймов к основному
- Предпочитаете объединение left по умолчанию
Сравним реализацию одинаковой задачи с использованием разных методов:
# Задача: объединить информацию о сотрудниках с их зарплатами
# Вариант с merge
result_merge = pd.merge(
employees,
salaries,
on='employee_id',
how='left'
)
# Вариант с join
result_join = employees.set_index('employee_id').join(
salaries.set_index('employee_id')
)
# Если бы данные были в одинаковом формате, можно было бы использовать concat
# Но для этого сценария concat не подходит оптимально
Иногда требуется комбинировать различные методы для достижения желаемого результата:
# Пример комбинированного подхода
# 1. Сначала объединяем сотрудников с отделами
employees_with_dept = pd.merge(
employees,
departments,
on='department_id',
how='left'
)
# 2. Затем объединяем два квартала данных о продажах вертикально
all_sales = pd.concat([sales_q1, sales_q2], ignore_index=True)
# 3. Наконец, связываем сводные данные о сотрудниках с продажами
final_report = pd.merge(
employees_with_dept,
all_sales,
left_on='name',
right_on='sales_person',
how='inner'
)
При выборе метода также важно учитывать следующие факторы:
- Читаемость кода — иногда более простой метод предпочтительнее, даже если он не оптимален по скорости
- Объём данных — для больших наборов критична эффективность обработки
- Частота операции — для часто выполняемых операций стоит оптимизировать производительность
- Требования к памяти — некоторые методы могут потреблять больше ресурсов
Помните, что для сложных сценариев объединения данных часто лучше использовать промежуточные шаги и временные датафреймы, чтобы сохранить читаемость кода и облегчить отладку. В командной работе понятный и поддерживаемый код обычно предпочтительнее компактных, но сложных для понимания конструкций.
Эффективное объединение данных в Pandas — не просто техническое умение, а стратегический навык, определяющий качество всего аналитического процесса. Зная особенности merge, concat и join, вы трансформируете работу с разрозненными источниками информации из болезненного опыта в предсказуемый, эффективный рабочий процесс. Всегда начинайте с понимания структуры данных и требуемого результата, затем выбирайте подходящий инструмент, а не наоборот. И помните — правильный метод объединения сэкономит не только строки кода, но и часы вашего времени.