Сообщить об ошибке.

Функция concat() библиотеки pandas в Python

Объединение объектов pandas вдоль определенной оси

Синтаксис:

import pandas

df = pandas.concat(objs, *, axis=0, join='outer', ignore_index=False, 
                    keys=None, levels=None, names=None, 
                    verify_integrity=False, sort=False, copy=None)

Параметры:

  • objs - принимает последовательность или словарь объектов Series или pandas.DataFrame. Если передан словарь, то отсортированные ключи будут использоваться в качестве аргумента keys, если со словарем передан аргумент keys, то в этом случае будут выбраны только значения. Любые объекты None будут удалены автоматически. Если все значения являются None, то в этом случае будет выдана ошибка ValueError.
  • axis=0 - ось для объединения. Принимает значения: 0 ('index') или 1 ('columns').
  • join='outer' - как обрабатывать индексы на другой оси (или осях). Принимает значения: 'inner' или 'outer'.
  • ignore_index=False - если True, то значения индекса вдоль оси конкатенации не используются. Результирующая ось будет обозначена как 0, ..., n - 1. Это полезно, если объединяются объекты, в которых ось конкатенации не имеет значимой информации об индексировании. Обратите внимание, что значения индексов на других осях по-прежнему учитываются при соединении.
  • keys=None - создает иерархический индекс, используя переданные ключи в качестве самого внешнего уровня. Если пройдено несколько уровней, должен содержать кортежи.
  • levels=None - конкретные уровни (уникальные значения), используемые для создания MultiIndex. В противном случае они будут выведены из ключей.
  • names=None - имена уровней в результирующем иерархическом индексе.
  • verify_integrity=False - проверяет, нет ли дубликатов на новой объединенной оси. Это дорогостоящая операция по сравнению с фактической конкатенацией данных.
  • sort=False - сортирует ось перед конкатенацией, если она еще не выровнена.
  • copy=None - если False, не копирует данные без необходимости.

    Аргумент copy изменит поведение в pandas 3.0. Копирование при записи будет включено по умолчанию, а это означает, что все методы с аргументом copy будут использовать механизм отложенного копирования и игнорировать аргумент copy. Ключевой аргумент copy будет удален в будущей версии pandas. Можно уже сейчас получить будущее поведение и улучшения, включив копирование при записи pd.options.mode.copy_on_write = True

Возвращаемое значение:

  • При объединении Series по индексу (axis=0) возвращается Series. Если objs содержит хотя бы один DataFrame, то возвращается DataFrame. При конкатенации по столбцам (axis=1) возвращается DataFrame.

Описание функции pandas.concat()

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

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

Примечание: Аргументы keys, levels и names являются необязательными.

Не рекомендуется создавать DataFrame, добавляя объекты в pandas.concat() по одному в цикле for. Лучше построить список этих объектов и создать DataFrame одним вызывом pandas.concat().

Прежде чем останавливаться на деталях и возможностях функции pandas.concat() смотрим простой пример:

import pandas as pd

df1 = pd.DataFrame(
    {
        "A": ["A0", "A1", "A2", "A3"],
        "B": ["B0", "B1", "B2", "B3"],
        "C": ["C0", "C1", "C2", "C3"],
        "D": ["D0", "D1", "D2", "D3"],
    },
    index=[0, 1, 2, 3],
)

df2 = pd.DataFrame(
    {
        "A": ["A4", "A5", "A6", "A7"],
        "B": ["B4", "B5", "B6", "B7"],
        "C": ["C4", "C5", "C6", "C7"],
        "D": ["D4", "D5", "D6", "D7"],
    },
    index=[4, 5, 6, 7],
)

df3 = pd.DataFrame(
    {
        "A": ["A8", "A9", "A10", "A11"],
        "B": ["B8", "B9", "B10", "B11"],
        "C": ["C8", "C9", "C10", "C11"],
        "D": ["D8", "D9", "D10", "D11"],
    },
    index=[8, 9, 10, 11],
)

>>> pd.concat([df1, df2, df3])
#       A    B    C    D
# 0    A0   B0   C0   D0
# 1    A1   B1   C1   D1
# 2    A2   B2   C2   D2
# 3    A3   B3   C3   D3
# 4    A4   B4   C4   D4
# 5    A5   B5   C5   D5
# 6    A6   B6   C6   D6
# 7    A7   B7   C7   D7
# 8    A8   B8   C8   D8
# 9    A9   B9   C9   D9
# 10  A10  B10  C10  D10
# 11  A11  B11  C11  D11

Функция pandas.concat() принимает список или словарь однородно типизированных объектов и объединяет их с некоторой настраиваемой обработкой того, "что делать с другими осями":

Без небольшого контекста, многие из принимаемых аргументов pandas.concat() не имеют особого смысла. Вернемся к приведенному выше примеру и предположим, что необходимо связать конкретные ключи с каждым переданным DataFrame. Cделать это можем с помощью аргумента keys:

>>> result = pd.concat([df1, df2, df3], keys=["x", "y", "z"])
>>> result
#         A    B    C    D
# x 0    A0   B0   C0   D0
#   1    A1   B1   C1   D1
#   2    A2   B2   C2   D2
#   3    A3   B3   C3   D3
# y 4    A4   B4   C4   D4
#   5    A5   B5   C5   D5
#   6    A6   B6   C6   D6
#   7    A7   B7   C7   D7
# z 8    A8   B8   C8   D8
#   9    A9   B9   C9   D9
#   10  A10  B10  C10  D10
#   11  A11  B11  C11  D11

Видно, что индекс результирующего объекта - стал MultiIndex. Это означает, что теперь можно выбрать каждый фрагмент по ключу. Вы не представляете, насколько это может быть полезно.

>>> result.loc["y"]
#     A   B   C   D
# 4  A4  B4  C4  D4
# 5  A5  B5  C5  D5
# 6  A6  B6  C6  D6
# 7  A7  B7  C7  D7

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

# абстрактный код
frames = [process_your_file(f) for f in files]
result = pd.concat(frames)

Примечание: при объединении DataFrame с именованными осями, pandas (когда это возможно) будет пытаться сохранить эти имена индексов/столбцов. В случае, когда все входные данные имеют общее имя, то это имя будет присвоено результату. Если имена входных данных не совпадают, то результат будет без имени. То же самое справедливо и для MultiIndex, но логика применяется отдельно, уровень за уровнем.

Установка логики конкатенации на других осях

При склеивании нескольких DataFrames есть выбор способа обработки других осей (кроме объединяемой). Это можно сделать двумя способами:

  • перекрестное объединение осей, join='inner'
  • объединение всех осей, join='outer'. Это вариант по умолчанию, так как он приводит к нулевой потере информации.

Поведение каждого из этих методов. Пример поведения join='outer' (по умолчанию):

df4 = pd.DataFrame(
    {
        "B": ["B2", "B3", "B6", "B7"],
        "D": ["D2", "D3", "D6", "D7"],
        "F": ["F2", "F3", "F6", "F7"],
    },
    index=[2, 3, 6, 7],
)

>>> pd.concat([df1, df4], axis=1)
#      A    B    C    D    B    D    F
# 0   A0   B0   C0   D0  NaN  NaN  NaN
# 1   A1   B1   C1   D1  NaN  NaN  NaN
# 2   A2   B2   C2   D2   B2   D2   F2
# 3   A3   B3   C3   D3   B3   D3   F3
# 6  NaN  NaN  NaN  NaN   B6   D6   F6
# 7  NaN  NaN  NaN  NaN   B7   D7   F7

Теперь то же самое, но с join='inner':

>>> pd.concat([df1, df4], axis=1, join="inner")
#     A   B   C   D   B   D   F
# 2  A2  B2  C2  D2  B2  D2  F2
# 3  A3  B3  C3  D3  B3  D3  F3

Наконец, предположим, что нужно повторно использовать точный индекс из исходного DataFrame:

>>> pd.concat([df1, df4], axis=1).reindex(df1.index)
#     A   B   C   D    B    D    F
# 0  A0  B0  C0  D0  NaN  NaN  NaN
# 1  A1  B1  C1  D1  NaN  NaN  NaN
# 2  A2  B2  C2  D2   B2   D2   F2
# 3  A3  B3  C3  D3   B3   D3   F3

# Точно так же можно индексировать перед конкатенацией:
>>> pd.concat([df1, df4.reindex(df1.index)], axis=1)
#     A   B   C   D    B    D    F
# 0  A0  B0  C0  D0  NaN  NaN  NaN
# 1  A1  B1  C1  D1  NaN  NaN  NaN
# 2  A2  B2  C2  D2   B2   D2   F2
# 3  A3  B3  C3  D3   B3   D3   F3

Привязка индексов к оси конкатенации

Для объектов DataFrame, у которых индекс не несет смысловой нагрузки, можно игнорировать тот факт, что они могут перекрываться. Для этого используем аргумент ignore_index:

>>> pd.concat([df1, df4], ignore_index=True, sort=False)
#      A   B    C   D    F
# 0   A0  B0   C0  D0  NaN
# 1   A1  B1   C1  D1  NaN
# 2   A2  B2   C2  D2  NaN
# 3   A3  B3   C3  D3  NaN
# 4  NaN  B2  NaN  D2   F2
# 5  NaN  B3  NaN  D3   F3
# 6  NaN  B6  NaN  D6   F6
# 7  NaN  B7  NaN  D7   F7

Объединение объектов Series и DataFrame

Можно объединить сочетание объектов Series и DataFrame. Серия будет преобразована в DataFrame с именем столбца, считанное из Series.name.

>>> s1 = pd.Series(["X0", "X1", "X2", "X3"], name="X")
>>> s1.name
# 'X'
>>> pd.concat([df1, s1], axis=1)
#     A   B   C   D   X
# 0  A0  B0  C0  D0  X0
# 1  A1  B1  C1  D1  X1
# 2  A2  B2  C2  D2  X2
# 3  A3  B3  C3  D3  X3

Заметка. Так как объединяется Series в DataFrame, то этого результата можно достичь с помощью DataFrame.assign(). Чтобы объединить произвольное количество объектов pandas (DataFrame или Series), необходимо использовать функцию pandas.concat().

Если передаются Series, у которых не задан атрибут Series.name, то они будут пронумерованы последовательно.

>>> s2 = pd.Series(["_0", "_1", "_2", "_3"])
>>> pd.concat([df1, s2, s2, s2], axis=1)
#     A   B   C   D   0   1   2
# 0  A0  B0  C0  D0  _0  _0  _0
# 1  A1  B1  C1  D1  _1  _1  _1
# 2  A2  B2  C2  D2  _2  _2  _2
# 3  A3  B3  C3  D3  _3  _3  _3

Передача ignore_index=True приведет к удалению всех ссылок на имена. Обратите внимание на индексные метки строк/столбцов.

>>> pd.concat([df1, s1], axis=1, ignore_index=True)
#     0   1   2   3   4
# 0  A0  B0  C0  D0  X0
# 1  A1  B1  C1  D1  X1
# 2  A2  B2  C2  D2  X2
# 3  A3  B3  C3  D3  X3

Дополнительное объединение с помощью групповых ключей

Распространенным использованием аргумента keys является переопределение имен столбцов при создании нового DataFrame на основе существующего Series. Заметьте, что поведение по умолчанию заключается в том, что результирующий DataFrame наследует имена родительских Series, когда они существуют.

>>> s3 = pd.Series([0, 1, 2, 3], name="foo")
>>> s4 = pd.Series([0, 1, 2, 3])
>>> s5 = pd.Series([0, 1, 4, 5])

>>> pd.concat([s3, s4, s5], axis=1)
#    foo  0  1
# 0    0  0  0
# 1    1  1  1
# 2    2  2  4
# 3    3  3  5

С помощью аргумента keys можно переопределить существующие имена столбцов.

>>> pd.concat([s3, s4, s5], axis=1, keys=["red", "blue", "yellow"])
#    red  blue  yellow
# 0    0     0       0
# 1    1     1       1
# 2    2     2       4
# 3    3     3       5

Рассмотрим вариацию самого первого примера:

>>> frames = [df1, df2, df3]
>>> pd.concat(frames, keys=["x", "y", "z"])

# эквивалентно
>>> mapping = {"x": df1, "y": df2, "z": df3}
>>> pd.concat(mapping)
#         A    B    C    D
# x 0    A0   B0   C0   D0
#   1    A1   B1   C1   D1
#   2    A2   B2   C2   D2
#   3    A3   B3   C3   D3
# y 4    A4   B4   C4   D4
#   5    A5   B5   C5   D5
#   6    A6   B6   C6   D6
#   7    A7   B7   C7   D7
# z 8    A8   B8   C8   D8
#   9    A9   B9   C9   D9
#   10  A10  B10  C10  D10
#   11  A11  B11  C11  D11

>>> result = pd.concat(mapping, keys=["z", "y"])
#         A    B    C    D
# z 8    A8   B8   C8   D8
#   9    A9   B9   C9   D9
#   10  A10  B10  C10  D10
#   11  A11  B11  C11  D11
# y 4    A4   B4   C4   D4
#   5    A5   B5   C5   D5
#   6    A6   B6   C6   D6
#   7    A7   B7   C7   D7

Созданный MultiIndex имеет уровни, которые строятся из переданных ключей и индекса фрагментов DataFrame:

>>> result.index.levels
# FrozenList([['z', 'y'], [4, 5, 6, 7, 8, 9, 10, 11]])

Если нужно указать другие уровни (что иногда случается), то можно сделать это с помощью аргумента levels:

result = pd.concat(
    mapping, keys=["x", "y", "z"], levels=[["z", "y", "x", "w"]], names=["group_key"]
)

>>> result
#                 A    B    C    D
# group_key                       
# x         0    A0   B0   C0   D0
#           1    A1   B1   C1   D1
#           2    A2   B2   C2   D2
#           3    A3   B3   C3   D3
# y         4    A4   B4   C4   D4
#           5    A5   B5   C5   D5
#           6    A6   B6   C6   D6
#           7    A7   B7   C7   D7
# z         8    A8   B8   C8   D8
#           9    A9   B9   C9   D9
#           10  A10  B10  C10  D10
#           11  A11  B11  C11  D11
>>> result.index.levels
# FrozenList([['z', 'y', 'x', 'w'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]])

Это немного непонятно, но на самом деле необходимо для реализации GroupBy, где порядок "категориальной" переменной имеет смысл.

Добавление строк к фрейму данных

Если есть ряд, который необходимо добавить как одну строку в DataFrame, то можно преобразовать строку в DataFrame

>>> s2 = pd.Series(["X0", "X1", "X2", "X3"], index=["A", "B", "C", "D"])
>>> pd.concat([df1, s2.to_frame().T], ignore_index=True)
#     A   B   C   D
# 0  A0  B0  C0  D0
# 1  A1  B1  C1  D1
# 2  A2  B2  C2  D2
# 3  A3  B3  C3  D3
# 4  X0  X1  X2  X3 (добавленная строка)

Чтобы DataFrame удалил свой индекс, нужно использовать аргумент ignore_index. Если необходимо сохранить индекс, то нужно создать соответствующим образом индексированный DataFrame и добавить или объединить эти объекты.

Примеры использования функции pandas.concat()

Объединение двух Series.

>>> s1 = pd.Series(['a', 'b'])
>>> s2 = pd.Series(['c', 'd'])
>>> pd.concat([s1, s2])
# 0    a
# 1    b
# 0    c
# 1    d
# dtype: object

Очистим и сбросим существующий индекс, установив аргумент ignore_index=True.

>>> pd.concat([s1, s2], ignore_index=True)
# 0    a
# 1    b
# 2    c
# 3    d
# dtype: object

Добавим MultiIndex на самом внешнем уровне данных с помощью аргумента keys .

>>> pd.concat([s1, s2], keys=['s1', 's2'])
# s1  0    a
#     1    b
# s2  0    c
#     1    d
# dtype: object

Добавим имена индексным ключам, используя аргумент names.

>>> pd.concat([s1, s2], keys=['s1', 's2'], names=['Series name', 'Row ID'])
# Series name  Row ID
# s1           0         a
#              1         b
# s2           0         c
#              1         d
# dtype: object

Объединяем два объекта DataFrame с одинаковыми столбцами.

>>> df1 = pd.DataFrame([['a', 1], ['b', 2]], columns=['letter', 'number'])
>>> df2 = pd.DataFrame([['c', 3], ['d', 4]], columns=['letter', 'number'])
>>> df1
#   letter  number
# 0      a       1
# 1      b       2

>>> df2
#   letter  number
# 0      c       3
# 1      d       4

>>> pd.concat([df1, df2])
#   letter  number
# 0      a       1
# 1      b       2
# 0      c       3
# 1      d       4

Объединяем объекты DataFrame с перекрывающимися столбцами. Столбцы за пределами пересечения будут заполнены значениями NaN.

>>> df3 = pd.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']], columns=['letter', 'number', 'animal'])
>>> df3
#   letter  number animal
# 0      c       3    cat
# 1      d       4    dog

>>> pd.concat([df1, df3], sort=False)
#   letter  number animal
# 0      a       1    NaN
# 1      b       2    NaN
# 0      c       3    cat
# 1      d       4    dog

Объединим объекты DataFrame с перекрывающимися столбцами и получим только те, которые являются общими, передав аргумент join='inner'.

>>> pd.concat([df1, df3], join="inner")
#   letter  number
# 0      a       1
# 1      b       2
# 0      c       3
# 1      d       4

Объедините объекты DataFrame по горизонтали вдоль оси x, передав axis=1.

>>> df4 = pd.DataFrame([['bird', 'polly'], ['monkey', 'george']], columns=['animal', 'name'])
>>> df4
#    animal    name
# 0    bird   polly
# 1  monkey  george

>>> pd.concat([df1, df4], axis=1)
#   letter  number  animal    name
# 0      a       1    bird   polly
# 1      b       2  monkey  george

Аргумента verify_integrity отвечает за проверку на дубликаты меток индекса в результирующем DataFrame и если они будут, то поднимает исключение ValueError.

>>> df5 = pd.DataFrame([1], index=['a'])
>>> df6 = pd.DataFrame([2], index=['a'])
>>> df5
#    0
# a  1

>>> df6
#    0
# a  2

>>> pd.concat([df5, df6], verify_integrity=True)
# Traceback (most recent call last):
#     ...
# ValueError: Indexes have overlapping values: ['a']

Добавим одну строку в конец объекта DataFrame.

>>> df7 = pd.DataFrame({'a': 1, 'b': 2}, index=[0])
>>> df7
#     a   b
# 0   1   2

>>> new_row = pd.Series({'a': 3, 'b': 4})
>>> new_row
# a    3
# b    4
# dtype: int64

>>> pd.concat([df7, new_row.to_frame().T], ignore_index=True)
#     a   b
# 0   1   2
# 1   3   4