Объединение, присоединение, конкатенация и сравнение
pandas предоставляет различные средства для легкого объединения Series или DataFrame с различными видами логических операций над индексами и функциональностью реляционной алгебры в случае операций типа join/merge.
Кроме того, pandas также предоставляет утилиты для сравнения двух Series или DataFrame и обобщения их различий.
Конкатенация объектов
Функция concat() (в основном пространстве имен pandas) выполняет все основные операции конкатенации вдоль оси, выполняя при этом необязательную логику множеств (объединение или пересечение) индексов (если таковые имеются) на других осях. Обратите внимание, что я говорю «если таковые имеются», поскольку для Series существует только одна возможная ось конкатенации.
Прежде чем углубиться во все подробности concat и того, что она может делать, вот простой пример:
In [1]: 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],
...: )
...:
In [2]: 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],
...: )
...:
In [3]: 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],
...: )
...:
In [4]: frames = [df1, df2, df3]
In [5]: result = pd.concat(frames)
Как и её функция-аналог для ndarrays, numpy.concatenate, pandas.concat принимает список или словарь однородных объектов и конкатенирует их с некоторым настраиваемым обработкой «что делать с другими осями»:
pd.concat(
objs,
axis=0,
join="outer",
ignore_index=False,
keys=None,
levels=None,
names=None,
verify_integrity=False,
copy=True,
)
objs: последовательность или отображение объектов Series или DataFrame. Если передан словарь, отсортированные ключи будут использоваться в качестве аргументаkeys, если он не передан, в противном случае будут выбраны значения (см. ниже). Любые объекты None будут молча удалены, если все они не None, в противном случае будет поднято исключение ValueError.axis: {0, 1, …}, по умолчанию 0. Ось, по которой происходит конкатенация.join: {‘inner’, ‘outer’}, по умолчанию ‘outer’. Как обрабатывать индексы на других осях. Outer для объединения и inner для пересечения.ignore_index: булево значение, по умолчанию False. Если True, не использовать значения индексов на оси конкатенации. Результирующая ось будет помечена как 0, …, n - 1. Это полезно, если вы конкатенируете объекты, где ось конкатенации не имеет осмысленной информации об индексации. Обратите внимание, что значения индексов на других осях всё ещё учитываются при соединении.keys: последовательность, по умолчанию None. Построение иерархического индекса с использованием переданных ключей в качестве самого внешнего уровня. Если передано несколько уровней, должно содержать кортежи.levels: список последовательностей, по умолчанию None. Конкретные уровни (уникальные значения) для использования при построении MultiIndex. В противном случае они будут вычислены из ключей.names: список, по умолчанию None. Имена уровней в результирующем иерархическом индексе.verify_integrity: булево значение, по умолчанию False. Проверить, содержит ли новая ось конкатенации дубликаты. Это может быть очень дорого по сравнению с фактической конкатенацией данных.copy: булево значение, по умолчанию True. Если False, не копировать данные лишний раз.
Без некоторого контекста многие из этих аргументов не имеют большого смысла. Давайте ещё раз рассмотрим предыдущий пример. Предположим, что мы хотим связать определённые ключи с каждой частью разбитого DataFrame. Мы можем сделать это, используя аргумент keys:
In [6]: result = pd.concat(frames, keys=["x", "y", "z"])
Как вы можете видеть (если вы прочитали остальную часть документации), индекс результирующего объекта имеет многоуровневый индекс. Это означает, что теперь мы можем выбрать каждую часть по ключу:
In [7]: result.loc["y"]
Out[7]:
A B C D
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
Легко понять, как это может быть очень полезно. Более подробная информация об этой функциональности ниже.
Примечание
Стоит отметить, что concat() (и, следовательно, append()) делает полную копию данных, и постоянное повторное использование этой функции может привести к существенной потере производительности. Если вам нужно использовать операцию над несколькими наборами данных, используйте цикл с пониманием списков.
frames = [ process_your_file(f) for f in files ]
result = pd.concat(frames)
Примечание
При конкатенации DataFrame с именованными осями pandas будет пытаться сохранить эти имена индексов/колонок по возможности. В случае, когда все входы имеют общее имя, это имя будет назначено результату. Когда имена входов не совпадают, результат будет безымянным. То же самое относится к MultiIndex, но логика применяется отдельно на уровне каждого уровня.
Логика множеств на других осях
При склеивании нескольких DataFrame у вас есть выбор того, как обработать другие оси (кроме той, по которой производится конкатенация). Это можно сделать двумя способами:
Взять объединение всех из них,
join='outer'. Это опция по умолчанию, так как она приводит к нулевому потери информации.Взять пересечение,
join='inner'.
Вот пример каждого из этих методов. Сначала поведение по умолчанию join='outer':
In [8]: df4 = pd.DataFrame(
...: {
...: "B": ["B2", "B3", "B6", "B7"],
...: "D": ["D2", "D3", "D6", "D7"],
...: "F": ["F2", "F3", "F6", "F7"],
...: },
...: index=[2, 3, 6, 7],
...: )
...:
In [9]: result = pd.concat([df1, df4], axis=1)
Вот то же самое с join='inner':
In [10]: result = pd.concat([df1, df4], axis=1, join="inner")
Наконец, предположим, что мы просто хотели повторно использовать точный индекс из исходного DataFrame:
In [11]: result = pd.concat([df1, df4], axis=1).reindex(df1.index)
Аналогично, мы могли бы индексировать перед конкатенацией:
In [12]: pd.concat([df1, df4.reindex(df1.index)], axis=1)
Out[12]:
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:
In [13]: result = pd.concat([df1, df4], ignore_index=True, sort=False)
Конкатенация с смешанными ndims
Вы можете конкатенировать смесь Series и DataFrame объектов. Series будет преобразовано в DataFrame с именем столбца в качестве имени Series.
In [14]: s1 = pd.Series(["X0", "X1", "X2", "X3"], name="X")
In [15]: result = pd.concat([df1, s1], axis=1)
Примечание
Поскольку мы конкатенируем Series с DataFrame, мы могли бы достичь того же результата с помощью DataFrame.assign(). Для конкатенации произвольного количества объектов pandas (DataFrame или Series) используйте concat.
Если переданные Series не имеют имени, они будут пронумерованы последовательно.
In [16]: s2 = pd.Series(["_0", "_1", "_2", "_3"])
In [17]: result = pd.concat([df1, s2, s2, s2], axis=1)
Передача ignore_index=True удалит все ссылки на имена.
In [18]: result = pd.concat([df1, s1], axis=1, ignore_index=True)
Дополнительная конкатенация с групповыми ключами
Довольно распространенным применением аргумента keys является перезапись имён столбцов при создании нового DataFrame на основе существующих Series. Обратите внимание, что по умолчанию результирующий DataFrame наследует имя родительского Series (если оно существует).
In [19]: s3 = pd.Series([0, 1, 2, 3], name="foo")
In [20]: s4 = pd.Series([0, 1, 2, 3])
In [21]: s5 = pd.Series([0, 1, 4, 5])
In [22]: pd.concat([s3, s4, s5], axis=1)
Out[22]:
foo 0 1
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
Через аргумент keys мы можем переопределить существующие имена столбцов.
In [23]: pd.concat([s3, s4, s5], axis=1, keys=["red", "blue", "yellow"])
Out[23]:
red blue yellow
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
Давайте рассмотрим вариацию самого первого примера:
In [24]: result = pd.concat(frames, keys=["x", "y", "z"])
Вы также можете передать словарь в concat, в этом случае ключи словаря будут использоваться для аргумента keys (если не указаны другие ключи):
In [25]: pieces = {"x": df1, "y": df2, "z": df3}
In [26]: result = pd.concat(pieces)
In [27]: result = pd.concat(pieces, keys=["z", "y"])
Созданный MultiIndex имеет уровни, которые построены из переданных ключей и индексов DataFrame частей:
In [28]: result.index.levels
Out[28]: FrozenList([['z', 'y'], [4, 5, 6, 7, 8, 9, 10, 11]])
Если вы хотите указать другие уровни (как это иногда бывает), вы можете сделать это, используя аргумент levels:
In [29]: result = pd.concat(
....: pieces, keys=["x", "y", "z"], levels=[["z", "y", "x", "w"]], names=["group_key"]
....: )
....:
In [30]: result.index.levels
Out[30]: FrozenList([['z', 'y', 'x', 'w'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]])
Это довольно экзотично, но на самом деле это необходимо для реализации таких вещей, как GroupBy, где порядок категориальной переменной имеет значение.
Добавление строк в DataFrame
Если у вас есть ряд, который вы хотите добавить в качестве единственной строки в DataFrame, вы можете преобразовать строку в DataFrame и использовать concat
In [31]: s2 = pd.Series(["X0", "X1", "X2", "X3"], index=["A", "B", "C", "D"])
In [32]: result = pd.concat([df1, s2.to_frame().T], ignore_index=True)
Вы должны использовать ignore_index с этим методом, чтобы указать DataFrame на отбрасывание своего индекса. Если вы хотите сохранить индекс, вы должны построить DataFrame с соответствующим индексом и добавить или конкатенировать эти объекты.
Объединение/слияние DataFrame или именованных Series в стиле баз данных
pandas предоставляет полные, высокопроизводительные операции объединения в оперативной памяти, очень похожие по стилю на реляционные базы данных, такие как SQL. Эти методы работают значительно быстрее (в некоторых случаях на порядок лучше), чем другие реализации с открытым исходным кодом (например, base::merge.data.frame в R). Причина в продуманном алгоритмическом дизайне и внутреннем расположении данных в DataFrame.
См. кулинарную книгу для некоторых расширенных стратегий.
Пользователи, знакомые с SQL, но новые для pandas, могут быть заинтересованы в сравнении с SQL.
pandas предоставляет единственную функцию merge() в качестве точки входа для всех стандартных операций объединения баз данных между DataFrame или именованными Series объектами:
pd.merge(
left,
right,
how="inner",
on=None,
left_on=None,
right_on=None,
left_index=False,
right_index=False,
sort=True,
suffixes=("_x", "_y"),
copy=True,
indicator=False,
validate=None,
)
left: Объект DataFrame или именованной Series.right: Другой объект DataFrame или именованной Series.on: Имена столбцов или уровней индекса для объединения. Должны быть найдены как в левом, так и в правом DataFrame и/или Series. Если не передано иleft_indexиright_indexявляютсяFalse, то пересечение столбцов в DataFrame и/или Series будет определено как ключи объединения.left_on: Столбцы или уровни индексов из левого DataFrame или Series для использования в качестве ключей. Может быть именами столбцов, именами уровней индекса или массивами длиной, равной длине DataFrame или Series.right_on: Столбцы или уровни индексов из правого DataFrame или Series для использования в качестве ключей. Может быть именами столбцов, именами уровней индекса или массивами длиной, равной длине DataFrame или Series.left_index: ЕслиTrue, использовать индекс (метки строк) из левого DataFrame или Series в качестве ключа(ей) объединения. В случае DataFrame или Series с MultiIndex (иерархическим), количество уровней должно совпадать с количеством ключей объединения из правого DataFrame или Series.right_index: Аналогичное использование, как уleft_indexдля правого DataFrame или Series.how: Один из'left','right','outer','inner','cross'. По умолчаниюinner. См. ниже подробное описание каждого метода.sort: Отсортировать результирующий DataFrame по ключам объединения в лексикографическом порядке. По умолчаниюTrue, установка наFalseсущественно повысит производительность во многих случаях.suffixes: Кортеж строчных суффиксов, которые необходимо применить к перекрывающимся столбцам. По умолчанию('_x', '_y').copy: Всегда копировать данные (по умолчаниюTrue) из переданных DataFrame или именованных Series, даже когда переиндексация не требуется. В многих случаях избежать копирования невозможно, но этот параметр всё же предоставляется.indicator: Добавить столбец в результирующий DataFrame под названием_mergeс информацией о источнике каждой строки._mergeимеет тип Categorical и принимает значениеleft_onlyдля наблюдений, чьи ключи объединения присутствуют только в'left'DataFrame или Series,right_onlyдля наблюдений, чьи ключи объединения присутствуют только в'right'DataFrame или Series, иbothесли ключ объединения наблюдения присутствует в обоих.-
validate: строка, по умолчанию None. Если указано, проверяет, является ли слияние указанного типа.“one_to_one” или “1:1”: проверяет, являются ли ключи объединения уникальными в обоих наборах данных.
“one_to_many” или “1:m”: проверяет, являются ли ключи объединения уникальными в левом наборе данных.
“many_to_one” или “m:1”: проверяет, являются ли ключи объединения уникальными в правом наборе данных.
“many_to_many” или “m:m”: разрешено, но не приводит к проверкам.
Примечание
Поддержка указания уровней индекса в качестве on, left_on, и right_on параметров была добавлена в версии 0.23.0. Поддержка объединения именованных Series объектов была добавлена в версии 0.24.0.
Тип возвращаемого значения будет таким же, как у left. Если left является DataFrame или именованным Series и right является подклассом DataFrame, тип возвращаемого значения всё равно будет DataFrame.
merge — функция в пространстве имён pandas, и она также доступна в качестве метода экземпляра DataFrame merge(), где вызываемый DataFrame неявно считается левым объектом в объединении.
Связанный метод join() использует merge в качестве внутренней реализации для объединения по индексу (по умолчанию) и столбцу(ам) по индексу. Если вы объединяете только по индексу, вы можете использовать DataFrame.join для экономии времени.
Краткий обзор методов объединения (реляционная алгебра)
Опытные пользователи реляционных баз данных, таких как SQL, будут знакомы с терминологией, используемой для описания операций объединения между двумя структурами, похожими на SQL-таблицы (DataFrame объекты). Существует несколько важных случаев, которые необходимо понять:
объединения один-к-одному: например, при объединении двух
DataFrameобъектов по их индексам (которые должны содержать уникальные значения).объединения многие-к-одному: например, при объединении индекса (уникального) с одним или несколькими столбцами в другом
DataFrame.объединения многие-ко-многим: объединение столбцов со столбцами.
Примечание
При объединении столбцов со столбцами (вероятно, объединение многие-ко-многим), все индексы на переданных DataFrame объектах будут удалены.
Стоит уделить некоторое время пониманию результата случая объединения многие-ко-многим. В SQL / стандартной реляционной алгебре, если комбинация ключей появляется более одного раза в обеих таблицах, результирующая таблица будет иметь декартово произведение связанных данных. Вот очень простой пример с одной уникальной комбинацией ключей:
In [33]: left = pd.DataFrame(
....: {
....: "key": ["K0", "K1", "K2", "K3"],
....: "A": ["A0", "A1", "A2", "A3"],
....: "B": ["B0", "B1", "B2", "B3"],
....: }
....: )
....:
In [34]: right = pd.DataFrame(
....: {
....: "key": ["K0", "K1", "K2", "K3"],
....: "C": ["C0", "C1", "C2", "C3"],
....: "D": ["D0", "D1", "D2", "D3"],
....: }
....: )
....:
In [35]: result = pd.merge(left, right, on="key")
Вот более сложный пример с несколькими ключами объединения. Присутствуют только ключи, появляющиеся в left и right (пересечение), так как how='inner' по умолчанию.
In [36]: left = pd.DataFrame(
....: {
....: "key1": ["K0", "K0", "K1", "K2"],
....: "key2": ["K0", "K1", "K0", "K1"],
....: "A": ["A0", "A1", "A2", "A3"],
....: "B": ["B0", "B1", "B2", "B3"],
....: }
....: )
....:
In [37]: right = pd.DataFrame(
....: {
....: "key1": ["K0", "K1", "K1", "K2"],
....: "key2": ["K0", "K0", "K0", "K0"],
....: "C": ["C0", "C1", "C2", "C3"],
....: "D": ["D0", "D1", "D2", "D3"],
....: }
....: )
....:
In [38]: result = pd.merge(left, right, on=["key1", "key2"])
Аргумент how к merge определяет, как определить, какие ключи должны быть включены в результирующую таблицу. Если комбинация ключей не встречается ни в левой, ни в правой таблицах, значения в объединённой таблице будут NA. Вот краткий обзор опций how и соответствующих им имён в SQL:
Метод объединения | Название SQL-объединения | Описание |
|---|---|---|
|
| Использовать ключи только из левой таблицы |
|
| Использовать ключи только из правой таблицы |
|
| Использовать объединение ключей из обеих таблиц |
|
| Использовать пересечение ключей из обеих таблиц |
|
| Создать декартово произведение строк обеих таблиц |
In [39]: result = pd.merge(left, right, how="left", on=["key1", "key2"])
In [40]: result = pd.merge(left, right, how="right", on=["key1", "key2"])
In [41]: result = pd.merge(left, right, how="outer", on=["key1", "key2"])
In [42]: result = pd.merge(left, right, how="inner", on=["key1", "key2"])
In [43]: result = pd.merge(left, right, how="cross")
Вы можете объединить многоуровневую Series и DataFrame, если имена MultiIndex соответствуют столбцам DataFrame. Преобразуйте Series в DataFrame, используя Series.reset_index(), перед объединением, как показано в следующем примере.
In [44]: df = pd.DataFrame({"Let": ["A", "B", "C"], "Num": [1, 2, 3]})
In [45]: df
Out[45]:
Let Num
0 A 1
1 B 2
2 C 3
In [46]: ser = pd.Series(
....: ["a", "b", "c", "d", "e", "f"],
....: index=pd.MultiIndex.from_arrays(
....: [["A", "B", "C"] * 2, [1, 2, 3, 4, 5, 6]], names=["Let", "Num"]
....: ),
....: )
....:
In [47]: ser
Out[47]:
Let Num
A 1 a
B 2 b
C 3 c
A 4 d
B 5 e
C 6 f
dtype: object
In [48]: pd.merge(df, ser.reset_index(), on=["Let", "Num"])
Out[48]:
Let Num 0
0 A 1 a
1 B 2 b
2 C 3 c
Вот ещё один пример с дублирующими ключами объединения в DataFrame:
In [49]: left = pd.DataFrame({"A": [1, 2], "B": [2, 2]})
In [50]: right = pd.DataFrame({"A": [4, 5, 6], "B": [2, 2, 2]})
In [51]: result = pd.merge(left, right, on="B", how="outer")
Предупреждение
Объединение/слияние по дублирующим ключам может привести к возврату фрейма, представляющего собой произведение размеров строк, что может привести к переполнению памяти. Пользователь несёт ответственность за управление дублирующими значениями в ключах перед объединением больших DataFrame.
Проверка на дубликаты ключей
Пользователи могут использовать аргумент validate для автоматической проверки наличия неожиданных дубликатов в ключах слияния. Уникальность ключей проверяется перед операциями слияния, и это должно защищать от переполнения памяти. Проверка уникальности ключей также является хорошим способом убедиться, что структуры данных пользователя соответствуют ожиданиям.
В следующем примере есть дублирующие значения B в правой DataFrame. Поскольку это не слияние один-к-одному — как указано в аргументе validate — будет возбуждено исключение.
In [52]: left = pd.DataFrame({"A": [1, 2], "B": [1, 2]})
In [53]: right = pd.DataFrame({"A": [4, 5, 6], "B": [2, 2, 2]})
In [53]: result = pd.merge(left, right, on="B", how="outer", validate="one_to_one")
...
MergeError: Merge keys are not unique in right dataset; not a one-to-one merge
Если пользователь осведомлён о дубликатах в правой DataFrame, но хочет убедиться, что в левой DataFrame нет дубликатов, можно использовать аргумент validate='one_to_many', который не вызовет исключение.
In [54]: pd.merge(left, right, on="B", how="outer", validate="one_to_many")
Out[54]:
A_x B A_y
0 1 1 NaN
1 2 2 4.0
2 2 2 5.0
3 2 2 6.0
Индикатор слияния
merge() принимает аргумент indicator. Если True, в выходной объект будет добавлен столбец типа Categorical с именем _merge, который принимает значения:
Источник наблюдения
Значение
_mergeКлюч слияния только в
'left'фрейме
left_onlyКлюч слияния только в
'right'фрейме
right_onlyКлюч слияния в обоих фреймах
both
In [55]: df1 = pd.DataFrame({"col1": [0, 1], "col_left": ["a", "b"]})
In [56]: df2 = pd.DataFrame({"col1": [1, 2, 2], "col_right": [2, 2, 2]})
In [57]: pd.merge(df1, df2, on="col1", how="outer", indicator=True)
Out[57]:
col1 col_left col_right _merge
0 0 a NaN left_only
1 1 b 2.0 both
2 2 NaN 2.0 right_only
3 2 NaN 2.0 right_only
Аргумент indicator также может принимать строковые аргументы, в этом случае функция индикатора будет использовать значение переданной строки в качестве имени столбца-индикатора.
In [58]: pd.merge(df1, df2, on="col1", how="outer", indicator="indicator_column")
Out[58]:
col1 col_left col_right indicator_column
0 0 a NaN left_only
1 1 b 2.0 both
2 2 NaN 2.0 right_only
3 2 NaN 2.0 right_only
Типы данных слияния
Слияние сохранит тип данных ключей объединения.
In [59]: left = pd.DataFrame({"key": [1], "v1": [10]})
In [60]: left
Out[60]:
key v1
0 1 10
In [61]: right = pd.DataFrame({"key": [1, 2], "v1": [20, 30]})
In [62]: right
Out[62]:
key v1
0 1 20
1 2 30
Мы можем сохранить ключи объединения:
In [63]: pd.merge(left, right, how="outer")
Out[63]:
key v1
0 1 10
1 1 20
2 2 30
In [64]: pd.merge(left, right, how="outer").dtypes
Out[64]:
key int64
v1 int64
dtype: object
Конечно, если введены пропущенные значения, то результирующий тип данных будет повышен.
In [65]: pd.merge(left, right, how="outer", on="key")
Out[65]:
key v1_x v1_y
0 1 10.0 20
1 2 NaN 30
In [66]: pd.merge(left, right, how="outer", on="key").dtypes
Out[66]:
key int64
v1_x float64
v1_y int64
dtype: object
Слияние сохранит типы данных category объектов слияния. Также см. раздел о категориальных типах данных.
Левый фрейм.
In [67]: from pandas.api.types import CategoricalDtype
In [68]: X = pd.Series(np.random.choice(["foo", "bar"], size=(10,)))
In [69]: X = X.astype(CategoricalDtype(categories=["foo", "bar"]))
In [70]: left = pd.DataFrame(
....: {"X": X, "Y": np.random.choice(["one", "two", "three"], size=(10,))}
....: )
....:
In [71]: left
Out[71]:
X Y
0 bar one
1 foo one
2 foo three
3 bar three
4 foo one
5 bar one
6 bar three
7 bar three
8 bar three
9 foo three
In [72]: left.dtypes
Out[72]:
X category
Y object
dtype: object
Правый фрейм.
In [73]: right = pd.DataFrame(
....: {
....: "X": pd.Series(["foo", "bar"], dtype=CategoricalDtype(["foo", "bar"])),
....: "Z": [1, 2],
....: }
....: )
....:
In [74]: right
Out[74]:
X Z
0 foo 1
1 bar 2
In [75]: right.dtypes
Out[75]:
X category
Z int64
dtype: object
Результат слияния:
In [76]: result = pd.merge(left, right, how="outer")
In [77]: result
Out[77]:
X Y Z
0 bar one 2
1 bar three 2
2 bar one 2
3 bar three 2
4 bar three 2
5 bar three 2
6 foo one 1
7 foo three 1
8 foo one 1
9 foo three 1
In [78]: result.dtypes
Out[78]:
X category
Y object
Z int64
dtype: object
Примечание
Категориальные типы данных должны быть точно одинаковыми, то есть с одинаковыми категориями и атрибутом упорядоченности. В противном случае результат будет приведён к типу данных категорий.
Примечание
Слияние по category типам данных, которые одинаковы, может быть достаточно производительным по сравнению со слиянием object типов данных.
Объединение по индексу
DataFrame.join() — удобный метод для объединения столбцов двух потенциально имеющих разные индексы DataFrames в один результирующий DataFrame. Вот очень простой пример:
In [79]: left = pd.DataFrame(
....: {"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]}, index=["K0", "K1", "K2"]
....: )
....:
In [80]: right = pd.DataFrame(
....: {"C": ["C0", "C2", "C3"], "D": ["D0", "D2", "D3"]}, index=["K0", "K2", "K3"]
....: )
....:
In [81]: result = left.join(right)
In [82]: result = left.join(right, how="outer")
То же самое, что выше, но с how='inner'.
In [83]: result = left.join(right, how="inner")
Выравнивание данных здесь происходит по индексам (меткам строк). Этот же результат можно получить с помощью merge с дополнительными аргументами, которые указывают на использование индексов:
In [84]: result = pd.merge(left, right, left_index=True, right_index=True, how="outer")
In [85]: result = pd.merge(left, right, left_index=True, right_index=True, how="inner")
Объединение столбцов ключей по индексу
join() принимает необязательный аргумент on, который может быть именем столбца или несколькими именами столбцов, указывающий на то, что переданный DataFrame должен быть выровнен по этому столбцу в DataFrame. Эти два вызова функций полностью эквивалентны:
left.join(right, on=key_or_keys)
pd.merge(
left, right, left_on=key_or_keys, right_index=True, how="left", sort=False
)
Очевидно, вы можете выбрать любую форму, которая вам кажется удобнее. Для объединений один-ко-многим (где один из DataFrame уже индексирован по ключу объединения), использование join может быть удобнее. Вот простой пример:
In [86]: left = pd.DataFrame(
....: {
....: "A": ["A0", "A1", "A2", "A3"],
....: "B": ["B0", "B1", "B2", "B3"],
....: "key": ["K0", "K1", "K0", "K1"],
....: }
....: )
....:
In [87]: right = pd.DataFrame({"C": ["C0", "C1"], "D": ["D0", "D1"]}, index=["K0", "K1"])
In [88]: result = left.join(right, on="key")
In [89]: result = pd.merge(
....: left, right, left_on="key", right_index=True, how="left", sort=False
....: )
....:
Для объединения по нескольким ключам переданная DataFrame должна иметь MultiIndex:
In [90]: left = pd.DataFrame(
....: {
....: "A": ["A0", "A1", "A2", "A3"],
....: "B": ["B0", "B1", "B2", "B3"],
....: "key1": ["K0", "K0", "K1", "K2"],
....: "key2": ["K0", "K1", "K0", "K1"],
....: }
....: )
....:
In [91]: index = pd.MultiIndex.from_tuples(
....: [("K0", "K0"), ("K1", "K0"), ("K2", "K0"), ("K2", "K1")]
....: )
....:
In [92]: right = pd.DataFrame(
....: {"C": ["C0", "C1", "C2", "C3"], "D": ["D0", "D1", "D2", "D3"]}, index=index
....: )
....:
Теперь это можно объединить, передав имена двух столбцов ключей:
In [93]: result = left.join(right, on=["key1", "key2"])
По умолчанию DataFrame.join выполняет левое объединение (по существу операция «VLOOKUP» для пользователей Excel), которое использует только ключи, найденные в вызываемой DataFrame. Другие типы объединений, например внутреннее объединение, можно также легко выполнить:
In [94]: result = left.join(right, on=["key1", "key2"], how="inner")
Как видите, при этом удаляются любые строки, для которых не было совпадений.
Объединение одноуровневого индекса с многоуровневым индексом
Вы можете объединить DataFrame с одноуровневым индексом с уровнем многоуровневого индекса DataFrame. Уровень будет соответствовать имени индекса одноуровневого фрейма имени уровня многоуровневого фрейма.
In [95]: left = pd.DataFrame(
....: {"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]},
....: index=pd.Index(["K0", "K1", "K2"], name="key"),
....: )
....:
In [96]: index = pd.MultiIndex.from_tuples(
....: [("K0", "Y0"), ("K1", "Y1"), ("K2", "Y2"), ("K2", "Y3")],
....: names=["key", "Y"],
....: )
....:
In [97]: right = pd.DataFrame(
....: {"C": ["C0", "C1", "C2", "C3"], "D": ["D0", "D1", "D2", "D3"]},
....: index=index,
....: )
....:
In [98]: result = left.join(right, how="inner")
Это эквивалентно, но менее громоздко и более эффективно с точки зрения памяти/быстрее, чем это.
In [99]: result = pd.merge(
....: left.reset_index(), right.reset_index(), on=["key"], how="inner"
....: ).set_index(["key","Y"])
....:
Объединение с двумя многоуровневыми индексами
Это поддерживается в ограниченной степени, при условии, что индекс правого аргумента полностью используется в объединении и является подмножеством индексов в левом аргументе, как в этом примере:
In [100]: leftindex = pd.MultiIndex.from_product(
.....: [list("abc"), list("xy"), [1, 2]], names=["abc", "xy", "num"]
.....: )
.....:
In [101]: left = pd.DataFrame({"v1": range(12)}, index=leftindex)
In [102]: left
Out[102]:
v1
abc xy num
a x 1 0
2 1
y 1 2
2 3
b x 1 4
2 5
y 1 6
2 7
c x 1 8
2 9
y 1 10
2 11
In [103]: rightindex = pd.MultiIndex.from_product(
.....: [list("abc"), list("xy")], names=["abc", "xy"]
.....: )
.....:
In [104]: right = pd.DataFrame({"v2": [100 * i for i in range(1, 7)]}, index=rightindex)
In [105]: right
Out[105]:
v2
abc xy
a x 100
y 200
b x 300
y 400
c x 500
y 600
In [106]: left.join(right, on=["abc", "xy"], how="inner")
Out[106]:
v1 v2
abc xy num
a x 1 0 100
2 1 100
y 1 2 200
2 3 200
b x 1 4 300
2 5 300
y 1 6 400
2 7 400
c x 1 8 500
2 9 500
y 1 10 600
2 11 600
Если это условие не выполняется, объединение с двумя многоуровневыми индексами можно выполнить с помощью следующего кода.
In [107]: leftindex = pd.MultiIndex.from_tuples(
.....: [("K0", "X0"), ("K0", "X1"), ("K1", "X2")], names=["key", "X"]
.....: )
.....:
In [108]: left = pd.DataFrame(
.....: {"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]}, index=leftindex
.....: )
.....:
In [109]: rightindex = pd.MultiIndex.from_tuples(
.....: [("K0", "Y0"), ("K1", "Y1"), ("K2", "Y2"), ("K2", "Y3")], names=["key", "Y"]
.....: )
.....:
In [110]: right = pd.DataFrame(
.....: {"C": ["C0", "C1", "C2", "C3"], "D": ["D0", "D1", "D2", "D3"]}, index=rightindex
.....: )
.....:
In [111]: result = pd.merge(
.....: left.reset_index(), right.reset_index(), on=["key"], how="inner"
.....: ).set_index(["key", "X", "Y"])
.....:
Объединение по комбинации столбцов и уровней индекса
Строки, переданные в качестве параметров on, left_on, и right_on, могут ссылаться либо на имена столбцов, либо на имена уровней индекса. Это позволяет объединять DataFrame по комбинации уровней индекса и столбцов без сброса индексов.
In [112]: left_index = pd.Index(["K0", "K0", "K1", "K2"], name="key1")
In [113]: left = pd.DataFrame(
.....: {
.....: "A": ["A0", "A1", "A2", "A3"],
.....: "B": ["B0", "B1", "B2", "B3"],
.....: "key2": ["K0", "K1", "K0", "K1"],
.....: },
.....: index=left_index,
.....: )
.....:
In [114]: right_index = pd.Index(["K0", "K1", "K2", "K2"], name="key1")
In [115]: right = pd.DataFrame(
.....: {
.....: "C": ["C0", "C1", "C2", "C3"],
.....: "D": ["D0", "D1", "D2", "D3"],
.....: "key2": ["K0", "K0", "K0", "K1"],
.....: },
.....: index=right_index,
.....: )
.....:
In [116]: result = left.merge(right, on=["key1", "key2"])
Примечание
При объединении DataFrame по строке, которая соответствует уровню индекса в обоих фреймах, уровень индекса сохраняется как уровень индекса в результирующей DataFrame.
Примечание
При объединении DataFrame, используя только некоторые уровни MultiIndex, дополнительные уровни будут удалены из результирующего объединения. Для сохранения этих уровней используйте reset_index для этих имён уровней, чтобы перенести эти уровни в столбцы перед выполнением объединения.
Примечание
Если строка совпадает как с именем столбца, так и с именем уровня индекса, то выдается предупреждение, и столбец имеет приоритет. В будущей версии это приведёт к ошибке неоднозначности.
Перекрывающиеся столбцы значений
Аргумент слияния suffixes принимает кортеж списков строк для добавления к именам перекрывающихся столбцов во входных DataFrame для устранения неоднозначности столбцов результата:
In [117]: left = pd.DataFrame({"k": ["K0", "K1", "K2"], "v": [1, 2, 3]})
In [118]: right = pd.DataFrame({"k": ["K0", "K0", "K3"], "v": [4, 5, 6]})
In [119]: result = pd.merge(left, right, on="k")
In [120]: result = pd.merge(left, right, on="k", suffixes=("_l", "_r"))
DataFrame.join() имеет аргументы lsuffix и rsuffix, которые ведут себя аналогично.
In [121]: left = left.set_index("k")
In [122]: right = right.set_index("k")
In [123]: result = left.join(right, lsuffix="_l", rsuffix="_r")
Объединение нескольких DataFrames
Список или кортеж DataFrames также можно передать в join() для их объединения по индексам.
In [124]: right2 = pd.DataFrame({"v": [7, 8, 9]}, index=["K1", "K1", "K2"])
In [125]: result = left.join([right, right2])
Объединение значений в столбцах Series или DataFrame
Другая довольно распространённая ситуация — это наличие двух объектов Series или DataFrame с одинаковыми (или похожими) индексами, и необходимость «заменить» значения одного объекта значениями другого объекта для совпадающих индексов. Вот пример:
In [126]: df1 = pd.DataFrame(
.....: [[np.nan, 3.0, 5.0], [-4.6, np.nan, np.nan], [np.nan, 7.0, np.nan]]
.....: )
.....:
In [127]: df2 = pd.DataFrame([[-42.6, np.nan, -8.2], [-5.0, 1.6, 4]], index=[1, 2])
Для этого используйте метод combine_first():
In [128]: result = df1.combine_first(df2)
Обратите внимание, что этот метод берёт значения из правого DataFrame только в том случае, если они отсутствуют в левом DataFrame. Похожий метод, update(), изменяет значения, отличные от NaN, на месте:
In [129]: df1.update(df2)
Дружественное к временным рядам объединение
Объединение упорядоченных данных
Функция merge_ordered() позволяет объединять временные ряды и другие упорядоченные данные. В частности, она имеет необязательный параметр fill_method для заполнения/интерполяции пропущенных данных:
In [130]: left = pd.DataFrame(
.....: {"k": ["K0", "K1", "K1", "K2"], "lv": [1, 2, 3, 4], "s": ["a", "b", "c", "d"]}
.....: )
.....:
In [131]: right = pd.DataFrame({"k": ["K1", "K2", "K4"], "rv": [1, 2, 3]})
In [132]: pd.merge_ordered(left, right, fill_method="ffill", left_by="s")
Out[132]:
k lv s rv
0 K0 1.0 a NaN
1 K1 1.0 a 1.0
2 K2 1.0 a 2.0
3 K4 1.0 a 3.0
4 K1 2.0 b 1.0
5 K2 2.0 b 2.0
6 K4 2.0 b 3.0
7 K1 3.0 c 1.0
8 K2 3.0 c 2.0
9 K4 3.0 c 3.0
10 K1 NaN d 1.0
11 K2 4.0 d 2.0
12 K4 4.0 d 3.0
Объединение asof
Функция merge_asof() похожа на левое объединение, но вместо точного соответствия ключей используется ближайшее соответствие. Для каждой строки в left DataFrame, выбирается последняя строка в right DataFrame, у которой ключ on меньше ключа левой строки. Оба DataFrame должны быть отсортированы по ключу.
Необязательно, объединение asof может выполнять групповое объединение. Это подразумевает соответствие по ключу by и, дополнительно, ближайшее соответствие по ключу on.
Например; у нас могут быть trades и quotes, и мы хотим asof их объединить.
In [133]: trades = pd.DataFrame(
.....: {
.....: "time": pd.to_datetime(
.....: [
.....: "20160525 13:30:00.023",
.....: "20160525 13:30:00.038",
.....: "20160525 13:30:00.048",
.....: "20160525 13:30:00.048",
.....: "20160525 13:30:00.048",
.....: ]
.....: ),
.....: "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
.....: "price": [51.95, 51.95, 720.77, 720.92, 98.00],
.....: "quantity": [75, 155, 100, 100, 100],
.....: },
.....: columns=["time", "ticker", "price", "quantity"],
.....: )
.....:
In [134]: quotes = pd.DataFrame(
.....: {
.....: "time": pd.to_datetime(
.....: [
.....: "20160525 13:30:00.023",
.....: "20160525 13:30:00.023",
.....: "20160525 13:30:00.030",
.....: "20160525 13:30:00.041",
.....: "20160525 13:30:00.048",
.....: "20160525 13:30:00.049",
.....: "20160525 13:30:00.072",
.....: "20160525 13:30:00.075",
.....: ]
.....: ),
.....: "ticker": ["GOOG", "MSFT", "MSFT", "MSFT", "GOOG", "AAPL", "GOOG", "MSFT"],
.....: "bid": [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01],
.....: "ask": [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03],
.....: },
.....: columns=["time", "ticker", "bid", "ask"],
.....: )
.....:
In [135]: trades
Out[135]:
time ticker price quantity
0 2016-05-25 13:30:00.023 MSFT 51.95 75
1 2016-05-25 13:30:00.038 MSFT 51.95 155
2 2016-05-25 13:30:00.048 GOOG 720.77 100
3 2016-05-25 13:30:00.048 GOOG 720.92 100
4 2016-05-25 13:30:00.048 AAPL 98.00 100
In [136]: quotes
Out[136]:
time ticker bid ask
0 2016-05-25 13:30:00.023 GOOG 720.50 720.93
1 2016-05-25 13:30:00.023 MSFT 51.95 51.96
2 2016-05-25 13:30:00.030 MSFT 51.97 51.98
3 2016-05-25 13:30:00.041 MSFT 51.99 52.00
4 2016-05-25 13:30:00.048 GOOG 720.50 720.93
5 2016-05-25 13:30:00.049 AAPL 97.99 98.01
6 2016-05-25 13:30:00.072 GOOG 720.50 720.88
7 2016-05-25 13:30:00.075 MSFT 52.01 52.03
По умолчанию мы выполняем asof для котировок.
In [137]: pd.merge_asof(trades, quotes, on="time", by="ticker")
Out[137]:
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
Мы выполняем asof только в 2ms между временем котировки и временем сделки.
In [138]: pd.merge_asof(trades, quotes, on="time", by="ticker", tolerance=pd.Timedelta("2ms"))
Out[138]:
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
1 2016-05-25 13:30:00.038 MSFT 51.95 155 NaN NaN
2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
Мы выполняем asof только в 10ms между временем котировки и временем сделки и исключаем точное соответствие по времени. Обратите внимание, что, хотя мы исключаем точные совпадения (котировок), предыдущие котировки будут распространяться до этого момента во времени.
In [139]: pd.merge_asof(
.....: trades,
.....: quotes,
.....: on="time",
.....: by="ticker",
.....: tolerance=pd.Timedelta("10ms"),
.....: allow_exact_matches=False,
.....: )
.....:
Out[139]:
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 NaN NaN
1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
2 2016-05-25 13:30:00.048 GOOG 720.77 100 NaN NaN
3 2016-05-25 13:30:00.048 GOOG 720.92 100 NaN NaN
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
Сравнение объектов
Методы compare() и compare() позволяют сравнить два DataFrame или Series соответственно и обобщить их различия.
Эта функция была добавлена в V1.1.0.
Например, вы можете сравнить два DataFrame и разместить их различия бок о бок.
In [140]: df = pd.DataFrame(
.....: {
.....: "col1": ["a", "a", "b", "b", "a"],
.....: "col2": [1.0, 2.0, 3.0, np.nan, 5.0],
.....: "col3": [1.0, 2.0, 3.0, 4.0, 5.0],
.....: },
.....: columns=["col1", "col2", "col3"],
.....: )
.....:
In [141]: df
Out[141]:
col1 col2 col3
0 a 1.0 1.0
1 a 2.0 2.0
2 b 3.0 3.0
3 b NaN 4.0
4 a 5.0 5.0
In [142]: df2 = df.copy()
In [143]: df2.loc[0, "col1"] = "c"
In [144]: df2.loc[2, "col3"] = 4.0
In [145]: df2
Out[145]:
col1 col2 col3
0 c 1.0 1.0
1 a 2.0 2.0
2 b 3.0 4.0
3 b NaN 4.0
4 a 5.0 5.0
In [146]: df.compare(df2)
Out[146]:
col1 col3
self other self other
0 a c NaN NaN
2 NaN NaN 3.0 4.0
По умолчанию, если два соответствующих значения равны, они будут показаны как NaN. Кроме того, если все значения в строке/столбце равны, строка/столбец будут опущены из результата. Остальные различия будут выровнены по столбцам.
Если нужно, вы можете выбрать вывод различий по строкам.
In [147]: df.compare(df2, align_axis=0)
Out[147]:
col1 col3
0 self a NaN
other c NaN
2 self NaN 3.0
other NaN 4.0
Если нужно сохранить все исходные строки и столбцы, установите параметр keep_shape в True.
In [148]: df.compare(df2, keep_shape=True)
Out[148]:
col1 col2 col3
self other self other self other
0 a c NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN 3.0 4.0
3 NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN
Вы также можете сохранить все исходные значения, даже если они равны.
In [149]: df.compare(df2, keep_shape=True, keep_equal=True)
Out[149]:
col1 col2 col3
self other self other self other
0 a c 1.0 1.0 1.0 1.0
1 a a 2.0 2.0 2.0 2.0
2 b b 3.0 3.0 3.0 4.0
3 b b NaN NaN 4.0 4.0
4 a a 5.0 5.0 5.0 5.0
© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/1.5.0/user_guide/merging.html