web-dev-qa-db-fra.com

Pandas sélection par étiquette parfois retourne Series, parfois retourne DataFrame

Dans Pandas, lorsque je sélectionne une étiquette qui n'a qu'une seule entrée dans l'index, je récupère une série, mais lorsque je sélectionne une entrée qui contient plus d'une entrée, je récupère une trame de données.

Pourquoi donc? Existe-t-il un moyen de garantir que je récupère toujours une trame de données?

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

Certes, le comportement est incohérent, mais je pense qu'il est facile d'imaginer des cas où cela est pratique. Quoi qu'il en soit, pour obtenir un DataFrame à chaque fois, passez simplement une liste à loc. Il existe d'autres moyens, mais à mon avis, c'est le plus propre.

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

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

Vous avez un index avec trois éléments d'index 3. Pour cette raison, df.loc[3] Renverra une trame de données.

La raison en est que vous ne spécifiez pas la colonne. Ainsi df.loc[3] Sélectionne trois éléments de toutes les colonnes (qui est la colonne 0), Tandis que df.loc[3,0] Renverra une série. Par exemple. df.loc[1:2] Renvoie également une trame de données, car vous coupez les lignes.

La sélection d'une seule ligne (comme df.loc[1]) Renvoie une série avec les noms de colonne comme index.

Si vous voulez être sûr de toujours avoir un DataFrame, vous pouvez découper comme df.loc[1:1]. Une autre option est l'indexation booléenne (df.loc[df.index==1]) Ou la méthode take (df.take([0]), mais cet emplacement utilisé n'est pas des étiquettes!).

13
joris

Utilisation df['columnName'] pour obtenir une série et df[['columnName']] pour obtenir un Dataframe.

5
user4422

Vous avez écrit dans un commentaire à la réponse de joris:

"Je ne comprends pas la décision de conception pour des lignes simples de se convertir en une série - pourquoi pas une trame de données avec une ligne?"

Une seule ligne n'est pas convertie dans une série.
C'est [~ # ~] est [~ # ~] une série: No, I don't think so, in fact; see the edit

La meilleure façon de penser aux structures de données pandas est comme des conteneurs flexibles pour les données de dimension inférieure. Par exemple, DataFrame est un conteneur pour Series et Panel est un conteneur pour les objets DataFrame. pour pouvoir insérer et retirer des objets de ces conteneurs à la manière d'un dictionnaire.

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

Le modèle de données des objets Pandas a été choisi comme ça. La raison réside certainement dans le fait qu'il assure certains avantages que je ne connais pas (je ne comprends pas bien la dernière phrase du citation, c'est peut-être la raison)

.

Edit: je ne suis pas d'accord avec moi

Un DataFrame ne peut pas être composé d'éléments qui seraient Série , car le code suivant donne le même type "Série" aussi bien pour une ligne que pour une colonne:

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

résultat

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

Donc, cela n'a aucun sens de prétendre qu'un DataFrame est composé de Series car que seraient ces dites Series: colonnes ou lignes? Question stupide et vision.

.

Alors qu'est-ce qu'un DataFrame?

Dans la version précédente de cette réponse, j'ai posé cette question en essayant de trouver la réponse au Why is that? une partie de la question du PO et l'interrogatoire similaire single rows to get converted into a series - why not a data frame with one row? dans l'un de ses commentaires,
tandis que le Is there a way to ensure I always get back a data frame? partie a été répondu par Dan Allan.

Ensuite, comme les documents des Pandas cités ci-dessus disent que les structures de données des pandas sont mieux vues comme conteneurs de données de dimension inférieure, il me semblait que la compréhension de pourquoi se trouverait dans les caractéristiques de la nature des structures DataFrame.

Cependant, je me suis rendu compte que ces conseils ne doivent pas être considérés comme une description précise de la nature des structures de données des Pandas.
Ce conseil ne signifie pas qu'un DataFrame est un conteneur de Series.
Il exprime que la représentation mentale d'un DataFrame comme un conteneur de Series (soit des lignes ou des colonnes selon l'option considérée à un moment du raisonnement) est une bonne façon de considérer les DataFrames, même si ce n'est pas strictement le cas en réalité. "Bon" signifie que cette vision permet d'utiliser les DataFrames avec efficacité. C'est tout.

.

Qu'est-ce qu'un objet DataFrame?

La classe DataFrame produit des instances qui ont une structure particulière originaire de la classe de base NDFrame , elle-même dérivée de la PandasContainer classe de base qui est également une classe parente de la classe Series .
Notez que cela est correct pour Pandas jusqu'à la version 0.12. Dans la prochaine version 0.13, la série dérivera également de NDFrame classe uniquement.

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

résultat

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

Donc, je comprends maintenant qu'une instance DataFrame a certaines méthodes qui ont été conçues pour contrôler la façon dont les données sont extraites des lignes et des colonnes.

Le fonctionnement de ces méthodes d'extraction est décrit dans cette page: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
On y retrouve la méthode donnée par Dan Allan et d'autres méthodes.

Pourquoi ces méthodes d'extraction ont-elles été conçues telles quelles?
C'est certainement parce qu'ils ont été évalués comme étant ceux qui offrent les meilleures possibilités et la facilité d'analyse des données.
C'est précisément ce qui est exprimé dans cette phrase:

La meilleure façon de penser aux structures de données pandas) est comme des conteneurs flexibles pour les données de dimension inférieure.

Le pourquoi de l'extraction des données d'une instance DataFRame ne réside pas dans sa structure, il réside dans le pourquoi de cette structure. Je suppose que la structure et le fonctionnement de la structure de données des Pandas ont été ciselés afin d'être aussi intuitivement intellectuels que possible, et que pour comprendre les détails, il faut lire le blog de Wes McKinney.

3
eyquem

Si l'objectif est d'obtenir un sous-ensemble de l'ensemble de données à l'aide de l'index, il est préférable d'éviter d'utiliser loc ou iloc. Au lieu de cela, vous devez utiliser une syntaxe similaire à celle-ci:

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
1
Ajit

Si vous sélectionnez également sur l'index de la trame de données, le résultat peut être soit un DataFrame ou une série o il peut s'agir d'une série ou d'un scalaire (valeur unique).

Cette fonction garantit que vous obtenez toujours une liste de votre sélection (si le df, l'index et la colonne sont valides):

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)
0
Wouter