# 7.5 数据索引和选择

> 原文：[Data Indexing and Selection](https://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/pandas/03.02-Data-Indexing-and-Selection.ipynb)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 
> 本节是[《Python 数据科学手册》](https://github.com/jakevdp/PythonDataScienceHandbook)（Python Data Science Handbook）的摘录。

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

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

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

## 序列中的数据选择

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

### 作为字典的序列

像字典一样，``Series``对象提供从一组键到一组值的映射：

```py
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 表达式和方法，来检查键/索引和值：

```py
'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``：

```py
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 数组相同的基本机制，提供数组式的项目选择，即切片，掩码和花式索引。这些例子如下：

```py
# 按照显式下标来切片
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]``），最终索引从切片中排除。

### 索引器：`loc`，`iloc`，和`ix`

这些切片和索引惯例可能会引起混淆。例如，如果你的``Series``拥有显式的整数索引，那么索引操作如``data[1]``将使用显式索引，而切片操作如``data[1:3]``将使用隐式的 Python 风格索引。

```py
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``属性让索引和切片始终引用显式索引：

```py
data.loc[1]

# 'a'

data.loc[1:3]

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

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

```py
data.iloc[1]

# 'b'

data.iloc[1:3]

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

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

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

## 数据帧中的数据选择

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

### 作为字典的数据帧

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

```py
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``，可以通过列名称的字典式索引来访问：

```py
data['area']

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

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

```py
data.area

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

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

```py
data.area is data['area']

# True
```

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

```py
data.pop is data['pop']

# False
```

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

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

```py
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``属性检查原始底层数据数组：

```py
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``来交换行和列：

```py
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 数组。特别是，将单个索引传递给数组会访问一行：

```py
data.values[0]

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

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

```py
data['area']

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

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

```py
data.iloc[:3, :2]
```

|  | area | pop |
| --- | --- | --- |
| California | 423967 | 38332521 |
| Florida | 170312 | 19552860 |
| Illinois | 149995 | 12882135 |

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

```py
data.loc[:'Illinois', :'pop']
```

|  | area | pop |
| --- | --- | --- |
| California | 423967 | 38332521 |
| Florida | 170312 | 19552860 |
| Illinois | 149995 | 12882135 |

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

```py
data.ix[:3, :'pop']
```

|  | area | pop |
| --- | --- | --- |
| California | 423967 | 38332521 |
| Florida | 170312 | 19552860 |
| Illinois | 149995 | 12882135 |

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

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

```py
data.loc[data.density > 100, ['pop', 'density']]
```

|  | pop | density |
| --- | --- | --- |
| Florida | 19552860 | 114.806121 |
| New York | 19651127 | 139.076746 |

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

```py
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``，并探索各种索引方法所允许的索引，切片，掩码和花式索引。

### 额外的索引惯例

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

```py
data['Florida':'Illinois']
```

|  | area | pop | density |
| --- | --- | --- | --- |
| Florida | 170312 | 19552860 | 114.806121 |
| Illinois | 149995 | 12882135 | 85.883763 |

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

```py
data[1:3]
```

|  | area | pop | density |
| --- | --- | --- | --- |
| Florida | 170312 | 19552860 | 114.806121 |
| Illinois | 149995 | 12882135 | 85.883763 |

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

```py
data[data.density > 100]
```

|  | area | pop | density |
| --- | --- | --- | --- |
| Florida | 170312 | 19552860 | 114.806121 |
| New York | 141297 | 19651127 | 139.076746 |

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