Los pandas que seleccionan por etiqueta a veces devuelven Series, a veces devuelven DataFrame

97

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

¿Porqué es eso? ¿Hay alguna forma de asegurar que siempre recupere un marco de datos?

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
jobevers
fuente

Respuestas:

101

Reconozco 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
Dan Allan
fuente
6
Gracias. Vale la pena señalar que esto devuelve un DataFrame incluso si la etiqueta no está en el índice.
jobevers
7
Para su información, con un índice no duplicado y un solo indexador (por ejemplo, una sola etiqueta), SIEMPRE obtendrá una Serie, es solo porque tiene duplicados en el índice que es un DataFrame.
Jeff
1
Tenga en cuenta que hay otro problema: si usa la solución alternativa sugerida y no hay filas coincidentes, el resultado será un DataFrame con una sola fila, todo NaN.
Paul Oyster
2
Paul, ¿qué versión de pandas estás usando? En la última versión, obtengo un KeyErrorcuando lo intento .loc[[nonexistent_label]].
Dan Allan
2
Usar una lista en .loces mucho más lento que sin ella. Para seguir siendo legible pero también mucho más rápido, mejor usodf.loc[1:1]
Jonathan
16

Tiene un índice con tres elementos de índice 3. Por esta razón df.loc[3], devolverá un marco de datos.

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

Al seleccionar una sola fila (como df.loc[1]) se devuelve una serie con los nombres de las columnas como índice.

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

joris
fuente
3
Ese es el comportamiento que esperaría. No entiendo la decisión de diseño de que las filas individuales se conviertan en una serie, ¿por qué no un marco de datos con una fila?
jobevers
Ah, realmente no sé por qué seleccionar una sola fila devuelve una serie.
joris
6

Úselo df['columnName']para obtener una serie y df[['columnName']]un marco de datos.

user4422
fuente
1
Cuidado que lleva una copia del df original.
smci
6

El TLDR

Cuando usas loc

df.loc[:]= Marco de datos

df.loc[int]= Marco de datos si tiene más de una columna y Serie si solo tiene 1 columna en el marco de datos

df.loc[:, ["col_name"]]= Marco de datos

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

No usando loc

df["col_name"]= Serie

df[["col_name"]]= Marco de datos

Colin Anthony
fuente
3

Escribiste en un comentario a la respuesta de joris:

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

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

La mejor manera de pensar en las estructuras de datos de pandas es como contenedores flexibles para datos de menor dimensión. 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 como en un diccionario.

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

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

.

Editar: no estoy de acuerdo conmigo

Una trama de datos no puede estar compuesto de elementos que ser Series, porque el código siguiente da el mismo tipo "Series", así como 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]) :  <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'>

Entonces, no tiene sentido pretender que un DataFrame está compuesto por Series porque, ¿qué se supone que son estas Series: columnas o filas? Estúpida pregunta y visión.

.

Entonces, ¿qué es un DataFrame?

En la versión anterior de esta respuesta, hice esta pregunta, tratando de encontrar la respuesta a la Why is that?parte de la pregunta del OP y la interrogación similar single rows to get converted into a series - why not a data frame with one row?en uno de sus comentarios,
mientras que la Is there a way to ensure I always get back a data frame?parte ha sido respondida 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 inferiores, 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 de un razonamiento) es una buena forma de considerar DataFrames, incluso si no es estrictamente el caso en la realidad. "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 , a su vez derivada de la clase base PandasContainer que también es una clase principal 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 solo de la clase 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__

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

Entonces, tengo entendido ahora que una instancia de DataFrame tiene ciertos métodos que se han diseñado para controlar la forma en que se extraen los datos de las 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 se han elaborado como estaban?
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 en las estructuras de datos de pandas es como contenedores flexibles para datos de menor dimensión.

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

eyquem
fuente
1
Para su información, DataFrame NO es una subclase ndarray, tampoco lo es una Serie (comenzando 0.13, antes de eso). Estos son más dict-like que cualquier cosa.
Jeff
Gracias por informarme. Realmente lo aprecio porque soy nuevo en el aprendizaje de Pandas. Pero necesito más información para entender bien. ¿Por qué está escrito en los documentos que una serie es una subclase de ndarray?
eyquem
fue antes de 0.13 (lanzamiento en breve), aquí están los documentos de desarrollo: pandas.pydata.org/pandas-docs/dev/dsintro.html#series
Jeff
OKAY. Muchas gracias. Sin embargo, no cambia la base de mi razonamiento y comprensión, ¿verdad? - En Pandas inferiores a 0.13, DataFrame y otros objetos de Pandas diferentes de Series: ¿de qué son subclase?
eyquem
@Jeff Gracias. Modifiqué mi respuesta después de tu información. Me complacería saber qué piensa de mi edición.
eyquem
1

Si el objetivo es obtener un subconjunto del conjunto de datos usando el índice, es mejor evitar usar loco iloc. En su lugar, debe usar una sintaxis 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
Ajit
fuente
0

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

Esta función asegura 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)
Wouter
fuente