Dataframes: multi-índices e concatenção

Índices Hierárquicos

É possível criar series e dataframes com índices e subíndices. Esse processo de indexação hierárquica é importante para a reformatação (reshaping ), formação de tabelas pivot e outras operações de agrupamento de dados.

» import pandas as pd
» import numpy as np

» # formamos uma series com índices duplos 
» sr = pd.Series([11, 12, 21, 22, 23, 31, 32, 41, 42],
                 index=[['A', 'A', 'B', 'B', 'B', 'C', 'C', 'D', 'D'],
                 [1, 2, 1, 2, 3, 1, 2, 1, 2]])
» sr
↳ A  1    11
     2    12
  B  1    21
     2    22
     3    23
  C  1    31
     2    32
  D  1    41
     2    42

» # essa series possui índices
» sr.index
↳ MultiIndex([('A', 1), ('A', 2),
              ('B', 1), ('B', 2), ('B', 3),
              ('C', 1), ('C', 2),
              ('D', 1), ('D', 2)],)

» # da mesma forma podemos transformar essa series um um dataframe
» df = pd.DataFrame(sr)

» # Os índices do dataframe são os mesmos: df.index

» # o índice B corresponde à 3 linhas
» df.loc['B']
↳        0
  1     21
  2     22
  3     23

» df.loc['B'].loc[2]
↳ 0    22

» # idem para a series
» sr['C']
↳ 1    31
  2    32

» sr['C'][1]
↳ 31

» # podemos listar as linhas de 'A' até 'C'
» sr['A':'C']
↳ A  1    11
     2    12
  B  1    21
     2    22
     3    23
  C  1    31
     2    32

» # ou as linhas correspondentes à 'A' e 'C'
» sr.loc[['A','C']]
↳ A  1    11
     2    12
  C  1    31
     2    32

» # seleção pelo índice interno pode feita diretamente
» sr.loc[:, 2]
↳ A    12
  B    22
  C    32
  D    42

» sr.loc[:, 3]
↳ B    23

stack() e unstack()

Os dados de uma series com índices hierárquicos podem ser rearranjados em um DataFrame com o uso de método Series.unstack(). Os índices internos se tornam nomes das colunas. Valores não existentes, como o correspondende aos índices A, 3, são preenchidos com NaN.

» df = sr.unstack()
» df
↳          1         2       3
  A     11.0     12.0      NaN
  B     21.0     22.0     23.0
  C     31.0     32.0      NaN
  D     41.0     42.0      NaN

» # para retornar à uma series
» df.unstack()
↳ 1  A    11.0
     B    21.0
     C    31.0
     D    41.0
  2  A    12.0
     B    22.0
     C    32.0
     D    42.0
  3  A     NaN
     B    23.0
     C     NaN
     D     NaN

No processo de desempilhar o dataframe (unstack ) os nomes das colunas foram usados como índices primários.

Um dataframe pode ter índices hierarquizados para linhas e colunas.

» clima = np.array([[25,20,30],[20,16,15],[15,25,27],[40,60,78]])
» dfClima = pd.DataFrame(clima,
»                       index=[['Temperatura','Temperatura','Umidade','Umidade'],
»                              ['dia','noite','dia','noite']],
»                       columns=[['Paraná','Paraná','Amazonas'],['Cascavel','Curitiba','Manaus']]
»                       )

» # inserindo nomes para as linhas e colunas
» dfClima.index.names = ['Característica', 'D/N']        # D/N = dia/noite
» dfClima.columns.names = ['Estado', 'Cidade']

» # o resultado é
» dfClima
↳                  Estado                   Paraná     Amazonas
                   Cidade     Cascavel     Curitiba      Manaus
  Característica      D/N             
  Temperatura         dia           25           20          30
                    noite           20           16          15
  Umidade             dia           15           25          27
                    noite           40           60          78

Se o processo de criação de dataframes com os mesmos índices será repetido várias vezes ,pode ser útil definir previamente os objetos multindexes.

» colunas = pd.MultiIndex.from_arrays([['Paraná', 'Paraná', 'Amazonas'],
»                                      ['Cascavel', 'Curitiba', 'Manaus']],
»                                      names=['Estado', 'Cidade'])

» linhas = pd.MultiIndex.from_arrays([['Temperatura','Temperatura','Umidade','Umidade'],
»                                     ['dia','noite','dia','noite']],
»                                     names=['Característica', 'D/N'])

» linhas
↳ MultiIndex([('Temperatura',   'dia'),
              ('Temperatura', 'noite'),
              (    'Umidade',   'dia'),
              (    'Umidade', 'noite')],
             names=['Característica', 'D/N'])

» pd.DataFrame(clima, index=linhas, columns=colunas)
↳                 Estado     Paraná             Amazonas
                  Cidade   Cascavel    Curitiba   Manaus
  Característica     D/N
  Temperatura        dia         25          20       30
                   noite         20          16       15
  Umidade            dia         15          25       27
                   noite         40          60       78

swaplevel() e groupby()

O ordenamento dos níveis nos dataframes pode ser alterado com o método dataframe.swaplevel(indice1, indice2). Índices primários podem ser permutados com índice secundários. Com dataframe.sort_index(level=n) podemos ordenar as linhas do dataframe segundo os nomes dos índices do nível n.

» dfClima.swaplevel('D/N', 'Característica')
↳                 Estado     Paraná               Amazonas
                  Cidade   Cascavel    Curitiba     Manaus
  D/N     Característica
  dia        Temperatura         25          20         30
  noite      Temperatura         20          16         15
  dia            Umidade         15          25         27
  noite          Umidade         40          60         78

» # ordenando as linhas pelos labels do nível 1 (D/N)
» dfClima.sort_index(level=1)

↳                  Estado                    Paraná    Amazonas
                   Cidade     Cascavel     Curitiba      Manaus
  Característica      D/N
  Temperatura         dia           25           20          30
  Umidade             dia           15           25          27
  Temperatura       noite           20           16          15
  Umidade           noite           40           60          78

» # alternativamente podemos inverter a ordem dos níveis e ordenar pelo nivel 0
» dfClima.swaplevel(0, 1).sort_index(level=0)
↳                 Estado                    Paraná    Amazonas
                  Cidade     Cascavel     Curitiba      Manaus
  D/N     Característica
  dia        Temperatura           25           20          30
                 Umidade           15           25          27
  noite      Temperatura           20           16          15
                 Umidade           40           60          78

» # soma dos valores agrupados pelo nível 1 (D/N)
» dfClima.groupby(level=1).sum()
 
↳ Estado                    Paraná    Amazonas
  Cidade     Cascavel     Curitiba      Manaus
  D/N 
  dia              40           45          57
  noite            60           76          93

O método dataframe.groupby(), que veremos mais tarde com maiores detalhes, permite o agrupamento dos dados de um determinado índice (ou nível de índices). Por ex., dataframe.groupby(level=n).sum() faz o agrupamento dos dados segundo o n-ésimo nível de índice e depois soma esses valores. Muitas outras funções estatísticas ficam disponíveis com agrupamentos por groupby.

» # soma dos valores agrupados pelo nível 0
» dfClima.groupby(level='Característica').mean()
↳                  Estado               Paraná    Amazonas
                   Cidade  Cascavel   Curitiba      Manaus
  Característica
  Temperatura                  22.5       18.0        22.5
  Umidade                      27.5       42.5        52.5

» # a média dos valores agrupados pelo índice D/N
» dfClima.groupby(level=0).mean()

↳                   Estado              Paraná   Amazonas
                    Cidade  Cascavel  Curitiba     Manaus
  Característica
  Temperatura                   22.5      18.0       22.5
  Umidade                       27.5      42.5       52.5

» # o valor máximo agrupado pelo nível 'Característica'
» dfClima.groupby(level='Característica').max()
↳                  Estado              Paraná    Amazonas
  Cidade                   Cascavel   Curitiba     Manaus
  Característica
  Temperatura                    25         20         30
  Umidade                        40         60         78

Vimos previamente que qualquer coluna pode ser transformada em índice do dataframe. Mais de uma coluna pode também ser usada: para isso usamos dataframe.set_index([coluna1, coluna2]). Por default essa operação coloca coluna1, coluna2 como índices e descarta as colunas usadas. Para alterar esse comportamento (e manter as colunas) usamos o parâmetro drop=False. O método dataframe.reset_index() remove os índices colocando-os como colunas e criando um novo conjunto de índices.

» # criamos um dataframe arbitrário
» dfNums = pd.DataFrame({'a': range(1,6),
»                        'texto-a': ['um','dois','três','quatro','cinco'],
»                        'b': range(5, 0, -1),
»                        'texto-b': ['cinco', 'quatro','três','dois','um']
»                       })

» # dataframe inicial
» dfNums
↳      a     texto-a     b     texto-b
  0    1          um     5       cinco
  1    2        dois     4      quatro
  2    3        três     3        três
  3    4      quatro     2        dois
  4    5       cinco     1          um

» # usamos as colunas 'a' e 'b' como índices
» dfNums2 = dfNums.set_index(['a', 'b'])

» dfNums2
↳          texto-a    texto-b
  a    b         
  1    5        um      cinco
  2    4      dois     quatro
  3    3      três       três
  4    2    quatro       dois
  5    1     cinco         um

» # para descartar os índices (e recuperar as colunas)
» dfNums2.reset_index()

↳       a     b     texto-a     texto-b
  0     1     5     um          cinco
  1     2     4     dois        quatro
  2     3     3     três        três
  3     4     2     quatro      dois
  4     5     1     cinco       um

» # podemos usar as colunas 'a' e 'texto-a' como índices sem descartar essas colunas
» dfNums.set_index(['a', 'texto-a'], drop=False)
↳                 a     texto-a    b    texto-b
  a     texto-a                 
  1     um        1     um         5      cinco
  2     dois      2     dois       4     quatro
  3     três      3     três       3       três
  4     quatro    4     quatro     2       dois
  5     cinco     5     cinco      1         um

Uma exceção é lançada se já existem colunas com os mesmos nomes recuperados por reset_index.

Combinando dataframes

Podemos juntar dataframes de várias formas. pandas.merge() junta dataframes usando um ou mais índices, em operações semelhantes àquelas de bancos de dados relacionais usando-se as operações de join do SQL. pandas.concat() faz a concatenação ou empilhamento dos dataframes ao longo do eixo escolhido. pandas.combine_first() permite a junção de dados que se superpõe (existem em mais de uma tabela), preenchendo valores ausentes um uma tabela com aqueles em outra tabela fornecida.

merge()

df1.merge(df2) retorna outro dataframe que é a junção dos dois dataframes. O método possui a seguinte assinatura:
df1.merge(df2, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True, indicator=False, validate=None)

A junção pode ser feita sobre nomes das colunas ou índices. Uma Series nomeada é tratada como um dataframe de coluna única. São parâmetros:

df1, df2 dataframe ou Series nomeada. Junção de df1 com df2
how tipo de junção: left, right, outer, inner, cross:
inner: usa apenas combinações de chaves existentes em ambas as tabelas preserva ordem das chaves.
outer: usa todas as combinações de chaves em cada uma das tabelas,
left: usa todas as combinações de chaves existentes na tabela à esquerda,
right: usa todas as combinações de chaves existentes na tabela à direita,
cross: cria o produto cartesiano das tabelas, preserva ordem dos índices.
on coluna ou índice para a junção. Deve existir em ambos os dataframes
left_on nome da coluna ou índice (ou lista) em df1.
right_on nome da coluna ou índice (ou lista) em df2.
left_index False/True: use o índice de df1 como chave.
right_index False/True: use o índice de df2 como chave.
sort False/True: Ordena os índices no resultado.
suffixes lista: default = (“_x”, “_y”). Sufixos para índices de mesmo nome
copy False/True: Se False evita a cópia, se possível
indicator False/True ou str: Se True acrescenta coluna “_merge” com informações sobre as linhas.
validate str, opcional. Se especificada verifica se a junção é do tipo:
one_to_one ou 1:1 : se chave da fusão é única nos dois dataframes,
one_to_many ou 1:m : se chave da fusão é única em df1 (lado esquerdo),
many_to_one ou m:1 : se chave da fusão é única em df2 (lado direito),
many_to_many ou m:m : embora permitida não resulta em nenhuma verificação.

Comparação de how='' com comandos SQL: (Pandas e SQL comparados).

how= similar ao SQL
left left outer join. Preserva ordem das chaves.
right right outer join. Preserva ordem das chaves.
outer full outer join. Ordena por nomes das chaves.
inner inner join. Preserva ordem das chaves à esquerda.
» # criando dataframes 
» df1 = pd.DataFrame({'chave': ['a', 'a', 'a', 'b', 'b', 'b', 'c'], 'data1': range(7)})
» df2 = pd.DataFrame({'chave': ['a', 'b', 'd'], 'data2': range(3)})

» # exibindo df1, df2 e sua junção com merge
» display(df1, df2, pd.merge(df1, df2))

↳    chave   data1
  0      a       0
  1      a       1
  2      a       2
  3      b       3
  4      b       4
  5      b       5
  6      c       6

↳    chave   data2
  0      a      0
  1      b      1
  2      d      2

↳    chave   data1   data2
  0      a       0       0
  1      a       1       0
  2      a       2       0
  3      b       3       1
  4      b       4       1
  5      b       5       1


Como os dois dataframes possuem uma coluna com nome comum a junção foi feita com base nos valores da coluna com esse nome. Essa informação pode ser explicitada com pd.merge(df1, df2, on='chave').

Se os nomes das colunas de cada dataframe for diferente eles devem ser definidos com os parâmetros left_on, right_on.

» df3 = pd.DataFrame({'chave1': ['a', 'a', 'a', 'b', 'b', 'c', 'd'], 'data1': range(7)})
» df4 = pd.DataFrame({'chave2': ['a', 'b', 'd'], 'data2': range(3)})

» display(df3, df4, pd.merge(df3, df4, left_on='chave1', right_on='chave2'))
↳    chave1     data1
  0       a     0
  1       a     1
  2       a     2
  3       b     3
  4       b     4
  5       c     5
  6       d     6

↳    chave2     data2
  0       a     0
  1       b     1
  2       d     2

↳    chave1     data1     chave2     data2
  0       a     0         a          0
  1       a     1         a          0
  2       a     2         a          0
  3       b     3         b          1
  4       b     4         b          1
  5       d     6         d          2

Vemos na concatenação acima que o método usado reune apenas valores existentes nas duas tabelas. Isso é equivalente a passar o parâmetro how=’inner’ (um inner join ). Outra opção consiste em fazer o ligamento externo.

» # para conseguir um outer join    
» pd.merge(df1, df2, how='outer')

↳     chave   data1   data2
  0     a       0.0     0.0
  1     a       1.0     0.0
  2     a       2.0     0.0
  3     b       3.0     1.0
  4     b       4.0     1.0
  5     b       5.0     1.0
  6     c       6.0     NaN
  7     d       NaN     2.0

» dd.merge(df1, df2, how='left')

↳   chave   data1   data2
  0     a       0     0.0
  1     a       1     0.0
  2     a       2     0.0
  3     b       3     1.0
  4     b       4     1.0
  5     b       5     1.0
  6     c       6     NaN

» pd.merge(df1, df2, how='right')

↳   chave     data1   data2
  0     a       0.0       0
  1     a       1.0       0
  2     a       2.0       0
  3     b       3.0       1
  4     b       4.0       1
  5     b       5.0       1
  6     d       NaN       2

Tabelas podem ser ligadas por mais de uma chave, quando os dataframes possuem índices hierarquizados. As chaves são usadas como se fossem uma única chave concatenada.

» df1 = pd.DataFrame({'chave_1': ['rato', 'rato', 'gato'],
»                      'chave_2': ['Jones', 'Jerry', 'Tom'],
»                      'valor_A': [10, 20, 30]})
» df2 = pd.DataFrame({'chave_1': ['rato', 'rato', 'gato', 'gato'],
»                       'chave_2': ['Jones', 'Jerry', 'Tom', 'Tim'],
»                       'valor_B': [40, 50, 60, 70]})
                      
» # exibindo os dataframes e a junção externa em duas chaves
» display(df1, df2, pd.merge(df1, df2, on=['chave_1','chave_2'], how='outer'))

↳     chave_1     chave_2    valor_A
  0      rato       Jones         10
  1      rato       Jerry         20
  2      gato         Tom         30

↳    chave_1     chave_2   valor_B
  0     rato       Jones        40
  1     rato       Jerry        50
  2     gato         Tom        60
  3     gato         Tim        70

↳     chave_1    chave_2    valor_A    valor_B
  0     rato       Jones       10.0         40
  1     rato       Jerry       20.0         50
  2     gato        Tom        30.0         60
  3     gato        Tim         NaN         70

» # a junção interna em duas chaves
» pd.merge(df1, df2, on=['chave_1','chave_2'], how='inner')

↳     chave_1   chave_2    valor_A    valor_B
  0      rato     Jones         10         40
  1      rato     Jerry         20         50
  2      gato      Tom          30         60

Se a junção for feita sobre campos (nomes de colunas) com o mesmo nome estes serão alterados para continuar a representar suas colunas de origem. No caso do exemplo as colunas com nome valor foram renomeadas para valor_x e valor_y.

» df1 = pd.DataFrame({'chave': ['a', 'b', 'c'], 'valor': [1,2,3]})
» df2 = pd.DataFrame({'chave': ['a', 'b', 'c'], 'valor': [10,20,30]})

» mrg = pd.merge(df1, df2, on='chave')

» display(df1, df2, mrg)

↳    chave  valor
  0      a      1
  1      b      2
  2      c      3

↳    chave  valor
  0      a     10
  1      b     20
  2      c     30

↳    chave  valor_x  valor_y
  0      a        1       10
  1      b        2       20
  2      c        3       30

A chave usada na fusão (merge) pode estar no índice de um ou ambas as tabelas. No exemplo usamos pd.merge(esquerda, direita, left_on='chave', right_index=True) que faz a junção de esquerda.chave com direita.index

» esquerda = pd.DataFrame({'chave': ['a1', 'a1', 'a2', 'a1', 'a2', 'a3'], 'valor_1': range(6)})
» direita = pd.DataFrame({'valor_2': [50, 70]}, index=['a1', 'a2'])

» mrg = pd.merge(esquerda, direita, left_on='chave', right_index=True)

» # exibindo dataframes e sua junção, usando o índice da tabela à direita
» display(esquerda, direita, mrg)

↳    chave   valor_1
  0     a1         0
  1     a1         1
  2     a2         2
  3     a1         3
  4     a2         4
  5     a3         5

↳    valor_2
  a1      50
  a2      70

↳    chave   valor_1    valor_2
  0     a1         0         50
  1     a1         1         50
  3     a1         3         50
  2     a2         2         70
  4     a2         4         70

» # se os dataframes forem invertidos conseguiríamos o
» # mesmo resultado, exceto pela ordem das colunas, usando:
» # pd.merge(direita, esquerda, right_on='chave', left_index=True)

Junções com join()

Junções podem ser feitas com dataframe.join(dfOutro) que, por default, faz a união outer join usando o índice como chave. Esse método tem a seguinte assinatura, onde os parâmetros são
dataframe.join(dfOutro, on, how, lsuffix, rsuffix, sort),
Todos os parâmetros são opcionais exceto dfOutro. Os defaults estão em negrito.

dfOutro DataFrame, Series ou lista de DataFrames.
on string, especifica em que chave(s) fazer a junção
how strings: left, right, outer, inner. Especifica o tipo de junção.
lsuffix/rsuffix Default = ”. String a concatenar à esquerda/direita em colunas com mesmo nome.
sort False/True. Se True ordena o dataframe pela chave de junção.
» # dataframe join
» df1 = pd.DataFrame({'nome': ['Paulo', 'Maria', 'Julio','Marta'],
                       'idade': [35, 43, 31, 56]})
» df2 = pd.DataFrame({'profissao': ['médico', 'engenheiro', 'advogado']})

» df1
↳      nome      idade
  0    Paulo     35
  1    Maria     43
  2    Julio     31
  3    Marta     56

» df2
↳      profissao
  0    médico
  1    engenheiro
  2    advogado

» df1.join(df2, on=df1.index,  lsuffix='_1', rsuffix='_2') # , how = 'left' (default)
↳      nome   idade_1    profissao    idade_2
  0   Paulo        35       médico       35.0
  1   Maria        43   engenheiro       40.0
  2   Julio        31     advogado       31.0
  3   Marta        56          NaN        NaN

» # um inner join
» df1.join(df2, lsuffix='_', how='inner')
↳      nome    idade_    profissao   idade
  0   Paulo       35        médico      35
  1   Maria       43    engenheiro      40
  2   Julio       31      advogado      31

Vários dataframes podem ser concatenados de uma vez. Para isso eles devem ter dimensões compatíveis.

» # Vários dataframes podem ser concatenados
» df1 = pd.DataFrame([[23, 83], [93, 10], [73, 89], [68, 90]],
»                    index=['a', 'b', 'e', 'f'],
»                    columns=['A', 'B'])

» df2 = pd.DataFrame([[2, 8], [9, 1], [7, 8], [6, 9]],
»                    index=['a', 'b', 'c', 'd'],
»                    columns=['C', 'D'])

» df3 = pd.DataFrame([[3, 3], [3, 0], [3, 9], [8, 0]],
»                    index=['a', 'c', 'd', 'e'],
»                    columns=['E', 'F'])

» # exibe os 3 dataframes
» display(df1, df2, df3)

↳ A    B
  a    23    83
  b    93    10
  e    73    89
  f    68    90

↳ C    D
  a    2    8
  b    9    1
  c    7    8
  d    6    9
  
↳ E    F
  a    3    3
  c    3    0
  d    3    9
  e    8    0

» # exibe a junção dos dataframes
» df1.join([df2, df3])

↳         A       B      C      D      E      F
  a    23.0    83.0    2.0    8.0    3.0    3.0
  b    93.0    10.0    9.0    1.0    NaN    NaN
  e    73.0    89.0    NaN    NaN    8.0    0.0
  f    68.0    90.0    NaN    NaN    NaN    NaN

Como sempre, campos não fornecidos são preenchidos por NaN. Por ex.: df1.join([df2, df3]).loc['f', 'F'] = NaN.

concatenate()

Podemos concatenar numpy.arrays, Series e dataframes ao longo do eixo desejado.

» # Concatenando um array ao longo de um eixo
» # criamos 2 arrays
» arr1 = np.arange(6).reshape((3, 2))

» arr1
↳ array([[0, 1],
         [2, 3],
         [4, 5]])

» # concatenando arr1 consigo mesmo, ao longo de colunas
» np.concatenate([arr1, arr1], axis=1)
↳ array([[0, 1, 0, 1],
         [2, 3, 2, 3],
         [4, 5, 4, 5]])

» # concatenando arr1 consigo mesmo, ao longo de linhas
» np.concatenate([arr1, arr1], axis=0)
↳ array([[0, 1],
         [2, 3],
         [4, 5],
         [0, 1],
         [2, 3],
         [4, 5]])

» # defina outro array, com shape (3, 1)
» arr2 = np.array([[0], [1], [2]])

» arr2
↳ array([[0],
         [1],
         [2]])

» # concatenando arr1 2 arr2 pelas colunas
» np.concatenate([arr1, arr2], axis=1)

↳ array([[0, 1, 0],
         [2, 3, 1],
         [4, 5, 2]])

» # (tentando) concatenar arr1 2 arr2 pelas linhas
» np.concatenate([arr1, arr2], axis=0)
↳ ValueError: all the input array dimensions for the concatenation axis must match exactly,
  but along dimension 1, the array at index 0 has size 2 and the array at index 1 has size 1


Vemos que podemos concatenar uma matriz coluna (3 × 1) com outra matriz (3 × 2) pelas colunas, mas não pelas linhas pois as dimensãos são incompatíveis.

combine() e combine_first()

O método df1.combine(df2, func, fill_value=None, overwrite=True) combina df1 e df2, coluna a coluna, aplicando func para decidir qual valor será usado.

Podemos criar uma função que receba duas colunas e realize alguma operação entre elas, retornando outra coluna. No ex., a função f faz a soma dos elementos de duas colunas e retorna aquela com menor soma. A função g seleciona, a cada linha, qual é o maior elemento. Quando o parâmetro fill_value=r é usado todos os valores NaN são substituídos por r antes de serem submetidos à função func, exceto se ambos os valores forem nulos, quando não existirá substituição.

» df1 = pd.DataFrame({'A': [0, 3], 'B': [7, 2]})
» df2 = pd.DataFrame({'A': [2, 6], 'B': [1, 3]})

» df1
↳      A    B
  0    0    7
  1    3    2

» df2
↳      A    B
  0    2    1
  1    6    3

» # a função de comparação pode ser
» def f(x,y):
»     if x.sum() < y.sum():
»         return x
»     else:
»         return y

» # a combinação, usando essa função
» df1.combine(df2, f)
↳      A    B
  0    0    1
  1    3    3

» # O mesmo resultado pode ser obtido com uma função lambda
» df1.combine(df2, lambda x, y: x if x.sum() < y.sum() else y)

» # funções mais complexas podem ser usadas
» df1.combine(df2, lambda x, y: (x+y)*(y-x))
↳       A     B
  0     4   -48
  1    27     5

» # outro exemplo, selecionar o maior elemento de cada df
» def g(x,y):
»     a = x[0] if x[0] > y[0] else y[0]
»     b = x[1] if x[1] > y[1] else y[1]
»     return pd.Series([a,b])

» df1.combine(df2,g)
↳      A    B
  0    2    7
  1    6    3

» # o mesmo poderia ser feito com uma funlão lambda
» maior = lambda x,y: pd.Series([x[0] if x[0] > y[0] else y[0],
                                x[1] if x[1] > y[1] else y[1]])
» df1.combine(df2,maior) # mesmo output
 
» # uso de fill_value
» df1 = pd.DataFrame({'A': [0, 0], 'B': [np.NaN, 4]})
» df2 = pd.DataFrame({'A': [1, 1], 'B': [3, 3]})

» df1.combine(df2, maior, fill_value=6)
↳      A      B
  0    1    6.0
  1    1    4.0

Já o método dataframe.combine_first(dfOutro) substitui os valores NaN no dataframe com os valores de dfOutro, quando esses valores existirem.

» df1 = pd.DataFrame({'a': [1, np.nan, 5, np.nan],
»                     'b': [np.nan, 2, np.nan, 6],
»                     'c': range(2, 18, 4)})
» df2 = pd.DataFrame({'a': [5, 4, np.nan, 3, 7],
»                     'b': [np.nan, 3, 4, 6, 8]})
» display(df1, df2)
↳        a      b     c
  0    1.0    NaN     2
  1    NaN    2.0     6
  2    5.0    NaN    10
  3    NaN    6.0    14
  
↳        a      b
  0    5.0    NaN
  1    4.0    3.0
  2    NaN    4.0
  3    3.0    6.0

» df1.combine_first(df2)
↳        a      b      c
  0    1.0    NaN    2.0
  1    4.0    2.0    6.0
  2    5.0    4.0   10.0
  3    3.0    6.0   14.0
  4    7.0    8.0    NaN

Bibliografia

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site: