最近在写个性化推荐的论文,经常用到Python来处理数据,被pandas和numpy中的数据选取和索引问题绕的比较迷糊,索性把这篇官方文档翻译出来,方便自查和学习,翻译过程中难免很多不到位的地方,但大致能看懂,错误之处欢迎指正~
官方文档链接 http://pandas.pydata.org/pandas-docs/stable/indexing.html
数据索引和选取
pandas对象中的轴标签信息有很多作用:
· 使用已知指标来标识数据(即提供元数据),这对于分析、可视化以及交互式控制台的显示都十分重要
· 使能够实现自动和显式的数据对齐
· 允许直观地获取和设置数据集的子集
在这一部分,我们将关注最终的目的:即如何切片,切丁以及一般地获取和设置pandas对象的子集。文章将主要集中在Series和DataFrame上,因为它们在这方面潜力很大。
提示:Python和Numpy的索引操作“[ ]”和属性操作“.”为pandas数据结构提供了非常快速和简便的方式。它们能给使交互工作更为直观,如果你已经知道如何操作Python字典和Numpy数组的话,那就没什么新的东西可以学习了。然而,由于数据的类型无法提前预知,直接使用标准操作将会有一些优化的限制。对于产品代码来说,我们建议你可以利用本文展示的优化的pandas数据使用方法。
警告:一个设置操作是会返回一个副本还是一个引用可能取决于具体情况。这种有时被称为“链式赋值”,我们应当避免这种情况。请参见返回视图与副本。
警告:在0.18.0中对基于浮点的整数索引的变化进行了总结说明,请参见这里。
多索引和更高级的索引文档请参见多索引/高级索引。
一些先进的策略见菜谱。
多样的索引方法
为了实现更简便的基于位置的索引,对象选取方法添加了一些用户的请求。pandas现在支持三种类型的多轴索引。
.loc是最基本的基于标签的索引,但是也可以用于布尔数组。当item无法找到时,.loc将会产生KeyError。合法的输入有:
· 一个单独的标签,如5或“a”,(注意5是作为索引标签,而不是一个整数的位置索引)
· 一个列表或者数组标签[“a”,”b”,”c”]
· 一个带有标签“a”:“f”的切片对象(注意,与Python切片相反,这种切片的第一个和最后一个都包含在内!请参见标签切片。)
· 一个布尔数组
· 一个可调用的函数(调用Series, DataFrame或Panel)并返回索引的有效输出(上面中的一个)
更多的基于标签选取。
.iloc主要是基于整数位置的索引(从轴的第0位到第length-1位),但是也可以用于布尔数组。除了允许超范围索引的索引器之外,如果一个请求的索引超出了索引范围,.iloc将会产生IndexError。合法的输入有:
· 一个整数。如5
· 一个列表或整数数组。如[4,3,0]
· 一个整型的切片对象,如1:7
· 一个布尔数组
· 一个可调用的函数(调用Series, DataFrame或Panel)并返回索引的有效输出(上面中的一个)
.loc, .iloc和[ ]索引能够接受一个可调用对象作为索引器。更多请参见基于可调用对象的选取。
使用以下标记从一个多轴对象中获取值(使用.loc为例,但同样适用于.iloc)。任何的轴访问器都可能是空的切片:假定不规范的轴。(如p.loc[‘a’]等价于p.loc[‘a’,:,:])
基础知识
正如在上一章节中介绍数据结构中所提到的那样,使用[ ]进行索引的主要功能(相当于Python中的__getitem__)是选取出低维切片。下面的表显示了使用[ ]索引pandas对象时返回值的类型:
对象类型 选取 返回值类型
Series series[label] 标量值
DataFrame frame[colname] 对应colname的Series
Panel panel[itemname] 对应itemname的DataFrame
这里我们构建了一个简单的时间序列数据集来说明索引功能:
注意:除非特殊说明,所有的索引功能都是通用的,不只适用于该时间序列。
因此,根据上述,我们使用[ ]能够实现最基本的索引:
你可以向[ ]中传递列的列表来按照顺序选取多列。如果某列不在DataFrame中,将引发一个异常。也可以通过这种方式设置多个列。
当将这种变换就地应用到列的子集的时候,你可能会发现这个方法的有用之处。
警告:当从.loc, .iloc设置Series和DataFrame时,pandas会将所有轴对齐。
这不会改变df,因为在赋值之前就进行了列对齐。
正确的做法是使用原始值
属性访问
你或许能够直接把Series的index,一个DataFrame的column,一个Panel的item作为一种属性来访问。
警告:
· 只有当索引元素是一个有效的Python标识符时才能使用这种方式进行访问,比如s.1就不可以。请参照关于有效标识符的解释。
· 如果属性和现存的方法名冲突的话,这种方式也不可行。如s.min
· 同样,如果属性名和任意一个如下的名字冲突的话也不可行:index,major_axis, minor_axis, items。
· 无论哪种情况,标准的索引总是可行的,如s[“1”], s[“min”]和s[“index”]都能够访问相应的元素或列。
如果你使用的是IPython的环境,你也可以使用tab-完成键来查看这些访问属性。
你也可以向一个DataFrame的一行分配一个dict。
你可以使用属性访问来修改一个Series或DataFrame的一个列中已有的元素,但是要谨慎操作;如果你试图使用属性访问来创建一列的话,它将会创建一个新的属性,而不是新列。在0.21.0和之后的版本里,该操作将会报出UserWarning:
切片范围
在沿任意轴的切片方法中,鲁棒性和一致性最强的方法是在使用位置选择部分详细介绍的.iloc方法。现在,我们介绍一下使用[ ]操作进行切片的语法。
对于Series来说,这个语法对应的就是ndarray,返回的是值的切片和相关的标签:
需要注意的是设置操作也是如此:
对于DataFrame来说,在[ ]中的切片是对行的操作。由于它的普适性,所以这样非常方便。
使用标签选择
警告:对于一个设置操作,是返回一个副本还是引用取决于当时的上下文。这叫做“链式赋值”,这种情况应当避免。请参见返回视图与副本。
警告:当你的切片器与索引类型不兼容(或不可转换)时,.loc是非常严格的。例如在一个DatatimeIndex中使用整数的话,将会引发一个TypeError。
在切片中的string能够转换为index的类型,这样才能正常切片。
警告:从0.21.0版本开始,如果使用带有缺失值的进行索引的话,pandas将会显示一个FurtherWarning。在未来的版本中将会引发KeyError。详见不推荐使用缺失的列表进行索引。
pandas提供了一系列的方法来基于标签进行索引。这是一个严格基于包的协议。所有请求的标签都需要确实存在于索引中,不然就会引发一个KeyError。切片范围的第一个和最后一个都被包含在内。整数是有效的标签,但是他们必须作为标签而不是位置。
.loc属性是基础的访问方法。下面的是有效的输入:
· 一个单独的标签,如5或“a”,(注意5是作为索引标签,而不是一个整数的位置索引)
· 一个列表或者数组标签[“a”,”b”,”c”]
· 一个带有标签“a”:“f”的切片对象(注意,与Python切片相反,这种切片的第一个和最后一个都包含在内!)
· 一个布尔数组
· 一个可调用对象,详见基于可调用对象的选取。
要注意的是设置操作也同样适用:
对于一个DataFrame来说:
通过标签切片访问:
使用一个标签来获取截取部分(相当于df.xs(“a"))
使用一个布尔型数组获取值
直接获取值(相当于过时的df.get_value(“a”,”A”))
# 等价于 ``df1.at['a','A']`
使用标签选择
当时用.loc进行切片时,如果当前索引中包含了开始标签和结束标签,那么将返回位于这两个标签中的元素(包括这两个端点):
如果缺失了至少其中一个标签,但是索引被排序了,并且能够与开始和结束标签进行比较,那么通过选择在两个之间排序的标签,切片将仍然按预期进行:
然而,如果缺失了至少一个标签并且索引已经被排序,将会引发一个错误(因为这样做在计算上是昂贵的,而且对于混合型索引可能是不明确的)比如,在上面的例子中,s.loc[2:6]将会引发一个KeyError。
使用位置选择
警告:对于一个设置操作,是返回一个复制还是引用取决于当时的上下文。这叫做“链式赋值”,这种情况应当避免。详见返回一个视图vs副本。
pandas提供了一系列的方法来获得纯粹的基于整数的索引。该语法遵循Python和numpy切片方法。这些是0-based索引。切片时,包含范围内的第一个,而不包含最后一个。如果试图使用非整数,即使是一个有效的标签也将会引发一个IndexError。
.loc属性是基础的访问方法。下面的是有效的输入:
· 一个单独的标签,如5
· 一个列表或者整数数组[4,3,0]
· 一个带有整数的切片对象1:7
· 一个布尔数组
· 一个可调用的函数,详见通过可调用对象选择。
要注意的是设置操作也同样如此:
对于一个DataFrame来说:
通过整数切片来选择:
通过整数列表来选择:
使用一个整数位置来获取一个截取部分(相当于df.xs(1))
正如在Python或Numpy中那样,超出索引范围的切片也是被允许的。
注意,超出范围进行切片将会导致一个空轴(如返回一个空的DataFrame)
一个单独的超出范围的索引器将会引发IndexError. 一个其元素超出了索引范围的索引列表也会引发IndexError。
使用可调用函数来选择
.loc, .iloc和[ ]索引都可以接收一个可调用函数作为索引。这个可调用函数必须是有一个参数的函数(Series, DataFrame或者Panel),并为索引返回有效的输出。
你可以在Series中使用可调用函数
使用这种方法/索引时,你可以不用临时变量就能够进行数据选择操作。
.IX已被禁用
从0.20.0版本开始,禁用.IX索引器,使用更为严谨的.iloc和.loc索引器将其取代。
使用缺失的列表进行索引的功能已被禁用
从0.20.0版本开始,使用.loc或[]来索引带有一个或多个缺失标签的列表的功能被禁用,在新版本中使用.reindex()替代。
重新索引
对可能找不到的元素进行索引的惯用方法是.reindex()。详见重新索引部分。
或者,如果你只想要选择有效键,下面的方法是惯用且有效的,它能保证保留选择的dtype。
a.reindex()将会重复索引:
通常,您可以使用当前轴交叉所需标签,然后重新索引。
然而,如果生成的索引被复制的话,将仍旧引发警告。
随机样本的选择
从一个Series或DataFrame或Panel的行或列中使用sample()方法来选择随机样本。这个方法默认是对列进行取样,并接收一个特定的行/列返回,或者行的一部分。
默认情况下,sample方法将会使每行最多返回一次,但是你也可以使用替换选项进行替换:
默认情况下,每行被取样的概率是相等的,但是如果你想让每行有不同的概率被抽到,你可以讲抽样权重作为权重来传递给抽样函数。这些权重可以是一个列表,一个numpy数组或者一个Series,但是他们的长度必须和你抽样的对象的长度一致。缺失值的权重将被设为0,inf值不被允许。如果所有权重的和不为1,他们将使用各权重除以目前权重的和进行重新归一化。例如:
对于一个DataFrame来说,你可以使用一个DataFrame的一列作为抽样权重(假设你正在对行进行抽样而不是列),把列名作为一个字符串传进去就可以。
sample还允许用户使用轴参数来对列进行抽样.
最后,你还可以使用random_state参数来为sample的随机数生成器设置一个种子,它将会接收一个整数或者一个numpy RandomState 对象。
使用扩展来设置
当为一个轴设置一个不存在的键值时,.loc[ ]操作可以进行扩容。
在Series中,这其实是一个追加操作。
一个DataFrame可以通过.loc或轴进行扩容
这就像是DataFrame的一个追加操作。
标量值的快速获取和设置
由于使用[ ]进行索引必须管理很多情况(单标签访问,切片,布尔索引等等),它会花费一些性能来识别你究竟请求的是什么。如果你想要访问一个标量,最快速的方法是使用at和iat方法,他们能够适用于所有数据结构。
与loc类似,at提供基于标签的标量查找,而iat提供基于整数的标量查找(与iloc类似)。
你也可以使用同样的索引进行设置
如果索引缺失的话,at方法可对对象进行原地扩充
布尔索引
另一个常见的操作是使用布尔向量来过滤数据。操作是:|对应或,&对应与,~对应非。这些必须使用括号进行分组。由于Python默认df.A > 2 & df.B < 3 等同于 df.A > (2 & df.B) < 3,但我们所需的其实是(df.A > 2) & (df.B < 3)。
使用一个布尔向量来对一个Series进行索引与对一个numpy的多维数组进行索引是一样一样的:
你可以使用一个与DataFrame的index相同长度的布尔向量从一个DataFrame中选择列(例如,一些来自DataFrame的一列的东西)
Series的list和map方法也可以用来产生更为复杂的匹配标准:
使用布尔向量和其他索引表达式共同索引时,使用选择方法 通过标签选择,通过位置选择和先进索引,你可能会选出不只一个轴的数据。
使用isin索引
Series的 isin()方法能够返回一个布尔向量,Series的元素在传递列表的地方显示为True。这使你能够选择那些一列或多列中有你需要的值的行。
该方法同样适用于Index对象,当你不知道哪个标签是真的存在的时候,这种方法也同样适用。
另外,MultiIndex方法能够允许选取一个单独的level来用于成员资格审查:
DataFrame也有isin方法。当使用isin时,需要传入一个值的集合,数组或字典都可以。如果是数组,isin返回一个布尔型的DataFrame,它和原DataFrame的大小一样,而且元素在值序列中的地方显示为True.
通常情况下,你会想要使用特定的列来匹配特定的值。只需要使值成为一个dict,其key是列,value是你想要检索的item列表即可。
将DataFrame的isin方法和any( )和all( )方法混合起来以一个给定的标准快速选择你的数据的子集。选择一行数据,它的每一列都有各自的标准:
where( )方法和伪装
使用一个布尔向量从一个Series中选取值通常会返回一个数据的子集。为了保证选取的输出与源数据有相同的规模,你可以使用Series和DataFrame中的where方法。
只返回选取的行:
返回一个与源数据具有相同规模的Series
从一个DataFrame中选取值也能保留输入数据的规模。在底层使用where作为实现。下面的代码等价于df.where(df<0)
另外,在返回的复制数据中,where需要一个可选的其他参数来当条件为假时进行替换。
你或许想要基于一些布尔标准来设置值。这能够直观地这样做:
默认情况下,where将会返回一个数据的修改副本。有一个可选择的参数inplace,它能够在源数据上直接修改,而不产生一个副本。
注意:DataFrame.where()和numpy.where()的识别标志不同。大体上,df1.where(m, df2)等价于np.where(m, df1, df2).
校准
与此同时,where使输入的环境对齐(ndarray 或DataFrame),因此部分选择与设置是可能的。这与使用.ix进行部分设置相似(但是在内容上倒不如轴标签)
在使用where时,where也可以接收axis和level参数来使输入对齐。
这个方法与下面的方法相同,但是比下面的快。
where能够接收一个可调用函数作为条件和其他参数。这个函数必须有一个参数(Series 或者 DataFrame),并返回有效的输出作为条件或其他参数。
伪装
mask() 是where的逆布尔运算。
query()方法
DataFrame对象有一个query()方法能够允许使用一个表达式来选取数据。
你可以获取到frame中列b中介于列a和列c之间的值,例如:
如果没有名为a的列,这样做的话将会把操作作用于一个命了名的index。
如果你不想或不能为你的index命名,你可以在你的query表达式中使用“index”这个名字。
注意: 如果你的index的名字和一个列名相同,那列名将会被赋予优先值。如:
即使index已经命名了,你还是可以在query表达式中使用“index”这个名字对index列进行使用:
如果由于某些原因你有一列名为index,那么你可以使用ilevel_0来使用index,但是这时你最好应该考虑给你的列换个名字。
MultiIndex的query( )语法
你也可以把MultiIndex和一个DataFrame的levels当做frame中的列进行使用。
如果MultiIndex的levels未命名,你可以使用特殊名字来使用它们:
惯例是level_0,它意味着为index的第0level“索引level0”
query( )使用示例
一个query()的使用示例是,当你有一个有着共同列名(或索引level/名称)的子集DataFrame的对象集,你可以向所有frame传递相同的query,而不用指定哪个frame是你要查询的。
query()Python与pandas语法比较
完全的numpy形式的语法
去掉括号会更好一点 (通过绑定比较操作符&/|)
使用英语来代替符号
可能和你在纸上写的非常相近
in 和 not in 操作
在比较操作中,query()也支持Python的特殊用法in和not in,为调用isin方法提供了一个简洁的语法。
你可以将这种方法和其他表达式混合来实现非常简洁的查询:
# 列a和列b有重复值且列c的值小于列d的值的所有行
注意:in和not in在Python中进行了评估,因为numexpr没有与该操作相等的操作。然而,只有表达式中的in和not in自身在普通Python中被评估了。例如,在表达式
df.query('a in b + c + d')
(b + c + d)通过numexpr进行评估,然后in操作就在普通Python评估了。通常来说,任何能够使用numexpr评估的操作都是这样。
==操作和list对象的特殊用法
使用==/!=对一列数值进行比较和in/not in的机制是相似的
布尔操作
你可以使用not或~操作来否定布尔表达式
当然,表达式也可以变得任意复杂:
query()的性能
对于大型frame来说,DataFrame.query()使用numexpr比Python快一些
注意: 当你的frame超过200,000行时DataFrame.query()的快速才能体现出来
这个图表是使用numpy.random.randn()生成的3列浮点值生成的.
重复数据
如果你想要识别并删除一个DataFrame中的重复行,有两种方法:duplicated和drop_duplicates。每种都需要传入一个列参数来识别重复行。
· duplicated返回一个布尔向量,该向量的长度是行数,且它会指出每一行是不是重复行
· drop_duplicates会删除重复行
默认情况下,几个重复行的第一行会被留下,但是每种方法都有一个keep参数来决定要留下哪一行。
· keep='first'(默认): 将第一行视为非重复行并留下
· keep='last':将最后一行视为非重复行并留下
· keep=False: 删除所有行/将所有行都标记为重复行
同样,你可以传入一个列来识别特定重复行
使用index.duplicated然后进行切片可以根据index的值去重。keep参数在这个方法中同样适用。
类似于字典的get( )方法
Series, DataFrame, 和 Panel都有一个get方法能够返回一个默认值
lookup( )方法
有些时候你想要按照某种特定顺序来获取行或列,那么lookup方法就能够实现,并返回一个numpy数组。例如,
索引对象
pandas的Index类和它的子类可以被视为实施一个有序的多集,允许重复。然而,如果你试图将一个有重复项的索引对象转换为一个集合,将会引发一个异常。
Index还为查找、数据规整和重新索引提供了必要的基础。创建一个Index的最简单的方式是向Index传递一个list或其他的序列。
你也可以给index起个名字,并存储在索引中:
如果名字是个集合,将会在控制台显示:
设置元数据
索引“大部分是不可变的”,但是设置和改变他们的元数据却是可能的,比如索引名(或者,对于MultiIndex来说,level和标签)
你可以使用rename,set_name ,set_levels set_labels来直接设置这些属性。它们默认返回一个副本,然而,你可以令关键字inplace=True来使数据直接原地改变。
详见MultiIndexes的Advanced Indexing用法。
set_names,set_levels, 和set_labels也有一个可选参数level:
Index对象的设置操作
两个主要的操作是union (|)和intersection (&)。它们可以作为实例方法被直接调用或通过重载操作使用。Difference 是由.difference()方法实现的。
symmetric_difference (^)操作也同样能用,它能够返回idx1或idx2中出现的元素,但不能二者都返回。这等价于使用idx1.difference(idx2).union(idx2.difference(idx1))来创建索引,没有重复项。
注意:从集合操作中得到的索引将升序排列。
缺失值
重要: 虽然Index能够保留缺失值(NaN),但如果你不想出现什么乱七八糟的结果,最好还是避免缺失值。例如,有些操作会隐性地直接排除缺失值。
Index.fillna能够使用指定值来填充缺失值。
设置/重置索引
有时候你会向一个DataFrame中加载或创建一个数据集,并给这个后来加入的数据子集加上索引。有以下几种方法:
设置索引
DataFrame有一个set_index方法,能够接收一个列名(对于常规Index来说)或一个列名list(对于MutiIndex来说)来创建一个新的,带有索引的DataFrame:
关键字append能够使你保留当前index,并把给定的列追加到一个MultiIndex中:
set_index中的其他选项能够使你保留索引列或者原地加上索引,而不用创建一个新的对象。
重置索引
考虑到方便性,DataFrame有一个新的功能叫做reset_index,它能够把index值转换为DataFrame的列并设置一个简单的整数索引。它是set_index()的逆运算。
输出的结果更像是一个SQL表或一个数组记录。来自index的列名被存储在names属性里。
你可以使用level关键字来移除index的一部分。
reset_index有一个可选参数drop,当drop=True时会直接丢弃index,而不是将index值放在DataFrame的列中。
添加ad hoc索引
如果你自己创建了一个index,你可以将它分配到索引字段
data.index = index
返回视图vs副本
在一个pandas对象中设置值时,必须小心避免链式索引的出现。示例如下:
比较一下这两个访问方法:
这两种方法产生的结果一样,所以你该用哪一种?了解它们的操作顺序、了解为什么第二种方法(.loc)比第一种好得多,是非常有意义的。
dfmi [“one”] 选取了列的第一个level,并返回一个单独索引的DataFrame,另一个Python操作ddmi_with_one[“second”]选取“second”索引的Series。这由可变的dfmi_with_one来指示,因为pandas把这些操作视为独立事件。例如对__getitem__的单独调用,所以它必须把他们作为线性运算,他们一个接一个地发生。
相反,df。loc[:,(“one”,”second”)]向单独调用的__getitem__传入了一个嵌套元组(slice(None),('one','second’)).这能够使pandas把它们作为一个单一的实体进行处理。此外,这个操作命令比第一种方法要快得多,并且如果需要的话,还能够允许同时索引多个轴。
为什么使用链式索引进行分配时会报错?
前面部分的问题仅仅是性能问题。为什么会有SettingWithCopy警告?当你的操作会多花费不必要的几毫秒时,我们通常不会报出警告!
但是事实证明,链式索引会导致不可预知的结果。要了解这一点,想想Python解释器如果执行这个代码的:
但这个代码的处理方式是完全不同的:
看到这里的__getitem__了吗?除了一些简单的情况之外,我们很难预测它到底会返回一个视图还是一个副本(这取决于数组的内存布局,pandas可不能保证这个),也不能预测__setitem__是将会直接修改dfmi还是修改一个用完即扔的临时对象。这也是SettingWithCopy在警告你的东西!
注意:你可能会想在第一个例子中我们是否应该考虑到loc的特性。但是我们能肯定在修改索引时,dfmi.loc是dfmi自身,所以dfmi.loc.__getitem__/dfmi.loc.__setitem__操作是直接作用在dfmi自身上的。当然,dfmi.loc.__getitem__ (idx)或许是dfmi的一个视图或副本。
有些时候,当没有明显的链式索引时,SettingWithCopy警告也会产生。这是SettingWithCopy的一些设计上的bug,pandas可能会试着发出警告,如果你这样做的话:
def do_something(df):
foo = df[['bar', 'baz']] # foo是一个视图还是副本,没人知道啊!
#许多行在此省略
foo['quux'] = value # 我们不知道这个操作到底有没有修改到df啊!
return foo
唉!真无奈啊!
评估事项
当你使用链式索引时,索引操作的命令和类型将部分决定是返回原始对象里的一个切片还是该切片的副本。
Pandas具有设置警告的功能,因为分配给切片的副本常常并非本意,而是由链式索引而引起的错误。
如果你想让Pandas或多或少相信链式索引表达式的任务,你可以通过选项mode.chained_assignment来控制一个链式分配:
· “warn”,默认选项,表示打印了一个带有警告的设置;
· “raise”,表示Pandas将会引发一个你必须处理的SettingWithCopyException;
· 无法完全阻止这个警告。
# 这将会引发SettingWithCopyWarning
# 但是frame的值确实被设置了
然而这是在副本上的操作,并木有什么卵用。
一个链式分配也可以在设置一个混合类型的frame时让人猝不及防地出现。
注意: 这些设置规则对.loc/.iloc通用
这才是正确的访问方法:
下面的方法有时能够正常使用,但不能保证任何时候都正常,所以应该避免使用:
下面的方法是错的,所以别用啊
警告:链式分配警告/异常是为了把可能无效的分配告知用户。有时候有可能是误报。