20 примеров для освоения слияния датафреймов данных в Python Pandas

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

В этой статье мы рассмотрим полный набор из 20 примеров, которые раскроют нюансы операций слияния. Мы начнем с базовых функций слияния и постепенно перейдем к более сложным сценариям, охватывая все детали слияния DataFrames с помощью Pandas.
Мы рассмотрим следующие функции:
merge
merge_asof
merge_ordered
Давайте начнем с создания двух DataFrames, которые будут использоваться в примерах.
import numpy as np
import pandas as pd
names = pd.DataFrame(
{
"id": [1, 2, 3, 4, 10],
"name": ["Emily", "Jane", "Joe", "Matt", "Lucas"],
"age": np.random.randint(20, 30, size=5)
}
)
scores = pd.DataFrame(
{
"id": np.arange(1, 8),
"score": np.random.randint(80, 100, size=7),
"group": list("ABCAACA")
}
)

Пример 1
Основной синтаксис функции слияния выглядит следующим образом. Параметр on указывает столбец или столбцы, которые будут использоваться при сравнении строк.
merged_df = names.merge(scores, on="id")

Пример 2 – как параметр
Существуют различные типы слияния. Параметр how определяет один из следующих типов:
- left: использовать только ключи из левого DataFrame
- right: использовать только ключи из правого DataFrame
- внешний: использовать объединение ключей из обоих DataFrame
- внутренний: использовать пересечение ключей из обоих DataFrame
- cross: создает картезианское произведение из обоих DataFrames
По умолчанию параметр how имеет значение inner, поэтому в предыдущем примере объединенный DataFrame содержит пересечение ключей.
Ключи – это значения в столбце(ах), указанном(ых) параметром on.
Давайте выполним слияние слева.
merged_df = names.merge(scores, on="id", how="left")

Объединенный DataFrame включает все ключи из левого DataFrame. Несовпадающие строки заполняются NaN, стандартным представлением отсутствующего значения.
Пример 3 – правое слияние
Это противоположность левому слиянию, но я бы не рекомендовал использовать правое слияние, поскольку его можно достичь, изменив порядок DataFrames и используя левое слияние.
# followings are the same
merged_df = names.merge(scores, on="id", how="left")
merged_df = scores.merge(names, on="id", how="right")
Пример 4- внешнее слияние
merged_df = names.merge(scores, on="id", how="outer")

Объединенный фрейм данных включает все ключи из обоих фреймов данных.
Пример 5- параметр индикатора
Параметр indicator создает столбец в объединенном DataFrame, который указывает, откуда берется ключевое значение в строках.
- both: значение ключа существует в обоих DataFrames
- left_only: только левый DataFrame
- right_only: только правый DataFrame
merged_df = names.merge(scores, on="id", how="outer", indicator=True)

Пример 6 – параметр индикатора
Параметр indicator также принимает в качестве аргумента строковое значение, которое используется в качестве имени колонки.
merged_df = names.merge(scores, on="id", how="left", indicator="source")

Пример 7 – параметры left_on и right_on
Если столбцы, используемые для объединения DataFrames, имеют разные имена, мы можем использовать параметры left_on и right_on.
# rename the id column in the scores DataFrame
scores = scores.rename(columns={"id": "id_number"})
merged_df = names.merge(scores, left_on="id", right_on="id_number")

Пример 8 – слияние по нескольким столбцам
Для этого примера мы создадим два новых DataFrames.
products = pd.DataFrame(
{
"pg": ["A", "A", "A", "B", "B", "B"],
"id": [101, 102, 103, 101, 102, 104],
"price": np.random.randint(50, 80, size=6),
"cost": np.random.randint(40, 50, size=6),
"discount": [0.1, 0.1, 0, 0, 0.2, 0]
}
)
sales = pd.DataFrame(
{
"pg": ["A", "A", "A", "B", "B", "B"],
"id": [101, 102, 105, 101, 102, 106],
"sales_qty": np.random.randint(1, 10, size=6),
"discount": [0, 0.1, 0.1, 0.2, 0, 0]
}
)

Чтобы объединить DataFrames по нескольким столбцам, мы записываем имена столбцов в виде списка Python.
merged_df = products.merge(sales, on=["pg", "id"])

Пример 9 – параметр суффикса
В предыдущем примере объединенный DataFrame имеет столбцы discount_x и discount_y . Суффиксы x и y добавляются для разделения столбцов, которые существуют в обоих DataFrame с одинаковым именем. Суффикс x используется для левого DataFrame, а y – для правого.
Мы можем использовать собственные суффиксы, чтобы сделать вывод более понятным.
merged_df = products.merge(sales, on=["pg", "id"], suffixes=["_products", "_sales"])

Пример 10 – несколько столбцов
Как и параметр on, параметры right_on и left_on принимают в качестве аргумента список в случае, если имена столбцов отличаются.
# rename the id column
sales = sales.rename(columns={"id": "product_id"})
merged_df = products.merge(
sales,
left_on=["pg", "id"],
right_on=["pg", "product_id"],
how="left",
suffixes=["_products", "_sales"]
)

Пример 11 – слияние по индексу
Мы также можем объединять DataFrames по значениям их индексов. Для этого примера мы создадим два новых DataFrames.
df1 = pd.DataFrame(
np.random.randint(0, 10, size=(5, 4)),
columns=list("ABCD")
)
df2 = pd.DataFrame(
np.random.randint(0, 10, size=(5, 4)),
columns=list("EFGH"),
index=[2, 3, 4, 5, 6]
)

Как мы видим на скриншоте выше, DataFrames имеют разные значения индекса. Один начинается с 0, а другой – с 2.
Для объединения по индексу мы используем параметры left_index и right_index.
merged_df = df1.merge(df2, left_index=True, right_index=True)

Поскольку мы использовали внутреннее слияние, объединенный DataFrame включает только те индексы, которые существуют в обоих DataFrame.
Пример 12 – как параметр с объединением по индексу
Мы можем использовать параметр how и при объединении по индексам.
merged_df = df1.merge(df2, left_index=True, right_index=True, how="left")

Пример 13 – объединение данных временных рядов
Данные временных рядов могут включать измерения, сделанные в очень короткие периоды времени (например, на уровне секунд). Поэтому, когда мы объединяем два DataFrames, состоящих из данных временного ряда, мы можем столкнуться с измерениями, отличающимися на секунду или две.
Для таких случаев Pandas предоставляет “умный” способ слияния с помощью функции merge_asof.
Предположим, что мы объединяем DataFrame A и B. Если строка в левом DataFrame не имеет соответствующей строки в правом DataFrame, merge_asof позволяет взять строку, значение которой близко к значению в левом DataFrame.
Это похоже на слияние слева, за исключением того, что мы сравниваем по ближайшему ключу, а не по одинаковым ключам. Оба DataFrames должны быть отсортированы по ключу.
Для каждой строки в левом DataFrame:
- При поиске “назад” выбирается последняя строка в правом DataFrame, ключ ‘on’ которой меньше или равен ключу левого DataFrame.
- При поиске “вперед” выбирается первая строка правого DataFrame, ключ ‘on’ которой больше или равен ключу левого DataFrame.
- При “ближайшем” поиске выбирается строка в правом DataFrame, чей ключ ‘on’ ближе всего по абсолютному расстоянию к левому ключу.
Давайте создадим два новых DataFrames, содержащих данные временных рядов.
df1 = pd.DataFrame(
{
"time": pd.date_range(start="2022-12-09", periods=7, freq="2S"),
"left_value": np.round(np.random.random(7), 2)
}
)
df2 = pd.DataFrame(
{
"time": pd.date_range(start="2022-12-09", periods=6, freq="3S"),
"right_value": np.round(np.random.random(6), 2)
}
)

Некоторые значения в столбце времени совпадают, в то время как другие отличаются на несколько секунд.
Давайте посмотрим, что произойдет, если мы объединим их.
merged_df = pd.merge_asof(df1, df2, on="time")

Правый DataFrame (df2) не имеет значения 00:00:02, поэтому в объединенном DataFrame значение 00:00:00 используется в качестве правого значения.
Пример 14 – параметр направления
В предыдущем примере функция merge_asof искала предыдущее значение для несовпадающих строк, поскольку значение параметра направления по умолчанию – “назад”.
Давайте изменим его на “ближайшее” и посмотрим, что произойдет.
merged_df = pd.merge_asof(df1, df2, on="time", direction="nearest")

Правое значение в первой строке равно 0,36, потому что следующее значение (00:00:03) ближе к значению в левом DataFrame (00:00:02), чем предыдущее (00:00:00).
Пример 15 – параметр допуска
Можно также задать допуск, который будет использоваться при проверке предыдущего и следующего значений.
В следующем примере направление – вперед, поэтому следующее значение проверяется на несовпадение строк. Мы также установили допуск в 1 секунду, поэтому для использования следующего значения оно должно отличаться не более чем на 1 секунду.
merged_df = pd.merge_asof(
df1,
df2,
on="time",
direction="forward",
tolerance=pd.Timedelta("1s")
)

Посмотрите на третью и шестую строки в объединенном DataFrame. Правое значение равно NaN, потому что следующее значение в правом DataFrame для этих строк отличается на 2 секунды.
- слева: 00:00:04, следующее значение справа: 00:00:06
- слева: 00:00:10, следующее значение справа: 00:00:12
Пример 16 – параметр allow_exact_matches
У нас также есть возможность не разрешать точные совпадения в объединенном DataFrame. По умолчанию точные совпадения присутствуют в объединенном DataFrame, но это можно изменить с помощью параметра allow_exact_matches.
merged_df = pd.merge_asof(df1, df2, on="time", allow_exact_matches=False)

Значения времени в первых строках одинаковы, но объединенный DataFrame имеет значение NaN в первой строке правой колонки значений, потому что мы установили значение параметра allow_exact_matches как False.
Пример 17 – по параметру
Параметр by можно использовать для разделения групп при объединении точек данных с предыдущим или следующим значением.
Давайте добавим столбец группы в наши DataFrames.
df1["group"] = ["AA"] * 4 + ["BB"] * 3
df2["group"] = ["AA"] * 3 + ["BB"] * 3

Допустим, мы хотим использовать merge_asof, но только внутри групп, чтобы значения в определенной группе не могли быть объединены с любыми значениями в другой группе. Для этого мы можем использовать параметр by.
merged_df = pd.merge_asof(df1, df2, on="time", by="group")

Правое значение в первой строке для группы BB – NaN . Мы объединяем на основе “обратного” направления, и предыдущее значение принадлежит другой группе.
Пример 18 – упорядоченное слияние
Функция merge_ordered выполняет слияние для упорядоченных данных с необязательным заполнением/интерполяцией. Она предназначена для упорядоченных данных, таких как временные ряды.
Ее легче понять на примере:
merged_df = pd.merge_ordered(df1, df2)

Строки упорядочены по столбцу времени. Если один из DataFrames не имеет определенного значения времени, столбцы, исходящие из него, заполняются значением NaN .
Пример 19 – параметр fill_method
При выполнении упорядоченного слияния с помощью merge_ordered , мы можем использовать параметр fill_method для определения метода интерполяции.
Значение по умолчанию – NaN, и единственная опция, которую мы можем использовать, это “ffill”, что означает прямое заполнение.
merged_df = pd.merge_ordered(df1, df2, fill_method="ffill")

Сравните результат с предыдущим примером, и вы заметите, как значения NaN заменяются предыдущим значением.
Пример 20- параметр left_by
Мы также можем выполнить упорядоченное слияние в каждой группе отдельно. Параметр left_by группирует левый DataFrame по столбцам группы и объединяет по частям с правым DataFrame.
merged_df = pd.merge_ordered(df1, df2, fill_method="ffill", left_by="group")

В отличие от предыдущего примера, правое значение в первой строке группы “BB” – NaN, потому что мы не можем использовать значения из другой группы.
Заключительные слова
В этих 20 примерах мы рассмотрели различные сценарии слияния, от самых простых до более сложных. Благодаря этим практическим примерам вы будете готовы к решению любой задачи по объединению, которая встанет перед вами.
Спасибо, что читаете!