Панды Слияния 101

366
  • Как выполнить ( LEFT| RIGHT| FULL) ( INNER| OUTER) соединение с пандами?
  • Как добавить NaN для пропущенных строк после слияния?
  • Как избавиться от NaNs после слияния?
  • Можно ли слить по индексу?
  • Крест соединиться с пандами?
  • Как объединить несколько фреймов данных?
  • merge? join? concat? update? ВОЗ? Какая? Почему?!

... и более. Я видел эти повторяющиеся вопросы о различных аспектах функциональности слияния панд. Большая часть информации о слиянии и ее различных вариантах использования сегодня фрагментирована по десяткам плохо сформулированных, неисследуемых сообщений. Цель здесь - собрать некоторые из наиболее важных моментов для потомков.

Предполагается, что эта QnA станет следующей статьей в серии полезных руководств по распространенным идиомам панд (см. Этот пост о поворотах и этот пост о конкатенации , о котором я расскажу позже).

Пожалуйста, обратите внимание, что этот пост не предназначен для замены документации , поэтому, пожалуйста, прочитайте это! Некоторые из примеров взяты оттуда.

cs95
источник

Ответы:

522

Цель этого поста - дать читателям представление о слиянии SQL-кода с пандами, о том, как его использовать и когда его не использовать.

В частности, вот что пройдет этот пост:

  • Основы - типы соединений (ВЛЕВО, ВПРАВО, ВНЕШНИЙ, ВНУТРЕННИЙ)

    • объединение с разными именами столбцов
    • избегая дублирования столбца ключа слияния в выводе
  • Слияние с индексом в разных условиях
    • эффективно используя ваш именованный индекс
    • объединить ключ как индекс одного и столбца другого
  • Многостороннее слияние по столбцам и индексам (уникальное и неуникальное)
  • Известные альтернативы mergeиjoin

Чему этот пост не пройдёт:

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

Примечание. В
большинстве примеров по умолчанию используются операции INNER JOIN при демонстрации различных функций, если не указано иное.

Кроме того, все DataFrames здесь могут быть скопированы и реплицированы, так что вы можете играть с ними. Также смотрите этот пост о том, как читать DataFrames из вашего буфера обмена.

Наконец, все визуальное представление операций JOIN было нарисовано от руки с помощью Google Drawings. Вдохновение отсюда .

Достаточно говорить, просто покажи мне, как использовать merge!

Настроить

np.random.seed(0)
left = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value': np.random.randn(4)})    
right = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value': np.random.randn(4)})

left

  key     value
0   A  1.764052
1   B  0.400157
2   C  0.978738
3   D  2.240893

right

  key     value
0   B  1.867558
1   D -0.977278
2   E  0.950088
3   F -0.151357

Для простоты ключевой столбец имеет то же имя (пока).

Внутреннее соединение представлено

Обратите внимание
, что вместе с предстоящими фигурами все следуют этой конвенции:

  • синий указывает на строки, которые присутствуют в результате слияния
  • красный цвет обозначает строки, которые исключены из результата (то есть удалены)
  • зеленый цвет указывает на отсутствующие значения, которые заменяются на NaNs в результате

Чтобы выполнить INNER JOIN, вызовите mergeлевый DataFrame, указав в качестве аргументов правый DataFrame и ключ соединения (как минимум).

left.merge(right, on='key')
# Or, if you want to be explicit
# left.merge(right, on='key', how='inner')

  key   value_x   value_y
0   B  0.400157  1.867558
1   D  2.240893 -0.977278

Это возвращает только строки из leftи rightкоторые имеют общий ключ (в этом примере, «B» и «D).

LEFT OUTER JOIN или LEFT JOIN представлена

Это можно выполнить, указав how='left'.

left.merge(right, on='key', how='left')

  key   value_x   value_y
0   A  1.764052       NaN
1   B  0.400157  1.867558
2   C  0.978738       NaN
3   D  2.240893 -0.977278

Внимательно обратите внимание на размещение NaNs здесь. Если вы укажете how='left', то leftбудут использоваться только ключи от , а отсутствующие данные rightзаменяются на NaN.

И точно так же для ПРАВИЛЬНОГО ВНЕШНЕГО СОЕДИНЕНИЯ или ПРАВИЛЬНОГО СОЕДИНЕНИЯ, которое ...

... указать how='right':

left.merge(right, on='key', how='right')

  key   value_x   value_y
0   B  0.400157  1.867558
1   D  2.240893 -0.977278
2   E       NaN  0.950088
3   F       NaN -0.151357

Здесь используются ключи from right, а отсутствующие данные leftзаменяются на NaN.

Наконец, для полного внешнего соединения , заданного

уточнять how='outer'.

left.merge(right, on='key', how='outer')

  key   value_x   value_y
0   A  1.764052       NaN
1   B  0.400157  1.867558
2   C  0.978738       NaN
3   D  2.240893 -0.977278
4   E       NaN  0.950088
5   F       NaN -0.151357

При этом используются ключи из обоих кадров, а NaN вставляются для пропущенных строк в обоих.

Документация суммирует эти различные слияния:

введите описание изображения здесь

Другие СОЕДИНЕНИЯ - ЛЕВЫЙ, ПРАВИЛЬНЫЙ, И ПОЛНЫЙ, исключающий / АНТИ-СОЕДИНЕНИЯ

Если вам нужны ЛЕВЫЕ исключающие СОЕДИНЕНИЯ и ПРАВИЛЬНЫЕ СОЕДИНЕНИЯ в два этапа.

Для LEFT-исключая JOIN, представленный как

Начните с выполнения LEFT OUTER JOIN, а затем отфильтруйте (исключая!) Строки, поступающие leftтолько от

(left.merge(right, on='key', how='left', indicator=True)
     .query('_merge == "left_only"')
     .drop('_merge', 1))

  key   value_x  value_y
0   A  1.764052      NaN
2   C  0.978738      NaN

Куда,

left.merge(right, on='key', how='left', indicator=True)

  key   value_x   value_y     _merge
0   A  1.764052       NaN  left_only
1   B  0.400157  1.867558       both
2   C  0.978738       NaN  left_only
3   D  2.240893 -0.977278       both

И аналогично, для ПРАВА, исключающего СОЕДИНЕНИЕ,

(left.merge(right, on='key', how='right', indicator=True)
     .query('_merge == "right_only"')
     .drop('_merge', 1))

  key  value_x   value_y
2   E      NaN  0.950088
3   F      NaN -0.151357

Наконец, если вам необходимо выполнить слияние, при котором ключи сохраняются только слева или справа, но не оба (IOW, выполняя ANTI-JOIN ),

Вы можете сделать это подобным образом -

(left.merge(right, on='key', how='outer', indicator=True)
     .query('_merge != "both"')
     .drop('_merge', 1))

  key   value_x   value_y
0   A  1.764052       NaN
2   C  0.978738       NaN
4   E       NaN  0.950088
5   F       NaN -0.151357

Разные имена для ключевых столбцов

Если ключевые столбцы названы по-разному - например, leftимеет keyLeftи rightимеет keyRightвместо - keyтогда вы должны будете указать left_onи в right_onкачестве аргументов вместо on:

left2 = left.rename({'key':'keyLeft'}, axis=1)
right2 = right.rename({'key':'keyRight'}, axis=1)

left2

  keyLeft     value
0       A  1.764052
1       B  0.400157
2       C  0.978738
3       D  2.240893

right2

  keyRight     value
0        B  1.867558
1        D -0.977278
2        E  0.950088
3        F -0.151357

left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')

  keyLeft   value_x keyRight   value_y
0       B  0.400157        B  1.867558
1       D  2.240893        D -0.977278

Как избежать дублирования ключевого столбца в выводе

При объединении keyLeftиз leftи keyRightиз right, если вы хотите, чтобы в выводе был только один из keyLeftили keyRight(но не оба), вы можете начать с установки индекса в качестве предварительного шага.

left3 = left2.set_index('keyLeft')
left3.merge(right2, left_index=True, right_on='keyRight')

    value_x keyRight   value_y
0  0.400157        B  1.867558
1  2.240893        D -0.977278

Сравните это с выводом команды непосредственно перед (thst, выход left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')), вы заметите, keyLeftчто отсутствует. Вы можете выяснить, какой столбец хранить, основываясь на том, какой индекс фрейма установлен в качестве ключа. Это может иметь значение, например, при выполнении какой-либо операции OUTER JOIN.

Слияние только одного столбца из одного из DataFrames

Например, рассмотрим

right3 = right.assign(newcol=np.arange(len(right)))
right3
  key     value  newcol
0   B  1.867558       0
1   D -0.977278       1
2   E  0.950088       2
3   F -0.151357       3

Если вам необходимо объединить только «new_val» (без каких-либо других столбцов), вы обычно можете просто подмножество столбцов перед объединением:

left.merge(right3[['key', 'newcol']], on='key')

  key     value  newcol
0   B  0.400157       0
1   D  2.240893       1

Если вы делаете LEFT OUTER JOIN, более эффективное решение будет включать map:

# left['newcol'] = left['key'].map(right3.set_index('key')['newcol']))
left.assign(newcol=left['key'].map(right3.set_index('key')['newcol']))

  key     value  newcol
0   A  1.764052     NaN
1   B  0.400157     0.0
2   C  0.978738     NaN
3   D  2.240893     1.0

Как уже упоминалось, это похоже на, но быстрее, чем

left.merge(right3[['key', 'newcol']], on='key', how='left')

  key     value  newcol
0   A  1.764052     NaN
1   B  0.400157     0.0
2   C  0.978738     NaN
3   D  2.240893     1.0

Слияние на нескольких столбцах

Чтобы объединить несколько столбцов, укажите список для on(или left_onи right_on, в зависимости от ситуации).

left.merge(right, on=['key1', 'key2'] ...)

Или, если имена разные,

left.merge(right, left_on=['lkey1', 'lkey2'], right_on=['rkey1', 'rkey2'])

Другие полезные merge*операции и функции

  • Слияние DataFrame с Series в индексе : см. Этот ответ .
  • Кроме того merge, DataFrame.updateи DataFrame.combine_firstтакже используются в некоторых случаях для обновления одного DataFrame с другим.

  • pd.merge_ordered полезная функция для упорядоченных соединений.

  • pd.merge_asof(читай: merge_asOf) полезно для приблизительных соединений.

Этот раздел охватывает только самые основы и предназначен только для разжигания аппетита. Для большего количества примеров и случаев, см документации на merge, joinиconcat так же , как и ссылки на функцию спецификацию.


На основе индекса * -JOIN (+ столбцы индекса merge)

Настроить

np.random.seed([3, 14])
left = pd.DataFrame({'value': np.random.randn(4)}, index=['A', 'B', 'C', 'D'])    
right = pd.DataFrame({'value': np.random.randn(4)}, index=['B', 'D', 'E', 'F'])
left.index.name = right.index.name = 'idxkey'

left
           value
idxkey          
A      -0.602923
B      -0.402655
C       0.302329
D      -0.524349

right

           value
idxkey          
B       0.543843
D       0.013135
E      -0.326498
F       1.385076

Как правило, слияние по индексу будет выглядеть так:

left.merge(right, left_index=True, right_index=True)


         value_x   value_y
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

Поддержка имен индексов

Если ваш индекс называется, то v0.23 пользователи могут также указать имя уровня для on(или left_onи по right_onмере необходимости).

left.merge(right, on='idxkey')

         value_x   value_y
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

Слияние по индексу одного столбца (столбцов) другого

Можно (и довольно просто) использовать индекс одного и столбец другого для выполнения слияния. Например,

left.merge(right, left_on='key1', right_index=True)

Или наоборот ( right_on=...а left_index=True).

right2 = right.reset_index().rename({'idxkey' : 'colkey'}, axis=1)
right2

  colkey     value
0      B  0.543843
1      D  0.013135
2      E -0.326498
3      F  1.385076

left.merge(right2, left_index=True, right_on='colkey')

    value_x colkey   value_y
0 -0.402655      B  0.543843
1 -0.524349      D  0.013135

В этом особом случае указывается имя для индекса left, поэтому вы также можете использовать имя индекса с помощью left_on, например:

left.merge(right2, left_on='idxkey', right_on='colkey')

    value_x colkey   value_y
0 -0.402655      B  0.543843
1 -0.524349      D  0.013135

DataFrame.join
Помимо этого, есть еще один лаконичный вариант. Вы можете использовать, DataFrame.joinкакие по умолчанию присоединения в индексе. DataFrame.joinделает LEFT OUTER JOIN по умолчанию, поэтому how='inner'здесь необходимо.

left.join(right, how='inner', lsuffix='_x', rsuffix='_y')

         value_x   value_y
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

Обратите внимание , что мне нужно было указать lsuffixи rsuffixаргументы , так как в joinпротивном случае ошибки будет из:

left.join(right)
ValueError: columns overlap but no suffix specified: Index(['value'], dtype='object')

Так как имена столбцов совпадают. Это не было бы проблемой, если бы они назывались по-другому.

left.rename(columns={'value':'leftvalue'}).join(right, how='inner')

        leftvalue     value
idxkey                     
B       -0.402655  0.543843
D       -0.524349  0.013135

pd.concat
Наконец, в качестве альтернативы для соединений на основе индекса вы можете использовать pd.concat:

pd.concat([left, right], axis=1, sort=False, join='inner')

           value     value
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

Опустите, join='inner'если вам нужно FULL OUTER JOIN (по умолчанию):

pd.concat([left, right], axis=1, sort=False)

      value     value
A -0.602923       NaN
B -0.402655  0.543843
C  0.302329       NaN
D -0.524349  0.013135
E       NaN -0.326498
F       NaN  1.385076

Для получения дополнительной информации см. Этот канонический пост pd.concat@piRSquared .


Обобщение: mergeобъединение нескольких фреймов данных

Часто возникает ситуация, когда несколько DataFrames должны быть объединены вместе. Наивно это можно сделать цепочкой mergeвызовов:

df1.merge(df2, ...).merge(df3, ...)

Тем не менее, это быстро выходит из-под контроля для многих DataFrames. Кроме того, может потребоваться обобщение для неизвестного числа фреймов данных.

Здесь я расскажу pd.concatо многоходовых соединениях с уникальными ключами и DataFrame.joinо многоходовых соединениях с неуникальными ключами. Сначала настройка.

# Setup.
np.random.seed(0)
A = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'valueA': np.random.randn(4)})    
B = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'valueB': np.random.randn(4)})
C = pd.DataFrame({'key': ['D', 'E', 'J', 'C'], 'valueC': np.ones(4)})
dfs = [A, B, C] 

# Note, the "key" column values are unique, so the index is unique.
A2 = A.set_index('key')
B2 = B.set_index('key')
C2 = C.set_index('key')

dfs2 = [A2, B2, C2]

Многостороннее слияние по уникальным ключам (или индексам)

Если ваши ключи (здесь ключ может быть столбцом или индексом) уникальны, то вы можете использовать pd.concat. Обратите внимание, что pd.concatприсоединяет DataFrames к индексу .

# merge on `key` column, you'll need to set the index before concatenating
pd.concat([
    df.set_index('key') for df in dfs], axis=1, join='inner'
).reset_index()

  key    valueA    valueB  valueC
0   D  2.240893 -0.977278     1.0

# merge on `key` index
pd.concat(dfs2, axis=1, sort=False, join='inner')

       valueA    valueB  valueC
key                            
D    2.240893 -0.977278     1.0

Пропустить join='inner'для полного внешнего соединения. Обратите внимание, что вы не можете указать соединения LEFT или RIGHT OUTER (если они вам нужны, используйте join, как описано ниже).

Многоходовое слияние ключей с дубликатами

concatбыстрый, но имеет свои недостатки. Он не может обрабатывать дубликаты.

A3 = pd.DataFrame({'key': ['A', 'B', 'C', 'D', 'D'], 'valueA': np.random.randn(5)})

pd.concat([df.set_index('key') for df in [A3, B, C]], axis=1, join='inner')
ValueError: Shape of passed values is (3, 4), indices imply (3, 2)

В этой ситуации мы можем использовать, joinтак как он может обрабатывать неуникальные ключи (обратите внимание, что joinприсоединяет DataFrames к их индексу; он вызывает изнутри mergeи выполняет LEFT OUTER JOIN, если не указано иное).

# join on `key` column, set as the index first
# For inner join. For left join, omit the "how" argument.
A.set_index('key').join(
    [df.set_index('key') for df in (B, C)], how='inner').reset_index()

  key    valueA    valueB  valueC
0   D  2.240893 -0.977278     1.0

# join on `key` index
A3.set_index('key').join([B2, C2], how='inner')

       valueA    valueB  valueC
key                            
D    1.454274 -0.977278     1.0
D    0.761038 -0.977278     1.0
cs95
источник
49

Дополнительный визуальный обзор pd.concat([df0, df1], kwargs). Обратите внимание, что значение kwarg axis=0or axis=1не столь интуитивно, как df.mean()илиdf.apply(func)


на pd.concat ([df0, df1])

Eliu
источник
9
Это хорошая диаграмма. Могу я спросить, как ты это сделал?
cs95
6
встроенный в Google документ "вставить ==> рисунок ... ==> новый" (по состоянию на 2019-май). Но, чтобы быть ясным: единственная причина, по которой я использовал Google Doc для этой фотографии, заключается в том, что мои заметки хранятся в Google DOC, и я хотел бы, чтобы изображение можно было быстро изменить в самом Google DOC. На самом деле теперь вы упомянули об этом, инструмент для рисования Google Doc довольно аккуратно.
Eliu
Вау, это здорово. Исходя из мира SQL, «вертикальное» соединение не является объединением в моей голове, так как структура таблицы всегда фиксирована. Теперь даже думать панды должны консолидировать concatи mergeс параметром направления является horizontalили vertical.
Ufos
2
@ Ufos Разве это не то, что axis=1и axis=0есть?
cs95
2
да, там уже в настоящее время mergeи concatи ось и все. Однако, как показывает @eliu, это все та же концепция слияния с «левым» и «правым» и «горизонтальным» или «вертикальным». Лично я должен заглядывать в документацию каждый раз, когда мне нужно вспомнить, какая "ось" есть, 0а какая есть 1.
Ufos