本文也是秦路老师python教程的学习笔记。这篇也是发给超哥看的:很多人说python很简单很好学,也有很多人说python没有java和c的功能强大。但是这都不重要,重要的是我们想学了,想画图也好做数据分析也罢,想学了就直接开始就好。听一个python的课程,大概20个小时,复习加练习40个小时,其实只要一个月就可以上手了。
如文章所示,基本是课程内容的复现与基本概念的介绍,行文逻辑混乱,排版也不好看。但是,没关系,学习就是从模仿开始的。因为想学就去做,just to do it,不要求自己做出来东西好看,质量高,只是要求行动起来。想学python就去直接去学,不想学更没关系没必要因为别人在做这个就去学。说给超哥,也说给自己。想做的就去做,哪怕做的不够好,都没关系的。
目录:
1 numpy
2 pandas
3 用pandas做数据分析案例
1 numpy
numpy的数据结构是n维的数组ndarray。Python的list虽然也能表示,但是不高效。首先载入numpy,as是命名为别名,np是numpy公认的的简写。
数组的创建使用numpy中array函数,前面加np。如图所示将列表list转换成了numpy的数组。
嵌套列表会被转换为多维数组,也可以叫做矩阵。array数组要注意的是,它内部的元素必须是相同类型,比如数值或者字符串。可以用dtype查询其类型,不用加括号。
numpy的数据类型比较丰富,当我们转换数据格式的时候,可以使用astype函数。
数组的计算很方便,不要大量的循环就可以批量运算。
数组索引和列表相同,使用方括号和数字,也可以直接赋值。
用递归对多维数组进行筛选,如果省略了后面的索引,则返回次一级的维度,这和list一样。
就数据分析而言,pandas是一个更常使用的包,有点像我们熟悉的excel和sql。
2 pandas
pandas有两个主要的数据结构,Series和DataFrame。Series类似于一维数组,和numpy的array接近,由一组数据和数据标签组成。数据标签有索引的作用。
2.1 Series
加载pandas包,通过Series函数生成一个对象。我们很明显地看到,在jupyter上它的样式不同于array,它是竖着的。右边是我们输入的一组数据,左边是数据的索引,即标签。数据标签是pandas区分于numpy的重要特征。索引不一定是从0开始的数字,它可以被定义。
索引的概念有点像SQL中的主键,不过它的功能更强大,分析师能够很轻松的通过索引选取一个数据或者一组数据。
index函数可以显示Series的索引。Series和array一样,通过方括号选取数据,当要选择多个数据时,需要使用列表。如果数据是一个字典,也能直接创建Series。
这时候,字典的key就是Series的索引。
Series有自动对齐索引的功能,当自定义的索引qinqin和字典队员不上时,会自动选择NaN,即结果为空,表示缺失。缺失值的处理会在后续讲解。
2.2 DataFrame
Series是一维的数据结构,DataFrame是一个表格型的数据结构,它含有不同的列,每列都是不同的数据类型。我们可以把DataFrame看作Series组成的字典,它既有行索引也有列索引。它类似一张excel表格或者SQL,只是功能更强大。构建DataFrame的方法有很多,最简单的的是导入一个字典。
DataFrame会自动补充索引,并且将字典的key作为列标签,即column。在这里,dict的key顺序是DataFame的顺序,不再是无序的。DataFrame中可以通过info函数直接查看数据类型和统计。
列名后面是列的非空值统计量,以及数据类型,最后一行是DataFrame占用的内存大小,对于pandas来说,千万行几百兆的数据处理起来都很方便。
DataFrame的数据类型变更和numpy一样,用astype就行。df.age = df.age.astype。DataFrame的索引方式和Series一样,它选取的是列。
返回的是一组Series,索引和原DataFrame一致。除了方括号选取,DataFrame还有更简单的选取方法。
行可以直接以切片的形式获取行,切片的作用和数组一样。但是,切片无法用单独的数字选取,只能用冒号切选范围。列可以通过赋值的方式修改和添加,当列的名称是全新,则会在DataFrame的最右边自动加上新的一列。
列表和数组都可以赋值到列,长度必须匹配,列表是按照顺序,数组的话,可以按index参数设置的索引对应,若为空,则填上缺失值。DataFrame的index不能改变,这是为了数据的安全性,但是可以通过index函数获取详情。
DataFrame在数据选取方面也很方便。
它支持常用的逻辑判断,比如通过布尔数组过滤出我们需要的结果。
df.sex == '男'返回了一个布尔数组(ps:是两个等号,一个等号是赋值),然后通过布尔数组找出df中符合条件为true的结果。现在我们希望找年龄在20以下,且性别为男的样本。
这里用到布尔类型数据的计算公式,true and true 为true,false and true 为false。pandas中的逻辑符号,并且是&,或者是|。接下来过滤出性别为女,或者年龄为18的人。
当逻辑条件复杂的时候,这种写法并不好看,比如性别为男,且年龄在18岁,以及性别为女,且年龄在18岁以上的两类人群,这种过滤条件就比较复杂了。pandas中可以用query函数以类SQL语言执行查询。
query中可以直接使用列名,它的功能远不至于此,大家有兴趣可以深入学习。
DataFrame可以直接在列上进行运算,当DataFrame和DataFrame之间运算时,按索引进行加减乘除。
如果直接用加法,若列名匹配不上,会产生NaN 的缺失值,我们可以在后续后续fillna函数填充。另外一种方法是add函数,它可以直接通过参数选择填充值。减乘除对应sub、mul、div。DataFrame还有两个常用函数,又特别容易搞混的是,iloc和loc。
它们都是通过索引选取行,iloc是通过所在行的数字为索引,loc是所在行的标签为索引,简单讲,iloc是第几行,loc是标签。当索引没有标签时,loc和iloc等价。两者支持冒号的范围选择。当行和列需要同时选择的时候,用逗号分割,逗号前是想要选择的行,逗号后是想要选择的列。
3 用pandas做数据分析案例
数据分析的过程包括数据读取、数据概述、数据清洗,整理分析和数据可视化。
首先加载数据。
在pandas中,常用的载入函数是read_csv、read_excel、read_table和read_sql。这里用read_csva,read_csv有几个重要参数:①encoding用来读取csv格式的编码,包括gb2312和utf-8;②sep是分割符,有些csv文件用逗号分割列,有些是分号,有些是\t,这些都需要具体设置;③header表示是否使用表头作为列名,默认是;④names可以为列设置额外的名字,比如csv中的表头是中文,使用name可以转换成英文。
因为数据集df的数据太多,如果我们只是大致浏览一下,可以使用head()或者tail()来看前5行或者后5行,也可以用sample(5)随机选取5行看一下。
在这个数据集中,薪资中的数据是最脏的,后续需要拆成两列,看一下有没有重复数据。
unique函数可返回唯一值,数据集中positionId是职位ID,唯一值。配合len函数计算出唯一值共有5031个,说明有多出来的重复值。这里使用drop_duplicates清洗掉。ps:建议每次删除数据后都重新设置一下索引。
drop_duplicates函数中也有一些重要参数:keep是保留方式,first是留第一个,last还是删除前面,保留最后一个。duplicated函数功能类似,不过它返回的是布尔值。接下来处理salary薪资。目的是计算出薪资下限以及薪资上限。
数据类型转换为数字,使用匿名函数lamba。很多时候我们并不需要复杂地使用def定义函数,而用lamdba作为一次性函数。axis是apply中的参数,axis=1表示将函数用在行,axis=0则是列。
到这里,数据清洗的工作完成。
对数据进行几个描述统计。
value_counts是计数,统计所有非零元素的个数,以降序的方式输出Series。数据中可以看到北京招募的数据分析师是最多的。ps:北京的岗位是多一些,包括风控策略的岗位在上海深圳杭州都没有北京多。我们可以再分析数据分析师的学历要求,工作年限要求等。针对数据分析师的薪资,我们用describe函数。
数据分析师的薪资的平均数是17k,中位数是15k,差别不大,最大薪资在75k,应该是数据科学家或者数据分析总监档位的水平。标准差在8.99k,有一定的波动性,大部分分析师薪资在17+—9k之间。一般分类数据用value_counts,数值数据用describe,这是最常用的两个统计函数。说了这么多文字,还是不够直观,我们用图表说话。pandas自带绘图函数,它是以matplotlib包为基础封装,所以两者能够结合使用。
%matplotlib inline是jupyter自带的方式,允许图表在cell中输出。
用hist函数很方便的就绘制除出直方图,比excel快多了。图表列出了数据分析师薪资的分布,因为大部分薪资集中20k以下,为了更细的粒度。将直方图的宽距缩小。
数据分布呈双峰状,因为原始数据来源于招聘网站的爬取,薪资非常容易集中在某个区间,不是真实薪资的反应(10~20k的区间,以本文的计算公式,只会粗暴地落在15k,而非均匀分布)。数据分析的一大思想是细分维度,现在观察不同城市、不同学历对薪资的影响。箱线图是最佳的观测方式。
图表的标签出了问题,出现了白框,主要是图表默认用英文字体,而这里的都是中文,导致了冲突。所以需要改用matplotlib。
首先加载字体管理包,设置一个载入中文字体的变量,不同系统的路径不一样。boxplot是我们调用的箱线图函数,column选择箱线图的数值,by是选择分类变量,figsize是尺寸。ax.get_xticklabels获取坐标轴刻度,即无法正确显示城市名的白框,利用set_fontpeoperties更改字体。于是获得了我们想要的箱线图。改变字体还有其他方法,大家可以网上搜索关键字「matplotlib 中文字体」,都有相应教程。从图上我们看到,北京的数据分析师薪资高于其他城市,尤其是中位数。上海和深圳稍次,广州甚至不如杭州。
从学历看,博士薪资遥遥领先,虽然在top区域不如本科和硕士,这点我们要后续分析。大专学历稍有弱势。
工作年限看,薪资的差距进一步拉大,毕业生和工作多年的不在一个梯度。虽然没有其他行业的数据对比,但是可以确定,数据分析师的职场上升路线还是挺光明的。到目前为止,我们了解了城市、年限和学历对薪资的影响,但这些都是单一的变量,现在想知道北京和上海这两座城市,学历对薪资的影响。
在by传递多个值,箱线图的刻度自动变成元组,也就达到了横向对比的作用(这方法其实并不好,以后会讲解其他方式)。这种方法并不适宜元素过多的场景。从图上可以看到,不同学历背景下,北京都是稍优于上海的,北京愿意花费更多薪资吸引数据分析师,而在博士这个档次,也是一个大幅度的跨越。我们不妨寻找其中的原因。在pandas中,需要同时用到多个维度分析时,可以用groupby函数。它和SQL中的group by差不多,能将不同变量分组。
上图是标准的用法,按city列,针对不同城市进行了分组。不过它并没有返回分组后的结果,只返回了内存地址。这时它只是一个对象,没有进行任何的计算,现在调用groupby的count方法。
它返回的是不同城市的各列计数结果,因为没有NaN,每列结果都是相等的。现在它和value_counts等价。
换成mean,计算出了不同城市的平均薪资。因为mean方法只针对数值,而各列中只有avgSalary是数值,于是返回了这个唯一结果。
groupby可以传递一组列表,这时得到一组层次化的Series。按城市和学历分组计算了平均薪资。
后面再调用unstack方法,进行行列转置,这样看的就更清楚了。在不同城市中,博士学历最高的薪资在深圳,硕士学历最高的薪资在杭州。北京综合薪资最好。这个分析结论有没有问题呢?不妨先看招聘人数。
这次换成count,我们在groupby后面加一个avgSalary,说明只统计avgSalary的计数结果,不用混入相同数据。图上的结果很明确了,要求博士学历的岗位只有6个,所谓的平均薪资,也只取决于公司开出的价码,波动性很强,毕竟这只是招聘薪资,不代表真实的博士在职薪资。这也解释了上面几个图表的异常。groupby其实和数据透视表很相似。pandas其实有专门的数据透视函数,在另外一方面,groupby确实能完成不少透视工作。接下来计算不同公司招聘的数据分析师数量,并且计算平均数。
这里使用了agg函数,同时传入count和mean方法,然后返回了不同公司的计数和平均值两个结果。所以前文的mean,count,其实都省略了agg。agg除了系统自带的几个函数,它也支持自定义函数。
上图用lamba函数,返回了不同公司中最高薪资和最低薪资的差值。agg是一个很方便的函数,它能针对分组后的列数据进行丰富多彩的计算。但是在pandas的分组计算中,它也不是最灵活的函数。
自定义了函数topN,将传入的数据计数,并且从大到小返回前五的数据。然后以city聚合分组,因为求的是前5的公司,所以对companyShortName调用topN函数。同样的,如果我想知道不同城市,各职位招聘数前五,也能直接调用topN
可以看到,虽说是数据分析师,其实有不少的开发工程师,数据产品经理等。这是抓取下来数据的缺点,它反应的是不止是数据分析师,而是数据领域。不同城市的需求不一样,北京的数据产品经理看上去要比上海高。agg和apply是不同的,虽然某些方法相近,比如求sum,count等,但是apply支持更细的粒度,它能按组进行复杂运算,将数据拆分合并,而agg则必须固定为列。运用group by,我们已经能随意组合不同维度。接下来配合group by作图。
多重聚合在作图上面没有太大差异,行列数据转置不要混淆即可。
上述的图例我们都是用pandas封装过的方法作图,如果要进行更自由的可视化,直接调用matplotlib的函数会比较好,它和pandas及numpy是兼容的。plt已经在上文中调用并且命名。
上图将上海和北京的薪资数据以直方图的形式进行对比。因为北京和上海的分析师人数相差较远,所以无法直接对比,需要用normed参数转化为密度。设置alpha透明度,它比箱线图更直观。另外一种分析思路是对数据进行深加工。我们将薪资设立出不同的level。
cut的作用是分桶,它也是数据分析常用的一种方法,将不同数据划分出不同等级,也就是将数值型数据加工成分类数据,在机器学习的特征工程中应用比较多。cut可以等距划分,传入一个数字就好。这里为了更好的区分,我传入了一组列表进行人工划分,加工成相应的标签。
用lambda转换百分比,然后作堆积百分比柱形图(matplotlib好像没有直接调用的函数)。这里可以较为清晰的看到不同等级在不同地区的薪资占比。它比箱线图和直方图的好处在于,通过人工划分,具备业务含义。0~3是实习生的价位,3~6是刚毕业没有基础的新人,整理数据那种,6~10是有一定基础的,以此类推。现在只剩下最后一列数据没有处理,标签数据。
现在的目的是统计数据分析师的标签。它只是看上去干净的数据,元素中的[]是无意义的,它是字符串的一部分,和数组没有关系。你可能会想到用replace这类函数。但是它并不能直接使用。df_clean.positionLables.replace会报错,为什么呢?因为df_clean.positionLables是Series,并不能直接套用replace。apply是一个好方法,但是比较麻烦。这里需要str方法。
str方法允许我们针对列中的元素,进行字符串相关的处理,这里的[1:-1]不再是DataFrame和Series的切片,而是对字符串截取,这里把[]都截取掉了。如果漏了str,就变成选取Series第二行至最后一行的数据,切记。
使用完str后,它返回的仍旧是Series,当我们想要再次用replace去除空格。还是需要添加str的。现在的数据已经干净不少。positionLables本身有空值,所以要删除,不然容易报错。再次用str.split方法,把元素中的标签按「,」拆分成列表。
这里是重点,通过apply和value_counts函数统计标签数。因为各行元素已经转换成了列表,所以value_counts会逐行计算列表中的标签,apply的灵活性就在于此,它将value_counts应用在行上,最后将结果组成一张新表。
用unstack完成行列转换,看上去有点怪,因为它是统计所有标签在各个职位的出现次数,绝大多数肯定是NaN。
将空值删除,并且重置为DataFrame,此时level_0为标签名,level_1为df_index的索引,也可以认为它对应着一个职位,0是该标签在职位中出现的次数,之前我没有命名,所以才会显示0。部分职位的标签可能出现多次,这里忽略它。
最后用groupby计算出标签出现的次数。到这里,已经计算出我们想要的结果。