Spec-Zone.ru › pandas 1

Объединение, присоединение, конкатенация и сравнение

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)
../_images/merging_concat_basic.png

Как и её функция-аналог для 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"])
../_images/merging_concat_keys.png

Как вы можете видеть (если вы прочитали остальную часть документации), индекс результирующего объекта имеет многоуровневый индекс. Это означает, что теперь мы можем выбрать каждую часть по ключу:

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)
../_images/merging_concat_axis1.png

Вот то же самое с join='inner':

In [10]: result = pd.concat([df1, df4], axis=1, join="inner")
../_images/merging_concat_axis1_inner.png

Наконец, предположим, что мы просто хотели повторно использовать точный индекс из исходного 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
../_images/merging_concat_axis1_join_axes.png

Игнорирование индексов на оси конкатенации

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

In [13]: result = pd.concat([df1, df4], ignore_index=True, sort=False)
../_images/merging_concat_ignore_index.png

Конкатенация с смешанными 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)
../_images/merging_concat_mixed_ndim.png

Примечание

Поскольку мы конкатенируем 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)
../_images/merging_concat_unnamed_series.png

Передача ignore_index=True удалит все ссылки на имена.

In [18]: result = pd.concat([df1, s1], axis=1, ignore_index=True)
../_images/merging_concat_series_ignore_index.png

Дополнительная конкатенация с групповыми ключами

Довольно распространенным применением аргумента 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"])
../_images/merging_concat_group_keys2.png

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

In [25]: pieces = {"x": df1, "y": df2, "z": df3}

In [26]: result = pd.concat(pieces)
../_images/merging_concat_dict.png
In [27]: result = pd.concat(pieces, keys=["z", "y"])
../_images/merging_concat_dict_keys.png

Созданный 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"]
   ....: )
   ....: 
../_images/merging_concat_dict_keys_names.png
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)
../_images/merging_append_series_as_row.png

Вы должны использовать 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")
../_images/merging_merge_on_key.png

Вот более сложный пример с несколькими ключами объединения. Присутствуют только ключи, появляющиеся в 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"])
../_images/merging_merge_on_key_multiple.png

Аргумент how к merge определяет, как определить, какие ключи должны быть включены в результирующую таблицу. Если комбинация ключей не встречается ни в левой, ни в правой таблицах, значения в объединённой таблице будут NA. Вот краткий обзор опций how и соответствующих им имён в SQL:

Метод объединения

Название SQL-объединения

Описание

left

LEFT OUTER JOIN

Использовать ключи только из левой таблицы

right

RIGHT OUTER JOIN

Использовать ключи только из правой таблицы

outer

FULL OUTER JOIN

Использовать объединение ключей из обеих таблиц

inner

INNER JOIN

Использовать пересечение ключей из обеих таблиц

cross

CROSS JOIN

Создать декартово произведение строк обеих таблиц

In [39]: result = pd.merge(left, right, how="left", on=["key1", "key2"])
../_images/merging_merge_on_key_left.png
In [40]: result = pd.merge(left, right, how="right", on=["key1", "key2"])
../_images/merging_merge_on_key_right.png
In [41]: result = pd.merge(left, right, how="outer", on=["key1", "key2"])
../_images/merging_merge_on_key_outer.png
In [42]: result = pd.merge(left, right, how="inner", on=["key1", "key2"])
../_images/merging_merge_on_key_inner.png
In [43]: result = pd.merge(left, right, how="cross")
../_images/merging_merge_cross.png

Вы можете объединить многоуровневую 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")
../_images/merging_merge_on_key_dup.png

Предупреждение

Объединение/слияние по дублирующим ключам может привести к возврату фрейма, представляющего собой произведение размеров строк, что может привести к переполнению памяти. Пользователь несёт ответственность за управление дублирующими значениями в ключах перед объединением больших 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)
../_images/merging_join.png
In [82]: result = left.join(right, how="outer")
../_images/merging_join_outer.png

То же самое, что выше, но с how='inner'.

In [83]: result = left.join(right, how="inner")
../_images/merging_join_inner.png

Выравнивание данных здесь происходит по индексам (меткам строк). Этот же результат можно получить с помощью merge с дополнительными аргументами, которые указывают на использование индексов:

In [84]: result = pd.merge(left, right, left_index=True, right_index=True, how="outer")
../_images/merging_merge_index_outer.png
In [85]: result = pd.merge(left, right, left_index=True, right_index=True, how="inner")
../_images/merging_merge_index_inner.png

Объединение столбцов ключей по индексу

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")
../_images/merging_join_key_columns.png
In [89]: result = pd.merge(
   ....:     left, right, left_on="key", right_index=True, how="left", sort=False
   ....: )
   ....: 
../_images/merging_merge_key_columns.png

Для объединения по нескольким ключам переданная 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"])
../_images/merging_join_multikeys.png

По умолчанию DataFrame.join выполняет левое объединение (по существу операция «VLOOKUP» для пользователей Excel), которое использует только ключи, найденные в вызываемой DataFrame. Другие типы объединений, например внутреннее объединение, можно также легко выполнить:

In [94]: result = left.join(right, on=["key1", "key2"], how="inner")
../_images/merging_join_multikeys_inner.png

Как видите, при этом удаляются любые строки, для которых не было совпадений.

Объединение одноуровневого индекса с многоуровневым индексом

Вы можете объединить 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")
../_images/merging_join_multiindex_inner.png

Это эквивалентно, но менее громоздко и более эффективно с точки зрения памяти/быстрее, чем это.

In [99]: result = pd.merge(
   ....:     left.reset_index(), right.reset_index(), on=["key"], how="inner"
   ....: ).set_index(["key","Y"])
   ....: 
../_images/merging_merge_multiindex_alternative.png

Объединение с двумя многоуровневыми индексами

Это поддерживается в ограниченной степени, при условии, что индекс правого аргумента полностью используется в объединении и является подмножеством индексов в левом аргументе, как в этом примере:

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"])
   .....: 
../_images/merging_merge_two_multiindex.png

Объединение по комбинации столбцов и уровней индекса

Строки, переданные в качестве параметров 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"])
../_images/merge_on_index_and_column.png

Примечание

При объединении 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")
../_images/merging_merge_overlapped.png
In [120]: result = pd.merge(left, right, on="k", suffixes=("_l", "_r"))
../_images/merging_merge_overlapped_suffix.png

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")
../_images/merging_merge_overlapped_multi_suffix.png

Объединение нескольких DataFrames

Список или кортеж DataFrames также можно передать в join() для их объединения по индексам.

In [124]: right2 = pd.DataFrame({"v": [7, 8, 9]}, index=["K1", "K1", "K2"])

In [125]: result = left.join([right, right2])
../_images/merging_join_multi_df.png

Объединение значений в столбцах 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)
../_images/merging_combine_first.png

Обратите внимание, что этот метод берёт значения из правого DataFrame только в том случае, если они отсутствуют в левом DataFrame. Похожий метод, update(), изменяет значения, отличные от NaN, на месте:

In [129]: df1.update(df2)
../_images/merging_update.png

Дружественное к временным рядам объединение

Объединение упорядоченных данных

Функция 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API