Nota
Questo post sarà strutturato nel modo seguente:
- Le domande poste nel PO verranno affrontate una per una
- Per ogni domanda, verranno dimostrati uno o più metodi applicabili per risolvere questo problema e ottenere il risultato atteso.
NotaVerranno incluse (molto simili a questa) per i lettori interessati a conoscere funzionalità aggiuntive, dettagli di implementazione e altre informazioni superficiali sull'argomento in questione. Queste note sono state compilate esaminando i documenti e scoprendo varie caratteristiche oscure e dalla mia esperienza (certamente limitata).
Tutti gli esempi di codice sono stati creati e testati su panda v0.23.4, python3.7 . Se qualcosa non è chiaro, o di fatto non è corretto, o se non hai trovato una soluzione applicabile al tuo caso d'uso, sentiti libero di suggerire una modifica, richiedere chiarimenti nei commenti o aprire una nuova domanda, .... a seconda dei casi .
Ecco un'introduzione ad alcuni idiomi comuni (d'ora in poi denominati i quattro idiomi) che visiteremo frequentemente
DataFrame.loc
- Una soluzione generale per la selezione per etichetta (+ pd.IndexSlice
per applicazioni più complesse che coinvolgono sezioni)
DataFrame.xs
- Estrarre una particolare sezione trasversale da un Series / DataFrame.
DataFrame.query
- Specificare dinamicamente le operazioni di affettamento e / o filtro (ovvero, come un'espressione valutata dinamicamente. È più applicabile ad alcuni scenari rispetto ad altri. Vedere anche questa sezione della documentazione per eseguire query su MultiIndex.
Indicizzazione booleana con una maschera generata utilizzando MultiIndex.get_level_values
(spesso insieme a Index.isin
, soprattutto quando si filtra con più valori). Questo è anche abbastanza utile in alcune circostanze.
Sarà utile esaminare i vari problemi di affettamento e filtraggio in termini di Quattro Idiomi per comprendere meglio cosa può essere applicato a una data situazione. È molto importante capire che non tutti gli idiomi funzioneranno ugualmente bene (se non del tutto) in ogni circostanza. Se un idioma non è stato elencato come una potenziale soluzione a un problema di seguito, significa che il linguaggio non può essere applicato efficacemente a quel problema.
Domanda 1
Come faccio a selezionare le righe che hanno "a" nel livello "uno"?
col
one two
a t 0
u 1
v 2
w 3
È possibile utilizzare loc
, come soluzione generica applicabile alla maggior parte delle situazioni:
df.loc[['a']]
A questo punto, se ottieni
TypeError: Expected tuple, got str
Ciò significa che stai utilizzando una versione precedente dei panda. Considera l'aggiornamento! Altrimenti, usadf.loc[('a', slice(None)), :]
.
In alternativa, puoi usare xs
qui, poiché stiamo estraendo una singola sezione trasversale. Notare gli argomenti levels
e axis
(qui si possono assumere valori predefiniti ragionevoli).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Qui, l' drop_level=False
argomento è necessario per evitare xs
di far cadere il livello "uno" nel risultato (il livello su cui abbiamo tagliato).
Ancora un'altra opzione qui sta usando query
:
df.query("one == 'a'")
Se l'indice non aveva un nome, sarebbe necessario modificare la stringa della query in "ilevel_0 == 'a'"
.
Infine, utilizzando get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
Inoltre, come potrei abbassare il livello "uno" nell'output?
col
two
t 0
u 1
v 2
w 3
Questo può essere fatto facilmente usando entrambi
df.loc['a'] # Notice the single string argument instead the list.
O,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Notare che possiamo omettere l' drop_level
argomento (si presume che sia True
di default).
Nota
È possibile notare che un DataFrame filtrato potrebbe avere ancora tutti i livelli, anche se non vengono visualizzati durante la stampa del DataFrame. Per esempio,
v = df.loc[['a']]
print(v)
col
one two
a t 0
u 1
v 2
w 3
print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Puoi sbarazzarti di questi livelli usando MultiIndex.remove_unused_levels
:
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Domanda 1b
Come affetto tutte le righe con valore "t" al livello "due"?
col
one two
a t 0
b t 4
t 8
d t 12
Intuitivamente, vorresti qualcosa che coinvolgesse slice()
:
df.loc[(slice(None), 't'), :]
Funziona e basta! ™ Ma è goffo. Possiamo facilitare una sintassi di slicing più naturale utilizzando l' pd.IndexSlice
API qui.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Questo è molto, molto più pulito.
Nota
Perché è necessaria la sezione finale :
tra le colonne? Questo perché, loc
può essere utilizzato per selezionare e tagliare lungo entrambi gli assi ( axis=0
o
axis=1
). Senza chiarire esplicitamente su quale asse deve essere eseguito lo slicing, l'operazione diventa ambigua. Vedere il grande riquadro rosso nella documentazione sull'affettatura .
Se vuoi rimuovere ogni sfumatura di ambiguità, loc
accetta un axis
parametro:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Senza il axis
parametro (cioè, semplicemente facendo df.loc[pd.IndexSlice[:, 't']]
), si presume che lo slicing si trovi sulle colonne e KeyError
in questa circostanza verrà sollevato a.
Ciò è documentato nelle affettatrici . Ai fini di questo post, tuttavia, specificheremo esplicitamente tutti gli assi.
Con xs
, lo è
df.xs('t', axis=0, level=1, drop_level=False)
Con query
, lo è
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
E infine, con get_level_values
, puoi farlo
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Tutto allo stesso effetto.
Domanda 2
Come posso selezionare le righe corrispondenti agli elementi "b" e "d" nel livello "uno"?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
Usando loc, ciò viene fatto in modo simile specificando un elenco.
df.loc[['b', 'd']]
Per risolvere il problema di cui sopra di selezionare "b" e "d", puoi anche utilizzare query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Nota
Sì, il parser predefinito è 'pandas'
, ma è importante evidenziare che questa sintassi non è convenzionalmente python. Il parser Pandas genera un albero di analisi leggermente diverso dall'espressione. Questo viene fatto per rendere alcune operazioni più intuitive da specificare. Per ulteriori informazioni, leggi il mio post sulla
valutazione dell'espressione dinamica nei panda utilizzando pd.eval () .
E, con get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Domanda 2b
Come posso ottenere tutti i valori corrispondenti a "t" e "w" nel livello "due"?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Con loc
, questo è possibile solo in combinazione con pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Il primo due punti :
in pd.IndexSlice[:, ['t', 'w']]
significa tagliare attraverso il primo livello. Man mano che la profondità del livello su cui viene eseguita la query aumenta, sarà necessario specificare più sezioni, una per livello da suddividere. Tuttavia, non sarà necessario specificare più livelli oltre a quello da tagliare.
Con query
, questo è
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
Con get_level_values
e Index.isin
(simile a sopra):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Domanda 3
Come si recupera una sezione trasversale, ovvero una singola riga con valori specifici per l'indice da df
? Nello specifico, come faccio a recuperare la sezione trasversale di ('c', 'u')
, data da
col
one two
c u 9
Utilizzare loc
specificando una tupla di chiavi:
df.loc[('c', 'u'), :]
O,
df.loc[pd.IndexSlice[('c', 'u')]]
Nota
A questo punto, potresti imbatterti in un PerformanceWarning
simile a questo:
PerformanceWarning: indexing past lexsort depth may impact performance.
Questo significa solo che il tuo indice non è ordinato. panda dipende dall'indice che viene ordinato (in questo caso, lessicograficamente, dato che si tratta di valori stringa) per una ricerca e un recupero ottimali. Una soluzione rapida sarebbe ordinare il tuo DataFrame in anticipo utilizzando DataFrame.sort_index
. Ciò è particolarmente desiderabile dal punto di vista delle prestazioni se si prevede di eseguire più query di questo tipo in tandem:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
È inoltre possibile utilizzare MultiIndex.is_lexsorted()
per verificare se l'indice è ordinato o meno. Questa funzione restituisce True
o di False
conseguenza. È possibile chiamare questa funzione per determinare se è necessario o meno un passaggio di ordinamento aggiuntivo.
Con xs
, questo è di nuovo semplicemente passare una singola tupla come primo argomento, con tutti gli altri argomenti impostati sui valori predefiniti appropriati:
df.xs(('c', 'u'))
Con query
, le cose diventano un po 'goffe:
df.query("one == 'c' and two == 'u'")
Ora puoi vedere che sarà relativamente difficile generalizzare. Ma è ancora OK per questo particolare problema.
Con accessi che si estendono su più livelli, get_level_values
può ancora essere utilizzato, ma non è consigliato:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Domanda 4
Come seleziono le due righe corrispondenti a ('c', 'u')
, e ('a', 'w')
?
col
one two
c u 9
a w 3
Con loc
, questo è ancora semplice come:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Con query
, sarà necessario generare dinamicamente una stringa di query ripetendo le sezioni trasversali e i livelli:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100% NON CONSIGLIATO! Ma è possibile.
Domanda 5
Come posso recuperare tutte le righe corrispondenti a "a" nel livello "uno" o "t" nel livello "due"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Questo è in realtà molto difficile da fare loc
garantendo la correttezza e mantenendo la chiarezza del codice. df.loc[pd.IndexSlice['a', 't']]
non è corretto, viene interpretato come df.loc[pd.IndexSlice[('a', 't')]]
(ovvero, selezione di una sezione trasversale). Potresti pensare a una soluzione pd.concat
per gestire ciascuna etichetta separatamente:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Ma noterai che una delle righe è duplicata. Questo perché quella riga soddisfaceva entrambe le condizioni di taglio e quindi è apparsa due volte. Avrai invece bisogno di farlo
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Ma se il tuo DataFrame contiene intrinsecamente indici duplicati (che desideri), questo non li manterrà. Utilizzare con estrema cautela .
Con query
, questo è stupidamente semplice:
df.query("one == 'a' or two == 't'")
Con get_level_values
, questo è ancora semplice, ma non così elegante:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Domanda 6
Come posso tagliare sezioni trasversali specifiche? Per "a" e "b", vorrei selezionare tutte le righe con sottolivelli "u" e "v", e per "d", vorrei selezionare le righe con sottolivello "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Questo è un caso speciale che ho aggiunto per aiutare a capire l'applicabilità dei Quattro Idiomi: questo è un caso in cui nessuno di essi funzionerà efficacemente, poiché lo slicing è molto specifico e non segue alcun modello reale.
Di solito, problemi di slicing come questo richiedono il passaggio esplicito di un elenco di chiavi a loc
. Un modo per farlo è con:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Se vuoi salvare un po 'di digitazione, riconoscerai che esiste uno schema per affettare "a", "b" e i suoi sottolivelli, quindi possiamo separare l'attività di divisione in due parti e concat
il risultato:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
La specifica di sezionamento per "a" e "b" è leggermente più pulita (('a', 'b'), ('u', 'v'))
perché gli stessi sottolivelli indicizzati sono gli stessi per ogni livello.
Domanda 7
Come ottengo tutte le righe in cui i valori nel livello "due" sono maggiori di 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Questo può essere fatto usando query
,
df2.query("two > 5")
E get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Nota
Simile a questo esempio, possiamo filtrare in base a qualsiasi condizione arbitraria utilizzando questi costrutti. In generale, è utile ricordarlo loc
e xs
sono specifici per l'indicizzazione basata su etichette, mentre query
e
get_level_values
sono utili per creare maschere condizionali generali per il filtraggio.
Domanda bonus
E se ho bisogno di suddividere una MultiIndex
colonna ?
In realtà, la maggior parte delle soluzioni qui sono applicabili anche alle colonne, con piccole modifiche. Tener conto di:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
Queste sono le seguenti modifiche che dovrai apportare ai Quattro modi di dire per farli funzionare con le colonne.
Per affettare loc
, usa
df3.loc[:, ....] # Notice how we slice across the index with `:`.
o,
df3.loc[:, pd.IndexSlice[...]]
Per utilizzare xs
in modo appropriato, è sufficiente passare un argomento axis=1
.
È possibile accedere direttamente ai valori a livello di colonna utilizzando df.columns.get_level_values
. Dovrai quindi fare qualcosa di simile
df.loc[:, {condition}]
Dove {condition}
rappresenta una condizione costruita utilizzando columns.get_level_values
.
Per utilizzarlo query
, l'unica opzione è trasporre, eseguire query sull'indice e trasporre di nuovo:
df3.T.query(...).T
Non consigliato, usa una delle altre 3 opzioni.
level
dell'argomento conIndex.isin
!