数据索引和选择
在第2章,我们详细研究用于在NumPy数组中访问、设置和修改值的方法和工具。这些包括索引(例如,arr[2,1]),切片(如,arr[:,1:5]),过滤(如,arr[arr>0]),花式索引(fancy indexing)(如arr[0,[1,5]])及其组合(如,arr[:,[1,5]]).这儿,我们将研究相似的方式用于访问,修改Pandas中Series和DataFrame对象的值。
如果你曾经用过NumPy模板,对Pandas中的模板将会感觉很熟悉,虽然有一些技巧需要注意。
我们将从一维Series对象的简单例子开始,然后移到复杂些的二维DataFrame对象。
Series中的数据选择
如我们在前面章节所见,Series对象的许多行为与一维Numpy数组相像,也在许多方面像标准的python字典。如果我们记住这两个重叠的类比,这将有助于我们理解这些数组中数据的索引和选择模式。
Series作为字典
像字典一样,Series对象提供了从键集合到值集合的映射。
import pandas as pd
data = pd.Series([0.25, 0.5, 0.75, 1.0],
index=['a', 'b', 'c', 'd'])
data
a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
data['b']
0.5
我们也可以使用字典类似的Python表达式和方法来检查键/切片和值:
'a' in data
True
data.keys()
Index(['a', 'b', 'c', 'd'], dtype='object')
list(data.items())
[('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
Series对象甚至可以使用字典相似的语法进行修改。就如你可以通过增加新键来扩展字典,你也能通过赋给新的索引值来扩展Series
data['e'] = 1.25
data
a 0.25
b 0.50
c 0.75
d 1.00
e 1.25
dtype: float64
对象的简单易变性是一个方便的特性:在底层,Pandas随时考虑内存布局以及可能发送的数据拷贝活动。用户通常不必担心这些问题。
Series作为一维数组
Series建立了以与字典操作相似的接口,并且提供了与NumPy数组同样风格的数据项(item)选择,包括切片,过滤和花式索引。例子如下:
# slicing by explicit index 通过显示索引切片
data['a':'c']
a 0.25
b 0.50
c 0.75
dtype: float64
# slicing by implicit integer index 通过非显示的整数索引切片
data[0:2]
a 0.25
b 0.50
dtype: float64
# masking 过滤
data[(data > 0.3) & (data < 0.8)]
b 0.50
c 0.75
dtype: float64
# fancy indexing 花式索引
data[['a', 'e']]
a 0.25
e 1.25
dtype: float64
在这些方法之中,切片方法可能带来最多的困惑。注意当使用显示索引切片的时候(如 data['a':'c']),最后的索引使包含在切片中的。然而当使用非显示索引(如,data[0:2])时,切片结果不包含最后的索引值
索引器:loc,iloc和ix
这些切片和索引做法可能会带来困惑。例如,如果你的Series有显示的整数索引,诸如data[1]这样的索引操作将会使用显示索引,而像data[1:3]这样的切片操作将会使用非显示的python风格索引。
data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
data
1 a
3 b
5 c
dtype: object
# explicit index when indexing 索引取值时使用显示索引
data[1]
'a'
# implicit index when slicing 切片时使用非显示索引
data[1:3]
3 b
5 c
dtype: object
为了避免整型索引带来的潜在困惑,Pandas提供了一些特殊的索引器属性,这些属性清晰的指定某种索引方案。它们不是功能方法,只是一些为访问Series数据而提供的特定切片接口的属性。
首先,loc属性允许索引操作和切片总是引用显示索引:
data.loc[1]
'a'
data.loc[1:3]
1 a
3 b
dtype: object
iloc属性允许索引操作和切片总是引用python风格的非显示索引:
data.iloc[1]
'b'
data.iloc[1:3]
3 b
5 c
dtype: object
第三个索引属性,ix,是前面两个的混合,对于Series对象它等同与标准的基于[]的索引操作。ix索引器的目的在随后讨论的DataFrame对象环境中会更明显。
Python代码的一个指导原则是“清晰胜过不清晰”。loc和iloc的清晰特性使它们在维护整洁可读代码时更有用;特别时在整数索引的情况下,建议使用这两种属性来让代码易于阅读和理解,并且它们容易减少由于混淆索引/切片约定带来而带来的那些不易察觉的bug。
DataFrame的数据选择
我们记得DataFrame的行为在许多方面像二维结构数组,在另外的方面有些像共享同样索引的Series结构字典。将这些类比记在心里有助于我们理解DataFrame结构的数据选择。
DataFrame作为字典
第一个类比是我们把DataFrame看成相关Series对象的字典。让我们回到州的面积和人口的例子:
area = pd.Series({'California': 423967, 'Texas': 695662,
'New York': 141297, 'Florida': 170312,
'Illinois': 149995})
pop = pd.Series({'California': 38332521, 'Texas': 26448193,
'New York': 19651127, 'Florida': 19552860,
'Illinois': 12882135})
data = pd.DataFrame({'area':area, 'pop':pop})
data
area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
New York 141297 19651127
Texas 695662 26448193
构成DataFrame列的那些单个Series可以通过字典风格的列名称索引来访问:
data['area']
California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
同样的,我们也可以使用字符串列名称属性来访问:
data.area
California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
属性风格的列访问实际上与字典风格访问的是同一个对象:
data.area is data['area']
True
虽然这种简记很有用,但我们要记住它并不是在所有情况下都有效!例如,如果列名称不是字符串,或列名称与DataFrame的方法冲突,这种属性风格的访问就会失效。比如,DataFrame有pop()方法,data.pop指向的就不是"pop"列了。
data.pop is data['pop']
False
特别的,你应该抵制通过属性给列赋值的诱惑(如,使用data['pop'] = z 而不是data.pop = z)
data['density'] = data['pop'] / data['area']
data
area pop density
California 423967 38332521 90.413926
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
这里简要的展示了Series对象间元素的直接算术语法。我们将对它在Operating on Data in Pandas章进行深入的挖掘。
DataFrame作为二维数组
如之前提到的,DataFrame可以被看做是增强的二维数组。我们可以使用values属性来检查数组中的原始数据:
data.values
array([[ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01],
[ 1.70312000e+05, 1.95528600e+07, 1.14806121e+02],
[ 1.49995000e+05, 1.28821350e+07, 8.58837628e+01],
[ 1.41297000e+05, 1.96511270e+07, 1.39076746e+02],
[ 6.95662000e+05, 2.64481930e+07, 3.80187404e+01]])
记住这点,许多数组上的操作也可以用在DataFrame上。例如,我们可以转置整个DataFrame来交换行和列:
data.T
California Florida Illinois New York Texas
area 4.239670e+05 1.703120e+05 1.499950e+05 1.412970e+05 6.956620e+05
pop 3.833252e+07 1.955286e+07 1.288214e+07 1.965113e+07 2.644819e+07
density 9.041393e+01 1.148061e+02 8.588376e+01 1.390767e+02 3.801874e+01
但是,当涉及DataFrame对象的索引时,很显然字典风格的列索引阻止我们简单将它看成是NumPy数组。特别的,传递一个单索引给数组来访问一行:
data.values[0]
array([ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
传递一个单“索引”来访问一列:
data['area']
California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
因此对于数组风格的索引,我们需要另外的约定。Pandas在这里使用的还是之前提到的loc,iloc和ix索引器。使用iloc索引器,我可以对基础数组进行索引,就像它是简单的NumPy数组一样,只是DataFrame索引和列标签保持在结构里面。
data.iloc[:3, :2]
area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
类似的,使用loc索引器我们可以以数组类似的风格对基础数据进行索引,只是它使用的是显示的索引值和列名称:
data.loc[:'Illinois', :'pop']
area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
ix索引器允许两种方法的混合:
data.ix[:3, :'pop']
area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
记住对于整数索引,ix索引器仍然有同Series对象整型索引一样的令人混淆的问题。
任何熟悉的NumPy风格的数据访问模式都可以被用在这些索引器上。例如,用loc索引器,我们可以将过滤和花式索引结合在一起。例子如下:
data.loc[data.density > 100, ['pop', 'density']]
pop density
Florida 19552860 114.806121
New York 19651127 139.076746
每种索引方法都可以被用来设置和修改数据;这和你可能熟悉的NumPy操作是一样的:
data.iloc[0, 2] = 90
data
area pop density
California 423967 38332521 90.000000
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
为了能熟练的操作Pandas数据,我建议在一个简单的DataFrame上花些时间来探索不同索引方法的,索引,切片,过滤及花式索引操作。
额外的索引规定
有几种额外的索引规定同之前讨论的相比看起来有点奇怪,但是在实践中是非常有用的。首先,索引指的是列,而切片用的是行:
data['Florida':'Illinois']
area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
这些切片操作也可以按编号而不是索引名称来表示行:
data[1:3]
area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
类似的,直接过滤操作也是按行解释的而不是按列:
data[data.density > 100]
area pop density
Florida 170312 19552860 114.806121
New York 141297 19651127 139.076746
这两种规定语法上与NumPy数组相似,这些可能不是十分符合Pandas的语法规定,但它们在实际中很有用。