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' levels
argument 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
Series
ou DataFrame
.
- Je vais montrer que cela
dict
peut également être très utile.
- Les générateurs peuvent également être utilisés et peuvent être utiles lors de l'utilisation
map
comme dansmap(f, list_of_df)
Pour l'instant, nous allons nous en tenir à une liste de certains des objets DataFrame
et Series
définis ci-dessus. Je vais montrer comment les dictionnaires peuvent être exploités pour donner des MultiIndex
ré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 axis
dont la valeur par défaut est 0
:
axis : {0 / 'index', 1 / 'columns'}, par défaut 0 L'axe à concaténer le long.
Deux DataFrame
s avec axis=0
(empilés)
Pour les valeurs de 0
ou index
nous voulons dire: "Alignez le long des colonnes et ajoutez à l'index".
Comme indiqué ci-dessus où nous avons utilisé axis=0
, car 0
est la valeur par défaut, et nous voyons que l'index de d2
étend l'index de d1
malgré 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 DataFrame
s avec axis=1
(côte à côte)
Pour les valeurs 1
ou columns
nous 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 d1
par les colonnes ded2
.
Deux (ou trois) Series
avecaxis=0
(empilés)
Lors de la combinaison le pandas.Series
long axis=0
, nous récupérons un pandas.Series
. Le nom du résultat Series
sera None
sauf si tous les éléments Series
combiné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 Series
nom 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) Series
avec axis=1
(côte à côte)
Lors de la combinaison le pandas.Series
long axis=1
, il est l' name
attribut 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 Series
et DataFrame
avec axis=0
(empilé)
Lors de la concaténation de a Series
et DataFrame
long axis=0
, nous convertissons tout Series
en une seule colonne DataFrame
s.
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 Series
colonne 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_frame
moi - même.
Cependant, je peux contrôler le nom de la colonne résultante avec un paramètre à to_frame
. Renommer le Series
avec la rename
mé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 Series
et DataFrame
avecaxis=1
(côte à côte)
C'est assez intuitif. Series
Le nom de la colonne est par défaut une énumération de ces Series
objets lorsqu'un name
attribut 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 join
qui 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 left
ou right
qui pd.concat
peut gérer plus de deux objets à fusionner.
Dans le cas de d1
et 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 left
fusion 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 d1
superpose 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' Series
objets le longaxis=0
(extension de l'index).
Ces clés deviennent un nouveau niveau initial d'un MultiIndex
objet 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' keys
argument pour créer un fichier encore plus profond MultiIndex
. Ici, nous passons tuples
de 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 keys
agi comme des MultiIndex
niveaux en plus de l'index existant. Car axis=1
, nous faisons référence à un axe quiSeries
objets n'ont pas, à savoir l' columns
attribut.
Variations de deux
Series
avec
axis=1
Notez que nommer s1
et s2
importe tant que no keys
est passé, mais il est remplacé s'il keys
est 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
MultiIndex
avec
Series
et
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
DataFrame
avec
axis=1
Comme pour les axis=0
exemples, keys
ajoutez des niveaux à a MultiIndex
, mais cette fois à l'objet stocké dans l' columns
attribut.
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
et
DataFrame
avec
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' Series
objet lorsqu'il devient une colonne tout en agissant également comme le premier niveau de a MultiIndex
pour le DataFrame
. Ainsi, Pandas utilisera à nouveau l' name
attribut de l' Series
objet 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
keys
et
MultiIndex
inférence.
Pandas semble uniquement déduire les noms de colonnes à partir du Series
nom, 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' MultiIndex
objet 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 dict
au lieu d'unlist
Lors du passage d'un dictionnaire, pandas.concat
utilisera les clés du dictionnaire comme keys
paramè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' keys
argument. Lorsqu'il levels
est laissé comme valeur par défaut de None
, Pandas prendra les valeurs uniques de chaque niveau du résultat MultiIndex
et l'utilisera comme objet utilisé dans l' index.levels
attribut 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' levels
argument est une liste de séquences. Cela signifie que nous pouvons en utiliser une autre pandas.Index
comme l'une de ces séquences.
Considérez le bloc de données df
qui est la concaténation de d1
, d2
et 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 sum
dans un groupby
nous 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 Third
et Fifth
? Et je voulais les inclure dans les résultats d'une groupby
agré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 df
comme:
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 groupby
ré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 names
liste 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 d1
et d2
n'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]
pd.concat(..., levels=[lvl]).groupby(axis=1, level=0).sum()
produit un résultat différent depd.concat(..., levels=[cats]).groupby(axis=1, level=0).sum()
. Est-ce que tu sais pourquoi? Les documents indiquent seulement quelevels
devrait être une liste de séquences.Passing a dict instead of a list
besoin d'un exemple utilisant un dict, pas une liste.dict
exemple, merci. La raison en est qu'illvl
s'agit d'un index catégorique etcats
n'est qu'une liste. Lors du regroupement par type catégoriel, les catégories manquantes sont remplies de zéros et de valeurs nulles, le cas échéant. Voir ça