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 levels
argumento 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
Series
u DataFrame
.
- Les mostraré que también
dict
puede ser muy útil.
- También se pueden usar generadores y pueden ser útiles cuando se usan
map
como enmap(f, list_of_df)
Por ahora, nos quedaremos con una lista de algunos de los objetos DataFrame
y Series
definidos anteriormente. Más MultiIndex
adelante , 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 axis
cuyo valor predeterminado es 0
:
eje : {0 / 'índice', 1 / 'columnas'}, predeterminado 0 El eje para concatenar.
Dos DataFrame
s con axis=0
(apilados)
Para valores de 0
o index
queremos decir: "Alinear a lo largo de las columnas y agregar al índice".
Como se muestra arriba, donde usamos axis=0
, porque 0
es el valor predeterminado, y vemos que el índice de d2
extiende el índice de a d1
pesar 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 DataFrame
s con axis=1
(lado a lado)
Para valores 1
o columns
queremos 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 d1
por las columnas de d2
.
Dos (o tres) Series
con axis=0
(apilados)
Al combinar a lo pandas.Series
largo axis=0
, obtenemos a pandas.Series
. El nombre del resultado Series
será a None
menos que todos los que Series
se combinen tengan el mismo nombre. Preste atención a 'Name: A'
cuando imprimimos el resultado Series
. Cuando no está presente, podemos asumir que el Series
nombre 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) Series
con axis=1
(lado a lado)
Cuando se combina pandas.Series
a lo largo de axis=1
, es el name
atributo 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 Series
y DataFrame
con axis=0
(apilado)
Al realizar una concatenación de a Series
y a lo DataFrame
largo axis=0
, convertimos todo Series
en una sola columna DataFrame
s.
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 Series
columna 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_frame
yo mismo.
Sin embargo, puedo controlar el nombre de la columna resultante con un parámetro para to_frame
. Cambiar el nombre de Series
con el rename
mé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 Series
y DataFrame
con axis=1
(lado a lado)
Esto es bastante intuitivo. Series
El nombre de columna tiene como valor predeterminado una enumeración de dichos Series
objetos cuando un name
atributo 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 join
que 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 left
o right
opción, ya que pd.concat
puede manejar más de sólo dos objetos de fusionarse.
En el caso de d1
y 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 left
fusionarnos 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 d1
encima 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 Series
objetos a lo largo axis=0
(ampliar el índice).
Esas claves se convierten en un nuevo nivel inicial de un MultiIndex
objeto 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 keys
argumento para crear un MultiIndex
. Aquí pasamos tuples
de 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 keys
actuaba como MultiIndex
niveles además del índice existente. Porque axis=1
nos referimos a un eje que los Series
objetos no tienen, a saber, el columns
atributo.
Las variaciones de dos
Series
wtih
axis=1
Observe que nombrar s1
y es s2
importante siempre que no keys
se pasen, pero se anula si keys
se 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
MultiIndex
con
Series
y
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
DataFrame
con
axis=1
Al igual que con los axis=0
ejemplos, keys
agregue niveles a a MultiIndex
, pero esta vez al objeto almacenado en el columns
atributo.
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
Series
y
DataFrame
con
axis=1
Esto es complicado. En este caso, un valor de clave escalar no puede actuar como el único nivel de índice para el Series
objeto cuando se convierte en una columna y también actúa como el primer nivel de a MultiIndex
para DataFrame
. Entonces, Pandas usará nuevamente el name
atributo del Series
objeto 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
keys
e
MultiIndex
inferencias.
Pandas solo parece inferir los nombres de las columnas a partir del Series
nombre, 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 MultiIndex
objeto 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 dict
lugar de unlist
Al pasar un diccionario, pandas.concat
utilizará las claves del diccionario como keys
pará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 keys
argumento. Cuando levels
se deja como su valor predeterminado de None
, Pandas tomará los valores únicos de cada nivel del resultado MultiIndex
y lo usará como el objeto usado en el index.levels
atributo 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 levels
argumento es una lista de secuencias. Esto significa que podemos usar otro pandas.Index
como una de esas secuencias.
Considere la trama de datos df
que es la concatenación de d1
, d2
y 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 sum
dentro de un groupby
obtenemos:
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 groupby
agregación? Podemos hacer esto si tuviéramos un pandas.CategoricalIndex
. Y podemos especificar eso de antemano con el levels
argumento.
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 groupby
resumen 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 d1
y d2
no 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 quelevels
debería haber una lista de secuencias.Passing a dict instead of a list
necesita un ejemplo usando un dictado, no una lista.dict
ejemplo, gracias. La razón es quelvl
es un índice categórico ycats
es solo una lista. Al agrupar por tipo categórico, las categorías que faltan se completan con ceros y nulos cuando corresponde. Vea esto