En répondant moi-même à cette question, j'ai appris beaucoup de choses et je voulais rassembler un catalogue d'exemples et quelques explications.
La réponse spécifique au point de l' levelsargument viendra vers la fin.
pandas.concat: Le manuel manquant
Lien vers la documentation actuelle
Importation et définition d'objets
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])
Arguments
objs
Le premier argument que nous rencontrons est objs:
objs : une séquence ou un mappage d'objets Series, DataFrame ou Panel Si un dict est passé, les clés triées seront utilisées comme argument de clés, à moins qu'il ne soit passé, auquel cas les valeurs seront sélectionnées (voir ci-dessous). Tous les objets None seront supprimés silencieusement à moins qu'ils ne soient tous None, auquel cas une ValueError sera déclenchée
- Nous voyons généralement cela utilisé avec une liste d' objets
Seriesou DataFrame.
- Je vais montrer que cela
dictpeut également être très utile.
- Les générateurs peuvent également être utilisés et peuvent être utiles lors de l'utilisation
mapcomme dansmap(f, list_of_df)
Pour l'instant, nous allons nous en tenir à une liste de certains des objets DataFrameet Seriesdéfinis ci-dessus. Je vais montrer comment les dictionnaires peuvent être exploités pour donner des MultiIndexrésultats très utiles plus tard.
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
Le deuxième argument que nous rencontrons est axisdont la valeur par défaut est 0:
axis : {0 / 'index', 1 / 'columns'}, par défaut 0 L'axe à concaténer le long.
Deux DataFrames avec axis=0(empilés)
Pour les valeurs de 0ou indexnous voulons dire: "Alignez le long des colonnes et ajoutez à l'index".
Comme indiqué ci-dessus où nous avons utilisé axis=0, car 0est la valeur par défaut, et nous voyons que l'index de d2étend l'index de d1malgré le chevauchement de la valeur 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
Deux DataFrames avec axis=1(côte à côte)
Pour les valeurs 1ou columnsnous voulons dire: "Alignez le long de l'index et ajoutez aux colonnes",
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
Nous pouvons voir que l'index résultant est l'union des indices et les colonnes résultantes sont l'extension des colonnes de d1par les colonnes ded2 .
Deux (ou trois) Seriesavecaxis=0 (empilés)
Lors de la combinaison le pandas.Serieslong axis=0, nous récupérons un pandas.Series. Le nom du résultat Seriessera Nonesauf si tous les éléments Seriescombinés portent le même nom. Faites attention au 'Name: A'moment où nous imprimons le résultat Series. Lorsqu'il n'est pas présent, nous pouvons supposer que le Seriesnom 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
Deux (ou trois) Seriesavec axis=1(côte à côte)
Lors de la combinaison le pandas.Serieslong axis=1, il est l' nameattribut que nous appelons pour en déduire un nom de colonne dans la résultante 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
Mixte Serieset DataFrameavec axis=0(empilé)
Lors de la concaténation de a Serieset DataFramelong axis=0, nous convertissons tout Seriesen une seule colonne DataFrames.
Notez tout particulièrement qu'il s'agit d'une concaténation axis=0; cela signifie étendre l'index (lignes) tout en alignant les colonnes. Dans les exemples ci-dessous, nous voyons que l'index devient [2, 3, 2, 3]qui est une adjonction indiscriminée d'indices. Les colonnes ne se chevauchent pas sauf si je force la dénomination de la Seriescolonne avec l'argument à 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
Vous pouvez voir les résultats de pd.concat([s1, d1]) sont les mêmes que si j'avais effectué le to_framemoi - même.
Cependant, je peux contrôler le nom de la colonne résultante avec un paramètre à to_frame. Renommer le Seriesavec la renameméthode ne contrôle pas le nom de la colonne dans le résultat 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
Mixte Serieset DataFrameavecaxis=1 (côte à côte)
C'est assez intuitif. SeriesLe nom de la colonne est par défaut une énumération de ces Seriesobjets lorsqu'un nameattribut n'est pas 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
Le troisième argument est joinqui décrit si la fusion résultante doit être une fusion externe (par défaut) ou une fusion interne.
join : {'interne', 'externe'}, par défaut 'externe'
Comment gérer les index sur d'autres axes.
Il s'avère qu'il n'y a pas d' option leftou rightqui pd.concatpeut gérer plus de deux objets à fusionner.
Dans le cas de d1et d2, les options ressemblent à:
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
Le quatrième argument est la chose qui nous permet de faire notre leftfusion et plus encore.
join_axes : liste des objets Index Index
spécifiques à utiliser pour les autres n - 1 axes au lieu d'exécuter une logique d'ensemble interne / externe.
Fusion à gauche
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
Fusion à droite
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, par défaut False
Si True, n'utilisez pas les valeurs d'index le long de l'axe de concaténation. L'axe résultant sera étiqueté 0, ..., n - 1. Ceci est utile si vous concaténez des objets où l'axe de concaténation ne contient pas d'informations d'indexation significatives. Notez que les valeurs d'index sur les autres axes sont toujours respectées dans la jointure.
Comme quand je d1superpose d2, si je ne me soucie pas des valeurs d'index, je pourrais les réinitialiser ou les ignorer.
| 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
Et lors de l'utilisation 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
Nous pouvons passer une liste de valeurs scalaires ou de tuples afin d'attribuer des valeurs de tuple ou de scalaire au MultiIndex correspondant. La longueur de la liste transmise doit être de la même longueur que le nombre d'éléments concaténés.
keys : sequence, default None
Si plusieurs niveaux sont passés, doit contenir des tuples. Construire un index hiérarchique en utilisant les clés transmises comme niveau le plus externe
axis=0
Lors de la concaténation d' Seriesobjets le longaxis=0 (extension de l'index).
Ces clés deviennent un nouveau niveau initial d'un MultiIndexobjet dans l'attribut index.
# 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
Cependant, nous pouvons utiliser plus que des valeurs scalaires dans l' keysargument pour créer un fichier encore plus profond MultiIndex. Ici, nous passons tuplesde longueur 2 le préfixe de deux nouveaux niveaux 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
C'est un peu différent lors de l'extension le long des colonnes. Lorsque nous avons utilisé axis=0(voir ci-dessus), nous avons keysagi comme des MultiIndexniveaux en plus de l'index existant. Car axis=1, nous faisons référence à un axe quiSeries objets n'ont pas, à savoir l' columnsattribut.
Variations de deux
Seriesavec
axis=1
Notez que nommer s1et s2importe tant que no keysest passé, mais il est remplacé s'il keysest passé.
| | | 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
MultiIndexavec
Serieset
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
Deux
DataFrameavec
axis=1
Comme pour les axis=0exemples, keysajoutez des niveaux à a MultiIndex, mais cette fois à l'objet stocké dans l' columnsattribut.
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
Serieset
DataFrameavec
axis=1
C'est délicat. Dans ce cas, une valeur de clé scalaire ne peut pas agir comme le seul niveau d'index pour l' Seriesobjet lorsqu'il devient une colonne tout en agissant également comme le premier niveau de a MultiIndexpour le DataFrame. Ainsi, Pandas utilisera à nouveau l' nameattribut de l' Seriesobjet comme source du nom de la colonne.
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
Limitations
keyset
MultiIndexinférence.
Pandas semble uniquement déduire les noms de colonnes à partir du Seriesnom, mais il ne remplira pas les espaces vides lors d'une concaténation analogue entre des blocs de données avec un nombre différent de niveaux de colonne.
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
Puis concaténez ceci avec un autre bloc de données avec un seul niveau dans l'objet de colonnes et Pandas refusera d'essayer de créer des tuples de l' MultiIndexobjet et de combiner tous les blocs de données comme s'il s'agissait d'un seul niveau d'objets, de scalaires et de tuples.
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
Passer un dictau lieu d'unlist
Lors du passage d'un dictionnaire, pandas.concatutilisera les clés du dictionnaire comme keysparamètre.
# 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
Ceci est utilisé en conjonction avec l' keysargument. Lorsqu'il levelsest laissé comme valeur par défaut de None, Pandas prendra les valeurs uniques de chaque niveau du résultat MultiIndexet l'utilisera comme objet utilisé dans l' index.levelsattribut résultant .
niveaux : liste de séquences, par défaut Aucun
Niveaux spécifiques (valeurs uniques) à utiliser pour construire un MultiIndex. Sinon, ils seront déduits des clés.
Si Pandas déduit déjà ce que devraient être ces niveaux, quel avantage y a-t-il à le spécifier nous-mêmes? Je vais vous montrer un exemple et vous laisser le soin de trouver d'autres raisons pour lesquelles cela pourrait être utile.
Exemple
Selon la documentation, l' levelsargument est une liste de séquences. Cela signifie que nous pouvons en utiliser une autre pandas.Indexcomme l'une de ces séquences.
Considérez le bloc de données dfqui est la concaténation de d1, d2et 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
Les niveaux de l'objet colonnes sont:
print(df, *df.columns.levels, sep='\n')
Index(['First', 'Second', 'Fourth'], dtype='object')
Index(['A', 'B', 'C', 'D'], dtype='object')
Si nous utilisons sumdans un groupbynous obtenons:
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
Mais que se passerait- ['First', 'Second', 'Fourth']il si à la place il y avait une autre catégorie manquante nommée Thirdet Fifth? Et je voulais les inclure dans les résultats d'une groupbyagrégation? Nous pouvons le faire si nous avions un fichier pandas.CategoricalIndex. Et nous pouvons spécifier cela à l'avance avec lelevels argument.
Alors, définissons plutôt dfcomme:
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
Mais le premier niveau de l'objet de colonnes est:
df.columns.levels[0]
CategoricalIndex(
['First', 'Second', 'Third', 'Fourth', 'Fifth'],
categories=['First', 'Second', 'Third', 'Fourth', 'Fifth'],
ordered=True, dtype='category')
Et notre groupbyrésumé ressemble à:
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
Ceci est utilisé pour nommer les niveaux d'un résultat MultiIndex. La longueur de la namesliste doit correspondre au nombre de niveaux dans le résultat MultiIndex.
names : liste, par défaut Aucun
Noms des niveaux dans l'index hiérarchique résultant
# 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
Documentation explicite
verify_integrity : booléen, par défaut False
Vérifie si le nouvel axe concaténé contient des doublons. Cela peut être très coûteux par rapport à la concaténation réelle des données.
Étant donné que l'indice résultant de concaténer d1et d2n'est pas unique, il échouerait le contrôle d'intégrité.
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
Et
pd.concat([d1, d2], verify_integrity=True)
> ValueError: les index ont des valeurs qui se chevauchent: [2]