La selección de pandas por etiqueta a veces devuelve una serie, a veces devuelve un dataframe

En Pandas, cuando selecciono una etiqueta que solo tiene una entrada en el índice, recupero una Serie, pero cuando selecciono una entrada que tiene más de una entrada, recupero un dataframe.

¿Porqué es eso? ¿Hay alguna manera de asegurar que siempre recupere un dataframe?

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 

Concedido que el comportamiento es inconsistente, pero creo que es fácil imaginar casos en los que esto sea conveniente. De todos modos, para obtener un DataFrame cada vez, simplemente pase una lista a loc . Hay otras formas, pero en mi opinión esta es la más limpia.

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

Usted tiene un índice con tres elementos de índice 3 . Por este motivo, df.loc[3] devolverá un dataframe.

La razón es que no especificas la columna. Entonces df.loc[3] selecciona tres elementos de todas las columnas (que es la columna 0 ), mientras que df.loc[3,0] devolverá una Serie. Por ejemplo, df.loc[1:2] también devuelve un dataframe, ya que corta las filas.

La selección de una sola fila (como df.loc[1] ) devuelve una Serie con los nombres de columna como índice.

Si desea asegurarse de tener siempre un DataFrame, puede df.loc[1:1] como df.loc[1:1] . Otra opción es la indexación booleana ( df.loc[df.index==1] ) o el método de toma ( df.take([0]) , ¡pero esta ubicación utilizada no está en las tags!).

Usted escribió en un comentario a la respuesta de Joris:

“No entiendo la decisión de diseño para que las filas individuales se conviertan en una serie, ¿por qué no un dataframe con una fila?”

Una sola fila no se convierte en una serie.
ES una serie: No, I don't think so, in fact; see the edit No, I don't think so, in fact; see the edit

La mejor manera de pensar acerca de las estructuras de datos de los pandas es como contenedores flexibles para datos de dimensiones inferiores. Por ejemplo, DataFrame es un contenedor para Series, y Panel es un contenedor para objetos DataFrame. Nos gustaría poder insertar y eliminar objetos de estos contenedores de forma similar a un diccionario.

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

El modelo de datos de los objetos de Pandas ha sido elegido así. La razón ciertamente radica en el hecho de que garantiza algunas ventajas que no conozco (no entiendo completamente la última oración de la cita, tal vez sea la razón)

.

Edit: no estoy de acuerdo conmigo

Un DataFrame no puede estar compuesto por elementos que serían Series, porque el siguiente código da el mismo tipo “Series” también para una fila como para una columna:

 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]) :  --------- df[0] ---------- 2 11 3 12 3 13 Name: 0, dtype: int64 type(df[0]) :  

Por lo tanto, no tiene sentido pretender que un DataFrame está compuesto de Series porque lo que se supone que deberían ser dichas series: ¿columnas o filas? La pregunta y la visión estúpida.

.

Entonces, ¿qué es un DataFrame?

En la versión anterior de esta respuesta, hice esta pregunta, tratando de encontrar la respuesta a la pregunta Why is that? parte de la cuestión del OP y las single rows to get converted into a series - why not a data frame with one row? interrogación similares single rows to get converted into a series - why not a data frame with one row? en uno de sus comentarios,
mientras que Is there a way to ensure I always get back a data frame? parte ha sido contestada por Dan Allan.

Luego, como los documentos de los Pandas citados anteriormente dicen que las estructuras de datos de los pandas se ven mejor como contenedores de datos de dimensiones más bajas, me pareció que la comprensión del por qué se encontraría en las características de la naturaleza de las estructuras de DataFrame.

Sin embargo, me di cuenta de que este consejo citado no debe tomarse como una descripción precisa de la naturaleza de las estructuras de datos de Pandas.
Este consejo no significa que un DataFrame sea un contenedor de Series.
Expresa que la representación mental de un DataFrame como un contenedor de Series (ya sea filas o columnas según la opción considerada en un momento del razonamiento) es una buena manera de considerar DataFrames, incluso si en realidad no es el caso. “Bueno” significa que esta visión permite utilizar DataFrames con eficiencia. Eso es todo.

.

Entonces, ¿qué es un objeto DataFrame?

La clase DataFrame produce instancias que tienen una estructura particular originada en la clase base NDFrame , derivada de la clase base PandasContainer que también es una clase primaria de la clase Series .
Tenga en cuenta que esto es correcto para Pandas hasta la versión 0.12. En la próxima versión 0.13, Series se derivará también de la clase NDFrame solamente.

 # 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 :  Series.__bases__ : (, ) DataFrame :  DataFrame.__bases__ : (,) ------------------- NDFrame.__bases__ : (,) PandasContainer.__bases__ : (,) PandasObject.__bases__ : (,) StringMixin.__bases__ : (,) 

Así que ahora entiendo que una instancia de DataFrame tiene ciertos métodos que han sido diseñados para controlar la forma en que los datos se extraen de filas y columnas.

Las formas en que funcionan estos métodos de extracción se describen en esta página: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
Encontramos en él el método dado por Dan Allan y otros métodos.

¿Por qué estos métodos de extracción han sido diseñados como lo fueron?
Eso es ciertamente porque han sido evaluados como los que ofrecen mejores posibilidades y facilidad en el análisis de datos.
Es precisamente lo que se expresa en esta frase:

La mejor manera de pensar acerca de las estructuras de datos de los pandas es como contenedores flexibles para datos de dimensiones inferiores.

El por qué de la extracción de datos de una instancia de DataFRame no reside en su estructura, sino en el porqué de esta estructura. Supongo que la estructura y el funcionamiento de la estructura de datos de los Pandas han sido cincelados para ser lo más intelectualmente intuitivos posible, y que para comprender los detalles, uno debe leer el blog de Wes McKinney.

Use df['columnName'] para obtener una serie y df[['columnName']] para obtener un Dataframe.

Si el objective es obtener un subconjunto del conjunto de datos utilizando el índice, es mejor evitar el uso de loc o iloc . En su lugar, debe utilizar una syntax similar 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 

Si también selecciona en el índice del dataframe, entonces el resultado puede ser un Marco de datos o una Serie o puede ser una Serie o un escalar (valor único).

Esta función garantiza que siempre obtenga una lista de su selección (si el df, el índice y la columna son 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)