【读书笔记】《利用Python进行数据分析》Ch7

本文介绍了使用pandas进行数据规整化的多种方法,包括数据的清理、转换、合并及重塑等。重点讲解了如何利用merge函数进行数据库风格的DataFrame合并,并演示了concat函数在轴向连接中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第7章 数据规整化:清理、转换、合并、重塑

合并数据集

pandas对象中的数据可以通过一些内置的方法进行合并:

  • pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。它就是在数据库中实现连接的操作。
  • pandas.concat可以沿着一条轴将多个对象堆叠到一起。
  • 实例方法conbine_first可以将重复数据编接在一起,用一个对象中的值填充另一个对象中的缺失值。

数据库风格的DataFrame合并

数据集的合并(merge)或连接(join)运算是通过一个或多个键将行链接起来的。这些运算是关系型数据库的核心。pandas的merge函数是对数据应用这些算法的主要切入点。
以一个简单的例子开始:

In [1]: from pandas import Series, DataFrame

In [2]: import pandas as pd

In [3]: import numpy as np

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

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

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

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

这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到:

In [8]: pd.merge(df1, df2)
Out[8]:
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0

注意,这里并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列明当作键。不过,最好显示指定一下:

In [9]: pd.merge(df1, df2, on = 'key')
Out[9]:
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0

如果两个对象的列名不同,也可以分别进行指定:

In [10]: df3 = DataFrame({'1key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'], 'data1': range(7)})

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

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

默认情况下,merge做的是“inner”连接;结果中的键是交集。其他方式还有“left”、“right”以及“outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:

In [13]: pd.merge(df1, df2, how = 'outer')
Out[13]:
   data1 key  data2
0    0.0   b    1.0
1    1.0   b    1.0
2    6.0   b    1.0
3    2.0   a    0.0
4    4.0   a    0.0
5    5.0   a    0.0
6    3.0   c    NaN
7    NaN   d    2.0

多对多的合并操作非常简单,无需额外的工作。如下所示:

In [14]: df1 = DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], 'data1': range(6)})

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

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

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

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

多对多连接产生的是行的笛卡儿积。由于左边的DataFrame有3个“b”行,右边的有2个,所以最终结果中就有6个“b”行。连接方式只影响出现在结果中的键:

In [19]: pd.merge(df1, df2, how = 'inner')
Out[19]:
   data1 key  data2
0      0   b      1
1      0   b      3
2      1   b      1
3      1   b      3
4      5   b      1
5      5   b      3
6      2   a      0
7      2   a      2
8      4   a      0
9      4   a      2

要根据多个键进行合并,传入一个由列名组成的列表即可:

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

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

In [23]: pd.merge(left, right, on = ['key1', 'key2'], how = 'outer')
Out[23]:
  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

对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(稍后将会介绍如何重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:

In [24]: pd.merge(left, right, on = 'key1')
Out[24]:
  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 [25]: pd.merge(left, right, on = 'key1', suffixes = ('_left', '_right'))
Out[25]:
  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

merge函数的参数:

  • left:参与合并的左侧DataFrame
  • right:参与合并的右侧DataFrame
  • how:“inner”、“outer”、“left”、“right”其中之一。默认为“inner”
  • on:用于连接的列名。必须存在于左右两个DataFrame对象中。如果未指定,且其他连接键也未指定,则以left和right列名的交集作为连接键
  • left_on:左侧DataFrame中用作连接键的列
  • right_on:右侧DataFrame中用作连接键的列
  • left_index:将左侧的行索引用作其连接键
  • right_index:类似于left_index
  • sort:根据连接键对合并后的数据进行排序,默认为True。有时在处理大数据集时,禁用该选项可获得更好的性能
  • suffixes:字符串值元组,用于追加到重叠列名的末尾,默认为(‘_x’, ‘_y’)。例如,如果左右两个DataFrame对象都有“data”,则结果中就会出现“data_x”和“data_y”
  • copy:设置为False,可以在某些特殊情况下避免将数据复制到结果数据结构中。默认总是复制

索引上的合并

有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:

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

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

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

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

In [30]: pd.merge(left1, right1, left_on = 'key', right_index = True)
Out[30]:
  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

由于默认的merge方法是求取连接键的交集,因此可以通过外连接的方式得到它们的并集:

In [31]: pd.merge(left1, right1, left_on = 'key', right_index = True, how = 'outer')
Out[31]:
  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 [32]: lefth = DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
    ...: 'key2': [2000, 2001, 2002, 2001, 2002],
    ...: 'data': np.arange(5.)})

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

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

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

这种情况下,你必须以列表的形式指明用作合并键的多个列(注意对重复索引值的处理):

In [36]: pd.merge(lefth, righth, left_on = ['key1', 'key2'], right_index = True)
Out[36]:
   data    key1  key2  event1  event2
0   0.0    Ohio  2000       4       5
0   0.0    Ohio  2000       6       7
1   1.0    Ohio  2001       8       9
2   2.0    Ohio  2002      10      11
3   3.0  Nevada  2001       0       1

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

同时使用合并双方的索引也没问题:

In [38]: left2 = DataFrame([[1., 2.], [3., 4.], [5., 6.]], index = ['a', 'c', 'e'], columns = ['Ohio', 'Nevada'])

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

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

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

In [42]: pd.merge(left2, right2, left_index = True, right_index = True)
Out[42]:
   Ohio  Nevada  Missouri  Alabama
c   3.0     4.0       9.0     10.0
e   5.0     6.0      13.0     14.0

DataFrame还有一个join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,而不管它们之间有没有重叠的列。在上面的例子中,我们可以编写:

In [43]: left2.join(right2, how = 'outer')
Out[43]:
   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

由于一些历史原因(早期版本的pandas),DataFrame的join方法是在连接键上做左连接。它还支持参数DataFrame的索引跟调用者的DataFrame的某个列之间的连接:

In [44]: left1.join(right1, on = 'key')
Out[44]:
  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 [45]: another = DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
    ...: index = ['a', 'c', 'e', 'f'], columns = ['New York', 'Oregon'])

In [46]: left2.join([right2, another])
Out[46]:
   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 [48]: left2.join([right2, another], how = 'outer')
Out[48]:
   Ohio  Nevada  Missouri  Alabama  New York  Oregon
a   1.0     2.0       NaN      NaN       7.0     8.0
b   NaN     NaN       7.0      8.0       NaN     NaN
c   3.0     4.0       9.0     10.0       9.0    10.0
d   NaN     NaN      11.0     12.0       NaN     NaN
e   5.0     6.0      13.0     14.0      11.0    12.0
f   NaN     NaN       NaN      NaN      16.0    17.0

轴向连接

另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。Numpy有一个用于合并原始Numpy数组的concatenation函数:

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

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

In [51]: np.concatenate([arr,arr], axis =1)
Out[51]:
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]])

对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西:

  • 如果各对象其他轴上的索引不同,那些轴应该是做并集还是交集?
  • 结果对象中的分组需要各不相同吗?
  • 用于连接的轴重要吗?
    pandas的concat函数提供了一种能够解决这些问题的可靠方式。下面将给出一些例子来讲解其使用方式。假设有三个没有重叠索引的Series:
In [52]: s1 = Series([0,1], index = ['a', 'b'])

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

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

对这些对象调用concat可以将值和索引粘合在一起:

In [55]: pd.concat([s1, s2, s3])
Out[55]:
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 [56]: pd.concat([s1, s2, s3], axis = 1)
Out[56]:
     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

这种情况下,另外一条轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入join=’inner’即可得到它们的交集:

In [60]: s4 = pd.concat([s1 * 5, s3])

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

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

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

也可通过join_axes指定要在其他轴上使用的索引:

In [64]: pd.concat([s1, s4], axis = 1, join_axes = [['a', 'c', 'b', 'e']])
Out[64]:
     0    1
a  0.0  0.0
c  NaN  NaN
b  1.0  5.0
e  NaN  NaN

有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引,使用keys参数即可达到这个目的:

In [65]: result = pd.concat([s1, s1, s3], keys = ['one', 'two', 'three'])

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

In [67]: result.unstack()
Out[67]:
         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 [68]: pd.concat([s1,s2,s3], axis = 1, keys = ['one', 'two', 'three'])
Out[68]:
   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 [69]: df1 = DataFrame(np.arange(6).reshape(3,2), index = ['a', 'b', 'c'], columns = ['one', 'two'])

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

In [71]: pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'])
Out[71]:
  level1     level2
     one two  three four
a      0   1    0.0  1.0
b      2   3    NaN  NaN
c      4   5    2.0  3.0

如果传入的不是列表而是一个字典,则字典的键就会被当做keys选项的值:

In [72]: pd.concat({'level1': df1, 'level2': df2}, axis = 1)
Out[72]:
  level1     level2
     one two  three four
a      0   1    0.0  1.0
b      2   3    NaN  NaN
c      4   5    2.0  3.0

此外还有两个用于管理层次化索引创建方式的参数:

In [73]: pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'], names = ['upper', 'lower'])
Out[73]:
upper level1     level2
lower    one two  three four
a          0   1    0.0  1.0
b          2   3    NaN  NaN
c          4   5    2.0  3.0

最后一个需要考虑的问题是,跟当前分析工作无关的DataFrame行索引:

In [74]: df1 = DataFrame(np.random.randn(3,4), columns = ['a', 'b', 'c', 'd'])

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

In [76]: df1
Out[76]:
          a         b         c         d
0  1.414313  0.436672 -0.288776 -0.286268
1  0.107107 -0.346125 -1.230709  0.743067
2 -1.584220  0.629817 -1.191732 -1.357533

In [77]: df2
Out[77]:
          b         d         a
0 -0.236294 -0.440592  0.051193
1 -0.073595 -0.562664  1.288526

在这种情况下,传入ignore_index=True即可:

In [78]: pd.concat([df1, df2], ignore_index=True)
Out[78]:
          a         b         c         d
0  1.414313  0.436672 -0.288776 -0.286268
1  0.107107 -0.346125 -1.230709  0.743067
2 -1.584220  0.629817 -1.191732 -1.357533
3  0.051193 -0.236294       NaN -0.440592
4  1.288526 -0.073595       NaN -0.562664

concat函数的参数:

  • objs:参与连接的pandas对象的列表或字典。唯一必须的参数
  • axis:指明连接的轴向,默认为0
  • join:“inner”、“outer”其中之一,默认为“outer”。指明其他轴向上的索引是按交集(inner)还是并集(outer)进行合并
  • join_axes:指明用于其他n-1条轴的索引,不执行并集/交集运算
  • keys:与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组、元组数组、数组列表(如果将levels设置成多级数组的话)
  • levels:指定用作层次化索引各级别上的索引,如果设置了keys的话
  • names:用于创建分层级别的名称,如果设置了keys和(或)levels的话
  • verify_integrity:检查结果对象新轴上的重复情况,如果发现则引发异常。默认(False)允许重复
  • ignore_index:不保留连接轴上的索引,产生一组新索引range(total_length)

合并重叠数据

还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,有可能索引全部或部分重叠的两个数据集。给这个例子增加一点启发性,我们使用NumPy的where函数,它用于表达一种矢量化的if-else:

In [79]: a = Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan], index = ['f', 'e', 'd', 'c', 'b', 'a'])

In [80]: b = Series(np.arange(len(a), dtype = np.float64),index = ['f' ,'e', 'd', 'c', 'b', 'a'])

In [81]: b[-1] = np.nan

In [82]: a
Out[82]:
f    NaN
e    2.5
d    NaN
c    3.5
b    4.5
a    NaN
dtype: float64

In [83]: b
Out[83]:
f    0.0
e    1.0
d    2.0
c    3.0
b    4.0
a    NaN
dtype: float64

In [84]: np.where(pd.isnull(a), b, a)
Out[84]: array([ 0. ,  2.5,  2. ,  3.5,  4.5,  nan])

Series有一个combine_first方法,实现的也是一样的功能,而且会进行数据对齐:

In [85]: b[:-2].combine_first(a[2:])
Out[85]:
a    NaN
b    4.5
c    3.0
d    2.0
e    1.0
f    0.0
dtype: float64

对于DataFrame,combine_first自然也会在列上做同样的事情,因此可以将其看作:用参数对象的数据为调用者对象的缺失数据“打补丁”

In [86]: df1 = DataFrame({'a': [1., np.nan, 5., np.nan],
    ...: 'b': [np.nan, 2., np.nan, 6.],
    ...: 'c': range(2, 18 ,4)})

In [87]: df2 = DataFrame({'a': [5., 4., np.nan, 3., 7.],
    ...: 'b': [np.nan, 3., 4., 6., 8.]})

In [88]: df1.combine_first(df2)
Out[88]:
     a    b     c
0  1.0  NaN   2.0
1  4.0  2.0   6.0
2  5.0  4.0  10.0
3  3.0  6.0  14.0
4  7.0  8.0   NaN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值