Pandas selecionando por rótulo às vezes retorna Series, às vezes retorna DataFrame

95

No Pandas, quando seleciono um rótulo que tem apenas uma entrada no índice, recebo uma série, mas quando seleciono uma entrada que tem mais de uma entrada, recebo um quadro de dados.

Por que é que? Existe uma maneira de garantir que sempre recebo um quadro de dados?

In [1]: import pandas as pd

In [2]: df = pd.DataFrame(data=range(5), index=[1, 2, 3, 3, 3])

In [3]: type(df.loc[3])
Out[3]: pandas.core.frame.DataFrame

In [4]: type(df.loc[1])
Out[4]: pandas.core.series.Series
empregadores
fonte

Respostas:

101

Admito que o comportamento é inconsistente, mas acho fácil imaginar casos em que isso seja conveniente. Enfim, para obter um DataFrame todas as vezes, basta passar uma lista para loc. Existem outras formas, mas na minha opinião esta é a mais limpa.

In [2]: type(df.loc[[3]])
Out[2]: pandas.core.frame.DataFrame

In [3]: type(df.loc[[1]])
Out[3]: pandas.core.frame.DataFrame
Dan Allan
fonte
6
Obrigado. É importante notar que isso retorna um DataFrame mesmo se o rótulo não estiver no índice.
jobevers
7
Para sua informação, com um índice não duplicado e um único indexador (por exemplo, um único rótulo), você SEMPRE receberá de volta uma Série, só porque você tem duplicatas no índice que é um DataFrame.
Jeff
1
Observe que há um outro problema: se estiver usando a solução alternativa sugerida, e não houver linhas correspondentes, o resultado será um DataFrame com uma única linha, todos NaN.
Paul Oyster
2
Paul, que versão de pandas você está usando? Na versão mais recente, recebo um KeyErrorquando tento .loc[[nonexistent_label]].
Dan Allan
2
Usar uma lista no .locé muito mais lento do que sem ela. Para ser ainda legível, mas também muito mais rápido, use melhordf.loc[1:1]
Jonathan
15

Você tem um índice com três itens de índice 3. Por esse motivo df.loc[3], retornará um dataframe.

O motivo é que você não especifica a coluna. Então df.loc[3]seleciona três itens de todas as colunas (que é coluna 0), enquanto df.loc[3,0]retornará uma Série. Por exemplo, df.loc[1:2]também retorna um dataframe, porque você divide as linhas.

Selecionar uma única linha (as df.loc[1]) retorna uma Série com os nomes das colunas como o índice.

Se você quiser ter certeza de sempre ter um DataFrame, pode fazer um slice como df.loc[1:1]. Outra opção é a indexação booleana ( df.loc[df.index==1]) ou o método take ( df.take([0]), mas este local usado não é rótulos!).

Joris
fonte
3
Esse é o comportamento que eu esperava. Não entendo a decisão de design para linhas únicas serem convertidas em uma série - por que não um quadro de dados com uma linha?
jobevers
Ah, por que selecionar uma única linha retorna uma série, eu realmente não sei.
joris
6

O TLDR

Ao usar loc

df.loc[:]= Dataframe

df.loc[int]= Dataframe se você tiver mais de uma coluna e Series se você tiver apenas 1 coluna no dataframe

df.loc[:, ["col_name"]]= Dataframe

df.loc[:, "col_name"]= Série

Não use loc

df["col_name"]= Série

df[["col_name"]]= Dataframe

Colin Anthony
fonte
5

Use df['columnName']para obter uma série e df[['columnName']]para obter um Dataframe.

user4422
fonte
1
Cuidado, isso leva uma cópia do df original.
smci
3

Você escreveu em um comentário à resposta de joris:

"Não entendo a decisão de design para que linhas únicas sejam convertidas em uma série - por que não um quadro de dados com uma linha?"

Uma única linha não é convertida em uma série.
Ele é uma série:No, I don't think so, in fact; see the edit

A melhor maneira de pensar sobre as estruturas de dados do pandas é como recipientes flexíveis para dados dimensionais inferiores. Por exemplo, DataFrame é um contêiner para Series e Panel é um contêiner para objetos DataFrame. Gostaríamos de poder inserir e remover objetos desses contêineres como um dicionário.

http://pandas.pydata.org/pandas-docs/stable/overview.html#why-more-than-1-data-structure

O modelo de dados dos objetos Pandas foi escolhido assim. A razão certamente está no fato de garantir algumas vantagens que não conheço (não entendi bem a última frase da citação, talvez seja o motivo)

.

Edit: Eu não concordo comigo

A trama de dados não pode ser composto de elementos que iria ser Series, porque o código a seguir dá o mesmo tipo "Series", bem como para uma linha como para uma coluna:

import pandas as pd

df = pd.DataFrame(data=[11,12,13], index=[2, 3, 3])

print '-------- df -------------'
print df

print '\n------- df.loc[2] --------'
print df.loc[2]
print 'type(df.loc[1]) : ',type(df.loc[2])

print '\n--------- df[0] ----------'
print df[0]
print 'type(df[0]) : ',type(df[0])

resultado

-------- df -------------
    0
2  11
3  12
3  13

------- df.loc[2] --------
0    11
Name: 2, dtype: int64
type(df.loc[1]) :  <class 'pandas.core.series.Series'>

--------- df[0] ----------
2    11
3    12
3    13
Name: 0, dtype: int64
type(df[0]) :  <class 'pandas.core.series.Series'>

Portanto, não faz sentido fingir que um DataFrame é composto de Séries, porque o que essas Séries seriam: colunas ou linhas? Pergunta e visão estúpidas.

.

Então o que é um DataFrame?

Na versão anterior desta resposta, fiz esta pergunta, tentando encontrar a resposta para a Why is that?parte da pergunta do OP e a interrogação semelhante single rows to get converted into a series - why not a data frame with one row?em um de seus comentários,
enquanto a Is there a way to ensure I always get back a data frame?parte foi respondida por Dan Allan.

Então, como os documentos dos Pandas citados acima dizem que as estruturas de dados dos pandas são mais bem vistas como contêineres de dados dimensionais inferiores, pareceu-me que a compreensão do porquê seria encontrada nas características da natureza das estruturas DataFrame.

No entanto, percebi que este conselho citado não deve ser tomado como uma descrição precisa da natureza das estruturas de dados do Pandas.
Este conselho não significa que um DataFrame seja um contêiner de Series.
Expressa que a representação mental de um DataFrame como um container de Séries (sejam linhas ou colunas conforme a opção considerada em um momento de raciocínio) é uma boa forma de considerar DataFrames, mesmo que não seja estritamente o caso na realidade. "Bom" significa que esta visão permite usar DataFrames com eficiência. Isso é tudo.

.

Então, o que é um objeto DataFrame?

A classe DataFrame produz instâncias que possuem uma estrutura particular originada na classe base NDFrame , ela própria derivada da classe base PandasContainer que também é uma classe pai da classe Series .
Observe que isso é correto para Pandas até a versão 0.12. Na próxima versão 0.13, Series derivará também da classe NDFrame apenas.

# with pandas 0.12

from pandas import Series
print 'Series  :\n',Series
print 'Series.__bases__  :\n',Series.__bases__

from pandas import DataFrame
print '\nDataFrame  :\n',DataFrame
print 'DataFrame.__bases__  :\n',DataFrame.__bases__

print '\n-------------------'

from pandas.core.generic import NDFrame
print '\nNDFrame.__bases__  :\n',NDFrame.__bases__

from pandas.core.generic import PandasContainer
print '\nPandasContainer.__bases__  :\n',PandasContainer.__bases__

from pandas.core.base import PandasObject
print '\nPandasObject.__bases__  :\n',PandasObject.__bases__

from pandas.core.base import StringMixin
print '\nStringMixin.__bases__  :\n',StringMixin.__bases__

resultado

Series  :
<class 'pandas.core.series.Series'>
Series.__bases__  :
(<class 'pandas.core.generic.PandasContainer'>, <type 'numpy.ndarray'>)

DataFrame  :
<class 'pandas.core.frame.DataFrame'>
DataFrame.__bases__  :
(<class 'pandas.core.generic.NDFrame'>,)

-------------------

NDFrame.__bases__  :
(<class 'pandas.core.generic.PandasContainer'>,)

PandasContainer.__bases__  :
(<class 'pandas.core.base.PandasObject'>,)

PandasObject.__bases__  :
(<class 'pandas.core.base.StringMixin'>,)

StringMixin.__bases__  :
(<type 'object'>,)

Portanto, meu entendimento é que uma instância de DataFrame tem certos métodos que foram criados para controlar a maneira como os dados são extraídos de linhas e colunas.

As maneiras como esses métodos de extração funcionam são descritas nesta página: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
Encontramos nele o método fornecido por Dan Allan e outros métodos.

Por que esses métodos de extração foram elaborados da forma como eram?
Certamente porque foram avaliados como os que apresentam melhores possibilidades e facilidade na análise dos dados.
É exatamente o que está expresso nesta frase:

A melhor maneira de pensar sobre as estruturas de dados do pandas é como recipientes flexíveis para dados dimensionais inferiores.

O porquê da extração de dados de uma instância de DataFRame não está em sua estrutura, mas no porquê dessa estrutura. Eu acho que a estrutura e o funcionamento da estrutura de dados do Pandas foram esculpidos para serem o mais intuitivos intelectualmente possíveis, e que para entender os detalhes, é preciso ler o blog de Wes McKinney.

eyquem
fonte
1
Para sua informação, DataFrame NÃO é uma subclasse ndarray, nem é uma série (começando com 0,13, antes que fosse). Estes são mais semelhantes do que qualquer coisa.
Jeff
Obrigado por me informar. Agradeço muito porque sou novo no aprendizado de Pandas. Mas preciso de mais informações para entender bem. Por que está escrito na documentação que Series é uma subclasse de ndarray?
eyquem
era antes de 0,13 (lançando em breve), aqui estão os documentos dev: pandas.pydata.org/pandas-docs/dev/dsintro.html#series
Jeff
ESTÁ BEM. Muito obrigado. No entanto, isso não muda a base do meu raciocínio e compreensão, muda? - Em Pandas inferiores a 0,13, DataFrame e outros objetos de Pandas diferentes de Series: de que são subclasses?
eyquem
@Jeff Obrigado. Eu modifiquei minha resposta após suas informações. Eu ficaria feliz em saber o que você achou da minha edição.
eyquem
1

Se o objetivo for obter um subconjunto do conjunto de dados usando o índice, é melhor evitar o uso de locou iloc. Em vez disso, você deve usar uma sintaxe semelhante a esta:

df = pd.DataFrame(data=range(5), index=[1, 2, 3, 3, 3])
result = df[df.index == 3] 
isinstance(result, pd.DataFrame) # True

result = df[df.index == 1]
isinstance(result, pd.DataFrame) # True
Ajit
fonte
0

Se você também selecionar o índice do dataframe, o resultado pode ser um DataFrame ou uma Série ou pode ser uma Série ou um escalar (valor único).

Esta função garante que você sempre obtenha uma lista de sua seleção (se o df, índice e coluna forem válidos):

def get_list_from_df_column(df, index, column):
    df_or_series = df.loc[index,[column]] 
    # df.loc[index,column] is also possible and returns a series or a scalar
    if isinstance(df_or_series, pd.Series):
        resulting_list = df_or_series.tolist() #get list from series
    else:
        resulting_list = df_or_series[column].tolist() 
        # use the column key to get a series from the dataframe
    return(resulting_list)
Wouter
fonte