本案例是Pandas数据分析课程【第三章】数据初步探索配套案例。案例使用酒品消耗量数据集,其记录了全球193个国家某年的各类酒品消耗数据,我们对其进行初步探索。
在本案例中,我们将使用全球193个国家某年的各类酒品消耗数据。主要数据集变量如下:
变量名称 | 含义说明 |
---|---|
country | 国家 |
beer_servings | 啤酒消耗量 |
spirit_servings | 烈酒消耗量 |
wine_servings | 红酒消耗量 |
total_litres_of_pure_alcohol | 总酒精消耗量 |
continent | 所在大洲 |
import numpy as np
import pandas as pd
本次数据文件为txt格式,我们使用read_table
读取。
drink = pd.read_table("./input/drinks.txt")
drink.head()
使用默认的参数进行读取出现了问题,可以看到,数据的分隔符应设定为"|",我们设置分隔符参数sep
为"|"。
drink = pd.read_table("./input/drinks.txt",sep="|")
使用head
查看数据前几行,参数n
可以指定行数。查看数据前十行。
drink.head(n=10)
tail
可以查看数据最后几行,同样可以通过参数n
指定行数。查看数据最后五行。
drink.tail(n=5)
2.2.1使用dtypes
查看数据类型
我们可以使用dtypes
查看数据中各变量类型。
drink.dtypes
dtypes
的返回值是一个Series,我们可以使用isinstance
进行确认,isinstance
函数用来判断一个对象是否是一个已知的类型。
isinstance(drink.dtypes,pd.Series)
2.2.2使用astype
修改数据类型
如果我们想更改某些变量的数据类型,可以使用astype
方法。例如,我们将float
类型的啤酒消耗量改为object
类型。
drink.beer_servings.astype('object')[:5]
2.2.3使用select_dtypes
查看特定数据类型的数据
有时,数据中的变量很多,我们只想查看特定类型的变量情况,我们应使用select_dtypes
。可以通过include
参数或exclude
参数选择要包含的数据类型或要剔除的数据类型。例如,我们只查看数据中的浮点型数据。
drink.select_dtypes(include=['float64']).head()
2.3.1使用rename
修改列名
中间的几个列名比较复杂,我们使用rename
对其进行简化。
在参数中,我们设定了inplace
参数为True
,这将不创建新的对象,直接对原始对象进行修改,这一参数在许多DataFrame的方法中都有,后文中我们还会多次见到。
drink.rename(columns={'beer_servings':'beer','spirit_servings':'spirit','wine_servings':'wine','total_litres_of_pure_alcohol':'pure_alcohol'},inplace=True)
drink.columns
2.3.2使用info
初步查看数据类型和大小
使用info
方法初步查看数据的变量类型、缺失值情况、占用空间大小。
drink.info()
这里我们使用了info
的默认参数设置,但若变量数很多,我们也可以设置一些参数来简化输出。
例如:如果不关心数据中的缺失值情况,可以设置null_counts
参数为False
。
drink.info(null_counts=False)
如果不想具体看每一个变量的情况,可以设置verbose
参数为False
。
drink.info(verbose=False)
我们看到info
返回结果的开头行可能会误以为info
的返回也是一个数据框。但事实上,info
方法是直接print
出各种汇总结果的,其没有返回值。我们可以通过type
函数查看返回的类型。
type(drink.info())
可以看到,返回的类型为NoneType,这就说明返回为None。
2.3.3使用describe
查看数据数值统计情况
describe
也是一种常用的数据基本描述方法。
drink.describe()
我们也可以通过调整percentile
参数来获得需要的百分位数。
drink.describe(percentiles=[0.05,0.95])
describe
默认只汇总统计所有连续型变量的情况。如果我们想查看特定类别的列或者排除部分列的结果,可以设定参数include
或exclude
。
例如我们想查看除了数值类型以外的其他类型变量的情况。
drink.describe(exclude=np.number)
值得一提的是,describe
返回的对象其实仍然是一个数据框,因此我们可以很轻松的提取需要的汇总统计量。
type(drink.describe())
des = drink.describe(include="all")
des
des.loc['mean','beer']
这等价于:
drink.beer.mean()
下面我们来对数据中的缺失值进行处理。通过前面的的汇总统计我们看到,beer、spirit和wine三个变量中都存在少量缺失值,continent中存在较多缺失。我们将数据中含有缺失值的行提取出来。
首先,我们使用isnull
。isnull
会对数据框中的每一个元素进行缺失值检查,是缺失值为True
,不是为False
,最终返回一个数据框。
miss = drink.isnull()
miss.head(6)
下面我们要找到存在缺失值的行,我们可以使用any
,并设定axis
参数为1。则当每一行中存在True
时,就返回True
注:如果我们想找到所有列都为缺失值的行,就可以使用all
方法。
miss.any(axis=1)
这样,我们就获得了列中含有缺失值的行的布尔型索引。我们将这部分数据单独查看一下:
drink[miss.any(axis=1)]
可以看到,continent为缺失值的都是北美洲国家,进一步查看原始数据可以发现,其实是因为北美洲的缩写为NA,在读入时被默认为了缺失值。我们对这部分值进行填充。(这提醒我们一定要对缺失原因进行查找,不然可能因此损失有效数据)
设定参数value
指定缺失值的填充值为'NA',设定inplace
参数对原始数据进行修改。
drink.continent.fillna(value='NA', inplace=True)
beer、spirit和wine数据缺失的情况都发生在同一国家,我们将这三条数据直接删除。
dropna
默认会将任意列含有缺失值的行都删除。
drink.dropna(inplace=True)
drink.info()
现在,我们得到了190个国家的完整数据。
2.5.1使用index
可以查看数据的索引
drink.index
2.5.2使用reindex
修改索引
可以使用reindex
修改索引。例如,我们修改索引为0至189的整数。
drink.reindex(range(0,190))
reindex
也可以用来调整列的顺序,这时需要设定axis
参数为'columns'或1
drink.reindex(['beer','spirit','wine','pure_alcohol','country','continent'],axis="columns").head()
2.5.3使用set_index
修改索引
set_index
也是修改索引的一种方法,与reindex
不同的是,set_index
需指定DataFrame中存在的某一列或某几列设定为索引。例如,我们修改索引为'continent'。
drink.set_index('continent')[:5]
这里会默认将指定为索引的列删除,若我们仍想在数据中保留该列,应设定参数drop
为False
。
drink.set_index('continent',drop=False)[:5]
若我们想在原来的索引基础上添加新的变量构成层次化索引,应设定append
参数为True
。
drink.set_index('continent',append=True)[:5]
也可以直接指定一组变量设定层次化索引。
drink2 = drink.set_index(['country','continent'])
drink2.head()
2.5.4使用swaplevel
和reorder_levels
调整索引顺序
若要调整层次化索引的顺序,可以使用swaplevel
。
drink2.swaplevel()[:5]
swaplevel
一次只能交换两层索引的顺序,当数据索引多于两层时,需要指定交换的层。我们构建一个小数据来看一下。
idx = pd.MultiIndex.from_arrays([['a1','a1','a2'],['b1','b2','b2'],['c1','c1','c2'],['d1','d1','d1']])
df = pd.DataFrame([[1,1,1],[2,2,2],[3,3,3]],index=idx)
df
我们记现在的索引顺序为a,b,c,d;若我们想改成c,b,a,d,则交换第一层和第三层索引的顺序即可。
df.swaplevel(0,2)
但若我们想改为c,d,a,b,则需要交换两次才可以。
df.swaplevel(0,2).swaplevel(1,3)
处理这种多层次索引时,使用reorder_levels
会更方便,它可以直接指定所有索引的顺序。还是将原来的顺序改为c,d,a,b。
df.reorder_levels([2,3,0,1])
我们通过corr
来计算各变量的相关系数。
drink.loc[:,['beer','spirit','wine']].corr()
可以看到,三类酒的消耗量都呈正相关。其中,啤酒与红酒最相关,相关系数为0.523;啤酒与烈酒也较相关,相关系数为0.450。corr
默认计算的是Pearson相关系数,我们可以通过设定corr
的参数method
来计算其他的相关系数。
drink.loc[:,['beer','spirit','wine']].corr(method="spearman")
与默认的Pearson相关系数相比,三类酒的Spearman相关系数更大。这主要是因为Pearson相关系数是基于样本的协方差与方差计算,更适用于大样本或正态分布数据。而观察前文describe
的结果我们可以看到本次数据是比较右偏的,不符合正态分布的假设条件。而Spearman秩相关系数是基于数据排序进行计算,对数据的分布没有要求,此时更适合。
我们查找啤酒、烈酒和红酒的消耗量都高于相应酒种消耗量75%分位数的国家。
alcohol=drink.loc[(drink.beer>des.loc['75%','beer'])&(drink.wine>des.loc['75%','wine'])&(drink.spirit>des.loc['75%','spirit'])]
alcohol
alcohol.continent.value_counts()
可以看到,在三种酒类消耗量都高于75%分位数的13个国家中,竟然有11个都是欧洲国家,而唯一上榜的亚洲国家是俄罗斯(其实也可以算作是欧洲国家)。
这里要注意,三个判别式(drink.xx>des.loc[xx,xx])两端的括号不能省略。例如,如下的代码将会报错:
alcohol=drink.loc[drink.beer>des.loc['75%','beer'] & drink.wine>des.loc['75%','wine']]
这是因为"&"运算符的等级是要高于">"的,而我们的实际运算应该是先进行">"运算再进行"&"运算。同理,"|","<","=="等运算时也应该注意这个问题。
为方便筛选,我们设定continent为新的索引。
drink.set_index(keys=["continent"],inplace=True)
drink.head()
下面计算各个大洲各类酒的消耗总量。
提取出各大洲名称。
continent = drink.index.unique()
continent
使用values方法将sum返回值转换为数组。
summarize = drink.loc['AS',['beer','spirit','wine']].sum().values
summarize
循环计算,使用vstack
对结果进行叠加,并将结果转化为数据框。
for name in continent[1:]:
summarize = np.vstack((summarize,drink.loc[name,['beer','spirit','wine']].sum().values))
summarize = pd.DataFrame(data=summarize,columns=['beer','spirit','wine'],index=continent)
summarize
不出所料,欧洲的三类酒的总消耗量都是最高的。特别是红酒消耗量,比其他五个大洲之和还要多。
下面我们计算各类酒占各大洲总消耗量的比重,来看看各大洲对不同酒类的喜好。
首先按列求和,计算各大洲的总消耗量。
summarize.sum(axis=1)
按行相除,Pandas会将$6\times1$的总消耗量广播为$6\times3$,再各元素对应相除。
summarize.div(summarize.sum(axis=1),axis=0)
可以看到,亚洲和北美洲更喜欢烈酒,其他四个大洲都更喜欢啤酒。相比之下,红酒在各个州的消耗占比都较低。
注意,上面我们使用div
并设定参数axis
为0;如果直接使用"/"进行运算是不行的:
summarize/summarize.sum(axis=1)
这是因为"/"默认的是按列相除,如果我们想使用"/"进行运算,就需要先将summarize进行转置:
summarize.T/summarize.sum(axis=1)