Pandas, выбирающий по метке, иногда возвращает Series, иногда возвращает DataFrame

98

В Pandas, когда я выбираю метку, которая имеет только одну запись в индексе, я возвращаю серию, но когда я выбираю запись, которая имеет более одной записи, я возвращаю фрейм данных.

Почему это? Есть ли способ гарантировать, что я всегда получу фрейм данных?

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
рабочие
источник

Ответы:

107

Допустим, поведение непоследовательно, но я думаю, что легко представить случаи, когда это удобно. В любом случае, чтобы каждый раз получать DataFrame, просто передайте список в loc. Есть и другие способы, но на мой взгляд, это самый чистый.

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
Спасибо. Стоит отметить, что это возвращает DataFrame, даже если метки нет в индексе.
Jobevers
7
К вашему сведению, с недублирующим индексом и одним индексатором (например, с одной меткой) вы ВСЕГДА получите обратно серию, и только потому, что у вас есть дубликаты в индексе, это DataFrame.
Джефф
1
Обратите внимание, что есть еще одна проблема: при использовании предлагаемого обходного пути и отсутствия совпадающих строк результатом будет DataFrame с одной строкой, все NaN.
Paul Oyster
2
Пол, какую версию панд вы используете? В последней версии я получаю, KeyErrorкогда пытаюсь .loc[[nonexistent_label]].
Дэн Аллан
2
Использование списка .locнамного медленнее, чем без него. Чтобы было по-прежнему читаемым, но при этом гораздо быстрее, лучше использоватьdf.loc[1:1]
Джонатан
16

У вас есть индекс с тремя элементами индекса 3. По этой причине df.loc[3]вернет фрейм данных.

Причина в том, что вы не указываете столбец. Итак, df.loc[3]выбирает три элемента из всех столбцов (это столбец 0), а df.loc[3,0]вернет Series. Например, df.loc[1:2]также возвращает фрейм данных, потому что вы нарезаете строки.

Выбор одной строки (как df.loc[1]) возвращает серию с именами столбцов в качестве индекса.

Если вы хотите быть уверены, что у вас всегда есть DataFrame, вы можете нарезать как df.loc[1:1]. Другой вариант - это логическое индексирование ( df.loc[df.index==1]) или метод take ( df.take([0])но это используемое местоположение, а не метки!).

Йорис
источник
3
Такого поведения я и ожидал. Я не понимаю дизайнерского решения для преобразования отдельных строк в серию - почему бы не создать фрейм данных с одной строкой?
Jobevers
Ах, почему выбор одной строки возвращает Series, я действительно не знаю.
Джорис
7

Используйте df['columnName']для получения серии и df[['columnName']]для получения фрейма данных.

user4422
источник
1
Остерегайтесь, что берет копию оригинального df.
smci
6

TLDR

Когда используешь loc

df.loc[:]= Фрейм данных

df.loc[int]= Dataframe, если у вас более одного столбца, и Series, если у вас только 1 столбец в dataframe

df.loc[:, ["col_name"]]= Фрейм данных

df.loc[:, "col_name"]= Серия

Не используя loc

df["col_name"]= Серия

df[["col_name"]]= Фрейм данных

Колин Энтони
источник
3

В комментарии к ответу Джориса вы написали:

«Я не понимаю конструктивного решения для преобразования отдельных строк в серию - почему бы не создать фрейм данных с одной строкой?»

Одна строка не преобразуется в серию.
Это IS Серия:No, I don't think so, in fact; see the edit

Лучше всего думать о структурах данных pandas как о гибких контейнерах для низкоразмерных данных. Например, DataFrame - это контейнер для Series, а Panel - это контейнер для объектов DataFrame. Мы хотели бы иметь возможность вставлять и удалять объекты из этих контейнеров аналогично словарю.

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

Так была выбрана модель данных объектов Pandas. Причина, безусловно, заключается в том, что он обеспечивает некоторые преимущества, о которых я не знаю (я не полностью понимаю последнее предложение цитаты, возможно, это причина)

.

Изменить: я не согласен со мной

DataFrame не может состоять из элементов, которые были бы Series, потому что следующий код дает тот же тип «Series» как для строки, так и для столбца:

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

результат

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

Итак, нет смысла притворяться, что DataFrame состоит из Series, потому что чем должны быть эти упомянутые Series: столбцами или строками? Глупый вопрос и видение.

.

Тогда что такое DataFrame?

В предыдущей версии этого ответа я задавал этот вопрос, пытаясь найти ответ на Why is that?часть вопроса ОП и аналогичного допроса single rows to get converted into a series - why not a data frame with one row?в одном из его комментариев, в
то время какIs there a way to ensure I always get back a data frame? часть ответил Дэн Аллан.

Затем, поскольку документы Pandas, процитированные выше, говорят, что структуры данных pandas лучше всего рассматривать как контейнеры низкоразмерных данных, мне казалось, что понимание причины можно найти в характеристиках природы структур DataFrame.

Однако я понял, что этот процитированный совет не следует воспринимать как точное описание природы структур данных Pandas.
Этот совет не означает, что DataFrame является контейнером Series.
Он выражает то, что мысленное представление DataFrame как контейнера Series (строк или столбцов в соответствии с вариантом, рассматриваемым в один момент рассуждения) - хороший способ рассмотреть DataFrames, даже если это не совсем так в действительности. «Хорошо» означает, что это видение позволяет эффективно использовать DataFrames. Вот и все.

.

Тогда что такое объект DataFrame?

Класс DataFrame создает экземпляры, имеющие определенную структуру, происходящую из базового класса NDFrame , который сам является производным от базового класса PandasContainer, который также является родительским классом класса Series .
Обратите внимание, что это верно для Pandas до версии 0.12. В следующей версии 0.13 Series будет производным только от класса 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__

результат

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

Итак, я понимаю, что теперь экземпляр DataFrame имеет определенные методы, которые были созданы для управления способом извлечения данных из строк и столбцов.

Принципы работы этих методов извлечения описаны на этой странице: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing В нем
мы находим метод, данный Дэном Алланом, и другие методы.

Почему эти методы извлечения созданы именно так?
Это определенно потому, что они были оценены как те, которые предоставляют лучшие возможности и легкость анализа данных.
Именно это выражено в этом предложении:

Лучше всего думать о структурах данных pandas как о гибких контейнерах для низкоразмерных данных.

Почему экстракции данных из экземпляра DataFRame не лежит в его структуре, она лежит в почему этой структуры. Я предполагаю, что структура и функционирование структуры данных Pandas были тщательно продуманы, чтобы быть максимально интуитивно понятными, и что для понимания деталей нужно прочитать блог Уэса МакКинни.

Eyquem
источник
1
FYI, DataFrame НЕ является подклассом ndarray и не является серией (начиная с 0,13, хотя до этого было). Это больше похоже на изречение.
Джефф
Спасибо, что сообщили мне. Я очень ценю, потому что я новичок в изучении Pandas. Но мне нужно больше информации, чтобы хорошо понять. Почему в документации написано, что серия является подклассом ndarray?
eyquem 04
это было до версии
Джефф
ОК. Большое спасибо. Однако это не меняет основы моих рассуждений и понимания, не так ли? - В Pandas ниже 0.13, DataFrame и другие объекты Pandas, отличные от Series: каков их подкласс?
eyquem 04
@ Джефф Спасибо. Я изменил свой ответ после вашей информации. Мне было бы приятно узнать, что вы думаете о моем редактировании.
eyquem 05
1

Если цель состоит в том, чтобы получить подмножество набора данных с помощью индекса, лучше избегать использования locили iloc. Вместо этого вы должны использовать синтаксис, подобный этому:

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

Если вы также выберете индекс фрейма данных, результатом может быть либо DataFrame, либо серия, либо серия или скаляр (одно значение).

Эта функция гарантирует, что вы всегда получите список из вашего выбора (если df, index и column действительны):

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)
Воутер
источник