En el proceso de responder a esta pregunta por mí mismo, aprendí muchas cosas y quería armar un catálogo de ejemplos y alguna explicación.
La respuesta específica al punto del levelsargumento llegará hacia el final.
pandas.concat: El manual perdido
Enlace a la documentación actual
Importaciones y definición 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
El primer argumento con el que nos encontramos es objs:
objs : una secuencia o mapeo de objetos Series, DataFrame o Panel. Si se pasa un dict, las claves ordenadas se utilizarán como argumento de claves, a menos que se pase, en cuyo caso se seleccionarán los valores (ver más abajo). Cualquier objeto None se eliminará silenciosamente a menos que todos sean None, en cuyo caso se generará un ValueError
- Normalmente vemos esto usado con una lista de objetos
Seriesu DataFrame.
- Les mostraré que también
dictpuede ser muy útil.
- También se pueden usar generadores y pueden ser útiles cuando se usan
mapcomo enmap(f, list_of_df)
Por ahora, nos quedaremos con una lista de algunos de los objetos DataFramey Seriesdefinidos anteriormente. Más MultiIndexadelante , mostraré cómo se pueden aprovechar los diccionarios para obtener resultados muy útiles .
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
El segundo argumento que encontramos es axiscuyo valor predeterminado es 0:
eje : {0 / 'índice', 1 / 'columnas'}, predeterminado 0 El eje para concatenar.
Dos DataFrames con axis=0(apilados)
Para valores de 0o indexqueremos decir: "Alinear a lo largo de las columnas y agregar al índice".
Como se muestra arriba, donde usamos axis=0, porque 0es el valor predeterminado, y vemos que el índice de d2extiende el índice de a d1pesar de que haya una superposición del 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
Dos DataFrames con axis=1(lado a lado)
Para valores 1o columnsqueremos decir: "Alinear a lo largo del índice y agregar a las columnas",
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 el índice resultante es la unión de índices y las columnas resultantes son la extensión de las columnas de d1por las columnas de d2.
Dos (o tres) Seriescon axis=0(apilados)
Al combinar a lo pandas.Serieslargo axis=0, obtenemos a pandas.Series. El nombre del resultado Seriesserá a Nonemenos que todos los que Seriesse combinen tengan el mismo nombre. Preste atención a 'Name: A'cuando imprimimos el resultado Series. Cuando no está presente, podemos asumir que el Seriesnombre lo es 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
Dos (o tres) Seriescon axis=1(lado a lado)
Cuando se combina pandas.Seriesa lo largo de axis=1, es el nameatributo que nos referimos a fin de inferir un nombre de columna en el resultante 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
Mixto Seriesy DataFramecon axis=0(apilado)
Al realizar una concatenación de a Seriesy a lo DataFramelargo axis=0, convertimos todo Seriesen una sola columna DataFrames.
Tenga especial en cuenta que se trata de una concatenación axis=0; eso significa extender el índice (filas) mientras alinea las columnas. En los ejemplos siguientes, vemos que el índice se convierte en [2, 3, 2, 3]una adición indiscriminada de índices. Las columnas no se superponen a menos que fuerce el nombre de la Seriescolumna con el 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
Puede ver que los resultados pd.concat([s1, d1])son los mismos que si lo hubiera realizado to_frameyo mismo.
Sin embargo, puedo controlar el nombre de la columna resultante con un parámetro para to_frame. Cambiar el nombre de Seriescon el renamemétodo no controla el nombre de la columna en el 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
Mixto Seriesy DataFramecon axis=1(lado a lado)
Esto es bastante intuitivo. SeriesEl nombre de columna tiene como valor predeterminado una enumeración de dichos Seriesobjetos cuando un nameatributo no está disponible.
| 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
El tercer argumento es joinque describe si la combinación resultante debe ser una combinación externa (predeterminada) o una combinación interna.
join : {'interno', 'externo'}, predeterminado 'externo'
Cómo manejar índices en otros ejes.
Resulta, no hay es lefto rightopción, ya que pd.concatpuede manejar más de sólo dos objetos de fusionarse.
En el caso de d1y d2, las opciones se ven así:
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
El cuarto argumento es lo que nos permite leftfusionarnos y más.
join_axes : lista de objetos de índice
Índices específicos para usar para los otros n - 1 ejes en lugar de realizar la lógica de conjunto interno / externo.
Fusionar a la izquierda
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
Fusionar a la derecha
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 : booleano, predeterminado False
Si es verdadero, no utilice los valores de índice a lo largo del eje de concatenación. El eje resultante se etiquetará como 0, ..., n - 1. Esto es útil si está concatenando objetos donde el eje de concatenación no tiene información de indexación significativa. Tenga en cuenta que los valores de índice en los otros ejes aún se respetan en la combinación.
Como cuando apilo d1encima d2, si no me importan los valores del índice, podría restablecerlos o ignorarlos.
| 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
Y al 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 pasar una lista de valores escalares o tuplas para asignar valores de tupla o escalares al MultiIndex correspondiente. La longitud de la lista pasada debe ser la misma que la cantidad de elementos que se concatenan.
claves : secuencia, predeterminado Ninguno
Si se pasan varios niveles, debe contener tuplas. Construya un índice jerárquico usando las claves pasadas como el nivel más externo
axis=0
Al concatenar Seriesobjetos a lo largo axis=0(ampliar el índice).
Esas claves se convierten en un nuevo nivel inicial de un MultiIndexobjeto en el 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
Sin embargo, podemos usar más que valores escalares en el keysargumento para crear un MultiIndex. Aquí pasamos tuplesde longitud 2 anteponer dos nuevos niveles de a 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
Es un poco diferente cuando se extiende a lo largo de columnas. Cuando usamos axis=0(ver arriba) nuestro keysactuaba como MultiIndexniveles además del índice existente. Porque axis=1nos referimos a un eje que los Seriesobjetos no tienen, a saber, el columnsatributo.
Las variaciones de dos
Serieswtih
axis=1
Observe que nombrar s1y es s2importante siempre que no keysse pasen, pero se anula si keysse pasan.
| | | 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
MultiIndexcon
Seriesy
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
Dos
DataFramecon
axis=1
Al igual que con los axis=0ejemplos, keysagregue niveles a a MultiIndex, pero esta vez al objeto almacenado en el 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
Seriesy
DataFramecon
axis=1
Esto es complicado. En este caso, un valor de clave escalar no puede actuar como el único nivel de índice para el Seriesobjeto cuando se convierte en una columna y también actúa como el primer nivel de a MultiIndexpara DataFrame. Entonces, Pandas usará nuevamente el nameatributo del Seriesobjeto como fuente del nombre de la columna.
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
Limitaciones
keyse
MultiIndexinferencias.
Pandas solo parece inferir los nombres de las columnas a partir del Seriesnombre, pero no completará los espacios en blanco cuando se haga una concatenación análoga entre marcos de datos con un número diferente de niveles de columna.
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
Luego, concatene esto con otro marco de datos con un solo nivel en el objeto de columnas y Pandas se negará a intentar hacer tuplas del MultiIndexobjeto y combinará todos los marcos de datos como si fueran un solo nivel de objetos, escalares y 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
Pasando un en dictlugar de unlist
Al pasar un diccionario, pandas.concatutilizará las claves del diccionario 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
Esto se usa junto con el keysargumento. Cuando levelsse deja como su valor predeterminado de None, Pandas tomará los valores únicos de cada nivel del resultado MultiIndexy lo usará como el objeto usado en el index.levelsatributo resultante .
niveles : lista de secuencias, por defecto Ninguno
Niveles específicos (valores únicos) para usar para construir un MultiIndex. De lo contrario, se deducirán de las claves.
Si Pandas ya infiere cuáles deberían ser estos niveles, ¿qué ventaja hay en especificarlo nosotros mismos? Le mostraré un ejemplo y dejaré que usted piense en otras razones por las que esto podría ser útil.
Ejemplo
Según la documentación, el levelsargumento es una lista de secuencias. Esto significa que podemos usar otro pandas.Indexcomo una de esas secuencias.
Considere la trama de datos dfque es la concatenación de d1, d2y 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
Los niveles del objeto columnas son:
print(df, *df.columns.levels, sep='\n')
Index(['First', 'Second', 'Fourth'], dtype='object')
Index(['A', 'B', 'C', 'D'], dtype='object')
Si usamos sumdentro de un groupbyobtenemos:
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
Pero, ¿y si en lugar de ['First', 'Second', 'Fourth']hubiera otras categorías faltantes nombradasThird y Fifth? ¿Y quería que se incluyeran en los resultados de una groupbyagregación? Podemos hacer esto si tuviéramos un pandas.CategoricalIndex. Y podemos especificar eso de antemano con el levelsargumento.
Entonces, en cambio, definamos df como:
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
Pero el primer nivel del objeto de columnas es:
df.columns.levels[0]
CategoricalIndex(
['First', 'Second', 'Third', 'Fourth', 'Fifth'],
categories=['First', 'Second', 'Third', 'Fourth', 'Fifth'],
ordered=True, dtype='category')
Y nuestro groupbyresumen se ve así:
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
Se utiliza para nombrar los niveles de un resultado MultiIndex. La longitud delnames lista debe coincidir con el número de niveles del archivo MultiIndex.
nombres : lista, por defecto Ninguno
Nombres para los niveles en el índice jerá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
Documentación autoexplicativa
verify_integrity : booleano, predeterminado Falso
Compruebe si el nuevo eje concatenado contiene duplicados. Esto puede resultar muy caro en relación con la concatenación de datos real.
Debido a que el índice resultante de la concatenación d1y d2no es único, fallaría la verificación de integridad.
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
Y
pd.concat([d1, d2], verify_integrity=True)
> ValueError: los índices tienen valores superpuestos: [2]
pd.concat(..., levels=[lvl]).groupby(axis=1, level=0).sum()produce un resultado diferente alpd.concat(..., levels=[cats]).groupby(axis=1, level=0).sum(). ¿Sabes por qué? Los documentos solo dicen quelevelsdebería haber una lista de secuencias.Passing a dict instead of a listnecesita un ejemplo usando un dictado, no una lista.dictejemplo, gracias. La razón es quelvles un índice categórico ycatses solo una lista. Al agrupar por tipo categórico, las categorías que faltan se completan con ceros y nulos cuando corresponde. Vea esto