I panda che selezionano per etichetta a volte restituiscono Serie, a volte restituisce DataFrame


97

In Pandas, quando seleziono un'etichetta che ha solo una voce nell'indice, ricevo una serie, ma quando seleziono una voce che ha più di una voce ricevo un frame di dati.

Perché? C'è un modo per assicurarmi di ottenere sempre un frame di dati?

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

Risposte:


101

Ammesso che il comportamento sia incoerente, ma penso sia facile immaginare casi in cui ciò sia conveniente. Ad ogni modo, per ottenere ogni volta un DataFrame, basta passare un elenco a loc. Ci sono altri modi, ma secondo me questo è il più pulito.

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

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

6
Grazie. Vale la pena notare che questo restituisce un DataFrame anche se l'etichetta non è nell'indice.
jobevers

7
Cordiali saluti, con un indice non duplicato e un singolo indicizzatore (ad esempio una singola etichetta), otterrai SEMPRE una serie, è solo perché hai duplicati nell'indice che è un DataFrame.
Jeff

1
Nota che c'è ancora un altro trucco: se usi la soluzione suggerita e non ci sono righe corrispondenti, il risultato sarà un DataFrame con una singola riga, tutta NaN.
Paul Oyster

2
Paul, quale versione di panda stai usando? Nell'ultima versione, ottengo un KeyErrorquando provo .loc[[nonexistent_label]].
Dan Allan

2
L'uso di un elenco in .locè molto più lento che senza di esso. Per essere ancora leggibile ma anche molto più veloce, meglio usaredf.loc[1:1]
Jonathan

16

Hai un indice con tre elementi indice 3. Per questo motivo df.loc[3]restituirà un dataframe.

Il motivo è che non specifichi la colonna. Quindi df.loc[3]seleziona tre elementi di tutte le colonne (che è colonna 0), mentre df.loc[3,0]restituirà una serie. Ad esempio, df.loc[1:2]restituisce anche un dataframe, perché dividi le righe.

La selezione di una singola riga (as df.loc[1]) restituisce una serie con i nomi delle colonne come indice.

Se vuoi essere sicuro di avere sempre un DataFrame, puoi tagliare come df.loc[1:1]. Un'altra opzione è boolean indexing ( df.loc[df.index==1]) o il metodo take ( df.take([0]), ma questa posizione utilizzata non è label!).


3
Questo è il comportamento che mi aspetterei. Non capisco la decisione di progettazione per la conversione di singole righe in una serie, perché non un frame di dati con una riga?
jobevers

Ah, perché selezionando una singola riga restituisce una serie, non lo so davvero.
joris

6

Utilizzare df['columnName']per ottenere una serie e df[['columnName']]per ottenere un dataframe.


1
Attenzione che prende una copia del df originale.
smci

6

Il TLDR

Quando si usa loc

df.loc[:]= Dataframe

df.loc[int]= Dataframe se hai più di una colonna e Series se hai solo 1 colonna nel dataframe

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

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

Non in uso loc

df["col_name"]= Serie

df[["col_name"]]= Dataframe


3

Hai scritto in un commento alla risposta di joris:

"Non capisco la decisione di progettazione per la conversione di singole righe in una serie, perché non un data frame con una riga?"

Una singola riga non viene convertita in una serie.
E ' È una Serie:No, I don't think so, in fact; see the edit

Il modo migliore per pensare alle strutture dati dei panda è come contenitori flessibili per dati di dimensioni inferiori. Ad esempio, DataFrame è un contenitore per Series e Panel è un contenitore per oggetti DataFrame. Vorremmo essere in grado di inserire e rimuovere oggetti da questi contenitori in modo simile a un dizionario.

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

Il modello di dati degli oggetti Pandas è stato scelto in questo modo. Il motivo sta sicuramente nel fatto che assicura alcuni vantaggi che non conosco (non capisco appieno l'ultima frase della citazione, forse è il motivo)

.

Modifica: non sono d'accordo con me

Un dataframe non può essere composta da elementi che essere serie, poiché il seguente codice dà lo stesso tipo "serie" anche per una fila come per una colonna:

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])

risultato

-------- 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'>

Quindi, non ha senso fingere che un DataFrame sia composto da Series perché cosa dovrebbero essere queste serie: colonne o righe? Domanda e visione stupide.

.

Allora cos'è un DataFrame?

Nella versione precedente di questa risposta, ho posto questa domanda, cercando di trovare la risposta alla Why is that?parte della domanda dell'OP e all'interrogatorio simile single rows to get converted into a series - why not a data frame with one row?in un suo commento,
mentre la Is there a way to ensure I always get back a data frame?parte ha avuto risposta da Dan Allan.

Quindi, poiché i documenti dei Panda citati sopra dicono che le strutture dati dei panda sono meglio viste come contenitori di dati di dimensioni inferiori, mi è sembrato che la comprensione del perché si trovasse nelle caratteristiche della natura delle strutture DataFrame.

Tuttavia, mi sono reso conto che questo consiglio citato non deve essere preso come una descrizione precisa della natura delle strutture dati di Panda.
Questo consiglio non significa che un DataFrame sia un contenitore di Series.
Esprime che la rappresentazione mentale di un DataFrame come un contenitore di Serie (righe o colonne a seconda dell'opzione considerata in un momento di un ragionamento) è un buon modo per considerare DataFrame, anche se non è strettamente il caso nella realtà. "Buono" significa che questa visione consente di utilizzare DataFrame con efficienza. È tutto.

.

Allora cos'è un oggetto DataFrame?

La classe DataFrame produce istanze che hanno una struttura particolare originata nella classe base NDFrame , a sua volta derivata dalla classe base PandasContainer che è anche una classe padre della classe Series .
Nota che questo è corretto per Panda fino alla versione 0.12. Nella prossima versione 0.13, Series deriverà anche dalla sola classe NDFrame .

# 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__

risultato

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'>,)

Quindi la mia comprensione è ora che un'istanza DataFrame ha alcuni metodi che sono stati predisposti per controllare il modo in cui i dati vengono estratti da righe e colonne.

I modi in cui funzionano questi metodi di estrazione sono descritti in questa pagina: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
Troviamo in esso il metodo fornito da Dan Allan e altri metodi.

Perché questi metodi di estrazione sono stati realizzati così com'erano?
Questo è certamente perché sono stati valutati come quelli che offrono le migliori possibilità e facilità nell'analisi dei dati.
È proprio quello che si esprime in questa frase:

Il modo migliore per pensare alle strutture dati dei panda è come contenitori flessibili per dati di dimensioni inferiori.

Il perché dell'estrazione dei dati da un'istanza DataFRame non risiede nella sua struttura, ma nel perché di questa struttura. Immagino che la struttura e il funzionamento della struttura dei dati dei Panda siano stati scolpiti in modo da essere il più intellettualmente intuitivo possibile, e che per comprenderne i dettagli, bisogna leggere il blog di Wes McKinney.


1
Cordiali saluti, DataFrame NON è una sottoclasse ndarray, né è una serie (a partire da 0.13, prima era però). Questi sono più dict-like che niente.
Jeff

Grazie per informarmi. Apprezzo molto perché sono nuovo nell'apprendimento dei Panda. Ma ho bisogno di più informazioni per capire bene. Perché è scritto nei documenti che una serie è una sottoclasse di ndarray?
eyquem

era prima della 0.13 (rilascio a breve), ecco i documenti dev: pandas.pydata.org/pandas-docs/dev/dsintro.html#series
Jeff

OK. Grazie mille. Tuttavia non cambia la base del mio ragionamento e della mia comprensione, vero? - In Panda inferiore a 0.13, DataFrame e altri oggetti Panda diversi da Series: di cosa sono sottoclasse?
eyquem

@ Jeff Grazie. Ho modificato la mia risposta dopo le tue informazioni. Mi farebbe piacere sapere cosa ne pensate della mia modifica.
eyquem

1

Se l'obiettivo è ottenere un sottoinsieme del set di dati utilizzando l'indice, è meglio evitare di utilizzare loco iloc. Invece dovresti usare una sintassi simile a questa:

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

0

Se selezioni anche sull'indice del dataframe, il risultato può essere un DataFrame o una Serie oppure può essere una Serie o uno scalare (valore singolo).

Questa funzione garantisce di ottenere sempre un elenco dalla selezione (se df, indice e colonna sono validi):

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.