# 7.6 Pandas 中的数据操作

> 原文：[Operating on Data in Pandas](http://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/pandas/03.03-Operations-in-Pandas.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 的一个重要部分是能够执行快速的逐元素运算，包括基本算术（加法，减法，乘法等），和更复杂的运算（三角函数，指数函数和对数函数等）。Pandas 从 NumPy 继承了大部分功能，我们在“NumPy 数组上的计算：通用函数”中介绍的`ufunc`对此至关重要。

Pandas 包含一些有用的调整，但是：对于一元操作，如取负和三角函数，这些`ufunc`将保留输出中的索引和列标签，对于二元操作，如加法和乘法，将对象传递给`ufunc`时，Pandas 将自动对齐索引。这意味着，保留数据的上下文并组合来自不同来源的数据 - 这两个在原始的 NumPy 数组中可能容易出错的任务 - 对于 Pandas 来说基本上是万无一失的。我们还将看到，在一维``Series``结构和二维``DataFrame``结构之间有明确定义的操作。

## 通用函数：索引保留

因为 Pandas 为兼容 NumPy 而设计，所以任何 NumPy `ufunc`都可以用于 Pandas ``Series``和``DataFrame``对象。让我们首先定义一个简单的``Series``和``DataFrame``来演示它：

```py
import pandas as pd
import numpy as np

rng = np.random.RandomState(42)
ser = pd.Series(rng.randint(0, 10, 4))
ser

'''
0    6
1    3
2    7
3    4
dtype: int64
'''

df = pd.DataFrame(rng.randint(0, 10, (3, 4)),
                  columns=['A', 'B', 'C', 'D'])
df
```

|  | A | B | C | D |
| --- | --- | --- | --- | --- |
| 0 | 6 | 9 | 2 | 6 |
| 1 | 7 | 4 | 3 | 7 |
| 2 | 7 | 2 | 5 | 4 |

如果我们在这些对象中的任何一个上应用 NumPy `ufunc`，结果将是保留索引的另一个 Pandas 对象：

```py
np.exp(ser)

'''
0     403.428793
1      20.085537
2    1096.633158
3      54.598150
dtype: float64
'''
```

或者，对于稍微复杂的计算：

```py
np.sin(df * np.pi / 4)
```

|  | A | B | C | D |
| --- | --- | --- | --- | --- |
| 0 | -1.000000 | 7.071068e-01 | 1.000000 | -1.000000e+00 |
| 1 | -0.707107 | 1.224647e-16 | 0.707107 | -7.071068e-01 |
| 2 | -0.707107 | 1.000000e+00 | -0.707107 | 1.224647e-16 |

“NumPy 数组上的计算：通用函数”中讨论的任何`ufunc`都可以以类似的方式使用。

## 通用函数：索引对齐

对于两个``Series``或``DataFrame``对象的二元操作，Pandas 将在执行操作的过程中对齐索引。这在处理不完整数据时非常方便，我们将在后面的一些示例中看到。

### 序列中的索引对齐

例如，假设我们正在组合两个不同的数据源，并且按照面积，找到美国前三的州，并且按人口找到美国前三的州：

```py
area = pd.Series({'Alaska': 1723337, 'Texas': 695662,
                  'California': 423967}, name='area')
population = pd.Series({'California': 38332521, 'Texas': 26448193,
                        'New York': 19651127}, name='population')
```

让我们看看，当我们将这些分开以计算人口密度时会发生什么：

```py
population / area

'''
Alaska              NaN
California    90.413926
New York            NaN
Texas         38.018740
dtype: float64
'''
```

所得数组包含两个输入数组的索引的并集，可以使用这些索引上的标准 Python 集合算法来确定：

```py
area.index | population.index

# Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')
```

任何没有条目的项目都标为`NaN`（非数字），这就是 Pandas 标记缺失数据的方式（请在“处理缺失数据”中参阅缺失数据的进一步讨论）。对于 Python 的任何内置算术表达式，索引匹配是以这种方式实现的；默认情况下，任何缺失值都使用`NaN`填充：

```py
A = pd.Series([2, 4, 6], index=[0, 1, 2])
B = pd.Series([1, 3, 5], index=[1, 2, 3])
A + B

'''
0    NaN
1    5.0
2    9.0
3    NaN
dtype: float64
'''
```

如果使用`NaN`值不是所需的行为，则可以使用适当的对象方法代替运算符来修改填充值。例如，调用``A.add(B)``相当于调用``A + B``，但对于``A``或``B`中的任何可能会缺失的元素，可以显式指定的填充值：

```py
A.add(B, fill_value=0)

'''
0    2.0
1    5.0
2    9.0
3    5.0
dtype: float64
'''
```

### 数据帧中的索引对齐

在``DataFrame``s上执行操作时，列和索引都会发生类似的对齐：

```py
A = pd.DataFrame(rng.randint(0, 20, (2, 2)),
                 columns=list('AB'))
A
```

|  | A | B |
| --- | --- | --- |
| 0 | 1 | 11 |
| 1 | 5 | 1 |

```py
B = pd.DataFrame(rng.randint(0, 10, (3, 3)),
                 columns=list('BAC'))
B
```

|  | B | A | C |
| --- | --- | --- | --- |
| 0 | 4 | 0 | 9 |
| 1 | 5 | 8 | 0 |
| 2 | 9 | 2 | 6 |

```py
A + B
```

|  | A | B | C |
| --- | --- | --- | --- |
| 0 | 1.0 | 15.0 | NaN |
| 1 | 13.0 | 6.0 | NaN |
| 2 | NaN | NaN | NaN |

请注意，索引是正确对齐的，无论它们在两个对象中的顺序如何，并且结果中的索引都是有序的。与``Series``的情况一样，我们可以使用相关对象的算术方法，并传递任何所需的``fill_value``来替代缺失的条目。这里我们将填充``A``中所有值的均值（通过首先堆叠``A``的行来计算）：

```py
fill = A.stack().mean()
A.add(B, fill_value=fill)
```

|  | A | B | C |
| --- | --- | --- | --- |
| 0 | 1.0 | 15.0 | 13.5 |
| 1 | 13.0 | 6.0 | 4.5 |
| 2 | 6.5 | 13.5 | 10.5 |

下表列出了 Python 运算符及其等效的 Pandas 对象方法：

|  Python 运算符  |             Pandas 方法               |
|-----------------|---------------------------------------|
| ``+``           | ``add()``                             |
| ``-``           | ``sub()``, ``subtract()``             |
| ``*``           | ``mul()``, ``multiply()``             |
| ``/``           | ``truediv()``, ``div()``, ``divide()``|
| ``//``          | ``floordiv()``                        |
| ``%``           | ``mod()``                             |
| ``**``          | ``pow()``                             |


## 通用函数：数据帧和序列之间的操作

执行``DataFrame``和``Series``之间的操作时，与之相似，索引和列是保持对齐的。`DataFrame`和`Series`之间的操作，类似于二维和一维 NumPy 数组之间的操作。考虑一个常见的操作，我们计算二维数组与其中一行的差：

```py
A = rng.randint(10, size=(3, 4))
A

'''
array([[3, 8, 2, 4],
       [2, 6, 4, 8],
       [6, 1, 3, 8]])
'''

A - A[0]

'''
array([[ 0,  0,  0,  0],
       [-1, -2,  2,  4],
       [ 3, -7,  1,  4]])
'''
```

根据 NumPy 的广播规则（参见“数据计算：广播”），二维数组与其中一行之间的减法是逐行应用的。

在 Pandas 中，按照惯例，默认情况下逐行操作：

```py
df = pd.DataFrame(A, columns=list('QRST'))
df - df.iloc[0]
```

|  | Q | R | S | T |
| --- | --- | --- | --- | --- |
| 0 | 0 | 0 | 0 | 0 |
| 1 | -1 | -2 | 2 | 4 |
| 2 | 3 | -7 | 1 | 4 |

如果你希望逐列操作，则可以使用前面提到的对象方法，同时指定``axis``关键字：

```py
df.subtract(df['R'], axis=0)
```

|  | Q | R | S | T |
| --- | --- | --- | --- | --- |
| 0 | -5 | 0 | -6 | -4 |
| 1 | -4 | 0 | -2 | 2 |
| 2 | 5 | 0 | 2 | 7 |

请注意，这些``DataFrame``或``Series``操作，如上面讨论的操作，将自动对齐两个元素之间的索引：

```py
halfrow = df.iloc[0, ::2]
halfrow

'''
Q    3
S    2
Name: 0, dtype: int64
'''

df - halfrow
```

|  | Q | R | S | T |
| --- | --- | --- | --- | --- |
| 0 | 0.0 | NaN | 0.0 | NaN |
| 1 | -1.0 | NaN | 2.0 | NaN |
| 2 | 3.0 | NaN | 1.0 | NaN |

索引和列的保留和对齐意味着，Pandas 中的数据操作将始终维护数据上下文，这可以防止在处理原始 NumPy 数组中的异构和/或未对齐数据时，可能出现的愚蠢错误。
