Pandas统计分析入门(3)



In [161]:
%matplotlib inline
from pandas import Series, DataFrame
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

数据分组技术

  • 多层索引、数据分组、计算、合并、处理缺失数据、输入输出

1. 多层索引

1.1 Series多层索引


In [162]:
data = Series(np.random.randn(10),index=[['a','a','a','b','b','b','c','c','d','d'],[0,1,2,0,1,2,0,1,0,1]])
data


Out[162]:
a  0   -0.849212
   1   -1.117469
   2    0.472500
b  0   -1.272573
   1    0.252942
   2    0.360228
c  0    0.425634
   1   -0.674811
d  0    1.335875
   1   -0.228149
dtype: float64
  • index参数中有两个list,其中第一个为外层索引,第二个为内层索引

In [163]:
data.index


Out[163]:
MultiIndex(levels=[['a', 'b', 'c', 'd'], [0, 1, 2]],
           labels=[[0, 0, 0, 1, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 1, 2, 0, 1, 0, 1]])
  • 查看index发现,有两个层次的索引,索引list分别为['a', 'b', 'c', 'd'], [0, 1, 2],各位置元素对应的索引标签为:[0, 0, 0, 1, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 1, 2, 0, 1, 0, 1],以索引标签在索引list中的位置来记录。

In [164]:
data['b']


Out[164]:
0   -1.272573
1    0.252942
2    0.360228
dtype: float64
  • 外层索引进行元素选取

In [165]:
data['a':'c']


Out[165]:
a  0   -0.849212
   1   -1.117469
   2    0.472500
b  0   -1.272573
   1    0.252942
   2    0.360228
c  0    0.425634
   1   -0.674811
dtype: float64
  • 外层标签切片

In [166]:
data[['a','c']]


Out[166]:
a  0   -0.849212
   1   -1.117469
   2    0.472500
c  0    0.425634
   1   -0.674811
dtype: float64
  • 选取外层多个标签

In [167]:
data[:,1]


Out[167]:
a   -1.117469
b    0.252942
c   -0.674811
d   -0.228149
dtype: float64
  • 选取全部外层索引,且内层索引为1的元素
  • 返回一个Series

In [168]:
data['a',0]


Out[168]:
-0.84921151400187134
  • 选取某外层索引,且内存索引为0的元素
  • 注意,目前多层索引并不支持形如data['a',:]这种符合逻辑的形式,个人认为可能也是设计缺陷之一

2.DataFrame多层索引


In [169]:
df = DataFrame(np.random.randn(6,3), 
               index = [['a','a','b','b','c','c'],[1,2,1,2,1,2]], 
               columns = [['beijing','beijing','shanghai'],['boy','girl','boy']])
df


Out[169]:
beijing shanghai
boy girl boy
a 1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
b 1 -1.196253 -2.840656 -2.057308
2 -0.758351 -1.166275 -0.464505
c 1 1.419848 -1.046008 -1.126064
2 -1.613154 1.410900 -1.241607
  • DataFrame可以在两个轴都有多层索引

In [170]:
df['beijing']


Out[170]:
boy girl
a 1 -0.226582 -0.858561
2 0.653603 -1.096843
b 1 -1.196253 -2.840656
2 -0.758351 -1.166275
c 1 1.419848 -1.046008
2 -1.613154 1.410900
  • 通过外层索引选择列数据

In [171]:
df[['beijing','shanghai']]


Out[171]:
beijing shanghai
boy girl boy
a 1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
b 1 -1.196253 -2.840656 -2.057308
2 -0.758351 -1.166275 -0.464505
c 1 1.419848 -1.046008 -1.126064
2 -1.613154 1.410900 -1.241607
  • 通过外层多个标签选取列数据

In [172]:
df['beijing']['boy']


Out[172]:
a  1   -0.226582
   2    0.653603
b  1   -1.196253
   2   -0.758351
c  1    1.419848
   2   -1.613154
Name: boy, dtype: float64
  • 外层索引选取列后再根据列内层标签选取列数据

In [173]:
df.loc['a',:]


Out[173]:
beijing shanghai
boy girl boy
1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
  • 利用loc及外层行索引选择行数据

In [174]:
df.loc[['a','b'],'beijing']


Out[174]:
boy girl
a 1 -0.226582 -0.858561
2 0.653603 -1.096843
b 1 -1.196253 -2.840656
2 -0.758351 -1.166275
  • 多个外层索引选择多行,并限定选择特定的列

In [175]:
df['beijing'].loc[['a','b']]


Out[175]:
boy girl
a 1 -0.226582 -0.858561
2 0.653603 -1.096843
b 1 -1.196253 -2.840656
2 -0.758351 -1.166275
  • 也可结合之前单层索引的思路来进行数据选取

1.3 重新分层(重塑)


In [176]:
data.index = [
    ['a','a','a','b','b','b','c','c','d','d'],
    ['0','1','2','0','1','2','0','1','0','1'],
    ['aa','aa','aa','aa','aa','bb','bb','bb','bb','bb']
]
data


Out[176]:
a  0  aa   -0.849212
   1  aa   -1.117469
   2  aa    0.472500
b  0  aa   -1.272573
   1  aa    0.252942
   2  bb    0.360228
c  0  bb    0.425634
   1  bb   -0.674811
d  0  bb    1.335875
   1  bb   -0.228149
dtype: float64
  • 新建立一个有3层索引的Series对象

In [177]:
data.swaplevel()


Out[177]:
a  aa  0   -0.849212
       1   -1.117469
       2    0.472500
b  aa  0   -1.272573
       1    0.252942
   bb  2    0.360228
c  bb  0    0.425634
       1   -0.674811
d  bb  0    1.335875
       1   -0.228149
dtype: float64
  • 利用swaplevel()函数将索引进行层级交换
  • 不指定参数时,将交换内层两个索引的层级

In [178]:
data.swaplevel(0)


Out[178]:
aa  0  a   -0.849212
    1  a   -1.117469
    2  a    0.472500
    0  b   -1.272573
    1  b    0.252942
bb  2  b    0.360228
    0  c    0.425634
    1  c   -0.674811
    0  d    1.335875
    1  d   -0.228149
dtype: float64
  • 可在swaplevel()函数中放入数字参数来变换要交换层次的索引

In [179]:
data.index.names = ['one','two','three']
data


Out[179]:
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
b    0    aa      -1.272573
     1    aa       0.252942
     2    bb       0.360228
c    0    bb       0.425634
     1    bb      -0.674811
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • 可以为Serise对象的多层索引分别指定名称(name)

In [180]:
data.swaplevel('one','three')


Out[180]:
three  two  one
aa     0    a     -0.849212
       1    a     -1.117469
       2    a      0.472500
       0    b     -1.272573
       1    b      0.252942
bb     2    b      0.360228
       0    c      0.425634
       1    c     -0.674811
       0    d      1.335875
       1    d     -0.228149
dtype: float64
  • 指定了name后,就可以在swaplevel()函数中指定索引的name进行索引层次交换

In [181]:
frame = data.unstack()
frame


Out[181]:
three aa bb
one two
a 0 -0.849212 NaN
1 -1.117469 NaN
2 0.472500 NaN
b 0 -1.272573 NaN
1 0.252942 NaN
2 NaN 0.360228
c 0 NaN 0.425634
1 NaN -0.674811
d 0 NaN 1.335875
1 NaN -0.228149
  • 多层索引的对象可以通过unstack()函数来进行索引退层,不指定参数时默认为最内层
  • 退层后,原来最内层的索引转换为每列的索引,无法对其的索引数据,则与统一赋值为NaN
  • 对Series进行unstack(),则回返回DataFrame对象

In [182]:
frame.stack()


Out[182]:
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
b    0    aa      -1.272573
     1    aa       0.252942
     2    bb       0.360228
c    0    bb       0.425634
     1    bb      -0.674811
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • unstack()的拟操作是stack()

In [183]:
frame = data.unstack('two')
frame


Out[183]:
two 0 1 2
one three
a aa -0.849212 -1.117469 0.472500
b aa -1.272573 0.252942 NaN
bb NaN NaN 0.360228
c bb 0.425634 -0.674811 NaN
d bb 1.335875 -0.228149 NaN
  • 可以根据索引的name来指定unstack的索引层
  • 返回一个以unstack的索引为列索引的DataFrame

In [184]:
frame = frame.unstack()
frame


Out[184]:
two 0 1 2
three aa bb aa bb aa bb
one
a -0.849212 NaN -1.117469 NaN 0.4725 NaN
b -1.272573 NaN 0.252942 NaN NaN 0.360228
c NaN 0.425634 NaN -0.674811 NaN NaN
d NaN 1.335875 NaN -0.228149 NaN NaN
  • 可以继续退层

In [185]:
frame['0']


Out[185]:
three aa bb
one
a -0.849212 NaN
b -1.272573 NaN
c NaN 0.425634
d NaN 1.335875
  • 选择列数据

In [186]:
frame['0']['aa'].loc['a']


Out[186]:
-0.84921151400187134
  • 仿照之前的方法,选择区域数据

1.4 利用层级择、汇总计算数据


In [187]:
data


Out[187]:
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
b    0    aa      -1.272573
     1    aa       0.252942
     2    bb       0.360228
c    0    bb       0.425634
     1    bb      -0.674811
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • 还是用先前的Series做例子

In [188]:
data.sum(level = 'one')


Out[188]:
one
a   -1.494180
b   -0.659403
c   -0.249177
d    1.107726
dtype: float64
  • 利用sum()求和,用参数level指明以哪层的index为基准进行求和

In [189]:
data.std(level='two')


Out[189]:
two
0    1.193577
1    0.588540
2    0.079388
dtype: float64
  • 其他函数也可以类似进行对特定层级的计算

In [190]:
df


Out[190]:
beijing shanghai
boy girl boy
a 1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
b 1 -1.196253 -2.840656 -2.057308
2 -0.758351 -1.166275 -0.464505
c 1 1.419848 -1.046008 -1.126064
2 -1.613154 1.410900 -1.241607
  • 仍然以前面的DataFrame作为例子

In [191]:
df.index.names = ['one', 'two']
df.columns.names = ['la','lb']
df


Out[191]:
la beijing shanghai
lb boy girl boy
one two
a 1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
b 1 -1.196253 -2.840656 -2.057308
2 -0.758351 -1.166275 -0.464505
c 1 1.419848 -1.046008 -1.126064
2 -1.613154 1.410900 -1.241607
  • 为DataFrame对象的index的names赋值,为其columns的names赋值

In [192]:
df.sum(level = 'one')


Out[192]:
la beijing shanghai
lb boy girl boy
one
a 0.427021 -1.955404 -2.963135
b -1.954603 -4.006931 -2.521813
c -0.193305 0.364892 -2.367671
  • 可以利用各类计算的level参数,根据索引的name,来对指定层次的索引进行求值
  • level参数默认为指定行索引的name

In [193]:
df.std(level = 'two')


Out[193]:
la beijing shanghai
lb boy girl boy
two
1 1.322559 1.094273 0.488820
2 1.144734 1.468300 0.416677
  • 之前所用到的各类计算,均可以这样对指定层进行计算

In [194]:
df.sum(level = 'la',axis=1)


Out[194]:
la beijing shanghai
one two
a 1 -1.085143 -1.849417
2 -0.443240 -1.113718
b 1 -4.036908 -2.057308
2 -1.924626 -0.464505
c 1 0.373841 -1.126064
2 -0.202254 -1.241607
  • 利用axis参数,可以指定列索引名称进行相应计算
  • 其余指定columns值的计算均类似

2. 数据分组(Groupby)计算

  • 数据分组是Pandas常用操作
  • 1.将数据按照某个规律(如标签、数据的规律)分组
  • 2.对每个分组施加(Apply)运算
  • 3.将运算结果合并(Combine)

2.1 对Series进行分组计算


In [195]:
data


Out[195]:
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
b    0    aa      -1.272573
     1    aa       0.252942
     2    bb       0.360228
c    0    bb       0.425634
     1    bb      -0.674811
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • 仍然是以之前的Series作为例子

In [196]:
grouped = data.groupby(level='one')
grouped


Out[196]:
<pandas.core.groupby.SeriesGroupBy object at 0x000001F0405D6DD8>
  • 利用groupby()函数,可以将数根据据指定level对应的index,进行分组。
  • grouped是groupby类型,可视为含有一些分组信息的视图(类似dict)

In [197]:
for name, group in grouped:
    print(name)
    print('---------------------------')
    print(group)


a
---------------------------
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
dtype: float64
b
---------------------------
one  two  three
b    0    aa      -1.272573
     1    aa       0.252942
     2    bb       0.360228
dtype: float64
c
---------------------------
one  two  three
c    0    bb       0.425634
     1    bb      -0.674811
dtype: float64
d
---------------------------
one  two  three
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • 利用类似dict一样对名字和对应的组进行迭代

In [198]:
grouped.size()


Out[198]:
one
a    3
b    3
c    2
d    2
dtype: int64
  • 可以利用groupby对象的size()方法,来查看分组对象的信息
  • 在本例中,对应索引a,b,c,d的数据个数分别为:3,3,2,2

In [199]:
grouped.sum()


Out[199]:
one
a   -1.494180
b   -0.659403
c   -0.249177
d    1.107726
dtype: float64
  • 对分好的组进行分组计算,之前的各种计算均可以直接应用

In [200]:
data.groupby(level='one').sum()


Out[200]:
one
a   -1.494180
b   -0.659403
c   -0.249177
d    1.107726
dtype: float64
  • 可以合并为一条语句来写,其功能与data.sum(level = 'one')一样

In [201]:
key = [1,1,1,1,1,2,2,2,2,2]
for name, group in data.groupby(key):
    print(name)
    print('-------------------')
    print(group)


1
-------------------
one  two  three
a    0    aa      -0.849212
     1    aa      -1.117469
     2    aa       0.472500
b    0    aa      -1.272573
     1    aa       0.252942
dtype: float64
2
-------------------
one  two  three
b    2    bb       0.360228
c    0    bb       0.425634
     1    bb      -0.674811
d    0    bb       1.335875
     1    bb      -0.228149
dtype: float64
  • groupby()强大还在于可以利用任意序列或数组对数据进行分组,不需要该数据一定在数据表中,前提是要两者等长
  • 本例中用[1,1,1,1,1,2,2,2,2,2]将data进行分组

2.2 对DataFrame进行分组计算


In [202]:
df


Out[202]:
la beijing shanghai
lb boy girl boy
one two
a 1 -0.226582 -0.858561 -1.849417
2 0.653603 -1.096843 -1.113718
b 1 -1.196253 -2.840656 -2.057308
2 -0.758351 -1.166275 -0.464505
c 1 1.419848 -1.046008 -1.126064
2 -1.613154 1.410900 -1.241607
  • 用先前已经添加index及colums的name的DataFrame对象作为例子

In [226]:
df_grouped = df.groupby(level = 'one')
df_grouped.size()


Out[226]:
one
a    2
b    2
c    2
dtype: int64
  • 对DataFrame对象进行groupby()与前述Series类似
  • 本例中以index为'a'的行进行分组
  • 利用size()函数显示:index为'a'的数据为2行,'b'的数据为两行

In [204]:
for name, group in df_grouped:
    print(name)
    print('---------------------')
    print(group)


a
---------------------
la        beijing            shanghai
lb            boy      girl       boy
one two                              
a   1   -0.226582 -0.858561 -1.849417
    2    0.653603 -1.096843 -1.113718
b
---------------------
la        beijing            shanghai
lb            boy      girl       boy
one two                              
b   1   -1.196253 -2.840656 -2.057308
    2   -0.758351 -1.166275 -0.464505
c
---------------------
la        beijing            shanghai
lb            boy      girl       boy
one two                              
c   1    1.419848 -1.046008 -1.126064
    2   -1.613154  1.410900 -1.241607
  • 依然可以对grouped对象进行迭代
  • 可以看出DataFrame已经基于'one'这个index进行了分组

In [205]:
df_grouped.sum()


Out[205]:
la beijing shanghai
lb boy girl boy
one
a 0.427021 -1.955404 -2.963135
b -1.954603 -4.006931 -2.521813
c -0.193305 0.364892 -2.367671
  • 可以对分组应用各种计算函数
  • 本例中是sum()函数

In [206]:
df.groupby(level = 'la', axis = 1).sum()


Out[206]:
la beijing shanghai
one two
a 1 -1.085143 -1.849417
2 -0.443240 -1.113718
b 1 -4.036908 -2.057308
2 -1.924626 -0.464505
c 1 0.373841 -1.126064
2 -0.202254 -1.241607
  • 可以在groupby()函数中指定axis=1,选取columns的name作为分组基准
  • 本例中,选取name为'la'的columns索引作为分组基准进行分组及计算sum()

In [208]:
df['key1'] = ['male', 'famale', 'male', 'famale','male', 'male']
df


Out[208]:
la beijing shanghai key1
lb boy girl boy
one two
a 1 -0.226582 -0.858561 -1.849417 male
2 0.653603 -1.096843 -1.113718 famale
b 1 -1.196253 -2.840656 -2.057308 male
2 -0.758351 -1.166275 -0.464505 famale
c 1 1.419848 -1.046008 -1.126064 male
2 -1.613154 1.410900 -1.241607 male
  • 为df增加一列

In [209]:
df.groupby('key1').mean()


Out[209]:
la beijing shanghai
lb boy girl boy
key1
famale -0.052374 -1.131559 -0.789112
male -0.404035 -0.833581 -1.568599
  • groupby()函数中参数可直接为columns中某列的索引,此时以某列为基准进行分组计算,

In [211]:
key2 = ['bat','abc','bat','abc','abc','abc']
df.groupby(key2).sum()


Out[211]:
la beijing shanghai
lb boy girl boy
abc -0.298053 -1.898226 -3.945894
bat -1.422835 -3.699217 -3.906725
  • 与前面Series对象的分组类似,利用任意序列或数组对DataFrame数据进行分组计算,不需要该数据一定在数据表中,前提是要两者等长

In [212]:
df


Out[212]:
la beijing shanghai key1
lb boy girl boy
one two
a 1 -0.226582 -0.858561 -1.849417 male
2 0.653603 -1.096843 -1.113718 famale
b 1 -1.196253 -2.840656 -2.057308 male
2 -0.758351 -1.166275 -0.464505 famale
c 1 1.419848 -1.046008 -1.126064 male
2 -1.613154 1.410900 -1.241607 male

In [222]:
df['key2'] = ['bat','abc','bat','abc', 'abc', 'abc']
df


Out[222]:
la beijing shanghai key1 key2
lb boy girl boy
one two
a 1 -0.226582 -0.858561 -1.849417 male bat
2 0.653603 -1.096843 -1.113718 famale abc
b 1 -1.196253 -2.840656 -2.057308 male bat
2 -0.758351 -1.166275 -0.464505 famale abc
c 1 1.419848 -1.046008 -1.126064 male abc
2 -1.613154 1.410900 -1.241607 male abc
  • 为df再增加一列

In [223]:
df.groupby(['key1', 'key2']).mean()


Out[223]:
la beijing shanghai
lb boy girl boy
key1 key2
famale abc -0.052374 -1.131559 -0.789112
male abc -0.096653 0.182446 -1.183836
bat -0.711417 -1.849608 -1.953362
  • 可以利用两或多个索引进行分组和计算
  • 有关分组聚合应用的进一步使用,可参考官方文档

3. 透视表(Pivot table)

是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具,可以动态地根据一 个或多个键对数据进行聚合改变它们的版面布置,并根据行和列上的分组键将数据分配到各个矩形区域,以便按照不同方式分析数据。


In [234]:
df = pd.DataFrame({'A' : ['one', 'one', 'two', 'three'] * 3,
     'B' : ['A', 'B', 'C'] * 4,
     'C' : ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
     'D' : np.random.randn(12),
     'E' : np.random.randn(12)})
df


Out[234]:
A B C D E
0 one A foo 0.252884 0.336282
1 one B foo -0.119435 -0.222245
2 two C foo 0.130536 2.150678
3 three A bar -1.801965 0.866635
4 one B bar 0.724717 2.154735
5 one C bar 0.713373 -0.538733
6 two A foo -0.063114 0.026495
7 three B foo -0.505313 0.779623
8 one C foo -0.053516 0.256542
9 one A bar 0.630396 0.424185
10 two B bar 0.195592 0.971294
11 three C bar -0.209705 -1.513072
  • 利用dict新建立一个DataFrame对象

In [239]:
pivot_df = df.pivot_table(values='D', index=['A', 'B'], columns=['C'])
pivot_df


Out[239]:
C bar foo
A B
one A 0.630396 0.252884
B 0.724717 -0.119435
C 0.713373 -0.053516
three A -1.801965 NaN
B NaN -0.505313
C -0.209705 NaN
two A NaN -0.063114
B 0.195592 NaN
C NaN 0.130536
  • 利用pivot_table()函数,选择'D'列的数据,以['A', 'B']为index索引,以['C']为columns索引来列出数据
  • pivot_table()函数中的各个参数,均为可选参数,但index或columns必须至少有一个,其值作为透视分组的依据

In [242]:
pivot_df['bar']


Out[242]:
A      B
one    A    0.630396
       B    0.724717
       C    0.713373
three  A   -1.801965
       B         NaN
       C   -0.209705
two    A         NaN
       B    0.195592
       C         NaN
Name: bar, dtype: float64
  • 本例中pivot_table()函数透视的结果,仍然是DataFrame对象,因此可以对该DataFrame对象施加前面介绍的各类操作与计算