Pandy wybierające według etykiety czasami zwracają Series, czasami zwracają DataFrame

98

W Pandas, kiedy wybieram etykietę, która ma tylko jeden wpis w indeksie, otrzymuję z powrotem serię, ale kiedy wybieram wpis, który ma więcej niż jeden wpis, otrzymuję ramkę danych.

Dlaczego? Czy istnieje sposób, aby zawsze odzyskać ramkę danych?

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
praca
źródło

Odpowiedzi:

106

Przyznaje, że zachowanie jest niespójne, ale myślę, że łatwo wyobrazić sobie przypadki, w których jest to wygodne. W każdym razie, aby uzyskać DataFrame za każdym razem, po prostu przekaż listę do loc. Są inne sposoby, ale moim zdaniem ten jest najczystszy.

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
źródło
6
Dzięki. Warto zauważyć, że zwraca to DataFrame, nawet jeśli etykieta nie znajduje się w indeksie.
jobevers
7
Do Twojej wiadomości, z indeksem, który nie jest zduplikowany i jednym indeksatorem (np. Pojedynczą etykietą), ZAWSZE otrzymasz Serię, tylko dlatego, że masz duplikaty w indeksie, że jest to DataFrame.
Jeff,
1
Zwróć uwagę, że jest jeszcze jeden problem: jeśli używasz sugerowanego obejścia i nie ma pasujących wierszy, wynikiem będzie DataFrame z jednym wierszem, wszystkie NaN.
Paul Oyster
2
Paul, jakiej wersji pand używasz? W najnowszej wersji otrzymuję, KeyErrorgdy próbuję .loc[[nonexistent_label]].
Dan Allan
2
Korzystanie z listy w programie .locjest znacznie wolniejsze niż bez niej. Aby nadal był czytelny, ale także znacznie szybszy, lepszy w użyciudf.loc[1:1]
Jonathan
16

Masz indeks z trzema elementami indeksu 3. Z tego powodu df.loc[3]zwróci ramkę danych.

Powodem jest to, że nie określasz kolumny. df.loc[3]Wybiera więc trzy elementy ze wszystkich kolumn (czyli kolumnę 0), a df.loc[3,0]zwróci Series. Np. df.loc[1:2]Zwraca również ramkę danych, ponieważ tniesz wiersze.

Wybranie pojedynczego wiersza (jako df.loc[1]) zwraca serię z nazwami kolumn jako indeksem.

Jeśli chcesz mieć pewność, że zawsze masz DataFrame, możesz pokroić w podobny sposób df.loc[1:1]. Inną opcją jest indeksowanie boolowskie ( df.loc[df.index==1]) lub metoda take ( df.take([0])ale ta lokalizacja nie zawiera etykiet!).

joris
źródło
3
Takiego zachowania bym się spodziewał. Nie rozumiem decyzji projektowej dotyczącej przekształcenia pojedynczych wierszy w serię - dlaczego nie ramka danych z jednym wierszem?
jobevers
Ach, dlaczego wybranie pojedynczego wiersza zwraca serię, naprawdę nie wiem.
joris
6

Użyj, df['columnName']aby otrzymać serię i df[['columnName']]otrzymać Dataframe.

user4422
źródło
1
Uważaj, ponieważ pobiera kopię oryginalnego pliku df.
smci
6

TLDR

Podczas używania loc

df.loc[:]= Dataframe

df.loc[int]= Dataframe, jeśli masz więcej niż jedną kolumnę i serie, jeśli masz tylko jedną kolumnę w ramce danych

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

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

Nie używam loc

df["col_name"]= Seria

df[["col_name"]]= Dataframe

Colin Anthony
źródło
3

W komentarzu do odpowiedzi Jorisa napisałeś:

„Nie rozumiem decyzji projektowej, zgodnie z którą pojedyncze wiersze mają zostać przekształcone w serię - dlaczego nie ramka danych z jednym wierszem?”

Pojedynczy wiersz nie jest konwertowany w serii.
To JEST Seria:No, I don't think so, in fact; see the edit

Najlepszym sposobem myślenia o strukturach danych pandy są elastyczne kontenery dla danych o niższych wymiarach. Na przykład DataFrame to kontener dla Series, a Panel to kontener dla obiektów DataFrame. Chcielibyśmy mieć możliwość wstawiania i usuwania obiektów z tych kontenerów w sposób podobny do słownika.

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

Model danych obiektów Pandy został wybrany w ten sposób. Przyczyna na pewno tkwi w tym, że zapewnia pewne korzyści, których nie znam (nie do końca rozumiem ostatnie zdanie cytatu, może to jest powód)

.

Edycja: Nie zgadzam się ze mną

DataFrame nie mogą składać się z elementów, które mogłyby być Series, ponieważ po kod daje ten sam typ „seria”, jak również do rzędu, jak dla kolumny:

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

wynik

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

Nie ma więc sensu udawać, że DataFrame składa się z Series, ponieważ czym powinny być te serie: kolumny czy wiersze? Głupie pytanie i wizja.

.

Więc co to jest DataFrame?

W poprzedniej wersji tej odpowiedzi zadałem to pytanie, próbując znaleźć odpowiedź na Why is that?część pytania PO i podobne przesłuchanie single rows to get converted into a series - why not a data frame with one row?w jednym z jego komentarzy,
podczas gdy na tę Is there a way to ensure I always get back a data frame?część odpowiedział Dan Allan.

Następnie, jak cytowane powyżej dokumenty Pand mówią, że struktury danych pand są najlepiej postrzegane jako pojemniki z danymi niższych wymiarów, wydawało mi się, że zrozumienie przyczyny można znaleźć w charakterystyce struktur DataFrame.

Jednak zdałem sobie sprawę, że tej cytowanej rady nie można traktować jako dokładnego opisu natury struktur danych Pand.
Ta rada nie oznacza, że ​​DataFrame jest kontenerem serii.
Wyraża, że ​​mentalna reprezentacja ramki DataFrame jako kontenera serii (wiersze lub kolumny zgodnie z opcją rozważaną w jednym momencie rozumowania) jest dobrym sposobem na rozważenie ramek DataFrame, nawet jeśli w rzeczywistości tak nie jest. „Dobra” oznacza, że ​​ta wizja umożliwia efektywne korzystanie z ramek DataFrames. To wszystko.

.

Co to jest obiekt DataFrame?

DataFrame klasa wytwarza przykładów, które mają szczególną strukturą pochodzi z NDFrame klasy bazowej sam pochodzi z PandasContainer klasy bazowej, która ma również klasa dominującą serii klasy.
Zauważ, że jest to poprawne dla Pand do wersji 0.12. W nadchodzącej wersji 0.13 Series będzie również wywodzić się tylko z klasy 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__

wynik

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

Dlatego teraz rozumiem, że instancja DataFrame ma pewne metody, które zostały utworzone w celu kontrolowania sposobu wyodrębniania danych z wierszy i kolumn.

Sposoby działania tych metod wyodrębniania są opisane na tej stronie: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing
Znajdujemy w niej metodę podaną przez Dana Allana i inne metody.

Dlaczego te metody ekstrakcji zostały stworzone tak, jak były?
Z pewnością dlatego, że zostały one ocenione jako dające lepsze możliwości i łatwość w analizie danych.
Dokładnie to wyraża to zdanie:

Najlepszym sposobem myślenia o strukturach danych pandy są elastyczne kontenery dla danych o niższych wymiarach.

Dlaczego ekstrakcji danych z instancji DataFRame nie leży w jej strukturze, leży w dlaczego tej struktury. Wydaje mi się, że struktura i funkcjonowanie struktury danych Pand zostały wyrzeźbione tak, aby były jak najbardziej intuicyjne intelektualnie, a żeby zrozumieć szczegóły, trzeba przeczytać blog Wesa McKinneya.

eyquem
źródło
1
FYI, DataFrame NIE jest podklasą ndarray, podobnie jak Series (począwszy od 0.13, ale wcześniej było). Te są bardziej dyktowane niż wszystko.
Jeff
Dziękuję za poinformowanie mnie. Naprawdę to doceniam, ponieważ jestem nowy w nauce pand. Ale potrzebuję więcej informacji, aby dobrze zrozumieć. Dlaczego w dokumentach jest napisane, że seria jest podklasą ndarray?
eyquem
to było przed 0.13 (wydanie wkrótce), oto dokumentacja deweloperska
Jeff
OK. Dziękuję Ci bardzo. Jednak nie zmienia to podstaw mojego rozumowania i zrozumienia, prawda? - W Pandach gorszych od 0.13 DataFrame i innych obiektach Pand różni się od Series: z czego są podklasą?
eyquem
@Jeff Dziękuję. Zmodyfikowałem moją odpowiedź po twoich informacjach. Z przyjemnością dowiem się, co myślisz o mojej edycji.
eyquem
1

Jeśli celem jest uzyskanie podzbioru zbioru danych za pomocą indeksu, najlepiej unikać używania loclub iloc. Zamiast tego powinieneś użyć składni podobnej do tej:

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
źródło
0

Jeśli zaznaczysz również indeks ramki danych, wynikiem może być DataFrame lub Series lub może to być Series lub skalar (pojedyncza wartość).

Ta funkcja zapewnia, że ​​zawsze otrzymasz listę z twojego wyboru (jeśli df, indeks i kolumna są prawidłowe):

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
źródło