Pandas, die nach Label auswählen, geben manchmal Serien zurück, manchmal geben sie DataFrame zurück

95

Wenn ich in Pandas eine Bezeichnung auswähle, die nur einen Eintrag im Index enthält, erhalte ich eine Serie zurück. Wenn ich jedoch einen Eintrag auswähle, der mehr als einen Eintrag enthält, erhalte ich einen Datenrahmen zurück.

Warum ist das so? Gibt es eine Möglichkeit, um sicherzustellen, dass ich immer einen Datenrahmen zurückerhalte?

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
quelle

Antworten:

101

Zugegeben, das Verhalten ist inkonsistent, aber ich denke, es ist leicht, sich Fälle vorzustellen, in denen dies zweckmäßig ist. Um jedes Mal einen DataFrame zu erhalten, übergeben Sie einfach eine Liste an loc. Es gibt andere Möglichkeiten, aber meiner Meinung nach ist dies die sauberste.

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
quelle
6
Vielen Dank. Beachten Sie, dass dies einen DataFrame zurückgibt, auch wenn die Bezeichnung nicht im Index enthalten ist.
Jobevers
7
Zu Ihrer Information: Mit einem nicht doppelten Index und einem einzelnen Indexer (z. B. einem einzelnen Label) erhalten Sie IMMER eine Serie zurück. Nur weil Sie Duplikate im Index haben, handelt es sich um einen DataFrame.
Jeff
1
Beachten Sie, dass es noch ein weiteres Problem gibt: Wenn Sie die vorgeschlagene Problemumgehung verwenden und keine übereinstimmenden Zeilen vorhanden sind, ist das Ergebnis ein DataFrame mit einer einzelnen Zeile, alles NaN.
Paul Oyster
2
Paul, welche Version von Pandas benutzt du? Bei der neuesten Version bekomme ich eine, KeyErrorwenn ich es versuche .loc[[nonexistent_label]].
Dan Allan
2
Die Verwendung einer Liste in .locist viel langsamer als ohne. Immer noch lesbar, aber auch viel schneller, besser nutzendf.loc[1:1]
Jonathan
15

Sie haben einen Index mit drei Indexelementen 3. Aus diesem Grund df.loc[3]wird ein Datenrahmen zurückgegeben.

Der Grund ist, dass Sie die Spalte nicht angeben. So df.loc[3]wählt drei Elemente aller Spalten (die Spalte 0), während df.loc[3,0]eine Reihe zurück. ZB gibt df.loc[1:2]auch einen Datenrahmen zurück, da Sie die Zeilen in Scheiben schneiden.

Wenn Sie eine einzelne Zeile (as df.loc[1]) auswählen, wird eine Serie mit den Spaltennamen als Index zurückgegeben.

Wenn Sie sicher sein möchten, immer einen DataFrame zu haben, können Sie wie schneiden df.loc[1:1]. Eine andere Option ist die boolesche Indizierung ( df.loc[df.index==1]) oder die Methode take ( df.take([0])aber diese verwendete Position ist keine Bezeichnung!).

Joris
quelle
3
Das ist das Verhalten, das ich erwarten würde. Ich verstehe die Entwurfsentscheidung für einzelne Zeilen, die in eine Reihe umgewandelt werden sollen, nicht - warum nicht einen Datenrahmen mit einer Zeile?
Jobevers
Ah, warum die Auswahl einer einzelnen Zeile eine Serie zurückgibt, weiß ich nicht genau.
Joris
6

Der TLDR

Beim Benutzen loc

df.loc[:]= Datenrahmen

df.loc[int]= Datenrahmen, wenn Sie mehr als eine Spalte haben, und Serie, wenn Sie nur 1 Spalte im Datenrahmen haben

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

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

Nicht verwenden loc

df["col_name"]= Serie

df[["col_name"]]= Datenrahmen

Colin Anthony
quelle
5

Verwenden Sie df['columnName']diese df[['columnName']]Option , um eine Serie und einen Datenrahmen abzurufen.

user4422
quelle
1
Beachten Sie, dass eine Kopie des Original-df erstellt wird.
smci
3

Sie haben in einem Kommentar zu Joris 'Antwort geschrieben:

"Ich verstehe die Entwurfsentscheidung für einzelne Zeilen , die in eine Reihe konvertiert werden sollen, nicht - warum nicht einen Datenrahmen mit einer Zeile?"

Eine einzelne Zeile wird nicht in eine Serie konvertiert .
Es IST eine Serie aus :No, I don't think so, in fact; see the edit

Der beste Weg, um über die Pandas-Datenstrukturen nachzudenken, sind flexible Container für niedrigdimensionale Daten. Beispielsweise ist DataFrame ein Container für Serien und Panel ist ein Container für DataFrame-Objekte. Wir möchten in der Lage sein, Objekte wörterbuchartig in diese Container einzufügen und daraus zu entfernen.

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

Das Datenmodell von Pandas-Objekten wurde so ausgewählt. Der Grund liegt sicherlich in der Tatsache, dass es einige Vorteile bietet, die ich nicht kenne (ich verstehe den letzten Satz des Zitats nicht vollständig, vielleicht ist es der Grund)

.

Edit: Ich stimme mir nicht zu

Ein Datenrahmen kann nicht aus Elementen zusammengesetzt werden, würde seine Serie, da der folgende Code die gleichen Typen „Serie“ auch für eine Reihe wie für eine Spalte gibt:

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

Ergebnis

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

Es macht also keinen Sinn vorzutäuschen, dass ein DataFrame aus Serien besteht, denn wie würden diese Serien aussehen: Spalten oder Zeilen? Dumme Frage und Vision.

.

Was ist dann ein DataFrame?

In der vorherigen Version dieser Antwort habe ich diese Frage gestellt und versucht, die Antwort auf den Why is that?Teil der Frage des OP und die ähnliche Befragung single rows to get converted into a series - why not a data frame with one row?in einem seiner Kommentare zu finden,
während der Is there a way to ensure I always get back a data frame?Teil von Dan Allan beantwortet wurde.

Dann, da die oben zitierten Dokumente der Pandas besagen, dass die Datenstrukturen der Pandas am besten als Container mit niedrigdimensionalen Daten angesehen werden können, schien es mir, dass das Verständnis des Warum in den Merkmalen der Natur von DataFrame-Strukturen zu finden wäre.

Mir wurde jedoch klar, dass dieser zitierte Rat nicht als genaue Beschreibung der Art der Datenstrukturen von Pandas verstanden werden darf.
Dieser Rat bedeutet nicht, dass ein DataFrame ein Container der Serie ist.
Es drückt aus, dass die mentale Darstellung eines DataFrames als Container von Serien (entweder Zeilen oder Spalten gemäß der Option, die zu einem bestimmten Zeitpunkt einer Überlegung in Betracht gezogen wird) eine gute Möglichkeit ist, DataFrames zu berücksichtigen, auch wenn dies in der Realität nicht unbedingt der Fall ist. "Gut" bedeutet, dass diese Vision es ermöglicht, DataFrames effizient zu nutzen. Das ist alles.

.

Was ist dann ein DataFrame-Objekt?

Die DataFrame- Klasse erzeugt Instanzen mit einer bestimmten Struktur, die aus der NDFrame- Basisklasse stammt und selbst von der PandasContainer- Basisklasse abgeleitet ist, die auch eine übergeordnete Klasse der Series- Klasse ist.
Beachten Sie, dass dies für Pandas bis Version 0.12 korrekt ist. In der kommenden Version 0.13 wird Series auch nur von der NDFrame- Klasse abgeleitet.

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

Ergebnis

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

Nach meinem Verständnis verfügt eine DataFrame-Instanz nun über bestimmte Methoden, mit denen gesteuert werden kann, wie Daten aus Zeilen und Spalten extrahiert werden.

Die Funktionsweise dieser Extraktionsmethoden wird auf dieser Seite beschrieben: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
Wir finden darin die von Dan Allan und anderen Methoden angegebene Methode.

Warum wurden diese Extraktionsmethoden so hergestellt, wie sie waren?
Das liegt sicherlich daran, dass sie als diejenigen bewertet wurden, die bessere Möglichkeiten und eine einfachere Datenanalyse bieten.
Genau das drückt sich in diesem Satz aus:

Der beste Weg, um über die Pandas-Datenstrukturen nachzudenken, sind flexible Container für niedrigdimensionale Daten.

Das Warum der Extraktion von Daten aus einer DataFRame-Instanz liegt nicht in ihrer Struktur, sondern im Warum dieser Struktur. Ich denke, dass die Struktur und Funktionsweise der Datenstruktur der Pandas gemeißelt wurden, um so intellektuell intuitiv wie möglich zu sein, und dass man den Blog von Wes McKinney lesen muss, um die Details zu verstehen.

eyquem
quelle
1
Zu Ihrer Information, DataFrame ist KEINE ndarray-Unterklasse und auch keine Serie (ab 0.13, vorher jedoch). Diese sind eher diktiert als alles andere.
Jeff
Vielen Dank, dass Sie mich informieren. Ich schätze es sehr, weil ich neu im Erlernen von Pandas bin. Aber ich brauche mehr Informationen, um gut zu verstehen. Warum steht in den Dokumenten geschrieben, dass eine Serie eine Unterklasse von ndarray ist?
Eyquem
Es war vor 0.13 (Veröffentlichung in Kürze), hier sind Entwicklerdokumente
Jeff
OK. Vielen Dank. Es ändert jedoch nichts an der Grundlage meiner Überlegungen und meines Verständnisses, oder? - In Pandas unter 0.13 unterscheiden sich DataFrame und andere Pandas-Objekte von Series: Wovon sind sie Unterklassen?
Eyquem
@ Jeff Danke. Ich habe meine Antwort nach Ihren Angaben geändert. Ich würde mich freuen zu wissen, was Sie von meiner Bearbeitung halten.
Eyquem
1

Wenn das Ziel darin besteht, eine Teilmenge des Datensatzes mithilfe des Index abzurufen, ist es am besten, die Verwendung von locoder zu vermeiden iloc. Stattdessen sollten Sie eine ähnliche Syntax verwenden:

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
quelle
0

Wenn Sie auch im Index des Datenrahmens auswählen, kann das Ergebnis entweder ein Datenrahmen oder eine Serie oder eine Serie oder ein Skalar (Einzelwert) sein.

Diese Funktion stellt sicher, dass Sie immer eine Liste aus Ihrer Auswahl erhalten (wenn df, Index und Spalte gültig sind):

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
quelle