数据科学 IPython 笔记本 7.5 数据索引和选择

2022-06-03 17:18:57 浏览数 (1)

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对象提供从一组键到一组值的映射:

代码语言:javascript复制
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 表达式和方法,来检查键/索引和值:

代码语言:javascript复制
'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

代码语言:javascript复制
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 数组相同的基本机制,提供数组式的项目选择,即切片,掩码和花式索引。这些例子如下:

代码语言:javascript复制
# 按照显式下标来切片
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 风格索引。

代码语言:javascript复制
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属性让索引和切片始终引用显式索引:

代码语言:javascript复制
data.loc[1]

# 'a'

data.loc[1:3]

'''
1    a
3    b
dtype: object
'''

iloc属性让索引和切片始终引用隐式的 Python 风格索引:

代码语言:javascript复制
data.iloc[1]

# 'b'

data.iloc[1:3]

'''
3    b
5    c
dtype: object
'''

第三个索引属性ix是两者的混合,对Series对象来说,相当于标准的[]风格的索引。在DataFrame对象的上下文中,ix索引器的目的将变得更加明显,我们将在稍后讨论。

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

数据帧中的数据选择

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

作为字典的数据帧

我们将考虑的第一个类比是,DataFrame作为相关Series对象的字典。让我们回到我们的州人口和面积的例子:

代码语言:javascript复制
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,可以通过列名称的字典式索引来访问:

代码语言:javascript复制
data['area']

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

同样,我们可以使用列名的字符串和属性风格来访问:

代码语言:javascript复制
data.area

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

属性风格的列访问,与字典风格的访问,实际上访问了完全相同的对象:

代码语言:javascript复制
data.area is data['area']

# True

虽然这是一个有用的简写,但请记住,它并不适用于所有情况!例如,如果列名不是字符串,或者列名与DataFrame的方法冲突,则无法进行属性风格的访问。例如,DataFramepop()方法,所以data.pop将指向它而不是pop列:

代码语言:javascript复制
data.pop is data['pop']

# False

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

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

代码语言:javascript复制
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属性检查原始底层数据数组:

代码语言:javascript复制
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来交换行和列:

代码语言:javascript复制
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 数组。特别是,将单个索引传递给数组会访问一行:

代码语言:javascript复制
data.values[0]

'''
array([  4.23967000e 05,   3.83325210e 07,   9.04139261e 01])
'''

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

代码语言:javascript复制
data['area']

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

因此,对于数组风格的索引,我们需要另一个惯例。在这里,Pandas 再次使用前面提到的locilocix索引器。使用iloc索引器,我们可以索引底层数组,好像它是一个简单的 NumPy 数组(使用隐式的 Python 风格索引),但结果中保留了DataFrame索引和列标签:

代码语言:javascript复制
data.iloc[:3, :2]

area

pop

California

423967

38332521

Florida

170312

19552860

Illinois

149995

12882135

与之类似,使用loc索引器,我们可以用数组风格索引底层数据,但是使用显式索引和列名称:

代码语言:javascript复制
data.loc[:'Illinois', :'pop']

area

pop

California

423967

38332521

Florida

170312

19552860

Illinois

149995

12882135

ix索引器是这两种方法的混合:

代码语言:javascript复制
data.ix[:3, :'pop']

area

pop

California

423967

38332521

Florida

170312

19552860

Illinois

149995

12882135

请记住,对于整数索引,ix索引器具有与整数索引的Series对象相同的潜在混淆。

任何熟悉的 NumPy 风格的数据访问模式,都可以在这些索引器中使用。例如,在loc索引器中,我们可以组合掩码和花式索引,如下所示:

代码语言:javascript复制
data.loc[data.density > 100, ['pop', 'density']]

pop

density

Florida

19552860

114.806121

New York

19651127

139.076746

任何这些索引惯例也可用于设置或修改值;你可能习惯使用 NumPy 的标准方式完成它们:

代码语言:javascript复制
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,并探索各种索引方法所允许的索引,切片,掩码和花式索引。

额外的索引惯例

有一些额外的索引约定可能与前面的讨论不一致,但在实践中可能非常有用。首先,索引引用列,切片引用行:

代码语言:javascript复制
data['Florida':'Illinois']

area

pop

density

Florida

170312

19552860

114.806121

Illinois

149995

12882135

85.883763

这样的切片也可以通过数字而不是索引来引用行:

代码语言:javascript复制
data[1:3]

area

pop

density

Florida

170312

19552860

114.806121

Illinois

149995

12882135

85.883763

与之类似,直接掩码操作也是按行而不是按列解释的:

代码语言:javascript复制
data[data.density > 100]

area

pop

density

Florida

170312

19552860

114.806121

New York

141297

19651127

139.076746

这两个惯例在语法上类似于 NumPy 数组上的惯例,虽然这些惯例可能不完全符合 Pandas 惯例,但它们在实践中非常有用。

0 人点赞