Мощные инструменты Pandas: объединяем данные с merge, join, concat

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

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

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

    Манипулирование данными без качественных инструментов для их объединения — всё равно что собирать мозаику в боксёрских перчатках. Вы можете потратить часы на попытки соединить разрозненные датафреймы вручную или изобретать сложные алгоритмы. А можете потратить 15 минут на изучение трёх ключевых методов Pandas — merge, join и concat — и получить чистый, структурированный датафрейм без лишних усилий. В этом руководстве я покажу, как превратить хаос данных в стройную систему, избегая распространённых ошибок, которые делают даже опытные аналитики. 🐼

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

Объединение датафреймов в Pandas: базовые концепции

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

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

  • merge — объединение по ключевым столбцам, аналогично SQL-join
  • concat — "склеивание" датафреймов по строкам или столбцам
  • join — специализированный метод для объединения на основе индексов

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

Python
Скопировать код
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() экономит часы разбирательств и защищает от ошибок.

Прежде чем выполнять любое объединение, необходимо ответить на несколько ключевых вопросов:

  1. Какие столбцы будут использоваться как ключи для объединения?
  2. Нужно ли сохранять все строки из обеих таблиц или только совпадающие?
  3. Как обрабатывать дубликаты в ключевых столбцах?
  4. Какие столбцы должны присутствовать в итоговом датафрейме?

Теперь, вооружившись базовыми концепциями, перейдём к подробному рассмотрению каждого метода, начиная с самого мощного и гибкого — pd.merge.

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

Мастерство pd.merge: варианты объединения таблиц

Метод pd.merge — это швейцарский нож для объединения данных в Pandas. Вдохновлённый SQL-операциями JOIN, он позволяет объединять датафреймы на основе общих столбцов или индексов с высокой гибкостью. 🛠️

Базовый синтаксис выглядит следующим образом:

Python
Скопировать код
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)

Давайте рассмотрим примеры различных типов объединения с нашими датафреймами:

Python
Скопировать код
# 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:

Python
Скопировать код
# Объединение по разным именам столбцов
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 может потреблять значительный объём памяти

Для оптимизации производительности при работе с большими наборами данных:

  1. Предварительно отфильтруйте ненужные строки и столбцы перед объединением
  2. Используйте индексы для ускорения операций объединения
  3. Применяйте категориальные типы данных для столбцов с повторяющимися значениями

pd.concat в действии: вертикальное и горизонтальное слияние

В отличие от merge, который специализируется на реляционном соединении данных, метод pd.concat предназначен для простого "склеивания" датафреймов — либо по строкам (вертикально), либо по столбцам (горизонтально). Если merge — это хирургическая операция, то concat — это скорее склеивание листов скотчем. 📏

Базовый синтаксис pd.concat выглядит так:

Python
Скопировать код
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 — горизонтальное объединение (добавление столбцов)

Создадим примеры датафреймов для демонстрации:

Python
Скопировать код
# Данные о продажах за первый квартал
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:

Python
Скопировать код
# Объединение данных о продажах за два квартала
all_sales = pd.concat([sales_q1, sales_q2])
print(all_sales)

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

Python
Скопировать код
# Объединение с сбросом индексов
all_sales = pd.concat([sales_q1, sales_q2], ignore_index=True)
print(all_sales)

Для отслеживания источника данных можно использовать параметр keys:

Python
Скопировать код
# Объединение с многоуровневым индексом для отслеживания источника
all_sales_tracked = pd.concat([sales_q1, sales_q2], keys=['Q1', 'Q2'])
print(all_sales_tracked)

# Выбор только данных за первый квартал
q1_data = all_sales_tracked.loc['Q1']

Горизонтальное объединение (добавление столбцов) также легко реализуется с помощью concat:

Python
Скопировать код
# Создаём датафрейм с дополнительной информацией
product_info = pd.DataFrame({
'product': ['A', 'B', 'C', 'D'],
'category': ['Electronics', 'Furniture', 'Clothing', 'Books']
})

# Горизонтальное объединение
product_details = pd.concat([prices, product_info], axis=1)

Однако в этом случае concat просто размещает датафреймы рядом, не учитывая связи между данными. В результате мы получим:

Python
Скопировать код
"""
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' — включает только столбцы, присутствующие во всех объединяемых объектах
Python
Скопировать код
# Датафреймы с разным набором столбцов
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:

  1. Объединение временных рядов данных (например, логов за разные периоды)
  2. Сборка разрозненных частей одного большого датасета
  3. Добавление вычисляемых столбцов к существующему датафрейму
  4. Реализация операции "union all" из SQL

Метод concat также может работать с Series и смешанными объектами Pandas, что делает его очень гибким инструментом для манипуляций с данными.

Эффективное использование pd.join для связывания данных

Метод pd.join — это специализированный вариант объединения датафреймов, оптимизированный для работы с индексами. Фактически, это своего рода обёртка вокруг метода merge, ориентированная на индексное объединение. 🔍

Базовый синтаксис join выглядит следующим образом:

Python
Скопировать код
DataFrame.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)

В отличие от merge, который вызывается как функция Pandas, join вызывается как метод датафрейма. Также join по умолчанию использует тип объединения 'left' вместо 'inner'.

Давайте создадим примеры, чтобы продемонстрировать особенности join:

Python
Скопировать код
# Установим индексы для наших датафреймов
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())

Теперь самый простой случай — объединение двух датафреймов по их индексам:

Python
Скопировать код
# Объединение сотрудников и их зарплат по индексу employee_id
employees_with_salaries = employees_indexed.join(salaries_indexed)
print(employees_with_salaries)

Метод join особенно удобен в случаях, когда вам нужно последовательно присоединить несколько датафреймов к основному:

Python
Скопировать код
# Создадим дополнительные данные
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:

Python
Скопировать код
# Приведём пример с использованием другого столбца для объединения
# Сначала вернём датафрейм сотрудников с обычными столбцами
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:

  1. Когда вы работаете с данными, где естественным ключом является индекс
  2. При последовательном присоединении нескольких датафреймов к основному
  3. Когда вам нужен более компактный и читаемый код для простых случаев объединения
  4. Когда объединение по индексу более производительно для вашего конкретного случая

Подводные камни при использовании join:

  • Неявное поведение с дубликатами индексов может привести к неожиданным результатам
  • По умолчанию используется left join, что может привести к потере данных, если вы ожидаете inner join
  • При работе с большими данными индексное объединение может быть медленнее, чем хешированное объединение с merge

Типичный паттерн эффективного использования join в рабочем процессе:

Python
Скопировать код
# 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
Направление объединения По горизонтали (столбцы) По горизонтали или вертикали По горизонтали (столбцы)
Объединение нескольких датафреймов Последовательно (два за раз) Одновременно (список) Одновременно (список)
Гибкость в ключах объединения Высокая Низкая (по позиции) Средняя
Производительность при больших данных Средняя Высокая Средняя
Удобство использования Требует явного указания параметров Простой и интуитивно понятный Компактный синтаксис

Вот рекомендации по выбору метода для типичных сценариев:

  1. Используйте pd.merge, когда:

    • Нужно объединить таблицы по значениям одного или нескольких столбцов
    • Требуется высокая степень контроля над процессом объединения
    • Необходимы дополнительные функции, такие как indicator или validate
    • Работаете в парадигме SQL-подобных операций
  2. Используйте pd.concat, когда:

    • Нужно "склеить" несколько датафреймов с одинаковой или похожей структурой
    • Объединяете данные по строкам (вертикально)
    • Формируете панельные данные или временные ряды из фрагментов
    • Нужна максимальная скорость на больших наборах данных
  3. Используйте DataFrame.join, когда:

    • Данные уже имеют логичный индекс для объединения
    • Нужен более краткий и читаемый синтаксис
    • Последовательно присоединяете несколько датафреймов к основному
    • Предпочитаете объединение left по умолчанию

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

Python
Скопировать код
# Задача: объединить информацию о сотрудниках с их зарплатами

# Вариант с 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 не подходит оптимально

Иногда требуется комбинировать различные методы для достижения желаемого результата:

Python
Скопировать код
# Пример комбинированного подхода
# 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, вы трансформируете работу с разрозненными источниками информации из болезненного опыта в предсказуемый, эффективный рабочий процесс. Всегда начинайте с понимания структуры данных и требуемого результата, затем выбирайте подходящий инструмент, а не наоборот. И помните — правильный метод объединения сэкономит не только строки кода, но и часы вашего времени.

Загрузка...