7.5 数据索引和选择

原文:Data Indexing and Selection

译者:飞龙

协议:CC BY-NC-SA 4.0

本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。

在第二章中,我们详细介绍了在 NumPy 数组中访问,设置和修改值的方法和工具。这些包括索引(例如,arr[2,1]),切片(例如,arr[:, 1:5]),掩码(例如,arr[arr > 0] ),花式索引(例如,arr[0, [1, 5]])及其组合(例如,arr[:, [1, 5]])。

在这里,我们将看看在 Pandas SeriesDataFrame对象中,访问和修改值的类似方法。如果你使用过 NumPy 模式,Pandas 中的相应模式将会非常熟悉,尽管有一些需要注意的怪异之处。

我们将从一维Series对象的简单情况开始,然后转向更复杂的二维DataFrame对象。

序列中的数据选择

我们在上一节中看到,Series对象在很多方面都像一维 NumPy 数组,并且在许多方面像标准的 Python 字典。如果我们记住这两个重叠的类比,它将帮助我们理解这些数组中的数据索引和选择的模式。

作为字典的序列

像字典一样,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建立字典式接口上,并通过与 NumPy 数组相同的基本机制,提供数组式的项目选择,即切片,掩码和花式索引。这些例子如下:

# 按照显式下标来切片
data['a':'c']

'''
a    0.25
b    0.50
c    0.75
dtype: float64
'''

# 按照隐式下标来切片
data[0:2]

'''
a    0.25
b    0.50
dtype: float64
'''

# 掩码
data[(data > 0.3) & (data < 0.8)]

'''
b    0.50
c    0.75
dtype: float64
'''

# 花式索引
data[['a', 'e']]

'''
a    0.25
e    1.25
dtype: float64
'''

其中,切片可能是混乱的根源。注意,当使用显式索引进行切片时(即data['a':'c']),切片中包含最终索引,而在使用隐式索引进行切片时(即data[0:2]),最终索引从切片中排除。

索引器:lociloc,和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
'''

# 索引的时候是显式索引
data[1]

# 'a'

# 切片的时候是隐式索引
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对象来说,相当于标准的[]风格的索引。在DataFrame对象的上下文中,ix索引器的目的将变得更加明显,我们将在稍后讨论。

Python 代码的一个指导原则是“显式优于隐式”。lociloc的显式特性,使它们在维护清晰可读的代码时非常有用;特别是在整数索引的情况下,我建议使用这两者,来使代码更容易阅读和理解,并防止由于混合索引/切片约定而导致的细微错误。

数据帧中的数据选择

回想一下,DataFrame在很多方面都类似二维或结构化数组,在其它方面莱斯共享相同索引的Series结构的字典。在我们探索此结构中的数据选择时,记住些类比是有帮助的。

作为字典的数据帧

我们将考虑的第一个类比是,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的方法冲突,则无法进行属性风格的访问。例如,DataFramepop()方法,所以data.pop将指向它而不是pop列:

data.pop is data['pop']

# False

特别是,你应该避免尝试通过属性对列赋值(即使用data['pop'] = z而不是data.pop = z)。

与前面讨论的Series对象一样,这种字典式语法也可用于修改对象,在这里添加一个新列:

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对象之间的逐元素算术的直接语法;我们将在“使用 Pandas 中的数据进行操作”中深入研究它。

作为二维数组的数据帧

如前所述,我们还可以将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])
'''

将单个“索引”传递给DataFrame会访问一列:

data['area']

'''
California    423967
Florida       170312
Illinois      149995
New York      141297
Texas         695662
Name: area, dtype: int64
'''

因此,对于数组风格的索引,我们需要另一个惯例。在这里,Pandas 再次使用前面提到的locilocix索引器。使用iloc索引器,我们可以索引底层数组,好像它是一个简单的 NumPy 数组(使用隐式的 Python 风格索引),但结果中保留了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 惯例,但它们在实践中非常有用。

results matching ""

    No results matching ""