# 7.9 组合数据集：连接和附加

> 原文：[Combining Datasets: Concat and Append](http://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/pandas/03.06-Concat-And-Append.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）的摘录。

一些最有趣的数据研究来自于不同的数据源的组合。这些操作可能涉及，从两个不同数据集的非常简单的连接，到更复杂的数据库风格的连接和合并，来正确处理数据集之间的任何重叠。``Series``和``DataFrame``是考虑到这类的操作而构建的，而 Pandas 包含的函数和方法使得这种数据整理变得快速而直接。

在这里，我们将使用``pd.concat``函数的，看一下``Series``和``DataFrame``的简单连接；稍后我们将深入研究 Pandas 中实现的内存中的更复杂的合并和连接。

我们从标准导入开始：

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

为方便起见，我们将定义这个函数，该函数创建一个特定形式的``DataFrame``，它将在下面有用：

```py
def make_df(cols, ind):
    """Quickly make a DataFrame"""
    data = {c: [str(c) + str(i) for i in ind]
            for c in cols}
    return pd.DataFrame(data, ind)

# 示例数据帧
make_df('ABC', range(3))
```

|  | A | B | C |
| --- | --- | --- | --- |
| 0 | A0 | B0 | C0 |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |

另外，我们将创建一个简单的类，允许我们并排显示多个`DataFrame`。代码使用了特殊的``_repr_html_``方法，IPython 使用该方法来实现其丰富的对象显示：

```py
class display(object):
    """Display HTML representation of multiple objects"""
    template = """<div style="float: left; padding: 10px;">
    <p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
    </div>"""
    def __init__(self, *args):
        self.args = args
        
    def _repr_html_(self):
        return '\n'.join(self.template.format(a, eval(a)._repr_html_())
                         for a in self.args)
    
    def __repr__(self):
        return '\n\n'.join(a + '\n' + repr(eval(a))
                           for a in self.args)
    
```

我们在下一节继续讨论时，这个函数的用法将变得更加清晰。

## 回忆：NumPy 数组的连接

``Series``和``DataFrame``对象的连接非常类似于 Numpy 数组的连接，这可以通过``np.concatenate``函数来完成，如[“NumPy 数组的基础知识”中所述。回想一下，使用它，你可以将两个或多个数组的内容组合到一个数组中：

```py
x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
np.concatenate([x, y, z])

# array([1, 2, 3, 4, 5, 6, 7, 8, 9])
```

第一个参数是要连接的数组的列表或元组。此外，它需要一个``axis``关键字，允许你指定沿着它连接结果的轴：

```py
x = [[1, 2],
     [3, 4]]
np.concatenate([x, x], axis=1)

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

## 使用``pd.concat``的简单连接

Pandas 拥有函数``pd.concat()``，它的语法与``np.concatenate``类似，但是包含了一些我们将要讨论的选项：

```py
# Pandas v0.18 中的签名
pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
          keys=None, levels=None, names=None, verify_integrity=False,
          copy=True)
```

``pd.concat()``可以用于``Series``或``DataFrame``对象的简单连接，就像``np.concatenate()``可以用于简单的数组连接：

```py
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1, ser2])

'''
1    A
2    B
3    C
4    D
5    E
6    F
dtype: object
'''
```

它还可以连接更高维的对象，例如``DataFrame``：

```py
df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
display('df1', 'df2', 'pd.concat([df1, df2])')
```

`df1`：

|  | A | B |
| --- | --- | --- |
| 1 | A1 | B1 |
| 2 | A2 | B2 |

`df2`：

|  | A | B |
| --- | --- | --- |
| 3 | A3 | B3 |
| 4 | A4 | B4 |

`pd.concat([df1, df2])`：

|  | A | B |
| --- | --- | --- |
| 1 | A1 | B1 |
| 2 | A2 | B2 |
| 3 | A3 | B3 |
| 4 | A4 | B4 |


默认情况下，连接在``DataFrame``中逐行进行（即``axis = 0``）。就像``np.concatenate``一样，``pd.concat``允许指定一个轴，沿着该轴进行连接。请考虑以下示例：

```py
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display('df3', 'df4', "pd.concat([df3, df4], axis='col')")
```

`df3`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |

`df4`：

|  | C | D |
| --- | --- | --- |
| 0 | C0 | D0 |
| 1 | C1 | D1 |

`pd.concat([df3, df4], axis='col')`：


|  | A | B | C | D |
| --- | --- | --- | --- | --- |
| 0 | A0 | B0 | C0 | D0 |
| 1 | A1 | B1 | C1 | D1 |


与之等价，我们可以指定``axis = 1``；在这里，我们使用了更直观的``axis ='col'``。

### 重复的索引

``np.concatenate``和``pd.concat``之间的一个重要区别是，Pandas 的连接保留了索引，即使结果会有重复的索引！考虑这个简单的例子：

```py
x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index  # 复制索引
display('x', 'y', 'pd.concat([x, y])')
```

`x`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |

`y`：

|  | A | B |
| --- | --- | --- |
| 0 | A2 | B2 |
| 1 | A3 | B3 |


`pd.concat([x, y])`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |
| 0 | A2 | B2 |
| 1 | A3 | B3 |


注意结果中的重复索引。虽然这在``DataFrame``中有效，但结果通常是不合需要的。``pd.concat()``为我们提供了一些处理它的方法。

#### 将重复捕获为错误

如果你想简单地验证，``pd.concat()``结果中的索引不重叠，你可以指定``verify_integrity``标志。将此设置为`True`，如果存在重复索引，则连接将引发异常。这是一个示例，为清楚起见，我们将捕获并打印错误消息：

```py
try:
    pd.concat([x, y], verify_integrity=True)
except ValueError as e:
    print("ValueError:", e)
    
'''
ValueError: Indexes have overlapping values: [0, 1]
'''
```

#### 忽略索引

有时索引本身无关紧要，你宁愿忽略它。可以使用``ignore_index``标志指定此选项。将此设置为`True`，连接将为生成的``Series``创建一个新的整数索引：

```py
display('x', 'y', 'pd.concat([x, y], ignore_index=True)')
```

`x`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |

`y`：

|  | A | B |
| --- | --- | --- |
| 0 | A2 | B2 |
| 1 | A3 | B3 |

`pd.concat([x, y], ignore_index=True)`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |
| 2 | A2 | B2 |
| 3 | A3 | B3 |


#### 添加`MultiIndex`的键

另一种选择是使用``keys``选项为数据源指定标签；结果将是包含数据的分层索引的序列：

```py
display('x', 'y', "pd.concat([x, y], keys=['x', 'y'])")
```

`x`：

|  | A | B |
| --- | --- | --- |
| 0 | A0 | B0 |
| 1 | A1 | B1 |

`y`：

|  | A | B |
| --- | --- | --- |
| 0 | A2 | B2 |
| 1 | A3 | B3 |

`pd.concat([x, y], keys=['x', 'y'])`：

|  |  | A | B |
| --- | --- | --- | --- |
| x | 0 | A0 | B0 |
| 1 | A1 | B1 |
| y | 0 | A2 | B2 |
| 1 | A3 | B3 |


结果是一个多重索引的``DataFrame``，我们可以使用“分层索引”中讨论的工具，将这些数据转换成我们感兴趣的表示。

### 使用`join`的连接

在我们刚看到的简单示例中，我们主要使用共享列名来连接``DataFrame``。实际上，来自不同来源的数据可能具有不同的列名称集，而``pd.concat``在这种情况下提供了几个选项。

考虑以下两个``DataFrame`的连接，它们有一些共同的列（但不是全部！）：

```py
df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
display('df5', 'df6', 'pd.concat([df5, df6])')
```

`df5`：

|  | A | B | C |
| --- | --- | --- | --- |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |

`df6`：

|  | B | C | D |
| --- | --- | --- | --- |
| 3 | B3 | C3 | D3 |
| 4 | B4 | C4 | D4 |

`pd.concat([df5, df6])`：

|  | A | B | C | D |
| --- | --- | --- | --- | --- |
| 1 | A1 | B1 | C1 | NaN |
| 2 | A2 | B2 | C2 | NaN |
| 3 | NaN | B3 | C3 | D3 |
| 4 | NaN | B4 | C4 | D4 |


默认情况下，没有数据可用的条目将填充 NA 值。要改变它，我们可以为`concatenate`函数的``join``和``join_axes``参数指定几个选项之一。默认情况下，连接是输入列的并集（``join ='outer'``），但我们可以使用``join ='inner'``将其更改为列的交集：

```py
display('df5', 'df6',
        "pd.concat([df5, df6], join='inner')")
```

`df5`：

|  | A | B | C |
| --- | --- | --- | --- |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |

`df6`：

|  | B | C | D |
| --- | --- | --- | --- |
| 3 | B3 | C3 | D3 |
| 4 | B4 | C4 | D4 |

`pd.concat([df5, df6], join='inner')`：

|  | B | C |
| --- | --- | --- |
| 1 | B1 | C1 |
| 2 | B2 | C2 |
| 3 | B3 | C3 |
| 4 | B4 | C4 |


另一种选择是，使用``join_axes``参数直接指定保留的列的索引，该参数接受索引对象列表。这里我们指定，返回的列应该与第一个输入的列相同：

```py
display('df5', 'df6',
        "pd.concat([df5, df6], join_axes=[df5.columns])")
```

`df5`：

|  | A | B | C |
| --- | --- | --- | --- |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |

`df6`：

|  | B | C | D |
| --- | --- | --- | --- |
| 3 | B3 | C3 | D3 |
| 4 | B4 | C4 | D4 |

`pd.concat([df5, df6], join_axes=[df5.columns])`：

|  | A | B | C |
| --- | --- | --- | --- |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |
| 3 | NaN | B3 | C3 |
| 4 | NaN | B4 | C4 |


在连接两个数据集时，``pd.concat``函数的选项组合，允许各种可能的行为；将这些工具用于你自己的数据时，请记住这些。

### ``append()``方法

因为直接的数组连接是如此常见，``Series``和``DataFrame``对象有``append``方法，可以用更少的打字完成同样的事情。例如，不是调用``pd.concat([df1, df2])``，而是简单地调用``df1.append(df2)``：

```py
display('df1', 'df2', 'df1.append(df2)')
```

`df1`：

|  | A | B |
| --- | --- | --- |
| 1 | A1 | B1 |
| 2 | A2 | B2 |

`df2`：

|  | A | B |
| --- | --- | --- |
| 3 | A3 | B3 |
| 4 | A4 | B4 |

`df1.append(df2)`：

|  | A | B |
| --- | --- | --- |
| 1 | A1 | B1 |
| 2 | A2 | B2 |
| 3 | A3 | B3 |
| 4 | A4 | B4 |


请记住，与Python列表的``append()``和``extend()``方法不同，Pandas 中的``append()``方法不会修改原始对象 - 而是创建一个新对象，带有组合的数据。

它也不是一种非常有效的方法，因为它涉及创建新的索引和数据缓冲区。因此，如果你计划进行多次``append``操作，通常最好建立一个``DataFrame``列表并将它们全部传递给``concat()``函数。

在下一节中，我们将介绍另一种更强大的方法，来组合来自多个源的数据，即``pd.merge``中实现的数据库风格的合并/连接。对于``concat()``，``append()``及相关函数的更多信息，请参阅“合并，连接（Join）和连接（Concatenate）”一节。