Pandas按标签选择有时返回Series,有时返回DataFrame

148
在Pandas中,当我选择一个在索引中仅有一个条目的标签时,返回的是Series,但是当我选择一个有多个条目的条目时,会返回数据帧。
为什么会这样?有办法确保我总是得到一个数据帧吗?
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
8个回答

163

虽然行为不一致,但我认为很容易想象出这种情况很方便的案例。无论如何,要每次获取一个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

7
值得注意的是,即使标签不在索引中,这个函数也会返回一个DataFrame。谢谢。 - jobevers
8
请注意,使用非重复索引和单个索引器(例如单个标签),您将始终返回一个Series,只有在索引中存在重复项时才会返回DataFrame。 - Jeff
2
Paul,你用的是pandas的哪个版本?在最新版本上,当我尝试使用.loc[[nonexistent_label]]时,会出现KeyError错误。 - Dan Allan
1
如果您在索引和列上都进行选择,则 loc 需要 2 个列表才能获取数据框而不是系列:df.loc[[indexlist],[columnlist]](即使列表只包含单个项目)。 - Wouter
2
.loc 中使用列表会比不使用它慢得多。为了仍然可读但速度更快,最好使用 df.loc[1:1] - Jonathan
显示剩余4条评论

32

简述

使用 loc

df.loc[:] = Dataframe

df.loc[int] = 如果数据框中有多个列,则返回Dataframe;如果只有一个列,则返回Series

df.loc[:, ["col_name"]] = 如果选择的范围包含多行,则返回Dataframe;如果只有一行,则返回Series

df.loc[:, "col_name"] = 返回Series

不使用 loc

df["col_name"] = 返回Series

df[["col_name"]] = 返回Dataframe


3
这是不正确的。如果只选择一行,则 df.loc[:, ["col_name"]] 将返回一个系列。 - MrR
如果数据框只包含单行,则使用 : 选择所有行,因此结果是正确的。 - Colin Anthony
1
因为我们关注结果的类型,所以也许可以添加不同的部分来指定结果的基数不同时类型也不同。 - MrR
df.loc[int] 返回一个系列,除非df的行没有用唯一的整数索引。(不确定这是否是版本相关的行为。) - undefined

20

您有一个有三个索引项3的索引。因此,df.loc[3]将返回一个数据帧。

原因是您没有指定列。因此,df.loc[3]选择所有列的三个项(即列0),而df.loc[3,0]将返回一个系列。例如,df.loc[1:2]也返回一个数据帧,因为您切片了行。

选择单个行(如df.loc[1])将返回具有列名称作为索引的系列。

如果您想要始终拥有DataFrame,请像这样切片df.loc[1:1]。另一个选项是布尔索引(df.loc[df.index==1])或take方法(df.take([0]),但它使用位置而不是标签!)。


4
这是我预期的行为。我不理解将单行转换为系列的设计决策——为什么不是只有一行的数据框架? - jobevers
1
啊,为什么选择单行会返回Series,我不是很清楚。 - joris
df.loc[1:1] is faster than df.loc[[1]] - Winand

8
使用 df['columnName'] 来获取一个 Series,使用 df[['columnName']] 来获取一个 Dataframe。

1
注意,这会复制原始数据框。 - smci

5
您在对Joris的回复中写道:
“我不理解为什么单行要转换为系列 - 为什么不是只有一行的数据框?”
单行并没有转换成Series。
它本身就是一个Series:不,我不这样认为;请参见编辑

最好的方式是将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是什么?
在之前的答案版本中,我问了这个问题,试图找到OP问题中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?部分已由Dan Allan回答。
然后,正如Pandas文档所述,Pandas的数据结构最好看作是低维数据的容器,因此我认为理解DataFrame结构的本质特征将会发现问题的why
然而,我意识到这个引用的建议不能被视为对Pandas数据结构本质的精确描述。
这个建议并不意味着DataFrame是Series的容器。
它表达的是将DataFrame作为Series的容器(根据推理过程中考虑的选项,可以是行或列)的心理表示法是一种良好的方式来考虑DataFrame,即使在现实中并不严格如此。 "良好"的含义是这种视角能够有效地使用DataFrame。就是这样。

.

那么什么是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,我们可以在其中找到Dan Allan等人提供的方法。
为什么这些提取方法被设计成这样?那肯定是因为它们被评价为在数据分析方面提供了更好的可能性和便利性。正如这句话所表达的那样:
“最好的方法是将pandas数据结构视为低维数据的灵活容器。”
从DataFrame实例中提取数据的原因不在于其结构,而在于该结构的原因。我猜Pandas数据结构的结构和功能被雕琢得尽可能符合直觉,并且要了解细节,必须阅读Wes McKinney的博客。

1
FYI,DataFrame 不是 ndarray 的子类,Series 也不是(从0.13开始,之前是这样)。它们更像字典。 - Jeff
谢谢您的告知。我非常感激,因为我是Pandas学习的新手。但是我需要更多的信息才能理解得更好。为什么文档中写道Series是ndarray的子类? - eyquem
这是在0.13版本之前(即将发布)的开发文档链接:http://pandas.pydata.org/pandas-docs/dev/dsintro.html#series - Jeff
好的。非常感谢您。但是这并不改变我的推理和理解基础,对吗?- 在Pandas低于0.13版本中,DataFrame和其他Pandas对象与Series不同:它们是什么子类? - eyquem
@Jeff 谢谢。在你的信息之后,我修改了我的答案。很高兴知道你对我的编辑有何看法。 - eyquem

3

如果目标是使用索引获取数据集的子集,最好避免使用lociloc。相反,您应该使用类似于以下语法:

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

语法 result = df[df.index == idx] 是一个非常好的选项;完美地适合我的需求。 - ghukill

3
每次我们使用 [['column name']] 时,它会返回 Pandas DataFrame 对象, 如果我们使用 ['column name'],则会得到 Pandas Series 对象。

0
如果您在数据框的索引上进行选择,那么结果可以是DataFrame或Series,也可以是Series或标量(单个值)。
此函数确保您始终从选择中获取列表(如果df、索引和列有效)。
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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接