利用python进行数据分析之pandas入门(二)

96
凌岸_ing
2017.05.31 12:55* 字数 4315

5.3汇总和计算描述性统计

pandas对象拥有一组常用的数学和统计方法。他们大部分都属于约简和汇总统计,用于从Series中提取单个值(如mean或sum)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,他们都是基于没有缺失数据的假设而构建的。

下面是一个简单的DataFrame:

In [90]: df=DataFrame([[1.4,np.nan],[7.1,-4.5],[np.nan,np.nan],[0.75,-1.3]],index=['a','b','c','d'],columns=['one','two'])

In [91]: df

Out[91]:

one two

a 1.40 NaN

b 7.10 -4.5

c NaN NaN

d 0.75 -1.3

调用DataFrame的sum方法将会返回一个含有列的小计的Series:

In [92]: df.sum()

Out[92]:

one 9.25

two -5.80

dtype: float64

传入axis=1 将会按行进行求和计算:

In [93]: df.sum(axis=1)

Out[93]:

a 1.40

b 2.60

c 0.00

d -0.55

dtype: float64

NA值将会被自动剔除,除非整个切片都是NA。通过skipna选项可以禁用该功能:

In [94]: df.mean(axis=1,skipna=False)

Out[94]:

a NaN

b 1.300

c NaN

d -0.275

dtype: float64

表5-9给出了约简方法常用选项

有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最大值或最小值的索引):

In [95]: df.idxmax()

Out[95]:

one b

two d

dtype: object

一些方法则是累计型的:

In [96]: df.cumsum()

Out[96]:

one two

a 1.40 NaN

b 8.50 -4.5

c NaN NaN

d 9.25 -5.8

还有一些方法,既不是约简型也不是累计型。

In [97]: df.describe()

Out[97]:

one two

count 3.000000 2.000000

mean 3.083333 -2.900000

std    3.493685 2.262742

min    0.750000 -4.500000

25%  1.075000 -3.700000

50%  1.400000 -2.900000

75%  4.250000 -2.100000

max  7.100000 -1.300000

对于非数值型的数据,describe会产生另一种汇总统计:

In [98]: obj=Series(['a','a','b','c']*4)

In [99]: obj.describe()

Out[99]:

count  16

unique  3

top        a

freq     8

dtype:    object

表5-10列出了所有与描述统计相关的方法。

5.3.1相关系数和协方差

有些汇总统计(如先关系数和协方差)是通过参数计算出来的。

5.3.2唯一值、值计数以及成员资格

从一维Series的值中抽取信息。以下这个Series为例:

In [107]: obj=Series(['c','a','d','a','a','b','b','c','c'])

第一个函数是unique,可以得到Sseries中的唯一数组:

In [108]: uniques=obj.unique()

In [109]: uniques

Out[109]: array(['c', 'a', 'd', 'b'], dtype=object)

返回值的唯一未排序的,如果需要的话,可以对结果再次进行排序(unique.sort())

value_counts 用于计算一个Series中各值出现的频率:

In [110]: obj.value_counts()

Out[110]:

c 3

a 3

b 2

d 1

dtype: int64

为了方便查看,结果Series是按值频率降序排列的。

value_counts 还是一个顶级的pandas方法,可用于任何数组的或序列:

In [111]: pd.value_counts(obj.values,sort=False)

Out[111]:

a 3

c 3

b 2

d 1

dtype: int64

最后isin,它用于判断矢量化最集合的成员资格,可用于选取Series中或DataFrame列中的数据的子集:

In [112]: mask=obj.isin(['b','c'])

In [113]: mask

Out[113]:

0 True

1 False

2 False

3 False

4 False

5 True

6 True

7 True

8 True

dtype: bool

In [114]: obj[mask]

Out[114]:

0 c

5 b

6 b

7 c

8 c

dtype: object

有时,你可能需要得到DataFrame中多个相关的一张柱状图。

In [115]: data=DataFrame({'Qu1':[1,3,4,3,4],'Qu2':[2,3,1,2,3],'Qu3':[1,5,2,4,4]})

In [116]: data

Out[116]:

Qu1 Qu2 Qu3

0 1 2 1

1 3 3 5

2 4 1 2

3 3 2 4

4 4 3 4

In [117]: result=data.apply(pd.value_counts).fillna(0)

In [118]: result

Out[118]:

Qu1 Qu2 Qu3

1 1.0 1.0 1.0

2 0.0 2.0 1.0

3 2.0 2.0 0.0

4 2.0 0.0 2.0

5 0.0 0.0 1.0

5.4处理缺失数据

处理缺失数据(missing data)在大部分的数据分析应用中很常见。pandas的设计目标之一就是让缺失数据的处理任务尽量轻松。

pandas使用浮点数NaN(Not a Number)表示浮点和非浮点数组中的缺失数据。只是一个便于检测出来的标记而已:

In [119]: string_data=Series(['aardvark','arrichoke',np.nan,'avocado'])

In [120]: string_data

Out[120]:

0    aardvark

1    arrichoke

2          NaN

3      avocado

dtype: object

In [121]: string_data.isnull()

Out[121]:

0    False

1    False

2    True

3    False

dtype: bool

python内置的None值也会被当做NA处理:

In [123]: string_data.isnull()

Out[123]:

0 True

1 False

2 True

3 False

dtype: bool

5.4.1滤除缺失数据

对于一个Series,dropna返回一个仅含有非空数据的和索引值的Series:

In [124]: from numpy import nan as NA

In [125]: data=Series([1,NA,3.5,NA,7])

In [126]: data.dropna()

Out[126]:

0 1.0

2 3.5

4 7.0

dtype: float64

还可以通过布尔型索引达到这个目的:

In [127]: data[data.notnull()]

Out[127]:

0 1.0

2 3.5

4 7.0

dtype: float64

对于DataFrame对象,如果希望丢弃全NA或NA的行货列,dropna默认丢弃任何含有缺失值的行:

In [129]: data=DataFrame([[1.,6.5,3.],[1.,NA,NA],[NA,NA,NA],[NA,6.5,3.]])

In [130]: cleaned=data.dropna()

In [131]: data

Out[131]:

0 1 2

0 1.0 6.5 3.0

1 1.0 NaN NaN

2 NaN NaN NaN

3 NaN 6.5 3.0

In [132]: cleaned

Out[132]:

0 1 2

0 1.0 6.5 3.0

传入how=‘all’ 将只丢弃全为NA的那些行:

In [133]: data.dropna(how='all')

Out[133]:

0 1 2

0 1.0 6.5 3.0

1 1.0 NaN NaN

3 NaN 6.5 3.0

要用这种方式丢弃列,只需传入axis=1即可:

In [134]: data[4]=NA

In [135]: data

Out[135]:

0 1 2 4

0 1.0 6.5 3.0 NaN

1 1.0 NaN NaN NaN

2 NaN NaN NaN NaN

3 NaN 6.5 3.0 NaN

In [136]: data.dropna(axis=1,how='all')

Out[136]:

0 1 2

0 1.0 6.5 3.0

1 1.0 NaN NaN

2 NaN NaN NaN

3 NaN 6.5 3.0

另一个滤出DataFrame行的问题涉及时间序列数据。

假设你只想留下一部分观测数据,可以用thresh参数实现此目的:

In [137]: df=DataFrame(np.random.randn(7,3))

In [138]: df.ix[:4,1]=NA;df.ix[:2,2]=NA

In [139]: df

Out[139]:

0 1 2

0 0.452896 NaN NaN

1 1.071009 NaN NaN

2 -0.135804 NaN NaN

3 0.010116 NaN 0.064880

4 -1.038639 NaN -0.756553

5 0.738134 -1.505505 -0.052306

6 -1.712077 0.386785 0.436945

In [140]: df.dropna(thresh=3)

Out[140]:

0 1 2

5 0.738134 -1.505505 -0.052306

6 -1.712077 0.386785 0.436945

5.4.2填充缺失数据

如果不想滤除数据,而是希望填充数据。

通过一个常数来fillna就会将缺失值替换为那个常数值:

In [141]: df.fillna(0)

Out[141]:

0 1 2

0 0.452896 0.000000 0.000000

1 1.071009 0.000000 0.000000

2 -0.135804 0.000000 0.000000

3 0.010116 0.000000 0.064880

4 -1.038639 0.000000 -0.756553

5 0.738134 -1.505505 -0.052306

6 -1.712077 0.386785 0.436945

通过一个字典调用fillna,就可以实现对不同的列填充不同的值:

In [142]: df.fillna({1:0.5,3:-1})

Out[142]:

0 1 2

0 0.452896 0.500000 NaN

1 1.071009 0.500000 NaN

2 -0.135804 0.500000 NaN

3 0.010116 0.500000 0.064880

4 -1.038639 0.500000 -0.756553

5 0.738134 -1.505505 -0.052306

6 -1.712077 0.386785 0.436945

fillna默认会返回新对象,但是也可以对现有的对象进行修改:

In [143]: _=df.fillna(0,inplace=True)

In [144]: df

Out[144]:

0 1 2

0 0.452896 0.000000 0.000000

1 1.071009 0.000000 0.000000

2 -0.135804 0.000000 0.000000

3 0.010116 0.000000 0.064880

4 -1.038639 0.000000 -0.756553

5 0.738134 -1.505505 -0.052306

6 -1.712077 0.386785 0.436945

对reindex有效的那些插值方法也可以用于fillna:

In [145]: df=DataFrame(np.random.randn(6,3))

In [146]: df.ix[2:,1]=NA;df.ix[4:,2]=NA

In [147]: df

Out[147]:

0 1 2

0 0.501394 -1.735750 0.197643

1 2.099104 1.441581 0.743717

2 -0.451567 NaN -0.150315

3 -0.032894 NaN 0.418310

4 1.285966 NaN NaN

5 -0.058611 NaN NaN

In [148]: df.fillna(method='ffill')

Out[148]:

0 1 2

0 0.501394 -1.735750 0.197643

1 2.099104 1.441581 0.743717

2 -0.451567 1.441581 -0.150315

3 -0.032894 1.441581 0.418310

4 1.285966 1.441581 0.418310

5 -0.058611 1.441581 0.418310

In [149]: df.fillna(method='ffill',limit=2)

Out[149]:

0 1 2

0 0.501394 -1.735750 0.197643

1 2.099104 1.441581 0.743717

2 -0.451567 1.441581 -0.150315

3 -0.032894 1.441581 0.418310

4 1.285966 NaN 0.418310

5 -0.058611 NaN 0.418310

只要稍微动脑子,就可以利用fillna的=实现许多的别的功能。

比如,传入Series的平均值或中位数:

In [150]: data=Series([1.,NA,3.5,NA,7])

In [151]: data.fillna(data.mean())

Out[151]:

0 1.000000

1 3.833333

2 3.500000

3 3.833333

4 7.000000

dtype: float64

5.5层次化索引

层次化索引,是你能在一个轴上拥有多个(两个以上)索引级别。

即能以低纬度形式处理高纬度数据。

我们来创建一个Series,并用一个由列表或数组组成的列表作为索引:

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

In [153]: data

Out[153]:

a  1    0.024162

2    0.969526

3  -0.215712

b  1    1.136933

2  -0.158487

3    0.482377

c  1  -0.551888

2  -1.090750

d  2  -0.073204

3  -1.217613

dtype: float64

这就是带有MultiIndex 索引的Series 的格式输出格式。索引之间的“间隔”表示“直接使用上面的标签”:

In [154]: data.index

Out[154]:

MultiIndex(levels=[[u'a', u'b', u'c', u'd'], [1, 2, 3]],

labels=[[0, 0, 0, 1, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 1, 2, 0, 1, 1, 2]])

对于一个层次化索引的对象,选取数据子集的操作很简单:

data['b']

Out[155]:

1    1.136933

2  -0.158487

3    0.482377

dtype: float64

data['b':'c']

Out[156]:

b  1    1.136933

2  -0.158487

3    0.482377

c  1  -0.551888

2  -1.090750

dtype: float64

data.ix[['b','d']]

Out[158]:

b  1    1.136933

2  -0.158487

3    0.482377

d  2  -0.073204

3  -1.217613

dtype: float64

甚至还可以在“内层”中进行选取:

In [159]: data[:,2]

Out[159]:

a    0.969526

b  -0.158487

c  -1.090750

d  -0.073204

dtype: float64

层次化索引的在数据重塑和基于分组的操作(如透视表生成)中扮演重要角色。比如说,这段数据可以通过unstack方法被重新安排到一个DataFrame中:

n [160]: data.unstack()

Out[160]:

1        2        3

a  0.024162  0.969526 -0.215712

b  1.136933 -0.158487  0.482377

c -0.551888 -1.090750      NaN

d      NaN -0.073204 -1.21761

unstack的逆运算是stack:

In [161]: data.unstack().stack()

Out[161]:

a  1    0.024162

2    0.969526

3  -0.215712

b  1    1.136933

2  -0.158487

3    0.482377

c  1  -0.551888

2  -1.090750

d  2  -0.073204

3  -1.217613

dtype: float64

对于一个DataFrame,每条轴都可以分层索引:

In [162]: frame=DataFrame(np.arange(12).reshape((4,3)),index=[['a','a','b','b'],[1,2,1,2]],columns=[['Ohio','Ohio','Colorado'],['Green','Red','Green']])

In [163]: frame

Out[163]:

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 [164]: frame.index.names=['key1','key2']

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

In [166]: frame

Out[166]:

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

有了分部的列索引,因此可以选取列分组:

In [167]: frame['Ohio']

Out[167]:

color      Green  Red

key1 key2

a    1        0    1

2        3    4

b    1        6    7

2        9  10

可以单独的创建MultiIndex 然后复用,上面那个DataFrame中的(分级)列可以这样创建:

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

5.5.1重排分级顺序

如果需要调整某条轴上各级别的顺序,或根据指定界级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换级别的新对象(但数据不会发生变化):

In [169]: frame.swaplevel('key1','key2')

Out[169]:

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

而sortleval则根据单个级别中的值对数据进行排序(稳定的)。交流级别时,常常也会用到sortlevel,这样最终的结果就是有序的了:

In [170]: frame.sortlevel(1)

Out[170]:

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 [172]: frame.swaplevel(0,1).sortlevel(0)

Out[172]:

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

5.5.2根据级别汇总统计

许多对DataFrame 和Series的描述和汇总统计都有一个leve选项,用于指定在某条轴上对求和的级别,再也上面的那个DataFrame为例子,我们根据行或列上的级别进行求和:

In [173]: frame.sum(level='color',axis=1)

Out[173]:

color Green Red

key1 key2

a 1 2 1

2 8 4

b 1 14 7

2 20 10

In [174]: frame.sum(level='key2')

Out[174]:

state Ohio Colorado

color Green Red Green

key2

1 6 8 10

2 12 14 16

5.5.3使用DataFrame的列

将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame要的列。

In [14]: frame=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 [15]: frame

Out[15]:

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:

In [17]: frame2

Out[17]:

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中移除,但是也可以将其保留:

In [18]: frame.set_index(['c','d'],drop=False)

Out[18]:

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 [20]: frame2.reset_index()

Out[20]:

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

5.6其他有关pandas的话题

5.6.1整数索引

操作由整数索引的pandas对象常常会让新手抓狂,因为他们跟内置的python数据结构(如列表和元组)在索引语义上有些不同。

例如,你可能认为下面的代码不会报错。

In [24]: ser=Series(np.arange(3.))

In [25]: ser[-1]

---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

in ()

----> 1 ser[-1]

pandas\src\hashtable_class_helper.pxi in pandas.hashtable.Int64HashTable.get_item (pandas\hashtable.c:85

08)()

KeyError: -1L

虽然pandas会“求助于”整数索引,但是没有哪种方法能够既不引入bug,又能解决问题的。

我们有一个含有0,1,2的索引,但是很难推断出用户想要什么:

In [26]: ser

Out[26]:

0    0.0

1    1.0

2    2.0

dtype: float64

相反,对于一个非整数索引,就没有这样的歧义:

In [30]: ser2=Series(np.arange(3.),index=['a','b','c'])

In [31]: ser2[-1]

Out[31]: 2.0

为了保持良好的一致性,如果你的轴索引含有索引器,那么根据整数进行数据选取的操作蒋总是面向标签的。这也包括用ix进行切片:

In [32]: ser.ix[:1]

Out[32]:

0    0.0

1    1.0

dtype: float64

如果需要可靠的,不考虑索引类型的,基于位置的索引,可以使用Series 的 iget_value 方法和DataFrame的irow和icol方法:

In [33]: ser3=Series(range(3),index=[-5,1,3])

In [35]: ser3.iget_value(2)

Out[35]: 2

In [37]: frame=DataFrame(np.arange(6).reshape(3,2),index=[2,0,1])

In [38]: frame.irow(0)

Out[38]:

0    0

1    1

Name: 2, dtype: int32

随笔