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

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

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!).


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.


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


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.


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

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.