Nota
Esta postagem será estruturada da seguinte maneira:
- As questões colocadas no OP serão abordadas, uma a uma
- Para cada pergunta, um ou mais métodos aplicáveis à solução desse problema e à obtenção do resultado esperado serão demonstrados.
Nota (muito parecidas com esta) serão incluídas para os leitores interessados em aprender sobre funcionalidades adicionais, detalhes de implementação e outras informações necessárias para o tópico em questão. Essas anotações foram compiladas através da análise dos documentos e da descoberta de vários recursos obscuros e de minha própria experiência (reconhecidamente limitada).
Todas as amostras de código foram criadas e testadas no pandas v0.23.4, python3.7 . Se algo não estiver claro ou factualmente incorreto, ou se você não encontrou uma solução aplicável ao seu caso de uso, sinta-se à vontade para sugerir uma edição, solicitar esclarecimentos nos comentários ou abrir uma nova pergunta, ... conforme aplicável .
Aqui está uma introdução a alguns idiomas comuns (doravante referidos como os quatro idiomas) que visitaremos frequentemente
DataFrame.loc
- Uma solução geral para seleção por etiqueta (+ pd.IndexSlice
para aplicações mais complexas envolvendo fatias)
DataFrame.xs
- Extraia uma seção transversal específica de um Series / DataFrame.
DataFrame.query
- Especifique operações de corte e / ou filtragem dinamicamente (ou seja, como uma expressão avaliada dinamicamente. É mais aplicável a alguns cenários do que outros. Consulte também esta seção dos documentos para consultar em MultiIndexes.
Indexação booleana com uma máscara gerada usando MultiIndex.get_level_values
(geralmente em conjunto com Index.isin
, especialmente ao filtrar com vários valores). Isso também é bastante útil em algumas circunstâncias.
Será benéfico analisar os vários problemas de fatia e filtragem em termos dos Quatro Idiomas para entender melhor o que pode ser aplicado a uma determinada situação. É muito importante entender que nem todos os idiomas funcionarão igualmente bem (se houver) em todas as circunstâncias. Se um idioma não foi listado como uma solução potencial para um problema abaixo, isso significa que o idioma não pode ser aplicado a esse problema efetivamente.
Questão 1
Como seleciono linhas com "a" no nível "um"?
col
one two
a t 0
u 1
v 2
w 3
Você pode usar loc
, como uma solução de uso geral aplicável à maioria das situações:
df.loc[['a']]
Neste ponto, se você receber
TypeError: Expected tuple, got str
Isso significa que você está usando uma versão mais antiga dos pandas. Considere atualizar! Caso contrário, use df.loc[('a', slice(None)), :]
.
Como alternativa, você pode usar xs
aqui, pois estamos extraindo uma única seção transversal. Observe os argumentos levels
e axis
(padrões razoáveis podem ser assumidos aqui).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Aqui, o drop_level=False
argumento é necessário para impedir a xs
queda do nível "um" no resultado (o nível em que fatiávamos).
Ainda outra opção aqui está usando query
:
df.query("one == 'a'")
Se o índice não tivesse um nome, você precisaria alterar sua string de consulta "ilevel_0 == 'a'"
.
Finalmente, usando 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']
Além disso, como eu seria capaz de reduzir o nível "um" na saída?
col
two
t 0
u 1
v 2
w 3
Isso pode ser feito facilmente usando
df.loc['a'] # Notice the single string argument instead the list.
Ou,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Observe que podemos omitir o drop_level
argumento (é assumido True
por padrão).
Nota
Você pode perceber que um DataFrame filtrado ainda pode ter todos os níveis, mesmo se eles não aparecerem ao imprimir o DataFrame. Por exemplo,
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'])
Você pode se livrar desses níveis 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'])
Pergunta 1b
Como faço para dividir todas as linhas com o valor "t" no nível "dois"?
col
one two
a t 0
b t 4
t 8
d t 12
Intuitivamente, você gostaria de algo envolvendo slice()
:
df.loc[(slice(None), 't'), :]
Simplesmente funciona! ™ Mas é desajeitado. Podemos facilitar uma sintaxe de fatiamento mais natural usando a pd.IndexSlice
API aqui.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Isso é muito, muito mais limpo.
Nota
Por que a fatia final :
nas colunas é necessária? Isso ocorre porque, loc
pode ser usado para selecionar e cortar ao longo dos dois eixos ( axis=0
ou
axis=1
). Sem explicitamente esclarecer em qual eixo o fatiamento será realizado, a operação se torna ambígua. Veja a grande caixa vermelha na documentação sobre fatiar .
Se você deseja remover qualquer tom de ambiguidade, loc
aceita um axis
parâmetro:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Sem o axis
parâmetro (ou seja, apenas fazendo df.loc[pd.IndexSlice[:, 't']]
), o fatiamento é assumido como estando nas colunas e a KeyError
será gerado nessa circunstância.
Isso está documentado em slicers . Para os fins deste post, no entanto, especificaremos explicitamente todos os eixos.
Com xs
, é
df.xs('t', axis=0, level=1, drop_level=False)
Com query
, é
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
E finalmente, com get_level_values
, você pode fazer
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Tudo com o mesmo efeito.
Questão 2
Como posso selecionar linhas correspondentes aos itens "b" e "d" no nível "um"?
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, isso é feito de maneira semelhante, especificando uma lista.
df.loc[['b', 'd']]
Para resolver o problema acima de selecionar "b" e "d", você também pode usar 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
Sim, o analisador padrão é 'pandas'
, mas é importante destacar que essa sintaxe não é convencionalmente python. O analisador Pandas gera uma árvore de análise ligeiramente diferente da expressão. Isso é feito para tornar algumas operações mais intuitivas para especificar. Para obter mais informações, leia meu post sobre
Avaliação de Expressão Dinâmica em pandas usando pd.eval () .
E, com get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Pergunta 2b
Como eu obteria todos os valores correspondentes a "t" e "w" no nível "dois"?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Com loc
, isso é possível apenas em conjunto com pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
O primeiro cólon :
em pd.IndexSlice[:, ['t', 'w']]
meios para cortar transversalmente o primeiro nível. À medida que a profundidade do nível que está sendo consultado aumenta, você precisará especificar mais fatias, uma por nível sendo fatiada. Você não precisará especificar mais níveis além do que está sendo fatiado, no entanto.
Com query
, isso é
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')
Com get_level_values
e Index.isin
(semelhante ao acima):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Questão 3
Como recupero uma seção transversal, ou seja, uma única linha com valores específicos para o índice df
? Especificamente, como recupero a seção transversal ('c', 'u')
, dada por
col
one two
c u 9
Use loc
especificando uma tupla de chaves:
df.loc[('c', 'u'), :]
Ou,
df.loc[pd.IndexSlice[('c', 'u')]]
Nota
Neste ponto, você pode encontrar um PerformanceWarning
que se parece com isso:
PerformanceWarning: indexing past lexsort depth may impact performance.
Isso significa apenas que seu índice não está classificado. pandas depende do índice que está sendo classificado (neste caso, lexicograficamente, já que estamos lidando com valores de string) para busca e recuperação ideais. Uma solução rápida seria classificar seu DataFrame com antecedência DataFrame.sort_index
. Isso é especialmente desejável do ponto de vista de desempenho, se você planeja fazer várias dessas consultas em conjunto:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Você também pode usar MultiIndex.is_lexsorted()
para verificar se o índice está classificado ou não. Esta função retorna True
ou de False
acordo. Você pode chamar esta função para determinar se uma etapa de classificação adicional é necessária ou não.
Com xs
, isso é simplesmente passar uma única tupla como o primeiro argumento, com todos os outros argumentos definidos com os padrões apropriados:
df.xs(('c', 'u'))
Com query
, as coisas ficam um pouco desajeitadas:
df.query("one == 'c' and two == 'u'")
Você pode ver agora que isso será relativamente difícil de generalizar. Mas ainda está bom para esse problema em particular.
Com acessos abrangendo vários níveis, get_level_values
ainda pode ser usado, mas não é recomendado:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Pergunta 4
Como seleciono as duas linhas correspondentes a ('c', 'u')
, e ('a', 'w')
?
col
one two
c u 9
a w 3
Com loc
, isso ainda é tão simples quanto:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Com query
, você precisará gerar dinamicamente uma sequência de consultas iterando sobre suas seções transversais e níveis:
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% NÃO RECOMENDO! Mas é possível.
Questão 5
Como recuperar todas as linhas correspondentes a "a" no nível "um" ou "t" no nível "dois"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Isso é realmente muito difícil loc
, garantindo a correção e ainda mantendo a clareza do código. df.loc[pd.IndexSlice['a', 't']]
está incorreto, é interpretado como df.loc[pd.IndexSlice[('a', 't')]]
(por exemplo, selecionando uma seção transversal). Você pode pensar em uma solução pd.concat
para lidar com cada rótulo separadamente:
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
Mas você notará que uma das linhas está duplicada. Isso ocorre porque essa linha atendeu às duas condições de fatiamento e, portanto, apareceu duas vezes. Você precisará fazer
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Mas se o DataFrame contiver inerentemente índices duplicados (que você deseja), isso não os manterá. Use com extrema cautela .
Com query
, isso é estupidamente simples:
df.query("one == 'a' or two == 't'")
Com get_level_values
, isso ainda é simples, mas não tão elegante:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Pergunta 6
Como posso cortar seções transversais específicas? Para "a" e "b", gostaria de selecionar todas as linhas com os subníveis "u" e "v" e, para "d", gostaria de selecionar as linhas com o subnível "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Esse é um caso especial que eu adicionei para ajudar a entender a aplicabilidade dos Quatro Idiomas - esse é um caso em que nenhum deles funcionará efetivamente, pois a fatia é muito específica e não segue nenhum padrão real.
Normalmente, problemas de fatiamento como esse exigirão a passagem explícita de uma lista de chaves para loc
. Uma maneira de fazer isso é com:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Se você quiser salvar alguma digitação, reconhecerá que existe um padrão para fatiar "a", "b" e seus subníveis, para que possamos separar a tarefa de fatiar em duas partes e concat
o resultado:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
A especificação de fatiamento para "a" e "b" é um pouco mais limpa, (('a', 'b'), ('u', 'v'))
porque os mesmos subníveis sendo indexados são os mesmos para cada nível.
Pergunta 7
Como obtenho todas as linhas em que os valores no nível "dois" são maiores que 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Isso pode ser feito usando query
,
df2.query("two > 5")
E get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Nota
Semelhante a este exemplo, podemos filtrar com base em qualquer condição arbitrária usando essas construções. Em geral, é útil lembrar que loc
e xs
são especificamente para indexação baseado em rótulo, enquanto query
e
get_level_values
são úteis para a construção de máscaras condicionais gerais para a filtragem.
Pergunta bônus
E se eu precisar dividir uma MultiIndex
coluna ?
Na verdade, a maioria das soluções aqui também é aplicável a colunas, com pequenas alterações. Considerar:
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
Estas são as seguintes alterações que você precisará fazer nos Quatro Idiomas para que eles trabalhem com colunas.
Para fatiar loc
, use
df3.loc[:, ....] # Notice how we slice across the index with `:`.
ou,
df3.loc[:, pd.IndexSlice[...]]
Para usar xs
conforme apropriado, basta passar um argumento axis=1
.
Você pode acessar diretamente os valores no nível da coluna usando df.columns.get_level_values
. Você precisará fazer algo como
df.loc[:, {condition}]
Onde {condition}
representa alguma condição criada usando columns.get_level_values
.
Para usar query
, sua única opção é transpor, consultar o índice e transpor novamente:
df3.T.query(...).T
Não recomendado, use uma das outras 3 opções.
level
argumentoIndex.isin
!