# 7.4 Pandas 对象介绍

> 原文：[Introducing Pandas Objects](http://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/pandas/03.01-Introducing-Pandas-Objects.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）的摘录。

在最基本的层面上，Pandas 对象可以认为是 NumPy 结构化数组的增强版本，其中行和列用标签而不是简单的整数索引来标识。我们将在本章的过程中看到，Pandas 在基本数据结构之上提供了许多有用的工具，方法和功能，但几乎所有后续内容都需要了解这些结构是什么。因此，在我们继续之前，让我们介绍这三个基本的 Pandas 数据结构：``Series``，``DataFrame``和``Index``。

我们将使用标准的 NumPy 和 Pandas 导入，来启动我们的代码会话：

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

## Pandas 序列对象

Pandas ``Series``是带索引的数据的一维数组。它可以从列表或数组创建，如下所示：

```py
data = pd.Series([0.25, 0.5, 0.75, 1.0])
data

'''
0    0.25
1    0.50
2    0.75
3    1.00
dtype: float64
'''
```

我们在输出中看到，``Series``包含了一系列值和一系列索引，我们可以使用``values``和``index``属性来访问它们。

``values``只是一个熟悉的 NumPy 数组：

```py
data.values

# array([ 0.25,  0.5 ,  0.75,  1.  ])
```

``index``是类型为``pd.Index``的数组式对象，我们将在稍后详细讨论。

```py
data.index

# RangeIndex(start=0, stop=4, step=1)
```

与 NumPy 数组一样，可以通过熟悉的 Python 方括号表示法，按照相关索引访问数据：

```py
data[1]

# 0.5

data[1:3]

'''
1    0.50
2    0.75
dtype: float64
'''
```

然而，我们将要看到，Pandas ``Series``比它模仿的一维 NumPy 数组更加通用和灵活。

### 作为扩展的 NumPy 数组的``Series``

从目前来看，``Series``对象看起来基本上可以与一维 NumPy 数组互换。本质区别在于索引的存在：虽然 Numpy 数组拥有隐式定义的整数索引，用于访问值，Pandas ``Series``拥有显式定义的索引，与值关联。

这个显式索引的定义，为``Series``对象提供了额外的功能。例如，索引不必是整数，还可以包含任何所需类型的值。例如，如果我们愿意，我们可以使用字符串作为索引：

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

项目的访问像预期一样工作：

```py
data['b']

# 0.5
```

我们甚至可以使用非连续的索引：

```py
data = pd.Series([0.25, 0.5, 0.75, 1.0],
                 index=[2, 5, 3, 7])
data

'''
2    0.25
5    0.50
3    0.75
7    1.00
dtype: float64
'''

data[5]

# 0.5
```

### 作为特化字典的序列

通过这种方式，你可以将 Pandas ``Series`视为 Python 字典的特化。字典是将任意键映射到一组任意值的结构，而``Series``是将类型化键映射到一组类型化值的结构。这种类型很重要：正如 NumPy 数组后面的特定于类型的编译代码，使其在某些操作方面，比 Python 列表更有效，Pandas ``Series``的类型信息使其比 Python 字典更有效。

通过直接从 Python 字典构造一个``Series``对象，可以使``Series``和字典的类比更加清晰：

```py
population_dict = {'California': 38332521,
                   'Texas': 26448193,
                   'New York': 19651127,
                   'Florida': 19552860,
                   'Illinois': 12882135}
population = pd.Series(population_dict)
population

'''
California    38332521
Florida       19552860
Illinois      12882135
New York      19651127
Texas         26448193
dtype: int64
'''
```

默认情况下，这将创建一个``Series``，其中索引是从有序键中提取的。从这里开始，我们可以执行典型的字典式的项目访问：

```py
population['California']

# 38332521
```

但是，与字典不同，``Series``也支持数组式的操作，例如切片：

```py
population['California':'Illinois']

'''
California    38332521
Florida       19552860
Illinois      12882135
dtype: int64
'''
```

我们将在“数据索引和选择”中讨论 Pandas 索引和切片的一些怪异之处。

### 构造序列对象

我们已经看到了从头开始构建 Pandas ``Series``的几种方法；所有这些都是以下内容的某个版本：

```py
>>> pd.Series(data, index=index)
```

其中``index``是一个可选参数，``data``可以是许多实体之一。

例如，``data``可以是列表或 NumPy 数组，在这种情况下``index``默认为整数序列：

```py
pd.Series([2, 4, 6])

'''
0    2
1    4
2    6
dtype: int64
'''
```

``data``可以是标量，被重复来填充指定的索引：

```py
pd.Series(5, index=[100, 200, 300])

'''
100    5
200    5
300    5
dtype: int64
'''
```

``data``可以是一个字典，其中``index``默认为有序的字典键：

```py
pd.Series({2:'a', 1:'b', 3:'c'})

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

在每种情况下，如果偏向不同的结果，则可以显式设置索引：

```py
pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])

'''
3    c
2    a
dtype: object
'''
```

请注意，在这种情况下，``Series``仅仅由明确标识的键填充。

## Pandas 数据帧对象

Pandas 的下一个基本结构是``DataFrame``。与前一节中讨论的``Series``对象一样，``DataFrame``可以被认为是 NumPy 数组的扩展，也可以被认为是 Python 字典的特化。我们现在来看看这些观点。

### 作为扩展的 NumPy 数组的``DataFrame``

如果``Series``是具有灵活索引的一维数组的模拟，则``DataFrame``是具有灵活行索引和灵活列名的二维数组的模拟。正如你可能将二维数组视为对齐的一维列的有序序列一样，你可以将`DataFrame`视为对齐的``Series``对象的序列。在这里，“对齐”是指它们共享相同的索引。

为了演示这一点，让我们首先构建一个新的``Series``，列出上一节讨论的五个州中的每个州的面积：

```py
area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,
             'Florida': 170312, 'Illinois': 149995}
area = pd.Series(area_dict)
area

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

现在我们已经有了它，以及之前的``population`序列，我们可以使用字典来构造包含这些信息的单个二维对象：

```py
states = pd.DataFrame({'population': population,
                       'area': area})
states
```

|  | area | population |
| --- | --- | --- |
| California | 423967 | 38332521 |
| Florida | 170312 | 19552860 |
| Illinois | 149995 | 12882135 |
| New York | 141297 | 19651127 |
| Texas | 695662 | 26448193 |

就像``Series``对象一样，``DataFrame``有一个``index``属性，可以访问索引标签：

```py
states.index

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

另外，``DataFrame``有``columns``属性，它是一个包含列标签的``Index``对象：

```py
states.columns

# Index(['area', 'population'], dtype='object')
```

因此，``DataFrame``可以认为是二维 NumPy 数组的扩展，其中行和列都具有用于访问数据的通用索引。

### 作为特化字典的`DataFrame`

同样，我们也可以将``DataFrame``视为字典的特化。
字典将键映射到值，`DataFrame`将列名称映射到列数据的`Series`。例如，要求``'area'``属性返回``Series``对象，包含我们之前看到的面积：

```py
states['area']

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

注意这里潜在的混淆点：在一个二维 NumPy 数组中，``data[0]``将返回第一行。对于``DataFrame``，``data ['col0']``将返回第一列。因此，最好将`DataFrame`视为扩展的字典而不是扩展的数组，尽管两种看待这个情况的方式都是实用的。我们将在“数据索引和选择”中，探索更灵活的索引`DataFrame`的方法。

### 构造`DataFrame`对象

Pandas ``DataFrame``可以通过多种方式构建。这里我们举几个例子。

#### 来自单个`Series`对象

``DataFrame``是``Series``对象的集合，单列``DataFrame``可以从单个``Series``构造：

```py
pd.DataFrame(population, columns=['population'])
```

|  | population |
| --- | --- |
| California | 38332521 |
| Florida | 19552860 |
| Illinois | 12882135 |
| New York | 19651127 |
| Texas | 26448193 |

#### 来自字典的列表

任何字典列表都可以制作成`DataFrame`。我们将使用简单的列表推导来创建一些数据：

```py
data = [{'a': i, 'b': 2 * i}
        for i in range(3)]
pd.DataFrame(data)
```

|  | a | b |
| --- | --- | --- |
| 0 | 0 | 0 |
| 1 | 1 | 2 |
| 2 | 2 | 4 |

即使字典中的某些键丢失，Pandas 也会用`NaN`（即“非数字”）值填充它们：

```py
pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])
```

|  | a | b | c |
| --- | --- | --- | --- |
| 0 | 1.0 | 2 | NaN |
| 1 | NaN | 3 | 4.0 |

#### 来自序列对象的字典

正如我们之前看到的那样，``DataFrame``也可以从``Series``对象的字典构造：

```py
pd.DataFrame({'population': population,
              'area': area})
```

|  | area | population |
| --- | --- | --- |
| California | 423967 | 38332521 |
| Florida | 170312 | 19552860 |
| Illinois | 149995 | 12882135 |
| New York | 141297 | 19651127 |
| Texas | 695662 | 26448193 |

#### 来自二维 NumPy 数组

给定一个二维数据数组，我们可以创建一个``DataFrame``，带有任何指定列和索引名称。如果省略，将为每个使用整数索引：

```py
pd.DataFrame(np.random.rand(3, 2),
             columns=['foo', 'bar'],
             index=['a', 'b', 'c'])
```

|  | foo | bar |
| --- | --- | --- |
| a | 0.865257 | 0.213169 |
| b | 0.442759 | 0.108267 |
| c | 0.047110 | 0.905718 |

#### 来自 NumPy 结构化数组

我们在“结构化数据：NumPy 的结构化数组”：中介绍了结构化数组。Pandas ``DataFrame``的原理与结构化数组非常相似，可以直接从它创建：

```py
A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')])
A

'''
array([(0, 0.0), (0, 0.0), (0, 0.0)], 
      dtype=[('A', '<i8'), ('B', '<f8')])
'''

pd.DataFrame(A)
```

|  | A | B |
| --- | --- | --- |
| 0 | 0 | 0.0 |
| 1 | 0 | 0.0 |
| 2 | 0 | 0.0 |

## Pandas 索引对象

我们在这里看到，``Series``和``DataFrame``对象都包含显式的索引，它允许你引用和修改数据。这个``Index``对象本身就是一个有趣的结构，它可以认为是不可变数组或有序集合（技术上是一个多值集合，因为``Index``对象可能包含重复的值）。

这些观点在``Index``对象所提供的操作中，有一些有趣的结果。举个简单的例子，让我们从整数列表构造一个``Index``：

```py
ind = pd.Index([2, 3, 5, 7, 11])
ind

# Int64Index([2, 3, 5, 7, 11], dtype='int64')
```

### 作为不可变数组的索引

``Index``在很多方面都像数组一样。例如，我们可以使用标准的 Python 索引表示法来检索值或切片：

```py
ind[1]

# 3

ind[::2]

# Int64Index([2, 5, 11], dtype='int64')
```

`Index``对象也有许多来自 NumPy 数组的熟悉的属性：

```py
print(ind.size, ind.shape, ind.ndim, ind.dtype)

# 5 (5,) 1 int64
```

``Index``对象和NumPy数组之间的一个区别是，索引是不可变的 - 也就是说，它们不能通过常规方式修改：

```py
ind[1] = 0

'''
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-34-40e631c82e8a> in <module>()
----> 1 ind[1] = 0


/Users/jakevdp/anaconda/lib/python3.5/site-packages/pandas/indexes/base.py in __setitem__(self, key, value)
   1243 
   1244     def __setitem__(self, key, value):
-> 1245         raise TypeError("Index does not support mutable operations")
   1246 
   1247     def __getitem__(self, key):


TypeError: Index does not support mutable operations
'''
```

这种不变性使得，在多个`DataFrame`和数组之间共享索引更安全，避免了由无意的索引修改而导致的潜在的副作用。

### 作为有序集合的索引

Pandas 对象旨在促进一些操作，例如跨数据集的连接，这取决于集合运算的许多方面。``Index``对象遵循 Python 内置的``set``数据结构使用的许多约定，因此可以用熟悉的方式计算并集，交集，差集和其他组合：

```py
indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])

indA & indB  # 交集

# Int64Index([3, 5, 7], dtype='int64')

indA | indB  # 并集

# Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')

indA ^ indB  # 对称差集

# Int64Index([1, 2, 9, 11], dtype='int64')
```

这些操作也可以通过对象方法访问，例如``indiA.intersection(imdB)``。
