查看原文
其他

《利用Python进行数据分析·第2版》第8章(下) 数据规整:聚合、合并和重塑

SeanCheney Python爱好者社区 2019-04-07

作者:SeanCheney   Python爱好者社区专栏作者

简书专栏:https://www.jianshu.com/u/130f76596b02


前文传送门:

【翻译】《利用Python进行数据分析·第2版》第1章 准备工作

【翻译】《利用Python进行数据分析·第2版》第2章(上)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第2章(中)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第2章(下)Python语法基础,IPython和Jupyter

【翻译】《利用Python进行数据分析·第2版》第3章(上)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第3章(中)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第3章(下)Python的数据结构、函数和文件

【翻译】《利用Python进行数据分析·第2版》第4章(上)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第4章(中)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第4章(下)NumPy基础:数组和矢量计算

【翻译】《利用Python进行数据分析·第2版》第5章(上)pandas入门

【翻译】《利用Python进行数据分析·第2版》第5章(中)pandas入门

【翻译】《利用Python进行数据分析·第2版》第5章(下)pandas入门

【翻译】《利用Python进行数据分析·第2版》第6章(上) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第6章(中) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第6章(下) 数据加载、存储与文件格式

【翻译】《利用Python进行数据分析·第2版》第7章(上)数据清洗和准备

【翻译】《利用Python进行数据分析·第2版》第7章(中) 数据清洗和准备

【翻译】《利用Python进行数据分析·第2版》第7章(下) 数据清洗和准备

【翻译】《利用Python进行数据分析·第2版》第8章(上) 数据规整:聚合、合并和重塑

【翻译】《利用Python进行数据分析·第2版》第8章(中) 数据规整:聚合、合并和重塑


合并重叠数据


还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例子,我们使用NumPy的where函数,它表示一种等价于面向数组的if-else:

In [108]: a = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],   .....:               index=['f', 'e', 'd', 'c', 'b', 'a']) In [109]: b = pd.Series(np.arange(len(a), dtype=np.float64),   .....:               index=['f', 'e', 'd', 'c', 'b', 'a']) In [110]: b[-1] = np.nan In [111]: a Out[111]: f    NaN e    2.5 d    NaN c    3.5 b    4.5 a    NaN dtype: float64 In [112]: b Out[112]: f    0.0 e    1.0 d    2.0 c    3.0 b    4.0 a    NaN dtype: float64 In [113]: np.where(pd.isnull(a), b, a) Out[113]: array([ 0. ,  2.5,  2. ,  3.5,  4.5,  nan])

Series有一个combine_first方法,实现的也是一样的功能,还带有pandas的数据对齐:

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

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

In [115]: df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],   .....:                     'b': [np.nan, 2., np.nan, 6.],   .....:                     'c': range(2, 18, 4)}) In [116]: df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],   .....:                     'b': [np.nan, 3., 4., 6., 8.]}) In [117]: df1 Out[117]:     a    b   c 0  1.0  NaN   2 1  NaN  2.0   6 2  5.0  NaN  10 3  NaN  6.0  14 In [118]: df2 Out[118]:     a    b 0  5.0  NaN 1  4.0  3.0 2  NaN  4.0 3  3.0  6.0 4  7.0  8.0 In [119]: df1.combine_first(df2) Out[119]:     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

8.3 重塑和轴向旋转


有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。


重塑层次化索引


层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:


  • stack:将数据的列“旋转”为行。

  • unstack:将数据的行“旋转”为列。


我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串数组:

In [120]: data = pd.DataFrame(np.arange(6).reshape((2, 3)),   .....:                     index=pd.Index(['Ohio','Colorado'], ame='state'),   .....:                     columns=pd.Index(['one', 'two', 'three'],   .....:                     name='number')) In [121]: data Out[121]: number    one  two  three state                     Ohio        0    1      2 Colorado    3    4      5

对该数据使用stack方法即可将列转换为行,得到一个Series:

In [122]: result = data.stack() In [123]: result Out[123]: state     number Ohio      one       0          two       1          three     2 Colorado  one       3          two       4          three     5 dtype: int64

对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:

In [124]: result.unstack() Out[124]: number    one  two  three state                     Ohio        0    1      2 Colorado    3    4      5

默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其它级别进行unstack操作:

In [125]: result.unstack(0) Out[125]: state   Ohio  Colorado number                 one        0         3 two        1         4 three      2         5 In [126]: result.unstack('state') Out[126]: state   Ohio  Colorado number                 one        0         3 two        1         4 three      2         5

如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:

In [127]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd']) In [128]: s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e']) In [129]: data2 = pd.concat([s1, s2], keys=['one', 'two']) In [130]: data2 Out[130]: one  a    0     b    1     c    2     d    3 two  c    4     d    5     e    6 dtype: int64 In [131]: data2.unstack() Out[131]:       a    b    c    d    e one  0.0  1.0  2.0  3.0  NaN two  NaN  NaN  4.0  5.0  6.0

stack默认会滤除缺失数据,因此该运算是可逆的:

In [132]: data2.unstack() Out[132]:       a    b    c    d    e one  0.0  1.0  2.0  3.0  NaN two  NaN  NaN  4.0  5.0  6.0 In [133]: data2.unstack().stack() Out[133]: one  a    0.0     b    1.0     c    2.0     d    3.0 two  c    4.0     d    5.0     e    6.0 dtype: float64 In [134]: data2.unstack().stack(dropna=False) Out[134]: one  a    0.0     b    1.0     c    2.0     d    3.0     e    NaN two  a    NaN     b    NaN     c    4.0     d    5.0     e    6.0 dtype: float64

在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:

In [135]: df = pd.DataFrame({'left': result, 'right': result + 5},   .....:                   columns=pd.Index(['left', 'right'], name='side')) In [136]: df Out[136]: side             left  right state    number             Ohio     one        0      5         two        1      6         three      2      7 Colorado one        3      8         two        4      9         three      5     10 In [137]: df.unstack('state') Out[137]: side   left          right state  Ohio Colorado  Ohio Colorado number                             one       0        3     5        8 two       1        4     6        9 three     2        5     7       10

当调用stack,我们可以指明轴的名字:

In [138]: df.unstack('state').stack('side') Out[138]: state         Colorado  Ohio number side                 one    left          3     0       right         8     5 two    left          4     1       right         9     6 three  left          5     2       right        10     7

将“长格式”旋转为“宽格式”


多个时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的。我们先加载一些示例数据,做一些时间序列规整和数据清洗:

In [139]: data = pd.read_csv('examples/macrodata.csv') In [140]: data.head() Out[140]:     year  quarter   realgdp  realcons  realinv  realgovt  realdpi    cpi  \ 0  1959.0      1.0  2710.349    1707.4  286.898   470.045   1886.9  28.98   1  1959.0      2.0  2778.801    1733.7  310.859   481.301   1919.7  29.15   2  1959.0      3.0  2775.488    1751.8  289.226   491.260   1916.4  29.35   3  1959.0      4.0  2785.204    1753.7  299.356   484.052   1931.3  29.37   4  1960.0      1.0  2847.699    1770.5  331.722   462.199   1955.5  29.54        m1  tbilrate  unemp      pop  infl  realint   0  139.7      2.82    5.8  177.146  0.00     0.00 1  141.7      3.08    5.1  177.830  2.34     0.74   2  140.5      3.82    5.3  178.657  2.74     1.09   3  140.0      4.33    5.6  179.386  0.27     4.06   4  139.6      3.50    5.2  180.007  2.31     1.19   In [141]: periods = pd.PeriodIndex(year=data.year, quarter=data.quarter,   .....:                          name='date') In [142]: columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item') In [143]: data = data.reindex(columns=columns) In [144]: data.index = periods.to_timestamp('D', 'end') In [145]: ldata = data.stack().reset_index().rename(columns={0: 'value'})


这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是date和item)的长格式。表中的每行代表一次观察。


关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item列中的值的种类能够增加。在前面的例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间戳则用作索引。DataFrame的pivot方法完全可以实现这个转换:

In [147]: pivoted = ldata.pivot('date', 'item', 'value') In [148]: pivoted Out[148]: item        infl    realgdp  unemp date                               1959-03-31  0.00   2710.349    5.8 1959-06-30  2.34   2778.801    5.1 1959-09-30  2.74   2775.488    5.3 1959-12-31  0.27   2785.204    5.6 1960-03-31  2.31   2847.699    5.2 1960-06-30  0.14   2834.390    5.2 1960-09-30  2.70   2839.022    5.6 1960-12-31  1.21   2802.616    6.3 1961-03-31 -0.40   2819.264    6.8 1961-06-30  1.47   2872.005    7.0 ...          ...        ...    ... 2007-06-30  2.75  13203.977    4.5 2007-09-30  3.45  13321.109    4.7 2007-12-31  6.38  13391.249    4.8 2008-03-31  2.82  13366.865    4.9 2008-06-30  8.53  13415.266    5.4 2008-09-30 -3.16  13324.600    6.0 2008-12-31 -8.79  13141.920    6.9 2009-03-31  0.94  12925.410    8.1 2009-06-30  3.37  12901.504    9.2 2009-09-30  3.56  12990.341    9.6 [203 rows x 3 columns]

前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:

In [149]: ldata['value2'] = np.random.randn(len(ldata)) In [150]: ldata[:10] Out[150]:        date     item     value    value2 0 1959-03-31  realgdp  2710.349  0.523772 1 1959-03-31     infl     0.000  0.000940 2 1959-03-31    unemp     5.800  1.343810 3 1959-06-30  realgdp  2778.801 -0.713544 4 1959-06-30     infl     2.340 -0.831154 5 1959-06-30    unemp     5.100 -2.370232 6 1959-09-30  realgdp  2775.488 -1.860761 7 1959-09-30     infl     2.740 -0.860757 8 1959-09-30    unemp     5.300  0.560145 9 1959-12-31  realgdp  2785.204 -1.265934

如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:

In [151]: pivoted = ldata.pivot('date', 'item') In [152]: pivoted[:5] Out[152]:           value                    value2                     item        infl   realgdp unemp      infl   realgdp     unemp date                                                           1959-03-31  0.00  2710.349   5.8  0.000940  0.523772  1.343810 1959-06-30  2.34  2778.801   5.1 -0.831154 -0.713544 -2.370232 1959-09-30  2.74  2775.488   5.3 -0.860757 -1.860761  0.560145 1959-12-31  0.27  2785.204   5.6  0.119827 -1.265934 -1.063512 1960-03-31  2.31  2847.699   5.2 -2.359419  0.332883 -0.199543 In [153]: pivoted['value'][:5] Out[153]: item        infl   realgdp  unemp date                             1959-03-31  0.00  2710.349    5.8 1959-06-30  2.34  2778.801    5.1 1959-09-30  2.74  2775.488    5.3 1959-12-31  0.27  2785.204    5.6 1960-03-31  2.31  2847.699    5.2

注意,pivot其实就是用set_index创建层次化索引,再用unstack重塑:

In [154]: unstacked = ldata.set_index(['date', 'item']).unstack('item') In [155]: unstacked[:7] Out[155]:           value                    value2                     item        infl   realgdp unemp      infl   realgdp     unemp date                                                           1959-03-31  0.00  2710.349   5.8  0.000940  0.523772  1.343810 1959-06-30  2.34  2778.801   5.1 -0.831154 -0.713544 -2.370232 1959-09-30  2.74  2775.488   5.3 -0.860757 -1.860761  0.560145 1959-12-31  0.27  2785.204   5.6  0.119827 -1.265934 -1.063512 1960-03-31  2.31  2847.699   5.2 -2.359419  0.332883 -0.199543 1960-06-30  0.14  2834.390   5.2 -0.970736 -1.541996 -1.307030 1960-09-30  2.70  2839.022   5.6  0.377984  0.286350 -0.753887

将“宽格式”旋转为“长格式”


旋转DataFrame的逆运算是pandas.melt。它不是将一列转换到多个新的DataFrame,而是合并多个列成为一个,产生一个比输入长的DataFrame。看一个例子:

In [157]: df = pd.DataFrame({'key': ['foo', 'bar', 'baz'],   .....:                    'A': [1, 2, 3],   .....:                    'B': [4, 5, 6],   .....:                    'C': [7, 8, 9]}) In [158]: df Out[158]:   A  B  C  key 0  1  4  7  foo 1  2  5  8  bar 2  3  6  9  baz

key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标:

In [159]: melted = pd.melt(df, ['key']) In [160]: melted Out[160]:   key variable  value 0  foo        A      1 1  bar        A      2 2  baz        A      3 3  foo        B      4 4  bar        B      5 5  baz        B      6 6  foo        C      7 7  bar        C      8 8  baz        C      9

使用pivot,可以重塑回原来的样子:

In [161]: reshaped = melted.pivot('key', 'variable', 'value') In [162]: reshaped Out[162]: variable  A  B  C key               bar       2  5  8 baz       3  6  9 foo       1  4  7

因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列:

In [163]: reshaped.reset_index() Out[163]: variable  key  A  B  C 0         bar  2  5  8 1         baz  3  6  9 2         foo  1  4  7

你还可以指定列的子集,作为值的列:

In [164]: pd.melt(df, id_vars=['key'], value_vars=['A', 'B']) Out[164]:   key variable  value 0  foo        A      1 1  bar        A      2 2  baz        A      3 3  foo        B      4 4  bar        B      5 5  baz        B      6

pandas.melt也可以不用分组指标:

In [165]: pd.melt(df, value_vars=['A', 'B', 'C']) Out[165]:  variable  value 0        A      1 1        A      2 2        A      3 3        B      4 4        B      5 5        B      6 6        C      7 7        C      8 8        C      9 In [166]: pd.melt(df, value_vars=['key', 'A', 'B']) Out[166]:  variable value 0      key   foo 1      key   bar 2      key   baz 3        A     1 4        A     2 5        A     3 6        B     4 7        B     5 8        B     6

8.4 总结


现在你已经掌握了pandas数据导入、清洗、重塑,我们可以进一步学习matplotlib数据可视化。我们在稍后会回到pandas,学习更高级的分析。


赞赏作者

Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门视频课程!!!

崔老师爬虫实战案例免费学习视频。

丘老师数据科学入门指导免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。

丘老师Python网络爬虫实战免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存