欢乐时光总是过的特别快,清明小长假一转眼就过去了。而我还是一身懒气未脱,没办法,该干的活总是还要干。否则逆水行舟,不进则退,那就太不值得了。

同时我还发现致敬大神的视频其实是出到第八章的,好吧,看来神仔还能再陪我走一段。

那么,开始吧。

8.1 分层索引

分层索引是pandas的重要特性,允许你在一个轴向上拥有多个(两个或两个以上)索引层级。笼统地说,分层索引提供了一种在更低维度的形式中处理更高维度数据的方式。让我们以一个简单的例子开始,先创建一个Series,以列表的列表(或数组)作为索引:

In [223]: data = pd.Series(np.random.randn(9),index=[['a','a','a','b','b','c','c','d','d'],[1,2,3,1,3,1,2,2,3]])

In [224]: data
Out[224]: 
a  1    1.007189
   2   -1.296221
   3    0.274992
b  1    0.228913
   3    1.352917
c  1    0.886429
   2   -2.001637
d  2   -0.371843
   3    1.669025
dtype: float64

你看到的是一个以MultiIndex作为索引的Series的美化视图。索引中的“间隙”表示“直接使用上面的标签”:
In [225]: data.index

这里出现了一个问题,在课本上,显示data.index的结果为:

In [11]: data.index
Out[11]: 
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])

但实际上,我自己输出的结果是:

In [228]: data.index
Out[228]: 
MultiIndex([('a', 1),
            ('a', 2),
            ('a', 3),
            ('b', 1),
            ('b', 3),
            ('c', 1),
            ('c', 2),
            ('d', 2),
            ('d', 3)],
           )

我也百度了一下,各种读书笔记、视频中是两种情况都有。具体为什么,好像大家都没有说。我尝试了jupyter和ipython两种工具,似乎都只能输出我上面的这种格式。原因未知。

那么没办法,我们只能继续了。

通过分层索引对象,也可以称为部分索引,允许你简洁地选择出数据的子集:
In [229]: data['b']
Out[229]: 
1    0.228913
3    1.352917
dtype: float64

In [230]: data['b':'c']
Out[230]: 
b  1    0.228913
   3    1.352917
c  1    0.886429
   2   -2.001637
dtype: float64

In [231]: data.loc[['b','d']]
Out[231]: 
b  1    0.228913
   3    1.352917
d  2   -0.371843
   3    1.669025
dtype: float64

在“内部”层级中进行选择也是可以的:
In [232]: data.loc[:,2]
Out[232]: 
a   -1.296221
c   -2.001637
d   -0.371843
dtype: float64

分层索引在重塑数据和数组透视表等分组操作中扮演了重要角色。例如,你可以使用unstack方法将数据在DataFrame中重新排列:
In [233]: data.unstack()
Out[233]: 
          1         2         3
a  1.007189 -1.296221  0.274992
b  0.228913       NaN  1.352917
c  0.886429 -2.001637       NaN
d       NaN -0.371843  1.669025

unstack的反操作是stack:
In [234]: data.unstack().stack()
Out[234]: 
a  1    1.007189
   2   -1.296221
   3    0.274992
b  1    0.228913
   3    1.352917
c  1    0.886429
   2   -2.001637
d  2   -0.371843
   3    1.669025
dtype: float64

stack和unstack 的细节将在本章后续部分进行探索。

在DataFrame中,每个轴都可以拥有分层索引:
In [237]: frame = pd.DataFrame(np.arange(12).reshape((4,3)),index=[['a','a','b','b'],[1,2,1,2]],columns=[['Ohio','Ohio','Colorado'
     ...: ],['Green','Red','Green']])

In [238]: frame
Out[238]: 
     Ohio     Colorado
    Green Red    Green
a 1     0   1        2
  2     3   4        5
b 1     6   7        8
  2     9  10       11

分层的层级可以有名称(可以是字符串或Python对象)。如果层级有名称,这些名称会在控制台输出中显示:
In [243]: frame.index.names=['key1','key2']

In [244]: frame.columns.names=['state','color']

In [245]: frame
Out[245]: 
state      Ohio     Colorado
color     Green Red    Green
key1 key2                   
a    1        0   1        2
     2        3   4        5
b    1        6   7        8
     2        9  10       11

请注意区分分行标签中的索引名称‘state’和‘color’。

通过部分列索引,你可以选出列中的组:
In [285]: frame['Ohio']
Out[285]: 
color      Green  Red
key1 key2            
a    1         0    1
     2         3    4
b    1         6    7
     2         9   10

一个MultiIndex对象可以使用其自身的构造函数创建并复用。前面介绍的带有层级名称的DataFrame 的列可以这样创建:
In [292]: MultiIndex.from_arrays([['Ohio','Ohio','Colorado'],['Green','Red','Green']],names=['state','color'])

这里又出问题了,按照这么输入,会报错:

In [292]: MultiIndex.from_arrays([['Ohio','Ohio','Colorado'],['Green','Red','Green']],names=['state','color'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-292-1e7fbe28e171> in <module>
----> 1 MultiIndex.from_arrays([['Ohio','Ohio','Colorado'],['Green','Red','Green']],names=['state','color'])

NameError: name 'MultiIndex' is not defined

我也百度了下,如果在前面增加pd.,似乎可以达到效果:

In [303]: pd.MultiIndex.from_arrays([['Ohio','Ohio','Colorado'],['Green','Red','Green']],
     ...:                        names=['state','color'])
Out[303]: 
MultiIndex([(    'Ohio', 'Green'),
            (    'Ohio',   'Red'),
            ('Colorado', 'Green')],
           names=['state', 'color'])

至于原因嘛,老样子,别问,问就是不知道。

8.1.1 重排序和层级排序

有时,你需要重新排列轴上的层级顺序,或者按照特定层级的值对数据进行排序。swaplevel接收两个层级序号或层级名称,返回一个进行了层级变更的新对象(但是数据是不变的):

In [304]: frame.swaplevel('key1','key2') # 将‘key2’与‘key1’互换位置
Out[304]: 
state      Ohio     Colorado
color     Green Red    Green
key2 key1                   
1    a        0   1        2
2    a        3   4        5
1    b        6   7        8
2    b        9  10       11

另一方面,sort_index只能在单一层级上对数据进行排序。在进行层级变换时,使用sort_index以使得结果按照层级进行字典排序也很常见:
In [305]: frame.sort_index(level=1) # 0和1是按照顺序排列的,在下列数据中第一个是key1,所以是0。第二个是key2,就是1。
Out[305]: 
state      Ohio     Colorado
color     Green Red    Green
key1 key2                   
a    1        0   1        2
b    1        6   7        8
a    2        3   4        5
b    2        9  10       11

In [306]: frame.swaplevel(0,1).sort_index(level=0) # 这里组合了一下,首先0和1互换,也就是key2在前,key1在后。然后根据0的位置,也就是key2进行排序。

Out[306]: 
state      Ohio     Colorado
color     Green Red    Green
key2 key1                   
1    a        0   1        2
     b        6   7        8
2    a        3   4        5
     b        9  10       11

如果索引按照字典顺序从最外层开始排序,那么数据选择性能会更好——调用sort_index(level=0)或sort_index可以得到这样的结果。

8.1.3 使用DataFrame的列进行索引

通常,我们不会使用DataFrame中的一个或多个列作为行索引;反而你可能想要将行索引移动到DataFrame的列中。下面是一个示例DataFrame:

In [317]: frame = pd.DataFrame({'a':range(7),'b':range(7,0,-1),'c':['one','one','one','two','two','two','two'],'d':[0,1,2,0,1,2,3]
     ...: })

In [318]: frame
Out[318]: 
   a  b    c  d
0  0  7  one  0
1  1  6  one  1
2  2  5  one  2
3  3  4  two  0
4  4  3  two  1
5  5  2  two  2
6  6  1  two  3

DataFrame的set_index函数会生成一个新的DataFrame,新的DataFrame使用一个或多个列作为索引:
In [319]: frame2 = frame.set_index(['c','d']) # 将‘c’和‘d’作为列索引

In [320]: frame2
Out[320]: 
       a  b
c   d      
one 0  0  7
    1  1  6
    2  2  5
two 0  3  4
    1  4  3
    2  5  2
    3  6  1

默认情况下,这些列会从DataFrame中移除,你也可以将它们留在DataFrame中:
In [321]: frame.set_index(['c','d'],drop=False)
Out[321]: 
       a  b    c  d
c   d              
one 0  0  7  one  0
    1  1  6  one  1
    2  2  5  one  2
two 0  3  4  two  0
    1  4  3  two  1
    2  5  2  two  2
    3  6  1  two  3

另一方面,reset_index是set_index的反操作,分层索引的索引层级会被移动到列中:
In [322]: frame2.reset_index()
Out[322]: 
     c  d  a  b
0  one  0  0  7
1  one  1  1  6
2  one  2  2  5
3  two  0  3  4
4  two  1  4  3
5  two  2  5  2
6  two  3  6  1

8.2 联合与合并数据集

包含在pandas对象的数据可以通过多种方式联合在一起:

1、pandas.merge根据一个或多个键将行进行连接。对于SQL或其他关系型数据库的用户来说,这种方式比较熟悉,它实现的是数据库的连接操作。

2、pandas.concat使对象在轴向上进行黏合或‘堆叠’。

3、combine_first实例方法允许将重叠的数据拼接在一起,以使用一个对象中的值填充另一个对象中的缺失值。

8.2.1 数据库风格的DataFrame连接

合并或连接操作通过一个或多个键连接行来联合数据集。这些操作是关系型数据库的核心内容(例如基于SQL的数据库)。pandas中的merge函数主要用于将各种join操作算法运用在你的数据上:

In [323]: df1 = pd.DataFrame({'key':['b','b','a','c','a','a','b'],'data1':range(7)})

In [324]: df2 = pd.DataFrame({'key':['a','b','d'],'data2':range(3)})

In [325]: df1
Out[325]: 
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   a      5
6   b      6

In [326]: df2
Out[326]: 
  key  data2
0   a      0
1   b      1
2   d      2

这是一个多对一连接的例子;df1的数据有多个行的标签为a和b,而df2在key列中每个值仅有一行。调用merge处理我们获得的对象:
In [327]: pd.merge(df1,df2) #
Out[327]: 
  key  data1  data2
0   b      0      1
1   b      1      1
2   b      6      1
3   a      2      0
4   a      4      0
5   a      5      0

请注意,我并没有指定在哪一列上进行连接。如果连接的键信息没有指定,merge会自动将重叠列名作为连接的键。但是,显式地指定连接键才是好的实现:
In [328]: pd.merge(df1,df2,on='key')
Out[328]: 
  key  data1  data2
0   b      0      1
1   b      1      1
2   b      6      1
3   a      2      0
4   a      4      0
5   a      5      0

这里要说明一下,因为可能不是太好理解。首先,当df1和df2联合之后产生的新数据,排列是将重叠列名为‘键’(或直接指定‘key’)。新数据里,key是将df1和df2里重叠里的数据调出来,df1和df2中的key列,第一个重叠的字幕是b(df1的第二行和df2的第二行),所以先把b拿出来排列。

data1的第一个数字0,就是df1中第一个b对应data1的数据。1就是第二个b对应的data1的数据,以此类推。

data2中因为只有一个b,所以无论出现几个b,对应的都是原数据(df2)中的b的对应数据,也即1。

如果每个对象的列名是不同的,你可以分别为它们指定列名:
In [329]: df3 = pd.DataFrame({'lkey':['b','b','a','c','a','a','b'],'data1':range(7)})

In [330]: df4 = pd.DataFrame({'rkey':['a','b','d'],'data2':range(3)})

In [331]: pd.merge(df3,df4,left_on='lkey',right_on='rkey')
Out[331]: 
  lkey  data1 rkey  data2
0    b      0    b      1
1    b      1    b      1
2    b      6    b      1
3    a      2    a      0
4    a      4    a      0
5    a      5    a      0

Ni可能注意到结果中缺少‘c’和‘d’的值以及相关的数据。默认情况下,merge做的是内连接(‘inner’join),结果中的键是两张表的交集。其他可选的选项有‘left’、‘right’和‘outer’。外连接(outer join)是键的并集,联合了左连接和右连接的效果:
In [332]: pd.merge(df1,df2,how='outer')
Out[332]: 
  key  data1  data2
0   b    0.0    1.0
1   b    1.0    1.0
2   b    6.0    1.0
3   a    2.0    0.0
4   a    4.0    0.0
5   a    5.0    0.0
6   c    3.0    NaN
7   d    NaN    2.0

how参数的不同连接类型

‘inner’:只对两张表都有的键的交集进行联合

‘left’:对所有左表的键进行联合

‘right’:对所有右表的键进行联合

‘outer’:对两张表都有的键的并集进行联合

尽管不是很直观,但多对多的合并有明确的行为。下面是一个例子:
In [333]: df1 = pd.DataFrame({'key':['b','b','a','c','a','b'],'data1':range(6)})

In [334]: df2 = pd.DataFrame({'key':['a','b','a','b','d'],'data2':range(5)})

In [335]: df1
Out[335]: 
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   b      5

In [336]: df2
Out[336]: 
  key  data2
0   a      0
1   b      1
2   a      2
3   b      3
4   d      4

In [337]: pd.merge(df1,df2,on='key',how='left')
Out[337]: 
   key  data1  data2
0    b      0    1.0
1    b      0    3.0
2    b      1    1.0
3    b      1    3.0
4    a      2    0.0
5    a      2    2.0
6    c      3    NaN
7    a      4    0.0
8    a      4    2.0
9    b      5    1.0
10   b      5    3.0

多对多连接是行的笛卡尔积。由于在左边的DataFrame中有三个‘b’行,而在右边有两行,因此在结果中有六个‘b’。连接方法仅影响结果中显示的不同键值:
In [338]: pd.merge(df1,df2,how='inner')
Out[338]: 
  key  data1  data2
0   b      0      1
1   b      0      3
2   b      1      1
3   b      1      3
4   b      5      1
5   b      5      3
6   a      2      0
7   a      2      2
8   a      4      0
9   a      4      2

使用多个键进行合并时,传入一个列名的列表:
In [343]: left = pd.DataFrame({'key1':['foo','foo','bar'],'key2':['one','two','one'],'lval':[1,2,3]})

In [344]: right = pd.DataFrame({'key1':['foo','foo','bar','bar'],'key2':['one','one','one','two'],'rval':[4,5,6,7]})

In [345]: pd.merge(left,right,on=['key1','key2'],how='outer')
Out[345]: 
  key1 key2  lval  rval
0  foo  one   1.0   4.0
1  foo  one   1.0   5.0
2  foo  two   2.0   NaN
3  bar  one   3.0   6.0
4  bar  two   NaN   7.0

要决定哪些键联合出现在结果中,取决于合并方法的选择,把多个键看做一个元组数据来作为单个连接键使用(尽管实际上并不是以这种方式来实现的)。

当你在进行列-列链接时,传递的DataFrame索引对象会被丢弃。

合并操作中最后一个要考虑的问题是如何处理重叠的列名。虽然你可以手动解决重叠问题,但是merge有一个suffixes后缀选项,用于在左右两边DataFrame对象的重叠列名后指定需要添加的字符串:
In [346]: pd.merge(left,right,on='key1')
Out[346]: 
  key1 key2_x  lval key2_y  rval
0  foo    one     1    one     4
1  foo    one     1    one     5
2  foo    two     2    one     4
3  foo    two     2    one     5
4  bar    one     3    one     6
5  bar    one     3    two     7

In [347]: pd.merge(left,right,on='key1',suffixes=('_left','_right'))
Out[347]: 
  key1 key2_left  lval key2_right  rval
0  foo       one     1        one     4
1  foo       one     1        one     5
2  foo       two     2        one     4
3  foo       two     2        one     5
4  bar       one     3        one     6
5  bar       one     3        two     7

这里又需要说明一下。

我们拿上面的一段代码来说明:

In [343]: left = pd.DataFrame({'key1':['foo','foo','bar'],'key2':['one','two','one'],'lval':[1,2,3]})

In [344]: right = pd.DataFrame({'key1':['foo','foo','bar','bar'],'key2':['one','one','one','two'],'rval':[4,5,6,7]})

In [345]: pd.merge(left,right,on=['key1','key2'],how='outer')
Out[345]: 
  key1 key2  lval  rval
0  foo  one   1.0   4.0
1  foo  one   1.0   5.0
2  foo  two   2.0   NaN
3  bar  one   3.0   6.0
4  bar  two   NaN   7.0

在代码中,首先是key1和key2的展示,为什么出现3个foo和两个bar呢?这个好理解,简单来说,就是笛卡尔积再排重。说人话,就是left中的key1和key2两个列表进行组合。key1是[‘foo’,’foo’,’bar’],key2是[‘one’,’two’,’one’],就形成了foo+one、foo+two、bar+one三种组合。

right里面也有这种组合:foo+one、foo+one、bar+one、bar+two。

那么开始并集联合(左右一模一样的就保留一个),就变成了foo+one、foo+one、foo+two、bar+one、bar+two五种组合了。

在key1中三种组合分别对应1,2,3,即foo+one=1,foo+two=2,bar+one=3。key2中也是这个逻辑。

最后形成了上面的代码。

8.2.2 根据索引合并

在某些情况下,DataFrame中用于合并的键是它的索引。在这种情况下,你可以传递left_index=True或right_index=True(或者都传)来表示索引需要用来作为合并的键:

In [348]: left
Out[348]: 
  key1 key2  lval
0  foo  one     1
1  foo  two     2
2  bar  one     3

In [349]: right
Out[349]: 
  key1 key2  rval
0  foo  one     4
1  foo  one     5
2  bar  one     6
3  bar  two     7

In [350]: left1=pd.DataFrame({'key':['a','b','a','a','b','c'],'value':range(6)})

In [351]: right1=pd.DataFrame({'group_val':[3.5,7]},index=['a','b'])

In [352]: left1
Out[352]: 
  key  value
0   a      0
1   b      1
2   a      2
3   a      3
4   b      4
5   c      5

In [353]: right1
Out[353]: 
   group_val
a        3.5
b        7.0

In [355]: pd.merge(left1,right1,left_on='key',right_index=True)
Out[355]: 
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0

由于默认的合并方法是连接键相交,你可以使用外连接来进行合并:
In [357]: pd.merge(left1,right1,left_on='key',right_index=True,how='outer')
Out[357]: 
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0
5   c      5        NaN

在多重索引数据的情况下,事情会更复杂,在索引上连接是一个隐式的多键合并:
In [365]: lefth = pd.DataFrame({'key1':['Ohio','Ohio','Ohio','Nevada','Nevada'],'key2':[2000,2001,2002,2001,2002],'data':np.arange
     ...: (5.)})

In [366]: righth = pd.DataFrame(np.arange(12).reshape((6,2)),index=[['Nevada','Nevada','Ohio','Ohio','Ohio','Ohio'],[2001,2000,200
     ...: 0,2000,2001,2002]],columns=['event1','event2'])

In [367]: lefth
Out[367]: 
     key1  key2  data
0    Ohio  2000   0.0
1    Ohio  2001   1.0
2    Ohio  2002   2.0
3  Nevada  2001   3.0
4  Nevada  2002   4.0

In [368]: righth
Out[368]: 
             event1  event2
Nevada 2001       0       1
       2000       2       3
Ohio   2000       4       5
       2000       6       7
       2001       8       9
       2002      10      11


这种情况下,你必须以列表的方式指明合并所需多个列(请注意使用how='outer'处理重复的索引值):
In [369]: pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True)
Out[369]: 
     key1  key2  data  event1  event2
0    Ohio  2000   0.0       4       5
0    Ohio  2000   0.0       6       7
1    Ohio  2001   1.0       8       9
2    Ohio  2002   2.0      10      11
3  Nevada  2001   3.0       0       1

In [370]: pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True,how='outer')
Out[370]: 
     key1  key2  data  event1  event2
0    Ohio  2000   0.0     4.0     5.0
0    Ohio  2000   0.0     6.0     7.0
1    Ohio  2001   1.0     8.0     9.0
2    Ohio  2002   2.0    10.0    11.0
3  Nevada  2001   3.0     0.0     1.0
4  Nevada  2002   4.0     NaN     NaN
4  Nevada  2000   NaN     2.0     3.0

使用两边的索引进行合并也是可以的:
In [371]: left2 = pd.DataFrame([[1.,2.],[3.,4.],[5.,6.]],index=['a','c','e'],columns=['Ohio','Nevada'])

In [372]: right2 = pd.DataFrame([[7.,8.],[9.,10.],[11.,12.],[13,14]],index=['b','c','d','e'],columns=['Missouri','Alabama'])

In [373]: left2
Out[373]: 
   Ohio  Nevada
a   1.0     2.0
c   3.0     4.0
e   5.0     6.0

In [374]: right2
Out[374]: 
   Missouri  Alabama
b       7.0      8.0
c       9.0     10.0
d      11.0     12.0
e      13.0     14.0

In [375]: pd.merge(left2,right2,how='outer',left_index=True,right_index=True)
Out[375]: 
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

DataFrame有一个方便的join实例方法,用于按照索引合并。该方法也可以用于合并多个索引相同或相似但没有重叠列的DataFrame对象。在之前的例子中,我们可以这样写:
In [376]: left2.join(right2,how='outer')
Out[376]: 
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

由于一些历史原因,DataFrame的join方法进行连接键上的左连接,完全保留左边DataFrame的行索引。它还支持在调用DataFrame的某一列上连接传递的DataFrame的索引:
In [377]: left1.join(right1,on='key')
Out[377]: 
  key  value  group_val
0   a      0        3.5
1   b      1        7.0
2   a      2        3.5
3   a      3        3.5
4   b      4        7.0
5   c      5        NaN

最后,对于一些简单索引-索引合并,你可以像join方法传入一个DataFrame列表,这个方法可以替代下一节中将要介绍的使用更为通用的concat函数的方法:
In [383]: left2.join([right2,aother])
Out[383]: 
   Ohio  Nevada  Missouri  Alabama  New York  Oregon
a   1.0     2.0       NaN      NaN       7.0     8.0
c   3.0     4.0       9.0     10.0       9.0    10.0
e   5.0     6.0      13.0     14.0      11.0    12.0

In [384]: left2.join([right2,aother],how='outer')
Out[384]: 
   Ohio  Nevada  Missouri  Alabama  New York  Oregon
a   1.0     2.0       NaN      NaN       7.0     8.0
c   3.0     4.0       9.0     10.0       9.0    10.0
e   5.0     6.0      13.0     14.0      11.0    12.0
b   NaN     NaN       7.0      8.0       NaN     NaN
d   NaN     NaN      11.0     12.0       NaN     NaN
f   NaN     NaN       NaN      NaN      16.0    17.0

8.2.3 沿轴向连接

另一种数据组合操作可互换地称为拼接、绑定或堆叠。Numpy的concatenate函数可以在numpy数组上实现该功能:

In [385]: arr = np.arange(12).reshape((3,4))

In [386]: arr
Out[386]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [387]: np.concatenate([arr,arr],axis=1)
Out[387]: 
array([[ 0,  1,  2,  3,  0,  1,  2,  3],
       [ 4,  5,  6,  7,  4,  5,  6,  7],
       [ 8,  9, 10, 11,  8,  9, 10, 11]])

在Series和DataFrame等pandas对象的上下文中,使用标记的轴可以进一步泛化数组连接。尤其是你还有许多需要考虑的事情:

1.如果对象在其他轴上的索引不同,我们是否应该将不同的元素组合在这些轴上,还是只是用共享的值(交集)?
2.连接的数据块是否需要在结果对象中被识别?
3.“连接轴”是否包含需要保存的数据?在许多情况下,DataFrame中的默认整数标签在连接期间最好丢弃。

pandas的concat函数提供了一种一致性的方式来解决以上问题。我将给出一些例子来表明它的工作机制。假设我们有三个索引不存在重叠的Series:
In [388]: s1 = pd.Series([0,1],index=['a','b'])

In [389]: s2 = pd.Series([2,3,4,],index=['c','d','e'])

In [390]: s3 = pd.Series([5,6],index=['f','g'])

用列表中的这些对象调用concat方法将值和索引粘在一起:
In [391]: pd.concat([s1,s2,s3])
Out[391]: 
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

默认情况下,concat方法是沿着axis=0的轴向生效的,生成另一个Series。如果你传递axis=1,返回的结果则是一个DataFrame(axis=1时是列):
In [392]: pd.concat([s1,s2,s3],axis=1)
Out[392]: 
     0    1    2
a  0.0  NaN  NaN
b  1.0  NaN  NaN
c  NaN  2.0  NaN
d  NaN  3.0  NaN
e  NaN  4.0  NaN
f  NaN  NaN  5.0
g  NaN  NaN  6.0

在这个案例中另一个轴向上并没有重叠,你可以看到排序后的索引合集(‘outer’ join外连接)。你也可以传入join=‘inner’:
In [395]: s4 = pd.concat([s1,s3])

In [396]: s4
Out[396]: 
a    0
b    1
f    5
g    6
dtype: int64

In [397]: pd.concat([s1,s4],axis=1)
Out[397]: 
     0  1
a  0.0  0
b  1.0  1
f  NaN  5
g  NaN  6

In [398]: pd.concat([s1,s4],axis=1,join='inner')
Out[398]: 
   0  1
a  0  0
b  1  1

在这个例子中,由于join = 'inner'的选项,‘f’和‘g’标签消失了。

你甚至可以使用join_axes来指定用于连接其他轴向的轴:
In [405]: pd.concat([s1,s4],axis=1,join_axes=[['a','c','b','e']])

到这步又报错了:

In [404]: pd.concat(join=ax)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-404-e76df5927a92> in <module>
----> 1 pd.concat(join=ax)

NameError: name 'ax' is not defined

In [405]: pd.concat([s1,s4],axis=1,join_axes=[['a','c','b','e']])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-405-633623e93fb8> in <module>
----> 1 pd.concat([s1,s4],axis=1,join_axes=[['a','c','b','e']])

TypeError: concat() got an unexpected keyword argument 'join_axes'

原因是因为没有join_axes这个关键字,原因未知。

拼接在一起的各部分无法在结果中区分是一个潜在的问题。假设你想在连接轴向上创建一个多层索引,可以使用kes参数来实现:
In [406]: result = pd.concat([s1,s1,s3],keys=['one','two','three'])

In [407]: result
Out[407]: 
one    a    0
       b    1
two    a    0
       b    1
three  f    5
       g    6
dtype: int64

In [408]: result.unstack()
Out[408]: 
         a    b    f    g
one    0.0  1.0  NaN  NaN
two    0.0  1.0  NaN  NaN
three  NaN  NaN  5.0  6.0

沿着轴向axis=1连接Series的时候,keys则成为DataFrame的列头:
In [409]: pd.concat([s1,s2,s3],axis=1,keys=['one','two','three'])
Out[409]: 
   one  two  three
a  0.0  NaN    NaN
b  1.0  NaN    NaN
c  NaN  2.0    NaN
d  NaN  3.0    NaN
e  NaN  4.0    NaN
f  NaN  NaN    5.0
g  NaN  NaN    6.0

将相同的逻辑拓展到DataFrame对象:
In [410]: df1 = pd.DataFrame(np.arange(6).reshape(3,2),index=['a','b','c'],columns=['one','two'])

In [411]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2,2),index=['a','c'],columns=['three','four'])

In [412]: df1
Out[412]: 
   one  two
a    0    1
b    2    3
c    4    5

In [413]: df2
Out[413]: 
   three  four
a      5     6
c      7     8

In [414]: pd.concat([df1,df2],axis=1,keys=['level1','level2'])
Out[414]: 
  level1     level2     
     one two  three four
a      0   1    5.0  6.0
b      2   3    NaN  NaN
c      4   5    7.0  8.0

如果你传递的是对象的字典而不是列表的话,则字典的键会用于keys选项:
In [415]: pd.concat({'level1':df1,'level2':df2},axis=1)
Out[415]: 
  level1     level2     
     one two  three four
a      0   1    5.0  6.0
b      2   3    NaN  NaN
c      4   5    7.0  8.0

还有一些额外的参数负责多层索引生成。例如,我们可以使用names参数命名生成的轴层级:
In [416]: pd.concat([df1,df2],axis=1,keys=['level1','level2'])
Out[416]: 
  level1     level2     
     one two  three four
a      0   1    5.0  6.0
b      2   3    NaN  NaN
c      4   5    7.0  8.0

最后需要考虑的是行索引中不包含任何相关数据的DataFrame:
In [417]: df1 = pd.DataFrame(np.random.randn(3,4),columns=['a','b','c','d'])

In [418]: df2 = pd.DataFrame(np.random.randn(2,3),columns=['b','d','a'])

In [419]: df1
Out[419]: 
          a         b         c         d
0 -0.438570 -0.539741  0.476985  3.248944
1 -1.021228 -0.577087  0.124121  0.302614
2  0.523772  0.000940  1.343810 -0.713544

In [420]: df2
Out[420]: 
          b         d         a
0 -0.831154 -2.370232 -1.860761
1 -0.860757  0.560145 -1.265934

在这个示例中,我们传入ignore_index = True:
In [421]: pd.concat([df1,df2],ignore_index=True)
Out[421]: 
          a         b         c         d
0 -0.438570 -0.539741  0.476985  3.248944
1 -1.021228 -0.577087  0.124121  0.302614
2  0.523772  0.000940  1.343810 -0.713544
3 -1.860761 -0.831154       NaN -2.370232
4 -1.265934 -0.860757       NaN  0.560145

在开篇时我说,神仔做完了这章,其实她只做了一半。这章实在是太无聊了,所以我打算跟随她的步伐,弃掉这章后面部分《重塑与透视》。

直接进入第9章的学习。

胭惜雨

2021年04月08日

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据