Cet article est une traduction automatique partielle de la documentation officielle de Pandas User Guide --MultiIndex / Advanced Indexing. C'est une modification d'une phrase non naturelle. La dernière version de Pandas est "0.25.3" au moment de la rédaction de cet article, mais le texte de cet article est basé sur le document de la version de développement "1.0.0" en tenant compte de l'avenir.
Si vous avez des erreurs de traduction, des traductions alternatives, des questions, etc., veuillez utiliser la section commentaires ou modifier la demande.
Ce chapitre décrit l'indexation avec multi-index (# Index hiérarchique (multi-index)) et d'autres fonctionnalités d'indexation avancées (#Other Advanced Indexing Features).
Pour la documentation de base sur l'indexation, consultez Indexation et sélection des données (https://qiita.com/nkay/items/d322ed9d9a14bdbf14cb).
: avertissement: ** Avertissement ** Le fait que l'opération d'affectation renvoie une copie ou une référence dépend du contexte. Cela s'appelle une affectation chaînée` et doit être évité. Voir [Retourner une vue ou une copie](https://qiita.com/nkay/items/d322ed9d9a14bdbf14cb#Returning a View ou Copy).
Voir aussi livre de recettes pour des opérations plus avancées.
Les index hiérarchiques et à plusieurs niveaux sont très utiles pour l'analyse et la manipulation avancées des données, en particulier lorsqu'il s'agit de données de grande dimension. En substance, vous pouvez stocker et manipuler n'importe quel nombre de dimensions dans une structure de données de faible dimension telle que «Series» (1d) ou «DataFrame» (2d).
Cette section montre ce que signifie un index «hiérarchique» et comment il s'intègre à toutes les fonctionnalités d'indexation Pandas décrites ci-dessus et dans les chapitres précédents. Plus tard, regroupement (https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#groupby) et pivotement et remodelage des données (https://pandas.pydata.org/) Lorsque nous parlons de pandas-docs / stable / user_guide / reshaping.html # reshaping), nous vous présenterons une application importante pour vous expliquer comment elle peut vous aider à structurer vos données à des fins d'analyse.
Voir aussi le livre de recettes pour des méthodes plus avancées.
_ Modifié dans la version 0.24.0 _: MultiIndex.labels
a été changé en MultiIndex.codes
et MultiIndex.set_labels
a été changé en MultiIndex.set_codes
.
Dans les objets Pandas, l'objet ʻIndex est couramment utilisé pour stocker les étiquettes d'axe, tandis que l'objet
MultiIndexen est une version hiérarchique. Vous pouvez considérer
MultiIndexcomme un tableau de taples, où chaque tapple est unique.
MultiIndex utilise une liste de tableaux (en utilisant
MultiIndex.from_arrays ()), un tableau de taples (en utilisant
MultiIndex.from_tuples () ) et (
MultiIndex.from_product () . Il peut être créé à partir d'un produit direct d'itérables,
DataFrame (en utilisant
MultiIndex.from_frame ()). Le constructeur ʻIndex
tente de renvoyer un MultiIndex
quand une liste de taples est passée. Vous trouverez ci-dessous différentes manières d'initialiser MultiIndex.
In [1]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
...: ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
...:
In [2]: tuples = list(zip(*arrays))
In [3]: tuples
Out[3]:
[('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')]
In [4]: index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
In [5]: index
Out[5]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
In [6]: s = pd.Series(np.random.randn(8), index=index)
In [7]: s
Out[7]:
first second
bar one 0.469112
two -0.282863
baz one -1.509059
two -1.135632
foo one 1.212112
two -0.173215
qux one 0.119209
two -1.044236
dtype: float64
Si vous voulez toutes les combinaisons (produits directs) des deux éléments itérables, il est pratique d'utiliser la méthode MultiIndex.from_product ()
.
In [8]: iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
In [9]: pd.MultiIndex.from_product(iterables, names=['first', 'second'])
Out[9]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
Vous pouvez également utiliser la méthode MultiIndex.from_frame ()
pour créer un MultiIndex
directement à partir du DataFrame
. C'est une méthode qui complète MultiIndex.to_frame ()
.
_ À partir de la version 0.24.0 _
In [10]: df = pd.DataFrame([['bar', 'one'], ['bar', 'two'],
....: ['foo', 'one'], ['foo', 'two']],
....: columns=['first', 'second'])
....:
In [11]: pd.MultiIndex.from_frame(df)
Out[11]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('foo', 'one'),
('foo', 'two')],
names=['first', 'second'])
Vous pouvez également créer automatiquement un MultiIndex
en passant la liste des tableaux directement à la Series
ou au DataFrame
, comme indiqué ci-dessous.
In [12]: arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
....: np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
....:
In [13]: s = pd.Series(np.random.randn(8), index=arrays)
In [14]: s
Out[14]:
bar one -0.861849
two -2.104569
baz one -0.494929
two 1.071804
foo one 0.721555
two -0.706771
qux one -1.039575
two 0.271860
dtype: float64
In [15]: df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
In [16]: df
Out[16]:
0 1 2 3
bar one -0.424972 0.567020 0.276232 -1.087401
two -0.673690 0.113648 -1.478427 0.524988
baz one 0.404705 0.577046 -1.715002 -1.039268
two -0.370647 -1.157892 -1.344312 0.844885
foo one 1.075770 -0.109050 1.643563 -1.469388
two 0.357021 -0.674600 -1.776904 -0.968914
qux one -1.294524 0.413738 0.276662 -0.472035
two -0.013960 -0.362543 -0.006154 -0.923061
Tous les constructeurs MultiIndex
prennent un argument names
qui stocke les propres noms de chaîne (étiquettes) du niveau. Si aucun nom n'est spécifié, «Aucun» sera attribué.
In [17]: df.index.names
Out[17]: FrozenList([None, None])
Cet index peut être défini sur l'axe dans n'importe quelle direction de l'objet Pandas, et le nombre de ** niveaux ** de l'index est arbitraire.
In [18]: df = pd.DataFrame(np.random.randn(3, 8), index=['A', 'B', 'C'], columns=index)
In [19]: df
Out[19]:
first bar baz foo qux
second one two one two one two one two
A 0.895717 0.805244 -1.206412 2.565646 1.431256 1.340309 -1.170299 -0.226169
B 0.410835 0.813850 0.132003 -0.827317 -0.076467 -1.187678 1.130127 -1.436737
C -1.413681 1.607920 1.024180 0.569605 0.875906 -2.211372 0.974466 -2.006747
In [20]: pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6])
Out[20]:
first bar baz foo
second one two one two one two
first second
bar one -0.410001 -0.078638 0.545952 -1.219217 -1.226825 0.769804
two -1.281247 -0.727707 -0.121306 -0.097883 0.695775 0.341734
baz one 0.959726 -1.110336 -0.619976 0.149748 -0.732339 0.687738
two 0.176444 0.403310 -0.154951 0.301624 -2.179861 -1.369849
foo one -0.954208 1.462696 -1.743161 -0.826591 -0.345352 1.314232
two 0.690579 0.995761 2.396780 0.014871 3.357427 -0.317441
Nous avons "épargné" un index de niveau supérieur pour rendre la sortie de la console plus facile à voir. Vous pouvez contrôler l'affichage de l'index en utilisant l'option multi_sparse
de pandas.set_options ()
.
In [21]: with pd.option_context('display.multi_sparse', False):
....: df
....:
Il convient de rappeler qu'il est normal d'utiliser des taples comme une étiquette unique et indivisible.
In [22]: pd.Series(np.random.randn(8), index=tuples)
Out[22]:
(bar, one) -1.236269
(bar, two) 0.896171
(baz, one) -0.487602
(baz, two) -0.082240
(foo, one) -2.182937
(foo, two) 0.380396
(qux, one) 0.084844
(qux, two) 0.432390
dtype: float64
La raison pour laquelle «MultiIndex» est important est que vous pouvez utiliser «MultiIndex» pour effectuer des opérations de regroupement, de sélection et de changement de forme, comme expliqué dans les chapitres suivants et suivants. Comme vous le verrez dans une section ultérieure, vous pouvez travailler avec des données indexées hiérarchiquement sans avoir à créer vous-même explicitement le MultiIndex
. Cependant, si vous chargez des données à partir d'un fichier, vous pouvez générer votre propre MultiIndex
lors de la préparation du jeu de données.
get_level_values ()
La méthode est pour chaque niveau spécifique Renvoie un vecteur d'étiquettes de position.
In [23]: index.get_level_values(0)
Out[23]: Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
In [24]: index.get_level_values('second')
Out[24]: Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')
L'une des principales caractéristiques d'un index hiérarchique est la possibilité de sélectionner des données par une étiquette «partielle» qui identifie un sous-groupe dans les données. La sélection ** partielle ** "supprime" le niveau de l'index hiérarchique résultant exactement de la même manière que la sélection d'une colonne dans un DataFrame normal.
In [25]: df['bar']
Out[25]:
second one two
A 0.895717 0.805244
B 0.410835 0.813850
C -1.413681 1.607920
In [26]: df['bar', 'one']
Out[26]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
In [27]: df['bar']['one']
Out[27]:
A 0.895717
B 0.410835
C -1.413681
Name: one, dtype: float64
In [28]: s['qux']
Out[28]:
one -1.039575
two 0.271860
dtype: float64
Voir les sections avec des index hiérarchiques pour plus d'informations sur la sélection à un niveau plus profond.
MultiIndex
a été défini même s'il n'a pas été réellement utilisé Contient des index pour tous les niveaux. Vous remarquerez peut-être cela lors du découpage de l'index. Par exemple
In [29]: df.columns.levels # original MultiIndex
Out[29]: FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
In [30]: df[['foo','qux']].columns.levels # sliced
Out[30]: FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
Cela permet d'éviter les recalculs de niveau afin d'améliorer les performances de la tranche. Si vous voulez voir uniquement les niveaux utilisés, [get_level_values ()
](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.MultiIndex.get_level_values.html#pandas. Vous pouvez utiliser la méthode MultiIndex.get_level_values).
In [31]: df[['foo', 'qux']].columns.to_numpy()
Out[31]:
array([('foo', 'one'), ('foo', 'two'), ('qux', 'one'), ('qux', 'two')],
dtype=object)
# for a specific level
In [32]: df[['foo', 'qux']].columns.get_level_values(0)
Out[32]: Index(['foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
Pour reconstruire MultiIndex
avec uniquement des niveaux d'utilisation, [remove_unused_levels ()
](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.MultiIndex.remove_unused_levels.html# Vous pouvez utiliser la méthode pandas.MultiIndex.remove_unused_levels).
In [33]: new_mi = df[['foo', 'qux']].columns.remove_unused_levels()
In [34]: new_mi.levels
Out[34]: FrozenList([['foo', 'qux'], ['one', 'two']])
Les opérations entre des objets avec différents index qui ont «MultiIndex» sur l'axe fonctionnent comme prévu. L'alignement des données fonctionne comme un index de tapple.
In [35]: s + s[:-2]
Out[35]:
bar one -1.723698
two -4.209138
baz one -0.989859
two 2.143608
foo one 1.443110
two -1.413542
qux one NaN
two NaN
dtype: float64
In [36]: s + s[::2]
Out[36]:
bar one -1.723698
two NaN
baz one -0.989859
two NaN
foo one 1.443110
two NaN
qux one -2.079150
two NaN
dtype: float64
[Reindex ()
] de Series
/ DataFrames
(https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.reindex.html#pandas.DataFrame.reindex) La méthode peut également recevoir un autre "MultiIndex", ou une liste ou un tableau de tuples.
In [37]: s.reindex(index[:3])
Out[37]:
first second
bar one -0.861849
two -2.104569
baz one -0.494929
dtype: float64
In [38]: s.reindex([('foo', 'two'), ('bar', 'one'), ('qux', 'one'), ('baz', 'one')])
Out[38]:
foo two -0.706771
bar one -0.861849
qux one -1.039575
baz one -0.494929
dtype: float64
Obtenir MultiIndex
pour faire une indexation avancée avec .loc
est un peu difficile sur le plan syntaxique, mais nous avons fait tout notre possible pour y arriver. En général, les clés MultiIndex prennent la forme de taples. Par exemple, ce qui suit fonctionne comme prévu.
In [39]: df = df.T
In [40]: df
Out[40]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [41]: df.loc[('bar', 'two')]
Out[41]:
A 0.805244
B 0.813850
C 1.607920
Name: (bar, two), dtype: float64
Pour cet exemple, df.loc ['bar', 'two']
fonctionnera également, mais sachez que cette abréviation peut être trompeuse en général.
Si vous utilisez .loc
pour indexer à partir d'une colonne particulière, vous devez utiliser le taple comme suit:
In [42]: df.loc[('bar', 'two'), 'A']
Out[42]: 0.8052440253863785
Si vous ne passez que le premier élément du taple, vous n'avez pas à spécifier tous les niveaux de MultiIndex. Par exemple, vous pouvez utiliser l'index «partiel» pour obtenir tous les éléments qui ont «bar» dans le premier niveau comme suit:
df.loc['bar']
Ceci est un raccourci pour la notation plus redondante df.loc [('bar',),]
(également équivalente à df.loc ['bar',]
dans cet exemple).
La tranche «Partielle» fonctionne également très bien.
In [43]: df.loc['baz':'foo']
Out[43]:
A B C
first second
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
Vous pouvez couper par la «plage» de valeurs en passant une tranche du taple.
In [44]: df.loc[('baz', 'two'):('qux', 'one')]
Out[44]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
In [45]: df.loc[('baz', 'two'):'foo']
Out[45]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
Comme pour la réindexation, vous pouvez également transmettre une liste d'étiquettes ou de tuples.
In [46]: df.loc[[('bar', 'two'), ('qux', 'one')]]
Out[46]:
A B C
first second
bar two 0.805244 0.813850 1.607920
qux one -1.170299 1.130127 0.974466
: ballot_box_with_check: ** Remarque ** Notez que les tapples et les listes ne sont pas traités de la même manière dans les pandas en matière d'indexation. Un taple est interprété comme une seule clé à plusieurs niveaux, mais une liste pointe vers plusieurs clés. En d'autres termes, le taple se déplace horizontalement (traversant le niveau) et la liste se déplace verticalement (balayant le niveau).
Surtout, une liste de taples tire plusieurs clés MultiIndex
complètes, mais un tapple d'une liste fait référence à une valeur dans un niveau.
In [47]: s = pd.Series([1, 2, 3, 4, 5, 6],
....: index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]))
....:
In [48]: s.loc[[("A", "c"), ("B", "d")]] # list of tuples
Out[48]:
A c 1
B d 5
dtype: int64
In [49]: s.loc[(["A", "B"], ["c", "d"])] # tuple of lists
Out[49]:
A c 1
d 2
B c 4
d 5
dtype: int64
Vous pouvez découper «MultiIndex» en fournissant plusieurs indexeurs.
Utilisez l'un des sélecteurs de liste d'étiquettes de tranches, d'étiquettes et de tableaux booléens que vous voyez dans Sélectionner par étiquette (https://qiita.com/nkay/items/d322ed9d9a14bdbf14cb#Select by Label) de la même manière. Je peux.
Vous pouvez utiliser slice (None)
pour sélectionner tous les éléments à * ce * niveau. Il n'est pas nécessaire de spécifier tous les niveaux * plus profonds *. Ils sont impliqués par «slice (None)».
Comme les autres, il s'agit d'une indexation d'étiquette, donc elle inclut ** les deux côtés ** du slicer.
: avertissement: ** Avertissement ** Dans
.loc
, spécifiez tous les axes (** index ** et ** colonnes **). Il y a des cas ambigus où l'indexeur passé peut être mal interprété comme un index sur * les deux * axes plutôt que surMultiIndex
d'une ligne. Écrivez comme suit.df.loc[(slice('A1', 'A3'), ...), :] # noqa: E999
N'écrivez pas comme suit.
df.loc[(slice('A1', 'A3'), ...)] # noqa: E999
In [50]: def mklbl(prefix, n):
....: return ["%s%s" % (prefix, i) for i in range(n)]
....:
In [51]: miindex = pd.MultiIndex.from_product([mklbl('A', 4),
....: mklbl('B', 2),
....: mklbl('C', 4),
....: mklbl('D', 2)])
....:
In [52]: micolumns = pd.MultiIndex.from_tuples([('a', 'foo'), ('a', 'bar'),
....: ('b', 'foo'), ('b', 'bah')],
....: names=['lvl0', 'lvl1'])
....:
In [53]: dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns))
....: .reshape((len(miindex), len(micolumns))),
....: index=miindex,
....: columns=micolumns).sort_index().sort_index(axis=1)
....:
In [54]: dfmi
Out[54]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9 8 11 10
D1 13 12 15 14
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 237 236 239 238
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249 248 251 250
D1 253 252 255 254
[64 rows x 4 columns]
Tranchage multi-index de base avec des étiquettes de liste de tranches.
In [55]: dfmi.loc[(slice('A1', 'A3'), slice(None), ['C1', 'C3']), :]
Out[55]:
lvl0 a b
lvl1 bar foo bah foo
A1 B0 C1 D0 73 72 75 74
D1 77 76 79 78
C3 D0 89 88 91 90
D1 93 92 95 94
B1 C1 D0 105 104 107 106
... ... ... ... ...
A3 B0 C3 D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[24 rows x 4 columns]
Dans slice (None)
en utilisant Pandas.IndexSlice
Vous pouvez utiliser une syntaxe plus naturelle que d'utiliser :
.
In [56]: idx = pd.IndexSlice
In [57]: dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[57]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
... ... ...
A3 B0 C3 D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
Vous pouvez utiliser cette méthode pour effectuer des sélections très complexes sur plusieurs axes en même temps.
In [58]: dfmi.loc['A1', (slice(None), 'foo')]
Out[58]:
lvl0 a b
lvl1 foo foo
B0 C0 D0 64 66
D1 68 70
C1 D0 72 74
D1 76 78
C2 D0 80 82
... ... ...
B1 C1 D1 108 110
C2 D0 112 114
D1 116 118
C3 D0 120 122
D1 124 126
[16 rows x 2 columns]
In [59]: dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[59]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
... ... ...
A3 B0 C3 D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
Vous pouvez utiliser l'indexeur booléen pour fournir des sélections liées aux * valeurs *.
In [60]: mask = dfmi[('a', 'foo')] > 200
In [61]: dfmi.loc[idx[mask, :, ['C1', 'C3']], idx[:, 'foo']]
Out[61]:
lvl0 a b
lvl1 foo foo
A3 B0 C1 D1 204 206
C3 D0 216 218
D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
Vous pouvez également spécifier l'argument ʻaxisdans
.loc` pour interpréter un slicer passé sur un seul axe.
In [62]: dfmi.loc(axis=0)[:, :, ['C1', 'C3']]
Out[62]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C1 D0 9 8 11 10
D1 13 12 15 14
C3 D0 25 24 27 26
D1 29 28 31 30
B1 C1 D0 41 40 43 42
... ... ... ... ...
A3 B0 C3 D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[32 rows x 4 columns]
De plus, vous pouvez * attribuer * des valeurs * en utilisant les méthodes suivantes:
In [63]: df2 = dfmi.copy()
In [64]: df2.loc(axis=0)[:, :, ['C1', 'C3']] = -10
In [65]: df2
Out[65]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 -10 -10 -10 -10
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
[64 rows x 4 columns]
Vous pouvez également utiliser le côté droit de l'objet alignable.
In [66]: df2 = dfmi.copy()
In [67]: df2.loc[idx[:, :, ['C1', 'C3']], :] = df2 * 1000
In [68]: df2
Out[68]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9000 8000 11000 10000
D1 13000 12000 15000 14000
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 237000 236000 239000 238000
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249000 248000 251000 250000
D1 253000 252000 255000 254000
[64 rows x 4 columns]
Cross-section
La méthode xs ()
de DataFrame
utilise également un argument de niveau pour faciliter la sélection de données à un niveau particulier de MultiIndex
.
In [69]: df
Out[69]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [70]: df.xs('one', level='second')
Out[70]:
A B C
first
bar 0.895717 0.410835 -1.413681
baz -1.206412 0.132003 1.024180
foo 1.431256 -0.076467 0.875906
qux -1.170299 1.130127 0.974466
#Utilisation de tranches
In [71]: df.loc[(slice(None), 'one'), :]
Out[71]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
baz one -1.206412 0.132003 1.024180
foo one 1.431256 -0.076467 0.875906
qux one -1.170299 1.130127 0.974466
Vous pouvez également sélectionner des colonnes avec «xs» en spécifiant l'argument Axis.
In [72]: df = df.T
In [73]: df.xs('one', level='second', axis=1)
Out[73]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
#Utilisation de tranches
In [74]: df.loc[:, (slice(None), 'one')]
Out[74]:
first bar baz foo qux
second one one one one
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
Avec xs
, vous pouvez également sélectionner en utilisant plusieurs touches.
In [75]: df.xs(('one', 'bar'), level=('second', 'first'), axis=1)
Out[75]:
first bar
second one
A 0.895717
B 0.410835
C -1.413681
#Utilisation de tranches
In [76]: df.loc[:, ('bar', 'one')]
Out[76]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
Vous pouvez conserver le niveau sélectionné en passant drop_level = False
à xs
.
In [77]: df.xs('one', level='second', axis=1, drop_level=False)
Out[77]:
first bar baz foo qux
second one one one one
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
Comparez le résultat ci-dessus avec le cas de drop_level = True
(valeur par défaut).
In [78]: df.xs('one', level='second', axis=1, drop_level=True)
Out[78]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
L'objet pandas reindex ()
et [ʻalign (ʻalign) ) ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.align.html#pandas.DataFrame.align) Si vous utilisez l'argument
level` dans la méthode, le niveau entier Utile pour diffuser des valeurs vers. Par exemple:
In [79]: midx = pd.MultiIndex(levels=[['zero', 'one'], ['x', 'y']],
....: codes=[[1, 1, 0, 0], [1, 0, 1, 0]])
....:
In [80]: df = pd.DataFrame(np.random.randn(4, 2), index=midx)
In [81]: df
Out[81]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [82]: df2 = df.mean(level=0)
In [83]: df2
Out[83]:
0 1
one 1.060074 -0.109716
zero 1.271532 0.713416
In [84]: df2.reindex(df.index, level=0)
Out[84]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
#alignement
In [85]: df_aligned, df2_aligned = df.align(df2, level=0)
In [86]: df_aligned
Out[86]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [87]: df2_aligned
Out[87]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
swaplevel
La méthode swaplevel ()
est un ordre à deux niveaux Peut être remplacé.
In [88]: df[:5]
Out[88]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [89]: df[:5].swaplevel(0, 1, axis=0)
Out[89]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
reorder_levels
La méthode reorder_levels ()
est la méthode swaplevel
. Généraliser pour vous permettre de remplacer le niveau d'un index hiérarchique en une seule étape.
In [90]: df[:5].reorder_levels([1, 0], axis=0)
Out[90]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
ou
MultiIndex`[Rename ()
](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.rename.html] généralement utilisé pour renommer les colonnes dans DataFrame
La méthode # pandas.DataFrame.rename) peut également renommer l'étiquette MultiIndex
. L'argument colonnes
de renommer
peut être un dictionnaire contenant uniquement les colonnes que vous souhaitez renommer.
In [91]: df.rename(columns={0: "col0", 1: "col1"})
Out[91]:
col0 col1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
Cette méthode peut également être utilisée pour renommer une étiquette particulière dans l'index principal de DataFrame
.
In [92]: df.rename(index={"one": "two", "y": "z"})
Out[92]:
0 1
two z 1.519970 -0.493662
x 0.600178 0.274230
zero z 0.132885 -0.023688
x 2.410179 1.450520
rename_axis ()
La méthode est ʻIndex ou
Renommer MultiIndex. En particulier, vous pouvez spécifier un nom de niveau pour le
MultiIndex, ce qui sera utile plus tard lorsque vous utiliserez
reset_index ()` pour déplacer la valeur du MultiIndex vers une colonne normale.
In [93]: df.rename_axis(index=['abc', 'def'])
Out[93]:
0 1
abc def
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
Notez que les colonnes dans le DataFrame
sont des index, donc utiliser rename_axis
avec l'argument columns
renommera l'index.
In [94]: df.rename_axis(columns="Cols").columns
Out[94]: RangeIndex(start=0, stop=2, step=1, name='Cols')
Rename
et rename_axis
prennent en charge la spécification d'un dictionnaire, Series
et une fonction de mappage pour mapper une étiquette / un nom à une nouvelle valeur.
Si vous voulez travailler directement avec l'objet ʻIndex au lieu de via le
DataFrame, alors [ʻIndex.set_names ()
](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Index) Vous pouvez le renommer en utilisant .set_names.html # pandas.Index.set_names).
In [95]: mi = pd.MultiIndex.from_product([[1, 2], ['a', 'b']], names=['x', 'y'])
In [96]: mi.names
Out[96]: FrozenList(['x', 'y'])
In [97]: mi2 = mi.rename("new name", level=0)
In [98]: mi2
Out[98]:
MultiIndex([(1, 'a'),
(1, 'b'),
(2, 'a'),
(2, 'b')],
names=['new name', 'y'])
: avertissement: ** Avertissement ** Avant pandas 1.0.0, il était également possible de définir le nom du MultiIndex en mettant à jour le nom du niveau.
>>> mi.levels[0].name = 'name via level' >>> mi.names[0] # only works for older panads 'name via level'
Depuis pandas 1.0, cela échoue implicitement à mettre à jour le nom de MultiIndex. Utilisez plutôt ʻIndex.set_names () ` S'il te plait donne moi.
MultiIndex
Les objets avec MultiIndex
appliqué doivent être triés afin d'être efficacement indexés et découpés. Comme tout autre index, sort_index ()
Tu peux l'utiliser.
In [99]: import random
In [100]: random.shuffle(tuples)
In [101]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))
In [102]: s
Out[102]:
qux one 0.206053
foo two -0.251905
bar two -2.213588
one 1.063327
qux two 1.266143
baz one 0.299368
foo one -0.863838
baz two 0.408204
dtype: float64
In [103]: s.sort_index()
Out[103]:
bar one 1.063327
two -2.213588
baz one 0.299368
two 0.408204
foo one -0.863838
two -0.251905
qux one 0.206053
two 1.266143
dtype: float64
In [104]: s.sort_index(level=0)
Out[104]:
bar one 1.063327
two -2.213588
baz one 0.299368
two 0.408204
foo one -0.863838
two -0.251905
qux one 0.206053
two 1.266143
dtype: float64
In [105]: s.sort_index(level=1)
Out[105]:
bar one 1.063327
baz one 0.299368
foo one -0.863838
qux one 0.206053
bar two -2.213588
baz two 0.408204
foo two -0.251905
qux two 1.266143
dtype: float64
Si le niveau de MultiIndex
est nommé, vous pouvez également passer le nom du niveau à sort_index
.
In [106]: s.index.set_names(['L1', 'L2'], inplace=True)
In [107]: s.sort_index(level='L1')
Out[107]:
L1 L2
bar one 1.063327
two -2.213588
baz one 0.299368
two 0.408204
foo one -0.863838
two -0.251905
qux one 0.206053
two 1.266143
dtype: float64
In [108]: s.sort_index(level='L2')
Out[108]:
L1 L2
bar one 1.063327
baz one 0.299368
foo one -0.863838
qux one 0.206053
bar two -2.213588
baz two 0.408204
foo two -0.251905
qux two 1.266143
dtype: float64
Pour les objets de dimension supérieure, si vous avez «MultiIndex», vous pouvez trier par niveau sur des axes autres que l'index.
In [109]: df.T.sort_index(level=1, axis=1)
Out[109]:
one zero one zero
x x y y
0 0.600178 2.410179 1.519970 0.132885
1 0.274230 1.450520 -0.493662 -0.023688
L'indexation fonctionne même si les données ne sont pas triées, mais c'est assez inefficace (et vous verrez un PerformanceWarning
). Il renvoie également une copie des données au lieu de la vue.
In [110]: dfm = pd.DataFrame({'jim': [0, 0, 1, 1],
.....: 'joe': ['x', 'x', 'z', 'y'],
.....: 'jolie': np.random.rand(4)})
.....:
In [111]: dfm = dfm.set_index(['jim', 'joe'])
In [112]: dfm
Out[112]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 z 0.537020
y 0.110968
In [4]: dfm.loc[(1, 'z')]
PerformanceWarning: indexing past lexsort depth may impact performance.
Out[4]:
jolie
jim joe
1 z 0.64094
De plus, l'indexation lorsqu'elle n'est pas complètement triée peut entraîner des erreurs similaires aux suivantes:
In [5]: dfm.loc[(0, 'y'):(1, 'z')]
UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'
La méthode [ʻis_lexsorted () ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.MultiIndex.is_lexsorted.html#pandas.MultiIndex.is_lexsorted) de
MultiIndexest un index. Indique si est trié et la propriété
lexsort_depth` renvoie la profondeur de tri.
In [113]: dfm.index.is_lexsorted()
Out[113]: False
In [114]: dfm.index.lexsort_depth
Out[114]: 1
In [115]: dfm = dfm.sort_index()
In [116]: dfm
Out[116]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 y 0.110968
z 0.537020
In [117]: dfm.index.is_lexsorted()
Out[117]: True
In [118]: dfm.index.lexsort_depth
Out[118]: 2
Les choix fonctionnent maintenant comme prévu.
In [119]: dfm.loc[(0, 'y'):(1, 'z')]
Out[119]:
jolie
jim joe
1 y 0.110968
z 0.537020
Comme NumPy ndarrays, pandas ʻIndex·
Series ·
DataFrame fournit également une méthode
take () pour obtenir des éléments le long d'un axe particulier à un index particulier. L'index spécifié doit être un ndarray à la position d'index de liste ou d'entier.
take` peut également accepter des entiers négatifs par rapport à la fin de l'objet.
In [120]: index = pd.Index(np.random.randint(0, 1000, 10))
In [121]: index
Out[121]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')
In [122]: positions = [0, 9, 3]
In [123]: index[positions]
Out[123]: Int64Index([214, 329, 567], dtype='int64')
In [124]: index.take(positions)
Out[124]: Int64Index([214, 329, 567], dtype='int64')
In [125]: ser = pd.Series(np.random.randn(10))
In [126]: ser.iloc[positions]
Out[126]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
In [127]: ser.take(positions)
Out[127]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
Pour DataFrames, l'index spécifié doit être une liste unidimensionnelle ou un ndarray qui spécifie la position de la ligne ou de la colonne.
In [128]: frm = pd.DataFrame(np.random.randn(5, 3))
In [129]: frm.take([1, 4, 3])
Out[129]:
0 1 2
1 -1.237881 0.106854 -1.276829
4 0.629675 -1.425966 1.857704
3 0.979542 -1.633678 0.615855
In [130]: frm.take([0, 2], axis=1)
Out[130]:
0 2
0 0.595974 0.601544
1 -1.237881 -1.276829
2 -0.767101 1.499591
3 0.979542 0.615855
4 0.629675 1.857704
Notez que la méthode take
de l'objet pandas n'est pas destinée à fonctionner avec des index booléens et peut renvoyer des résultats inattendus.
In [131]: arr = np.random.randn(10)
In [132]: arr.take([False, False, True, True])
Out[132]: array([-1.1935, -1.1935, 0.6775, 0.6775])
In [133]: arr[[0, 1]]
Out[133]: array([-1.1935, 0.6775])
In [134]: ser = pd.Series(np.random.randn(10))
In [135]: ser.take([False, False, True, True])
Out[135]:
0 0.233141
0 0.233141
1 -0.223540
1 -0.223540
dtype: float64
In [136]: ser.iloc[[0, 1]]
Out[136]:
0 0.233141
1 -0.223540
dtype: float64
Enfin, une petite note de performance, la méthode take
gère une gamme plus étroite d'entrées, ce qui peut fournir des performances beaucoup plus rapides qu'un index sophistiqué. ]
In [137]: arr = np.random.randn(10000, 5)
In [138]: indexer = np.arange(10000)
In [139]: random.shuffle(indexer)
In [140]: %timeit arr[indexer]
.....: %timeit arr.take(indexer, axis=0)
.....:
219 us +- 1.23 us per loop (mean +- std. dev. of 7 runs, 1000 loops each)
72.3 us +- 727 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
In [141]: ser = pd.Series(arr[:, 0])
In [142]: %timeit ser.iloc[indexer]
.....: %timeit ser.take(indexer)
.....:
179 us +- 1.54 us per loop (mean +- std. dev. of 7 runs, 10000 loops each)
162 us +- 1.6 us per loop (mean +- std. dev. of 7 runs, 10000 loops each)
Jusqu'à présent, nous avons couvert le «MultiIndex» de manière assez approfondie. La documentation pour DatetimeIndex
et PeriodIndex
est ici, la documentation pour TimedeltaIndex
est [ici](https: / Voir /dev.pandas.io/docs/user_guide/timedeltas.html#timedeltas-index).
Les sous-sections suivantes mettent en évidence d'autres types d'index.
CategoricalIndex
CategoricalIndex
est un index qui permet de prendre en charge les index en double. .. Il s'agit d'un conteneur qui entoure Categorical
de nombreux éléments en double. Permet une indexation et un stockage efficaces des index contenant.
In [143]: from pandas.api.types import CategoricalDtype
In [144]: df = pd.DataFrame({'A': np.arange(6),
.....: 'B': list('aabbca')})
.....:
In [145]: df['B'] = df['B'].astype(CategoricalDtype(list('cab')))
In [146]: df
Out[146]:
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
In [147]: df.dtypes
Out[147]:
A int64
B category
dtype: object
In [148]: df['B'].cat.categories
Out[148]: Index(['c', 'a', 'b'], dtype='object')
La définition de l'index crée un CatégoricalIndex
.
In [149]: df2 = df.set_index('B')
In [150]: df2.index
Out[150]: CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
Les index utilisant __getitem__ /. Iloc / .loc
fonctionnent de la même manière que ʻIndex. L'indexeur doit ** appartenir à une catégorie **. Sinon, vous obtiendrez une
KeyError`.
In [151]: df2.loc['a']
Out[151]:
A
B
a 0
a 1
a 5
L'index «catégorique» est ** conservé ** après l'index.
In [152]: df2.loc['a'].index
Out[152]: CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
Lorsque vous triez les index, ils sont triés par catégorie (puisque vous avez créé l'index avec CategoricalDtype (list ('cab'))
, il est trié par cab
).
In [153]: df2.sort_index()
Out[153]:
A
B
c 4
a 0
a 1
a 5
b 2
b 3
Les opérations Groupby sur les index conservent également les propriétés de l'index.
In [154]: df2.groupby(level=0).sum()
Out[154]:
A
B
c 4
a 6
b 5
In [155]: df2.groupby(level=0).sum().index
Out[155]: CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
L'opération de réindexation renvoie un index du résultat basé sur le type d'indexeur passé. Passer une liste renverra l''Index 'habituel. Passer un Catégorique
renvoie unCategoricalIndex
indexé selon la ** catégorie de type Catégorique
transmise. Cela vous permet d'indexer arbitrairement des valeurs qui ** n'existent pas dans la catégorie, comme pour réindexer les pandas.
In [156]: df3 = pd.DataFrame({'A': np.arange(3),
.....: 'B': pd.Series(list('abc')).astype('category')})
.....:
In [157]: df3 = df3.set_index('B')
In [158]: df3
Out[158]:
A
B
a 0
b 1
c 2
In [159]: df3.reindex(['a', 'e'])
Out[159]:
A
B
a 0.0
e NaN
In [160]: df3.reindex(['a', 'e']).index
Out[160]: Index(['a', 'e'], dtype='object', name='B')
In [161]: df3.reindex(pd.Categorical(['a', 'e'], categories=list('abe')))
Out[161]:
A
B
a 0.0
e NaN
In [162]: df3.reindex(pd.Categorical(['a', 'e'], categories=list('abe'))).index
Out[162]: CategoricalIndex(['a', 'e'], categories=['a', 'b', 'e'], ordered=False, name='B', dtype='category')
: avertissement: ** Avertissement ** Les opérations de formatage et de comparaison dans
CatégoricalIndex
doivent être dans la même catégorie. Sinon, vous obtiendrez unTypeError
.In [163]: df4 = pd.DataFrame({'A': np.arange(2), .....: 'B': list('ba')}) .....: In [164]: df4['B'] = df4['B'].astype(CategoricalDtype(list('ab'))) In [165]: df4 = df4.set_index('B') In [166]: df4.index Out[166]: CategoricalIndex(['b', 'a'], categories=['a', 'b'], ordered=False, name='B', dtype='category') In [167]: df5 = pd.DataFrame({'A': np.arange(2), .....: 'B': list('bc')}) .....: In [168]: df5['B'] = df5['B'].astype(CategoricalDtype(list('bc'))) In [169]: df5 = df5.set_index('B') In [170]: df5.index Out[170]: CategoricalIndex(['b', 'c'], categories=['b', 'c'], ordered=False, name='B', dtype='category')
In [1]: pd.concat([df4, df5]) TypeError: categories must match existing categories when appending
ʻInt64Index` est le fondement de la fondation de l'indice pandas. Il s'agit d'un tableau immuable qui implémente un ensemble de tranches ordonnées.
RangeIndex
fournit l'index par défaut pour tous les objets NDFrame
ʻIl s'agit d'une sous-classe de Int64Index.
RangeIndex est une version de ʻInt64Index
optimisée pour représenter un ensemble d'ordres monotone. Ceux-ci sont similaires au [type de plage
] de Python (https://docs.python.org/3/library/stdtypes.html#typesseq-range).
Float64Index
Par défaut, Float64Index
est une minorité flottante ou un entier dans l'indexation. Créé automatiquement lors du passage d'une valeur mixte de fractions flottantes. Cela permet un paradigme de découpage purement basé sur les étiquettes qui fait que l'index scalaire et le découpage «[]», «x» et «loc» fonctionnent exactement de la même manière.
In [171]: indexf = pd.Index([1.5, 2, 3, 4.5, 5])
In [172]: indexf
Out[172]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')
In [173]: sf = pd.Series(range(5), index=indexf)
In [174]: sf
Out[174]:
1.5 0
2.0 1
3.0 2
4.5 3
5.0 4
dtype: int64
Les sélections scalaires pour «[]» et «.loc» sont toujours basées sur des étiquettes. La spécification d'un entier correspond à un index flottant égal (par exemple, «3» équivaut à «3.0»).
In [175]: sf[3]
Out[175]: 2
In [176]: sf[3.0]
Out[176]: 2
In [177]: sf.loc[3]
Out[177]: 2
In [178]: sf.loc[3.0]
Out[178]: 2
Le seul indice de position est via ʻiloc`.
In [179]: sf.iloc[3]
Out[179]: 3
L'index scalaire introuvable lève une KeyError
. Les tranches sont principalement basées sur les valeurs d'index lors de l'utilisation de []
, ʻixet
loc, et ** toujours basées sur la position lors de l'utilisation de ʻiloc
. L'exception est lorsque la tranche est une valeur booléenne. Dans ce cas, il est toujours basé sur la position.
In [180]: sf[2:4]
Out[180]:
2.0 1
3.0 2
dtype: int64
In [181]: sf.loc[2:4]
Out[181]:
2.0 1
3.0 2
dtype: int64
In [182]: sf.iloc[2:4]
Out[182]:
3.0 2
4.5 3
dtype: int64
L'index flottant vous permet d'utiliser des tranches avec des fractions flottantes.
In [183]: sf[2.1:4.6]
Out[183]:
3.0 2
4.5 3
dtype: int64
In [184]: sf.loc[2.1:4.6]
Out[184]:
3.0 2
4.5 3
dtype: int64
S'il ne s'agit pas d'un index flottant, les tranches utilisant des flottants lèveront un TypeError
.
In [1]: pd.Series(range(5))[3.5]
TypeError: the label [3.5] is not a proper indexer for this index type (Int64Index)
In [1]: pd.Series(range(5))[3.5:4.5]
TypeError: the slice start [3.5] is not a proper indexer for this index type (Int64Index)
Voici quelques cas d'utilisation courants pour l'utilisation de ce type d'index: Imaginez un schéma d'indexation irrégulier de type timedelta où les données sont enregistrées sous forme de flottants. Cela peut être, par exemple, un décalage en millisecondes.
In [185]: dfir = pd.concat([pd.DataFrame(np.random.randn(5, 2),
.....: index=np.arange(5) * 250.0,
.....: columns=list('AB')),
.....: pd.DataFrame(np.random.randn(6, 2),
.....: index=np.arange(4, 10) * 250.1,
.....: columns=list('AB'))])
.....:
In [186]: dfir
Out[186]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
1000.4 -0.179734 0.993962
1250.5 -0.212673 0.909872
1500.6 -0.733333 -0.349893
1750.7 0.456434 -0.306735
2000.8 0.553396 0.166221
2250.9 -0.101684 -0.734907
Les opérations de sélection fonctionnent toujours sur une base de valeur pour tous les opérateurs de sélection.
In [187]: dfir[0:1000.4]
Out[187]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
1000.4 -0.179734 0.993962
In [188]: dfir.loc[0:1001, 'A']
Out[188]:
0.0 -0.435772
250.0 -0.808286
500.0 -1.815703
750.0 -0.243487
1000.0 1.162969
1000.4 -0.179734
Name: A, dtype: float64
In [189]: dfir.loc[1000.4]
Out[189]:
A -0.179734
B 0.993962
Name: 1000.4, dtype: float64
Vous pouvez obtenir la première seconde (1000 millisecondes) des données comme suit:
In [190]: dfir[0:1000]
Out[190]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
Si vous avez besoin d'une sélection basée sur une position entière, utilisez ʻiloc`.
In [191]: dfir.iloc[0:5]
Out[191]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
IntervalIndex
[ʻIntervalIndex](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.IntervalIndex.html#pandas.IntervalIndex) et son propre dtype, ʻIntervalDtype
, et ʻInterval` Le type scalaire permet aux pandas de fournir un support de première classe pour la notation d'intervalle. Je vais.
ʻInterval Index permet une indexation unique, [
cut ()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.cut.html#pandas En tant que type de retour des catégories .cut) et [
qcut ()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.qcut.html#pandas.qcut) Est également utilisé.
ʻIntervalIndex peut être utilisé comme index dans
Serieset
DataFrame`.
In [192]: df = pd.DataFrame({'A': [1, 2, 3, 4]},
.....: index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4]))
.....:
In [193]: df
Out[193]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
(3, 4] 4
Les index basés sur des étiquettes via .loc
qui suivent les extrémités de l'intervalle fonctionnent comme prévu et sélectionnent cet intervalle particulier.
In [194]: df.loc[2]
Out[194]:
A 2
Name: (1, 2], dtype: int64
In [195]: df.loc[[2, 3]]
Out[195]:
A
(1, 2] 2
(2, 3] 3
Si vous sélectionnez le libellé * inclus * dans un intervalle, il sera sélectionné pour chaque intervalle.
In [196]: df.loc[2.5]
Out[196]:
A 3
Name: (2, 3], dtype: int64
In [197]: df.loc[[2.5, 3.5]]
Out[197]:
A
(2, 3] 3
(3, 4] 4
Lorsqu'elle est sélectionnée à l'aide d'intervalles, seules les correspondances exactes sont renvoyées (pandas 0.25.0 et versions ultérieures).
In [198]: df.loc[pd.Interval(1, 2)]
Out[198]:
A 2
Name: (1, 2], dtype: int64
Si vous essayez de sélectionner un intervalle qui n'est pas exactement inclus dans ʻIntervalIndex, vous obtiendrez une
KeyError`.
In [7]: df.loc[pd.Interval(0.5, 2.5)]
---------------------------------------------------------------------------
KeyError: Interval(0.5, 2.5, closed='right')
Pour sélectionner tous les ʻIntervals qui chevauchent un ʻInterval
particulier, [ʻoverlaps () `](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.IntervalIndex. Créez un indexeur booléen à l'aide de la méthode overlaps.html # pandas.IntervalIndex.overlaps).
In [199]: idxr = df.index.overlaps(pd.Interval(0.5, 2.5))
In [200]: idxr
Out[200]: array([ True, True, True, False])
In [201]: df[idxr]
Out[201]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
cut
et qcut
cut ()
et [qcut ()
](https: / /pandas.pydata.org/pandas-docs/stable/reference/api/pandas.qcut.html#pandas.qcut) tous les deux renvoient des objets «catégoriques» et les bacs qu'ils créent sont «dans l'attribut« .categories ». Il est enregistré comme IntervalIndex`.
In [202]: c = pd.cut(range(4), bins=2)
In [203]: c
Out[203]:
[(-0.003, 1.5], (-0.003, 1.5], (1.5, 3.0], (1.5, 3.0]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
In [204]: c.categories
Out[204]:
IntervalIndex([(-0.003, 1.5], (1.5, 3.0]],
closed='right',
dtype='interval[float64]')
cut ()
transmet ʻIntervalIndex à l'argument
binspeut aussi faire. Cela permet un idiome pandas pratique. Tout d'abord, définissez certaines données et
bins sur un nombre fixe [
cut () ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.cut. Appelez html # pandas.cut) pour créer un bac. Ensuite, la valeur de
.categories a été appelée par la suite [
cut () ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.cut.html# Vous pouvez regrouper de nouvelles données dans le même bac en les passant à l'argument
bins` de pandas.cut).
In [205]: pd.cut([0, 3, 5, 1], bins=c.categories)
Out[205]:
[(-0.003, 1.5], (1.5, 3.0], NaN, (-0.003, 1.5]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
Les valeurs en dehors de toutes les classes reçoivent la valeur «NaN».
Si vous avez besoin d'intervalles à fréquence normale, utilisez la fonction [ʻinterval_range () ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.interval_range.html#pandas.interval_range). Vous pouvez l'utiliser pour créer un «index d'intervalle» en utilisant différentes combinaisons de «début», «fin» et «périodes». La période par défaut pour ʻinterval_range
est 1 pour les intervalles numériques et les jours calendaires pour les intervalles de type datetime.
In [206]: pd.interval_range(start=0, end=5)
Out[206]:
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
closed='right',
dtype='interval[int64]')
In [207]: pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4)
Out[207]:
IntervalIndex([(2017-01-01, 2017-01-02], (2017-01-02, 2017-01-03], (2017-01-03, 2017-01-04], (2017-01-04, 2017-01-05]],
closed='right',
dtype='interval[datetime64[ns]]')
In [208]: pd.interval_range(end=pd.Timedelta('3 days'), periods=3)
Out[208]:
IntervalIndex([(0 days 00:00:00, 1 days 00:00:00], (1 days 00:00:00, 2 days 00:00:00], (2 days 00:00:00, 3 days 00:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
L'argument freq
peut être utilisé pour spécifier une période autre que celle par défaut, avec divers alias de période
pour des intervalles de type datetime. # timeseries-offset-aliases) est disponible.
In [209]: pd.interval_range(start=0, periods=5, freq=1.5)
Out[209]:
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0], (6.0, 7.5]],
closed='right',
dtype='interval[float64]')
In [210]: pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4, freq='W')
Out[210]:
IntervalIndex([(2017-01-01, 2017-01-08], (2017-01-08, 2017-01-15], (2017-01-15, 2017-01-22], (2017-01-22, 2017-01-29]],
closed='right',
dtype='interval[datetime64[ns]]')
In [211]: pd.interval_range(start=pd.Timedelta('0 days'), periods=3, freq='9H')
Out[211]:
IntervalIndex([(0 days 00:00:00, 0 days 09:00:00], (0 days 09:00:00, 0 days 18:00:00], (0 days 18:00:00, 1 days 03:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
De plus, vous pouvez utiliser l'argument closed
pour spécifier qui ferme l'intervalle. Par défaut, l'intervalle est fermé à droite.
In [212]: pd.interval_range(start=0, end=4, closed='both')
Out[212]:
IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4]],
closed='both',
dtype='interval[int64]')
In [213]: pd.interval_range(start=0, end=4, closed='neither')
Out[213]:
IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)],
closed='neither',
dtype='interval[int64]')
_ À partir de la version 0.23.0 _
Si vous spécifiez start
・ ʻend ・
period, le résultat ʻIntervalIndex
créera un intervalle de start
à ʻendavec autant d'éléments que de
périodes`.
In [214]: pd.interval_range(start=0, end=6, periods=4)
Out[214]:
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
closed='right',
dtype='interval[float64]')
In [215]: pd.interval_range(pd.Timestamp('2018-01-01'),
.....: pd.Timestamp('2018-02-28'), periods=3)
.....:
Out[215]:
IntervalIndex([(2018-01-01, 2018-01-20 08:00:00], (2018-01-20 08:00:00, 2018-02-08 16:00:00], (2018-02-08 16:00:00, 2018-02-28]],
closed='right',
dtype='interval[datetime64[ns]]')
L'indexation basée sur des étiquettes avec des étiquettes d'axe entier est un sujet délicat. Il est fréquemment discuté entre les différents membres de la liste de diffusion et la communauté scientifique Python. Chez les pandas, notre opinion générale est que les étiquettes sont plus importantes que les positions entières. Par conséquent, pour les index d'axes entiers, les outils standard tels que .loc
autorisent l'indexation basée sur les étiquettes * uniquement *. Le code suivant lève une exception.
In [216]: s = pd.Series(range(5))
In [217]: s[-1]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-217-76c3dce40054> in <module>
----> 1 s[-1]
~/work/1/s/pandas/core/series.py in __getitem__(self, key)
1076 key = com.apply_if_callable(key, self)
1077 try:
-> 1078 result = self.index.get_value(self, key)
1079
1080 if not is_scalar(result):
~/work/1/s/pandas/core/indexes/base.py in get_value(self, series, key)
4623 k = self._convert_scalar_indexer(k, kind="getitem")
4624 try:
-> 4625 return self._engine.get_value(s, k, tz=getattr(series.dtype, "tz", None))
4626 except KeyError as e1:
4627 if len(self) > 0 and (self.holds_integer() or self.is_boolean()):
~/work/1/s/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()
~/work/1/s/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()
~/work/1/s/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
~/work/1/s/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()
~/work/1/s/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()
KeyError: -1
In [218]: df = pd.DataFrame(np.random.randn(5, 4))
In [219]: df
Out[219]:
0 1 2 3
0 -0.130121 -0.476046 0.759104 0.213379
1 -0.082641 0.448008 0.656420 -1.051443
2 0.594956 -0.151360 -0.069303 1.221431
3 -0.182832 0.791235 0.042745 2.069775
4 1.446552 0.019814 -1.389212 -0.702312
In [220]: df.loc[-2:]
Out[220]:
0 1 2 3
0 -0.130121 -0.476046 0.759104 0.213379
1 -0.082641 0.448008 0.656420 -1.051443
2 0.594956 -0.151360 -0.069303 1.221431
3 -0.182832 0.791235 0.042745 2.069775
4 1.446552 0.019814 -1.389212 -0.702312
Cette décision délibérée a été prise pour éviter toute ambiguïté et bogues subtils (de nombreux utilisateurs trouvent des bogues lorsqu'ils modifient l'API pour arrêter le «repli» dans l'indexation basée sur la position. J'ai signalé).
Si l'index de la Series
ou DataFrame
augmente ou diminue de manière monotone, il est possible que les bordures des tranches basées sur des étiquettes soient en dehors de la plage de l'index, comme dans l'indexation de tranche de liste Python normale. La monotonie de l'index est ʻis_monotonic_increasing () ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Index.is_monotonic_increasing.html#pandas.Index.is_monotonic_increasing) et [ ʻIs_monotonic_decreasing ()
Vous pouvez tester avec l'attribut.
In [221]: df = pd.DataFrame(index=[2, 3, 3, 4, 5], columns=['data'], data=list(range(5)))
In [222]: df.index.is_monotonic_increasing
Out[222]: True
#Les lignes 0 et 1 n'existent pas, mais renvoie les lignes 2 et 3 (les deux), 4
In [223]: df.loc[0:4, :]
Out[223]:
data
2 0
3 1
3 2
4 3
#Un DataFrame vide est renvoyé car la tranche est hors d'index
In [224]: df.loc[13:15, :]
Out[224]:
Empty DataFrame
Columns: [data]
Index: []
D'autre part, si l'index n'est pas monotone, les deux limites de tranche doivent être des valeurs * uniques * de l'index.
In [225]: df = pd.DataFrame(index=[2, 3, 1, 4, 3, 5],
.....: columns=['data'], data=list(range(6)))
.....:
In [226]: df.index.is_monotonic_increasing
Out[226]: False
#Il n'y a pas de problème car 2 et 4 sont dans l'index
In [227]: df.loc[2:4, :]
Out[227]:
data
2 0
3 1
1 2
4 3
#0 n'existe pas dans l'index
In [9]: df.loc[0:4, :]
KeyError: 0
#3 n'est pas un label unique
In [11]: df.loc[2:3, :]
KeyError: 'Cannot get right slice bound for non-unique label: 3'
ʻIndex.is_monotonic_increasing et ʻIndex.is_monotonic_decreasing
vérifient légèrement si l'index est monotone. Pour voir la monotonie exacte, soit [ʻis_unique () `](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Index.is_unique.html# À combiner avec l'attribut pandas.Index.is_unique).
In [228]: weakly_monotonic = pd.Index(['a', 'b', 'c', 'c'])
In [229]: weakly_monotonic
Out[229]: Index(['a', 'b', 'c', 'c'], dtype='object')
In [230]: weakly_monotonic.is_monotonic_increasing
Out[230]: True
In [231]: weakly_monotonic.is_monotonic_increasing & weakly_monotonic.is_unique
Out[231]: False
Contrairement aux tranches de séquence Python standard qui ne contiennent pas de points de terminaison, les tranches basées sur des étiquettes pandas le font. La raison principale en est qu'il n'est souvent pas facile de déterminer «l'étiquette suivante» ou l'élément suivant après une étiquette particulière dans l'index. Par exemple, considérez les «Seires» suivants.
In [232]: s = pd.Series(np.random.randn(6), index=list('abcdef'))
In [233]: s
Out[233]:
a 0.301379
b 1.240445
c -0.846068
d -0.043312
e -1.658747
f -0.819549
dtype: float64
Supposons que vous souhaitiez découper de c à e en utilisant un entier. Cela se fait comme suit:
In [234]: s[2:5]
Out[234]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
Cependant, si vous spécifiez uniquement à partir de «c» et «e», la détermination de l'élément suivant dans l'index peut être un peu compliquée. Par exemple, ce qui suit ne fonctionne pas:
s.loc['c':'e' + 1]
Un cas d'utilisation très courant consiste à spécifier une série chronologique spécifique qui commence et se termine à deux dates spécifiques. Pour rendre cela possible, nous avons conçu la tranche basée sur les étiquettes pour inclure les deux points de terminaison.
In [235]: s.loc['c':'e']
Out[235]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
C'est certainement "plus pratique que pur", mais soyez prudent si vous vous attendez à ce que les tranches basées sur des étiquettes se comportent exactement comme les tranches d'entiers Python standard.
Différentes opérations d'indexation peuvent changer le dtype de «Series».
In [236]: series1 = pd.Series([1, 2, 3])
In [237]: series1.dtype
Out[237]: dtype('int64')
In [238]: res = series1.reindex([0, 4])
In [239]: res.dtype
Out[239]: dtype('float64')
In [240]: res
Out[240]:
0 1.0
4 NaN
dtype: float64
In [241]: series2 = pd.Series([True])
In [242]: series2.dtype
Out[242]: dtype('bool')
In [243]: res = series2.reindex_like(series1)
In [244]: res.dtype
Out[244]: dtype('O')
In [245]: res
Out[245]:
0 True
1 NaN
2 NaN
dtype: object
En effet, l'opération de (ré) indexation ci-dessus insère implicitement "NaNs" et modifie le "type" en conséquence. Cela peut poser des problèmes lors de l'utilisation de numpy ufuncs
comme numpy.logical_and
.
Voir ce dernier numéro (https://github.com/pydata/pandas/issues/2388) pour plus d'informations.
Recommended Posts