No processo de responder a essa pergunta para mim mesmo, aprendi muitas coisas e queria montar um catálogo de exemplos e algumas explicações.
A resposta específica para o ponto do levelsargumento virá no final.
pandas.concat: The Missing Manual
Link para a documentação atual
Importação e definição de objetos
import pandas as pd
d1 = pd.DataFrame(dict(A=.1, B=.2, C=.3), index=[2, 3])
d2 = pd.DataFrame(dict(B=.4, C=.5, D=.6), index=[1, 2])
d3 = pd.DataFrame(dict(A=.7, B=.8, D=.9), index=[1, 3])
s1 = pd.Series([1, 2], index=[2, 3])
s2 = pd.Series([3, 4], index=[1, 2])
s3 = pd.Series([5, 6], index=[1, 3])
Argumentos
objs
O primeiro argumento que encontramos é objs:
objs : uma sequência ou mapeamento de objetos Series, DataFrame ou Panel Se um dict for passado, as chaves classificadas serão usadas como o argumento keys, a menos que seja passado, caso em que os valores serão selecionados (veja abaixo). Qualquer objeto None será descartado silenciosamente a menos que sejam todos None, caso em que um ValueError será gerado
- Normalmente vemos isso usado com uma lista de objetos
Seriesou DataFrame.
- Vou mostrar que também
dictpode ser muito útil.
- Geradores também podem ser usados e podem ser úteis quando usados
mapcomo emmap(f, list_of_df)
Por enquanto, ficaremos com uma lista de alguns dos objetos DataFramee Seriesdefinidos acima. Mostrarei como os dicionários podem ser aproveitados para fornecer MultiIndexresultados muito úteis posteriormente.
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
axis
O segundo argumento que encontramos é axiscujo valor padrão é 0:
eixo : {0 / 'índice', 1 / 'colunas'}, padrão 0 O eixo ao longo do qual concatenar.
Dois DataFrames com axis=0(empilhados)
Para valores de 0ou indexqueremos dizer: "Alinhe ao longo das colunas e adicione ao índice".
Conforme mostrado acima, onde usamos axis=0, porque 0é o valor padrão, e vemos que o índice de d2estende o índice de d1apesar de haver sobreposição do valor 2:
pd.concat([d1, d2], axis=0)
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
Dois DataFrames com axis=1(lado a lado)
Para valores 1ou columnsqueremos dizer: "Alinhe ao longo do índice e adicione às colunas",
pd.concat([d1, d2], axis=1)
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
Podemos ver que o índice resultante é a união dos índices e as colunas resultantes são a extensão das colunas de d1pelas colunas de d2.
Dois (ou três) Seriescom axis=0(empilhados)
Ao combinar pandas.Seriesjunto axis=0, obtemos a pandas.Series. O nome do resultado Seriesserá a Nonemenos que todos Seriessendo combinados tenham o mesmo nome. Preste atenção ao 'Name: A'quando imprimirmos o resultado Series. Quando não está presente, podemos assumir que o Seriesnome está None.
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('A'),
pd.concat( | [s1.rename('A'), | [s1.rename('A'), | s2.rename('B'),
[s1, s2]) | s2]) | s2.rename('A')]) | s3.rename('A')])
-------------- | --------------------- | ---------------------- | ----------------------
2 1 | 2 1 | 2 1 | 2 1
3 2 | 3 2 | 3 2 | 3 2
1 3 | 1 3 | 1 3 | 1 3
2 4 | 2 4 | 2 4 | 2 4
dtype: int64 | dtype: int64 | Name: A, dtype: int64 | 1 5
| | | 3 6
| | | dtype: int64
Dois (ou três) Seriescom axis=1(lado a lado)
Ao combinar pandas.Seriesjunto axis=1, é o nameatributo ao qual nos referimos para inferir um nome de coluna no resultado pandas.DataFrame.
| | pd.concat(
| pd.concat( | [s1.rename('X'),
pd.concat( | [s1.rename('X'), | s2.rename('Y'),
[s1, s2], axis=1) | s2], axis=1) | s3.rename('Z')], axis=1)
---------------------- | --------------------- | ------------------------------
0 1 | X 0 | X Y Z
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 5.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 NaN
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN 6.0
Misturado Seriese DataFramecom axis=0(empilhado)
Ao realizar uma concatenação de a Seriese DataFramealong axis=0, convertemos todos Seriesem uma única coluna DataFrames.
Observe que esta é uma concatenação junto axis=0; isso significa estender o índice (linhas) enquanto alinha as colunas. Nos exemplos abaixo, vemos que o índice se torna, [2, 3, 2, 3]que é um acréscimo indiscriminado de índices. As colunas não se sobrepõem, a menos que eu force a nomenclatura da Seriescoluna com o argumento para to_frame:
pd.concat( |
[s1.to_frame(), d1]) | pd.concat([s1, d1])
------------------------- | ---------------------
0 A B C | 0 A B C
2 1.0 NaN NaN NaN | 2 1.0 NaN NaN NaN
3 2.0 NaN NaN NaN | 3 2.0 NaN NaN NaN
2 NaN 0.1 0.2 0.3 | 2 NaN 0.1 0.2 0.3
3 NaN 0.1 0.2 0.3 | 3 NaN 0.1 0.2 0.3
Você pode ver que os resultados pd.concat([s1, d1])são os mesmos como se eu tivesse executado a to_framemim mesmo.
No entanto, posso controlar o nome da coluna resultante com um parâmetro para to_frame. Renomear o Seriescom o renamemétodo não controla o nome da coluna no resultado DataFrame.
# Effectively renames | |
# `s1` but does not align | # Does not rename. So | # Renames to something
# with columns in `d1` | # Pandas defaults to `0` | # that does align with `d1`
pd.concat( | pd.concat( | pd.concat(
[s1.to_frame('X'), d1]) | [s1.rename('X'), d1]) | [s1.to_frame('B'), d1])
---------------------------- | -------------------------- | ----------------------------
A B C X | 0 A B C | A B C
2 NaN NaN NaN 1.0 | 2 1.0 NaN NaN NaN | 2 NaN 1.0 NaN
3 NaN NaN NaN 2.0 | 3 2.0 NaN NaN NaN | 3 NaN 2.0 NaN
2 0.1 0.2 0.3 NaN | 2 NaN 0.1 0.2 0.3 | 2 0.1 0.2 0.3
3 0.1 0.2 0.3 NaN | 3 NaN 0.1 0.2 0.3 | 3 0.1 0.2 0.3
Misturado Seriese DataFramecom axis=1(lado a lado)
Isso é bastante intuitivo. SeriesO nome da coluna é padronizado para uma enumeração de tais Seriesobjetos quando um nameatributo não está disponível.
| pd.concat(
pd.concat( | [s1.rename('X'),
[s1, d1], | s2, s3, d1],
axis=1) | axis=1)
------------------- | -------------------------------
0 A B C | X 0 1 A B C
2 1 0.1 0.2 0.3 | 1 NaN 3.0 5.0 NaN NaN NaN
3 2 0.1 0.2 0.3 | 2 1.0 4.0 NaN 0.1 0.2 0.3
| 3 2.0 NaN 6.0 0.1 0.2 0.3
join
O terceiro argumento é joinque descreve se a mesclagem resultante deve ser uma mesclagem externa (padrão) ou uma mesclagem interna.
join : {'interno', 'externo'}, padrão 'externo'
Como lidar com índices em outro (s) eixo (s).
Acontece que não há nenhuma opção leftou rightque pd.concatpossa lidar com mais do que apenas dois objetos para mesclar.
No caso de d1e d2, as opções são semelhantes a:
outer
pd.concat([d1, d2], axis=1, join='outer')
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
inner
pd.concat([d1, d2], axis=1, join='inner')
A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6
join_axes
O quarto argumento é o que nos permite fazer nossa leftfusão e muito mais.
join_axes : lista de objetos Index
Índices específicos a serem usados para os outros n - 1 eixos em vez de realizar a lógica de conjunto interno / externo.
Esquerda Unir
pd.concat([d1, d2, d3], axis=1, join_axes=[d1.index])
A B C B C D A B D
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
Unir à direita
pd.concat([d1, d2, d3], axis=1, join_axes=[d3.index])
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
ignore_index
ignore_index : boolean, default False
Se True, não use os valores de índice ao longo do eixo de concatenação. O eixo resultante será rotulado 0, ..., n - 1. Isso é útil se você estiver concatenando objetos onde o eixo de concatenação não tem informações de indexação significativas. Observe que os valores de índice nos outros eixos ainda são respeitados na junção.
Como quando eu empilhar d1em cima do d2, se eu não me importar com os valores do índice, posso redefini-los ou ignorá-los.
| pd.concat( | pd.concat(
| [d1, d2], | [d1, d2]
pd.concat([d1, d2]) | ignore_index=True) | ).reset_index(drop=True)
--------------------- | ----------------------- | -------------------------
A B C D | A B C D | A B C D
2 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6
E ao usar axis=1:
| pd.concat(
| [d1, d2], axis=1,
pd.concat([d1, d2], axis=1) | ignore_index=True)
------------------------------- | -------------------------------
A B C B C D | 0 1 2 3 4 5
1 NaN NaN NaN 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 3 0.1 0.2 0.3 NaN NaN NaN
keys
Podemos passar uma lista de valores escalares ou tuplas para atribuir tupla ou valores escalares ao MultiIndex correspondente. O comprimento da lista passada deve ser igual ao número de itens sendo concatenados.
keys : sequence, default None
Se vários níveis forem passados, deve conter tuplas. Construa um índice hierárquico usando as chaves passadas como o nível mais externo
axis=0
Ao concatenar Seriesobjetos ao longo axis=0(estendendo o índice).
Essas chaves se tornam um novo nível inicial de um MultiIndexobjeto no atributo de índice.
# length 3 length 3 # length 2 length 2
# /--------\ /-----------\ # /----\ /------\
pd.concat([s1, s2, s3], keys=['A', 'B', 'C']) pd.concat([s1, s2], keys=['A', 'B'])
---------------------------------------------- -------------------------------------
A 2 1 A 2 1
3 2 3 2
B 1 3 B 1 3
2 4 2 4
C 1 5 dtype: int64
3 6
dtype: int64
No entanto, podemos usar mais do que valores escalares no keysargumento para criar um ainda mais profundo MultiIndex. Aqui, passamos tuplesdo comprimento 2 antes de dois novos níveis de um MultiIndex:
pd.concat(
[s1, s2, s3],
keys=[('A', 'X'), ('A', 'Y'), ('B', 'X')])
-----------------------------------------------
A X 2 1
3 2
Y 1 3
2 4
B X 1 5
3 6
dtype: int64
axis=1
É um pouco diferente quando se estende ao longo de colunas. Quando usamos axis=0(veja acima) nossos níveis keysagiram como MultiIndexadicionais ao índice existente. Pois axis=1, estamos nos referindo a um eixo que os Seriesobjetos não têm, ou seja, o columnsatributo.
Variações de dois
Seriescom
axis=1
Observe que nomear o s1e é s2importante, desde que não keysseja passado, mas será substituído se keysfor aprovado.
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('U'),
pd.concat( | [s1, s2], | [s1.rename('U'), | s2.rename('V')],
[s1, s2], | axis=1, | s2.rename('V')], | axis=1,
axis=1) | keys=['X', 'Y']) | axis=1) | keys=['X', 'Y'])
-------------- | --------------------- | ---------------------- | ----------------------
0 1 | X Y | U V | X Y
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN
MultiIndexcom
Seriese
axis=1
pd.concat(
[s1, s2],
axis=1,
keys=[('W', 'X'), ('W', 'Y')])
-----------------------------------
W
X Y
1 NaN 3.0
2 1.0 4.0
3 2.0 NaN
Dois
DataFramecom
axis=1
Como nos axis=0exemplos, keysadicione níveis a a MultiIndex, mas desta vez ao objeto armazenado no columnsatributo.
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=[('First', 'X'), ('Second', 'X')])
------------------------------- | --------------------------------------------
X Y | First Second
A B C B C D | X X
1 NaN NaN NaN 0.4 0.5 0.6 | A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
| 3 0.1 0.2 0.3 NaN NaN NaN
Seriese
DataFramecom
axis=1
Isso é complicado. Nesse caso, um valor de chave escalar não pode atuar como o único nível de índice para o Seriesobjeto quando ele se torna uma coluna, ao mesmo tempo que atua como o primeiro nível de a MultiIndexpara o DataFrame. Portanto, o Pandas usará novamente o nameatributo do Seriesobjeto como a origem do nome da coluna.
pd.concat( | pd.concat(
[s1, d1], | [s1.rename('Z'), d1],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=['X', 'Y'])
--------------------- | --------------------------
X Y | X Y
0 A B C | Z A B C
2 1 0.1 0.2 0.3 | 2 1 0.1 0.2 0.3
3 2 0.1 0.2 0.3 | 3 2 0.1 0.2 0.3
Limitações
keyse
MultiIndexinferências.
O Pandas parece apenas inferir nomes de colunas a partir do Seriesnome, mas não preencherá os espaços em branco ao fazer uma concatenação análoga entre quadros de dados com um número diferente de níveis de coluna.
d1_ = pd.concat(
[d1], axis=1,
keys=['One'])
d1_
One
A B C
2 0.1 0.2 0.3
3 0.1 0.2 0.3
Em seguida, concatene isso com outro quadro de dados com apenas um nível no objeto colunas e Pandas se recusará a tentar fazer tuplas do MultiIndexobjeto e combinar todos os quadros de dados como se fosse um único nível de objetos, escalares e tuplas.
pd.concat([d1_, d2], axis=1)
(One, A) (One, B) (One, C) B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
Passando por um em dictvez de umlist
Ao passar um dicionário, pandas.concatusará as chaves do dicionário como keysparâmetro.
# axis=0 | # axis=1
pd.concat( | pd.concat(
{0: d1, 1: d2}) | {0: d1, 1: d2}, axis=1)
----------------------- | -------------------------------
A B C D | 0 1
0 2 0.1 0.2 0.3 NaN | A B C B C D
3 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
levels
Isso é usado em conjunto com o keysargumento. Quando levelsfor deixado como seu valor padrão de None, o Pandas pegará os valores exclusivos de cada nível do resultante MultiIndexe os usará como o objeto usado no index.levelsatributo resultante .
níveis : lista de sequências, padrão Nenhum
Níveis específicos (valores exclusivos) para usar para construir um MultiIndex. Caso contrário, eles serão inferidos das chaves.
Se o Pandas já infere quais deveriam ser esses níveis, qual a vantagem de especificá-los nós mesmos? Vou mostrar um exemplo e deixar para você pensar em outras razões pelas quais isso pode ser útil.
Exemplo
De acordo com a documentação, o levelsargumento é uma lista de sequências. Isso significa que podemos usar outro pandas.Indexcomo uma dessas sequências.
Considere o quadro de dados dfque é a concatenação de d1, d2e d3:
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'])
df
First Second Fourth
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
Os níveis do objeto colunas são:
print(df, *df.columns.levels, sep='\n')
Index(['First', 'Second', 'Fourth'], dtype='object')
Index(['A', 'B', 'C', 'D'], dtype='object')
Se usarmos sumem um groupby, obtemos:
df.groupby(axis=1, level=0).sum()
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
Mas e se, em vez de, ['First', 'Second', 'Fourth']houvesse outra categoria ausente chamada Thirde Fifth? E eu queria que eles fossem incluídos nos resultados de uma groupbyagregação? Podemos fazer isso se tivermos um pandas.CategoricalIndex. E podemos especificar isso com antecedência com o levelsargumento.
Então, em vez disso, vamos definir dfcomo:
cats = ['First', 'Second', 'Third', 'Fourth', 'Fifth']
lvl = pd.CategoricalIndex(cats, categories=cats, ordered=True)
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'],
levels=[lvl]
)
df
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
Mas o primeiro nível do objeto de colunas é:
df.columns.levels[0]
CategoricalIndex(
['First', 'Second', 'Third', 'Fourth', 'Fifth'],
categories=['First', 'Second', 'Third', 'Fourth', 'Fifth'],
ordered=True, dtype='category')
E nosso groupbyresumo se parece com:
df.groupby(axis=1, level=0).sum()
First Second Third Fourth Fifth
1 0.0 1.5 0.0 2.4 0.0
2 0.6 1.5 0.0 0.0 0.0
3 0.6 0.0 0.0 2.4 0.0
names
Isso é usado para nomear os níveis de um resultante MultiIndex. O comprimento da nameslista deve corresponder ao número de níveis no resultado MultiIndex.
nomes : lista, padrão Nenhum
Nomes para os níveis no índice hierárquico resultante
# axis=0 | # axis=1
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
keys=[0, 1], | axis=1, keys=[0, 1],
names=['lvl0', 'lvl1']) | names=['lvl0', 'lvl1'])
----------------------------- | ----------------------------------
A B C D | lvl0 0 1
lvl0 lvl1 | lvl1 A B C B C D
0 2 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
2 NaN 0.4 0.5 0.6 |
verify_integrity
Documentação autoexplicativa
verify_integrity : boolean, default False
Verifique se o novo eixo concatenado contém duplicatas. Isso pode ser muito caro em relação à concatenação de dados real.
Como o índice resultante da concatenação d1e d2não é exclusivo, ele falhará na verificação de integridade.
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
E
pd.concat([d1, d2], verify_integrity=True)
> ValueError: os índices têm valores sobrepostos: [2]