Python大数据分析2:Python环境配置

工欲善其事,必先利其器。我们首先来看看怎么准备下基本工作条件,也就是说怎么安装Python。

这里的安装包括两个软件:

一个就是Python,大家可以在官网直接下载。一般点击下载即可,即可根据自己电脑的操作系统选择下载。关于版本,为了讲课保持一致,大家可以选择3.7版本。事实上,不同的版本有时会有些差别,尤其是3.7以前的版本和3.7及以后的版本差别更大。

为此可以在下载页面的下部,寻找到合适的版本,并点击相应的下载功能。我们这里选择的是3.7.4。这个页面的下部就有各种操作系统的版本下载。一般Windows较新版本的电脑可以选择倒数第六个,同时为了方便大家,我在课程讨论群里也直接给出了下载链接。

安装过程很简单,在安装首页建议选择“立刻安装”,并且选择这个最下方的选项,以方便后续操作。其实,这个时候就已经可以进行我们的学习了,但是默认的代码编辑工具并不好用,因此我们接下来再来安装一个更好用的代码编辑工具。

Python有很多开发工具,我们这里使用的是一个非常著名而且好用的,称为Pycharm。大家可以到PyCharm官网上下载。大家点击下载即可。在版本中,可以根据自己的电脑操作系统版本,比如Windows等,选择社区版本,这是一个免费的版本,但是提供了各种常见功能,完全满足我们日常使用。

安装过程很简单,大部分只需点击下一步即可,请注意,默认的版本是英文版,我们为了以后学习的方便,不妨就使用英文版本,反而无需翻译更容易使用和查找资料。安装好后,即可在开始菜单中找到新安装的PyCharm,点击启动即可。可能会出现一些是否需要导入设置信息的提示,但凡遇到此类就直接点击OK确定即可,还可能出现隐私政策说明提示,同意确认即可。还可能出现一个是否同意自动上传用户使用统计状态以便于系统分析完善,我们可以选择允许。接下来会出现一个界面风格的选择,它默认提供了黑色系和白色系两种风格,默认选择黑色系,我这里为了讲授方便和清楚,改选为白色系。此时为了方便,可以直接点选左下角的按钮,忽略其他所有设置选项并选择默认值。

第一次启动,会出现一个项目新建的窗体。所谓项目,是指一个功能组合,比如我们可以为不同的课程作业新建不同的项目来保存,更方便管理。因此,这里就需要创建新项目。

在创建后,系统会提示你给新项目起个名称,你可以改变,也可以不改变,直接点击创建按钮即可开始创建项目。首次创建速度会很慢,因此,大家要耐心等待下。直到完整的PyCharm界面出现。于是我们就打开了PyCharm。安装到此也完成了,本课程的大部分内容都可以在这里不用更换就可以实现学习。下面我们通过一个最简单的例子,先来试一下。

右击左边窗格中的项目名称,选择新建,再选择新建一个Python文件。输入新建的Python文件名称,比如Exec,即可回车确定。此时不妨输入一个最简单的输出功能,输出著名的Hello world!这里使用一个print函数,所谓函数,就是一个功能,打印输出的意思,很简单也很常用,括号里面的东西就是要输出的内容。点击运行菜单,选择运行,出现一个配置界面,不必管它,以后也不会再出现,直接点击Exec运行。如果有修改再次,运行的话,就不用如此麻烦,只需直接点击右上角那个三角形就可以了。

如果你能在屏幕的下方看到那个输出,就说明一切都准备好了,而且安装过程也没有问题。如果有问题,则需要根据具体的情况来百度下或者在课程讨论区里询问下,预祝大家安装顺利。

Python大数据分析1:关于数据分析

大家好,今天开始我们一起来学习Python大数据处理这门课。我想大家一定都是冲着课程介绍中的两个重要关键词来的。一个肯定是Python,另一个就是大数据,把这两个热词搅到一起,会产生什么曼妙的风景呢?不错,确实能产生非常有意思的结果。

但是真正应该引起你注意的却是数据分析。工具和技术都会过时,但是应用需求却往往呈现历史发展的高度相似性。今天是第一次课,所以我想谈谈我对数据分析及其Python在其中应用的理解。

我想大家一定在各种场合中看到类似的宣传,某某企业大数据分析产生什么结论,社交大数据产生什么有趣的观点等等。但是这些其实都只是一种数据分析方法,我想各位在自己的专业学习中也一定会遇到。

比如经济管理类的学生常常需要处理各种财经数据,新闻专业学生也需要分析网络信息,甚至连艺术专业有时都需要通过更为漂亮的可视化效果来展示艺术发展的轨迹。这些都属于数据分析的范畴。只是随着互联网的出现,现在人们进行的数据分析往往呈现出数据量更大的特点,你可以理解为这就是最简单的大数据含义。因此传统的一些数据分析方法逐渐暴露出一些问题,尤其在处理较大规模数据的时候,往往操作的复杂度很大。

比如Excel,大家很都熟悉,你不妨打开它,按下Ctrl+向下箭头组合键,你会发现当前版本支持的最大行数,比如2016专业版最大支持100万行多一些。这种规模的数据其实非常小,我们在这门课的第一个章节的案例练习都已经达到10万条记录。而且这些工具只能处理一些常见的数据分析任务,对于略微复杂灵活一些的,往往无法实现,甚至连可视化图表的功能也较为固化,想自由设计和组装合适的外观并不容易。

大家所看到的这个图其实是利用不到20行Python代码写出来了可视化效果,展示了花朵分类的结果,大家感觉到了这种方便了吗?

这里我们提到了Python,是的,这就是一门非常有用的工具,你可以认为它是一种编程语言,不过大家可能听到名称就会觉得似乎太难了吧,还要编程?其实,你倒不如理解它为一个软件工具,可以非常简单轻松的实现各种常见的数据分析任务,因此,我们今天所讲的大数据分析就利用这个工具来讲,它就是专门解决这个问题的,而且在易用性方面,你学习过就会知道它的魅力和特点了。

说句实话,对于计算机编程学习较好的同学,往往在刚学习这门语言的时候,反而比那些没学过任何计算机编程的同学有时觉得更别扭。主要原因在于这个工具采用的是一种非常接近所谓小白的思维设计方式,以尽可能和最直观理解的方式来提供设计功能,而不是强迫大家按照传统计算机编程的习惯来进行。大家可以在学习中多体会!

那么我们来进一步思考下,如果不考虑数据规模,不考虑大数据,也不考虑什么工具,不管你是Python还是Excel,那么什么是数据分析?同学可能也了解一些其他的专业数据分析工具,比如SPSS、R、SAS等,数据分析这个含义很广,一般而言,人们要进行数据分析,常常有这几类人员。

比如懂数学的,往往借助于数学推导进行数据分析,虽然是纸上谈兵,但是却能给出解决问题的最好方式和最优途径,这门课我们需要知道吗?不用,我们不必了解,我们课程的目标是让大家学会数据分析,但是无需了解这背后的原理,正如会开车并不需要懂得发动机是如何工作的。

再如懂统计的,这一类人员是传统意义上最为常见的数据分析人员,他们借助于各种统计数据资源和统计分析方法,完成一些常见的统计操作,这些数据量通常有限,甚至采取抽样的方法,在指标方面也较为固定,比如平均数、标准差等等。当然,对它们的学习也需要数学的计算,但是更多的是强调这些计算出来的结果有什么意义?我们如何去理解这些数据?

再如懂计算机的,这一类人员是出现最晚的一类数据分析人员,他们甚至没有统计学的背景知识,只是使用各种计算机计算工具来自己编程实现各种数据分析,由于方法完全自己定义,确实效果千奇百怪,有时还很惊人,但是对计算机的能力要求太高了。

那么大家有没有看出来,这几个方面的数据分析人才,其实存在一个需要融合的需要,而且似乎存在一个缺口,那就是随着数字经济的不断发展,数据分析工作变得越来越普遍,如果我们想去做数据分析,那么该选择哪一个?有没有这样的一个组合:第一无需了解背后的计算原理;第二功能上更为自由和可以自行扩展;第三对于不会计算机编程的人来说,也能掌握。

我想大家看出来了,这就是今年这门课程的主要教授目标,它可以看成是一种数据科学,而Python就是这门科学中最为常见的一种工具。

Python大数据分析9:案例-电影评分数据集的分析2

在进行数据分析的时候,除了我们要掌握必要的方法外,在很多时候我们还要清楚的了解数据可能存在的问题。比如这个平均值没有考虑到评分者的数量,退一步说,一个广受评价的电影居然始终是满分5分,难道不可疑吗?有没有一种情况,只有一位观众对这部电影打分,如果是5分,则这部电影平均评分就是5分。显然这是有问题的。

为此,我们改进了方法,不仅要考虑平均分,还要考虑评分的人数。为了实现对结果同时进行两种不同的聚合运算,我们可以使用agg方法来集成:

print(frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).agg([‘mean’, ‘count’]))

这里的agg方法是DataFrame提供的方法,它可以将多个不同的聚合函数一起进行计算。

然而此时的结果没有排序,并不能有效的看出最有价值的数据。

为此,我们再次排序,那么是应该先进行以个数为依据的排序呢,还是以平均评分为依据呢?不妨,我们先按照个数来排,个数相同的再按照平均评分来排:

pd.set_option(‘display.max_rows’, None)

print(frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).agg([‘mean’, ‘count’]).

      sort_values(by=[‘count’, ‘mean’], ascending=[False, False]))

此时代码比较长,我们进行了换行分隔,其实是一条语句。从中我们果然发现刚才有些电影虽然是平均5分,但是其实评分观众很少,实际意义并不大,比如这个Star Kid(1997)。

为此,我们再次调整次序,先按照平均评分再按照个数:

print(frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).agg([‘mean’, ‘count’]).

      sort_values(by=[‘mean’, ‘count’], ascending=[False, False]))

请注意,为了看清全部结果,我们增加设置显示了全部记录。此时的结果比较有意义,可以看出男性观众最喜爱看的电影确实是一些经典影片,而且平均评分相对较高,评分数量也较多。

而女性观众显然和男性观众的差异性很明显。但是这种分析依然显得结果比较乱,因为最高分往往并不在前面,如这个排在女性观众第一位的电影之所以排在第一位完全是因为它有最高的评分数量。

因此我们再次进行了调整,这次我们首先保存了进行两种聚合运算结果的新DataFrame:

frame1 = frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).agg([‘mean’, ‘count’])

print(frame1)

然后对于这个结果我们进行必要的过滤和排序,比如首先过滤到全部的评分次数小于100的电影,然后只需按照平均评分排序即可:

frame1 = frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).agg([‘mean’, ‘count’])

print(frame1[frame1[‘count’] > 100].sort_values(by=’mean’, ascending=False))

明显看出排在前面的记录,都是一些高分电影。

并且也容易看出女性观众最喜欢的电影情况,显然这种分析更为合理。因此在很多时候,我们需要综合利用各种方法来比较分析结果,找到更为适合和更能展示效果的分析结论。

当然,分组并不是唯一解决此类问题的方法。我们也可以使用一些其他的方法来实现,比如透视图。所谓透视图,可以理解为一个多维矩阵,过去按照几个列来分组,现在就可以按照几个列来展示,比如我们还是以性别和电影来分析,看看按性别来计算每部电影的平均得分。此时,就以分别将性别和电影标题分别设置为行和列或者列和行。考虑到电影很多,更适合作为行的内容,因此设置为:

print(frame.pivot_table(‘rating’, index=’title’, columns=’gender’, aggfunc=’mean’))

其中index表示行为电影标题,列表示性别,每个矩阵单元格显示的是评分的平均值,这个依赖于第一个参数指定的评分字段和最后一个参数指定的聚合方法。

此时按照电影标题排序的结果意义不是很大,可以进一步调整结果的次序,比如按照女性观众的平均得分重新调整:

print(frame.pivot_table(‘rating’, index=’title’, columns=’gender’, aggfunc=’mean’).

      sort_values(by=’F’, ascending=False))

此时的结果更为明确些,可以更容易看到有价值的数据。

但是依然也会遇到刚才同样的问题,有些电影评分数量不足,却具有较高的平均评分。为此我们也可以进行必要的过滤。

首先我们先得到一个每部电影评分数量的汇总信息,这次我们使用了分组:

ratings_by_title = frame.groupby(‘title’).size()

print(ratings_by_title)

其中的size返回每组的个数,相当于count,只是不区分多个不同的列,只显示一个总数。

可以进一步选择其中总数大于100的记录:

print(ratings_by_title.index[ratings_by_title > 100])

由于此时这个结果只有一列,是个序列类型的变量,因此可以直接使用序列变量的名称作为具体数值列的表示,选择数值范围内的记录。

此时,这些电影都满足至少100个评分以上。

然后把这个范围内的电影标题作为选择条件,在当前排好序的透视图中过滤,其实就是按照透视图的索引来过滤,只显示符合loc方法参数中电影范围内的所有电影元素。

print(frame.pivot_table(‘rating’, index=’title’, columns=’gender’, aggfunc=’mean’).

      loc[ratings_by_title.index[ratings_by_title > 100]])

甚至可以再次排序:

print(frame.pivot_table(‘rating’, index=’title’, columns=’gender’, aggfunc=’mean’).

      loc[ratings_by_title.index[ratings_by_title > 100]].

      sort_values(by=’F’, ascending=False))

虽然有点复杂了,但是所有的过程,其实就是一个组合应用的过程。大家多练习才能更好的掌握。

到此为止,我们已经尝试了多种分析性别和观影喜好的关系,并且注意到不同性别对于电影的评价等级差异很明显,那么究竟哪些电影的不同性别分歧更大一些呢?

在数据透视表的基础上,我们可以很容易进行分析。首先为了方便处理,我们将当前透视表保存下来:

frame1 = frame.pivot_table(‘rating’, index=’title’, columns=’gender’, aggfunc=’mean’)

print(frame1)

我们可以发现,此时的男女两列数值对比很明显。

为此,我们增加一个计算列:

frame1[‘diff’] = frame1[‘M’] – frame1[‘F’]

print(frame1)

这是我们为什么要保存下这个数据透视表的原因,后续可以对它进行修改。其中的diff就是差值。

接下来只需按照diff降序排序即可看到评分分歧最大的电影:

print(frame1.sort_values(by=’diff’, ascending=False))

当然细心的同学可以也得到,如果按照升序来排,这种排序最大值和最小值都是分歧大的表现,只是男女比较时谁先谁后的问题。

print(frame1.sort_values(by=’diff’, ascending=True))

因此更为合理的设计应该是计算差值的绝对值,这一次我们再次使用了apply方法,应用了绝对值处理。

frame1[‘diff’] = (frame1[‘M’] – frame1[‘F’]).apply(abs)

这个练习也充分展示了透视表的强大之处,换成一般分分组来处理并不容易进行,因为分组通常是将所有的分组都作为行的索引,不同行之间的比较并不容易,相反,借助于透视表,我们可以将所有的信息灵活的放置在列中,从而方便实现一些特殊功能。

当然,利用评价平均分的差值来计算差异度,其实并不十分科学,最为有效的方法还是应该考虑使用标准差之类的方法。

print(frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).std().sort_values(ascending=False))

比如这里我们按照性别和电影标题分组,计算每组的标准差,并按照降序排序,可以看到不同性别下哪些电影评分的差异度更为明显。

Python大数据分析8:案例-电影评分数据集的分析1

这次我们准备结合一个著名的电影评价数据集合来检验下我们前段时间学习的数据分析方法。

这个数据集合可以免费的从官网上获取,主要提供了观众对电影的评价数据,其中还对观众和电影给出了较为完整的属性描述。其中主页的下部有一个older datasets,其中就有一个100K的数据集,直接点击即可下载。我们下载后解压后可以看到很多文件,我们只需使用其中的三个文件,分别是观众数据u.user、电影数据u.item、和评分数据u.data。

其中的u.user为观众数据,共有943个观众记录,分别记录了观众ID、年龄、性别、职业和邮编,此处为美国五位邮编号码,通过竖线分隔。

u.item为电影数据,共有1682部电影,每部电影都提供了电影ID、名称、拍摄时间、上映时间、网址,后面19个一零列表示19种电影类别,具体情况大家可以参见README文档中的说明,如果为1表示属于该类别。

u.data为评分数据,共有10万个记录,这也是为什么称为100K的原因。其中给出了观众ID、电影ID、评分数据和评分时间。其中的评分数据为五级评分,5表示最喜欢,1表示最不喜欢,时间类型需要转换后才能正确显示,这些以后学习了时间序列分析后再做说明。

我们直接通过练习来切入数据分析。

在数据分析的一开始,我们首先需要读取数据。以前我们介绍的都是直接在程序中表达数据集合,如何从数据文件来读取数据并生成DataFrame呢?其实很简单。Pandas的read_table函数就可以直接实现这个效果。

unames = [‘uid’, ‘age’, ‘gender’, ‘occupation’, ‘zip’]

users = pd.read_table(‘c:\\temp\\MovieLens\\u.user’, sep=’|’, header=None, names=unames)

这几个参数含义都比较直观,第一个是数据文件,第二个是一行记录中不同列的分隔符,第三个参数表示第一行是否是行标题,最后一个是映射关系,说明如何将读取的每一行多个列信息映射到DataFrame的数据列中。

为了能看到最终效果,我们可以读取显示下数据效果。请注意,由于真实数据处理往往所涉及的数据量都很大,因此不建议全部读取,常见的方法还是取样读取,比如只读取前5个记录:

print(users.head(5))

当然类似的效果也可以通过切片来实现:

print(users[:5])

接下来,我们读取评分数据,并通过连接和现有观众数据关联起来。

rnames = [‘uid’, ‘mid’, ‘rating’, ‘timestamp’]

ratings = pd.read_table(‘c:\\temp\\MovieLens\\u.data’, sep=’\t’, header=None, names=rnames)

print(ratings[:5])

这里要注意分隔符要根据数据实际情况来定义,一般而言,最好在分析前先将数据读取并显示几条记录,以确定数据读取正常。

我们通过自然连接将两个数据集合关联起来:

frame = pd.merge(ratings, users)

print(frame.head(5))

到此为止,数据准备好了。

对于电影评分,从多种角度我们可以发现很多有趣的现象。比如我们想看看观众的不同性别、年龄对电影的平均评分情况。首先看看性别评分的差异:

print(frame[‘rating’].groupby(frame[‘gender’]).mean())

能看出女性观众比男性观众评分平均值更高些,只是幅度不大。其实这种过于汇总的分析往往很难看出真正的差别。下面我们继续分析。

再看看不同年龄段,我们按照四舍五入的方法将年龄映射为年龄段:

print(frame[‘rating’].groupby(frame[‘age’].apply(round, args=[-1])).mean())

-1的round参数表示四舍五入到十位。

看得出,随着年龄的增加,电影平均评分似乎呈现不断上升的趋势,但是达到60岁左右达到最高峰。

然后结合年龄段和性别,看看情况究竟是什么情况。

print(frame[‘rating’].groupby([frame[‘age’].apply(round, args=[-1]), frame[‘gender’]]).mean())

从结果看的出来,在不同年龄段,男性女性观众的评分差异非常明显。比如在30岁左右,男女评分的区别其实并不明显,而在70岁这个年龄段,可以看出,女性评分似乎更为苛刻些,与男性评分的差距最大。而反过来,在40岁年龄段,女性又比男性宽容,虽然这个差距远远小于70岁的情况。这些有趣的分析结果在一定程度可以揭示出观众不同性别在不同年龄段的兴趣差异。

下面我们继续增加数据,将电影也关联进来。这里有两个主要问题:一是读取电影数据,这个数据读取并不容易,首先是列很多,因此需要结合说明文档仔细定义列的名称:

mnames = [‘mid’, ‘title’, ‘date1’, ‘date2’, ‘url’,

          ‘unknown’, ‘Action’, ‘Adventure’, ‘Animation’,

          ‘Children’, ‘Comedy’, ‘Crime’, ‘Documentary’, ‘Drama’,

          ‘Fantasy’, ‘Film-Noir’, ‘Horror’, ‘Musical’,

          ‘Mystery’, ‘Romance’, ‘Sci-Fi’, ‘Thriller’, ‘War’, ‘Western’]

movies = pd.read_table(‘c:\\temp\\MovieLens\\u.item’, sep=’|’, header=None, names=mnames)

然而还是出现错误,大致的信息是字符集合转换的问题。

对于此类问题,可以通过指定编码字符集来解决,这里一般需要从两个地方处理:

一是在Python中重新设置字符集,比如在设置中选择编辑器,在文件编码中更换当前默认的GBK为ISO-8859-1。

同时还要考虑在代码中通过encoding指定标准字符集。我们于是输出些结果看看,此时没问题了。

movies = pd.read_table(‘c:\\temp\\MovieLens\\u.item’, sep=’|’, header=None, names=mnames, encoding=’ISO-8859-1′)

print(movies.head(5))

第二,如何对于已经连接起来的两个数据集合再连接一个数据集合呢?其实很简单,再次使用merge即可:

frame = pd.merge(pd.merge(ratings, users), movies)

print(frame.head(5))

此时的输出结果看出来没问题。

这次有了电影详细信息,我们可以继续深入了解评分情况。比如按性别来计算每部电影的平均得分,

print(frame[‘rating’].groupby([frame[‘gender’], frame[‘title’]]).mean().sort_values(ascending=False))

说明下,我们这里使用电影标题作为分组依据主要是考虑到输出结果的直观,其实真正有效的电影标识列应该是电影ID。

这里不仅做了分组统计,而且还进行按照平均评分的倒序输出。从中可以看出很多打5分的好评情况,如男性观众对“Star Kid(1997)”这部电影排名满分。但是这种数据是真实有效的吗?

Python大数据分析7:数据的连接

前面我们所介绍的各种数据处理都在一个数据表格中进行。在真实的各种应用中,我们也需要处理来自于不同数据表格的内容。此时,就需要对这些数据进行连接,才能继续进一步的操作。

比如,我们再新建一个DataFrame,保存所有的学生课程的成绩。

# coding:utf-8
import pandas as pd
from pandas import DataFrame

data1 = {'ID': ['000001', '000002', '000003', '000004', '000005', '000006', '000007'],
         'name': ['黎明', '赵怡春', '张富平', '白丽', '牛玉德', '姚华', '李南'],
         'gender': [True, False, True, False, True, False, True],
         'age': [16, 20, 18, 18, 17, 18, 16],
         'height': [1.88, 1.78, 1.81, 1.86, 1.74, 1.75, 1.76]
         }
students = pd.DataFrame(data1)
data2 = {
    'SID': ['000001', '000001', '000002', '000003', '000003', '000003', '000004', '000004', '000005', '000006',
            '000006'],
    'CID': ['A01', 'A02', 'A01', 'A01', 'A02', 'B01', 'A01', 'A03', 'B01', 'A02', 'B01'],
    'score': [56, 78, 90, 74, 86, 89, 67, 80, 77, 76, 90]
}
scores = pd.DataFrame(data2)
print(pd.merge(students, scores, left_on='ID', right_on='SID'))

这里有三列,分别是学号SID,和前面学生的ID对应,然后是课程号CID,以及当前学生该课程的成绩score。

具体构造方法依然使用和构建学生数据的方式一样,这里不再细说。

我们先尝试这将这两个数据连接起来看看是什么样子?

print(pd.merge(students, scores, left_on=’ID’, right_on=’SID’))

显示效果很清楚,因此在单独的成绩数据中,只能看到每个学生的学号,但是通过连接后,我们可以看到更多对应的完整学生信息:

这个merge函数就是合并的意思,第一个和第二个参数分别表示要合并的两个DataFrame,通常称第一个为左数据,第二个称为右数据,相应的,第三个和第四个参数分别代表着依据左数据的哪个列和右数据的哪个列按照相等的原则进行连接。

需要补充说明下,如果两个数据含有的关联列名称一样,这个时候可以省略对关联所需列的说明。比如将data2的SID换成ID,就可以直接书写:

print(pd.merge(students, scores))

从结果来看,自动按照相同列连接,而且还自动去除一个多余的同名连接列。这种连接也被称为自然连接。

连接后的表格可以进行各种常见的数据选择等操作。这里有一个潜在的问题,如果有来自于不同数据表格的两个列具有一样的名称,此时数据选择会出错,因此,遇到相同列名称的时候,可以增加一个连接参数indicator,当它为真时,系统会自动对重名列分别加上不同的后缀来做区分。比如我们把成绩表格的course列名称改为name,此时就可以通过name_x获得左数据(x)的列:

print(pd.merge(students, scores, left_on=’ID’, right_on=’SID’, indicator=True))

为了后续操作方便,我们仍然保留CID的课程代码,细心的同学也会发现,这张表中有一个学生没有出现,对!那就是学号为000007的学生,因为没有选修的课程成绩,所以连接没有显示。但是有时我们也希望即使没有对应的成绩,也可以连接,那么这种扩展的连接称之为外连接。于此对应,现在这种默认的连接称之为内连接。外连接由于涉及两个数据表格,因此要说明是以左数据全部显示为准,还是以右数据全部显示为准。这里由于以学生左数据为准,所以为左外连接:

print(pd.merge(students, scores, left_on=’ID’, right_on=’SID’, how=’left’))

这个how参数就是指左外连接。由于000007学生没有选修课程,因此对应的课程和成绩都为空值,在pandas中,NaN就表示空值,它的字面意思是指“不是一个数(Not a Number)”。

大家可能也会觉得这好像没有什么意义。其实这种特殊的外连接往往可以起到非常有效的查询效果。比如查询哪些学生没有选修课程,我们就可以看出此时课程或者成绩为空的就是这样的学生,于是我们可以写出完整的查询:

frame = pd.merge(students, scores, left_on=’ID’, right_on=’SID’, how=’left’)

print(frame[pd.isna(frame[‘score’]) == True][‘name’])

我们首先将连接后的DataFrame保存下来,然后再利用以前的数据选择方法即可获得该学生的姓名。

这里要补充说明几个问题:

一是空值是什么?空值其实并不是没有值,而是有值但是无法知道。因此,在很多时候,我们都需要单独考虑空值可能产生的影响,同时也不建议大家随意保留空值。

二是判断是否为空值,正确的语法是使用pandas的isna函数或者isnull函数。它返回是一个真或者假。因此也可以简写为:

print(frame[pd.isna(frame[‘score’])][‘name’])

除了能按照列进行连接外,还可以按照索引号进行连接。此时可以使用join函数方法。Merge也可以实现,不过,join更为方便。大家要注意,merge为pandas工具包的函数方法,而join是DataFrame的函数方法,哪个DataFrame调用就以哪个为主进行以索引号相等为条件的外连接。

为此,我们重新设定了成绩表格的所有索引号,并进行了以学生表格为主的join:

scores.index = [0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 5]

print(students.join(scores))

结果和刚才merge的左外连接没有区别。

请注意这个为主的含义,如比我们调换join前后的数据:

print(scores.join(students))

此时可以看到没有7号学生记录,因为该学生没有成绩,成绩为主的连接将不采用该记录。

最后我们通过一个结合分组和聚合统计运算的例子来看看。比如我们想知道每位学生的姓名和平均分:

frame = pd.merge(students, scores, left_on=’ID’, right_on=’SID’)

print(frame[[‘score’]].groupby(frame[‘name’]).mean())

大家能看得出这个方法的特点吗?首先需要连接,我们通过frame保存。然后选择分组的依据,以groupby表示,然后选择聚合运算,这需要两个方面:一是在前面可以指定聚合的字段,后面指定聚合的方法。

当然,我们也经常看到这样的写法:

print(frame.groupby(frame[‘name’])[[‘score’]].mean())

除了性能有所差异,功能没有差异。这样的写法会意味着Frame将整体性的参与分组,显然计算开销更大,虽然看起来似乎更好理解一些。

好了,到此为此,第一阶段的Pandas数据分析基本方法都已经介绍完毕,我们下面就以结合一些案例来真正的分析下数据了。请大家这段时间好好练习基本功!

Python大数据分析6:数据的分组和聚合运算

在我们处理各种数据的时候,常常需要进行必要的数据汇总分析,比如我们想看看目前男女学生各有多少人

这里就使用了其实三个重要过程:一个是分组,比如我们按照性别进行了组别的划分,我们不妨称之为拆分;

第二个是统计分析,比如计算个数、总和、平均数、最大值、最小值等,这种运算我们常常称之为聚合运算,我们也可以称之为应用,即对每个组应用特定的聚合运算方法来计算结果;

第三是显示结果,这个过程可以根据用户的喜好采取多种方式来显示同样的数据结果,我们称之为合并。

我们就以刚才这个男女学生人数的例子来看看。

groups = frame.groupby(frame[‘gender’])

print(groups.count())

这里的第一行表示得到一个分组变量,groupby就是根据什么分组的意思,正好对应第一个过程,其中的参数表示我们要按照性别列来分组。可以想象,因为性别列这里只有两个值,此时的frame数据变成了两组,

第二行表示统计分析,count表示统计个数,这里有几组就分别在几组中应用该聚合函数,分别算出各自的结果。

有时可以省略groupby里面的frame,显得更为简单些。但是在一些复杂操作时,这种省略会导致更多的歧义可能。

默认的呈现方式看的出来意义不大,我们没有必要显示所有列的聚合结果,因此,可以在第三步过程中选择所需的列。于是我们再改进下,选择下所需的显示列:

print(groups[‘gender’].count())

这里其实选择一个列都可以,毕竟是个数:

print(groups[‘ID’].count())

不过,既然我们只需一列就够了,那为何还要对所有列进行统计呢?每个列都算一算个数显然没有意义。因此更为高效的写法应该是:

groups = frame[‘gender’].groupby(frame[‘gender’])

print(groups.count())

此时的结果很清楚。

当然有时为了书写方便,我们也可以合并起来一起写:

print(frame[‘gender’].groupby(frame[‘gender’]).count())

大家只要知道了其执行过程,那么意思很好理解。

关于显示问题,我们可以再次优化下。

这里有一个细节,如果希望在显示结果中显示聚合运算后的统计列名称,可以尝试在前面选择frame列使用列表参数,也就是使用两个方括号,未来可以据此实现对多个字段的选择。

groups = frame[[‘gender’]].groupby(frame[‘gender’])

print(groups.count())

此时还可以改动这个统计列的名称,比如使用rename:

print(groups.count().rename(columns={‘gender’: ‘genderCount’}))

可能也有学生会说,这种过程不可以更简单的实现吗?

print(frame[‘gender’].value_counts())

不错,对于这个例子,确实这个函数更简单,但是要想实现更为灵活和复杂的统计效果,还得要自己使用分组和聚合函数来组合。

我们再试一下,计算下不同年龄的人数:

print(frame[‘age’].groupby(frame[‘age’]).count())

我们可以按照排序依据排列结果,默认是按照升序来排列:

print(frame[‘age’].groupby(frame[‘age’]).count().sort_index())

我们也可以按照计算后的值,比如这里为个数来排序,并且为降序排列:

print(frame[‘age’].groupby(frame[‘age’]).count().sort_values(ascending=False))

看得出来,一般倒序输出意义更大些。

我们也可以按照多个列来分组:

比如我们按照性别和年龄一起分组,分别统计各自的人数:

print(frame[[‘ID’]].groupby([frame[‘gender’], frame[‘age’]]).count())

甚至可以继续对结果进行排序

print(frame[[‘ID’]].groupby([frame[‘gender’], frame[‘age’]]).count().

      sort_values(by=[‘gender’, ‘age’], ascending=[False, True]))

此时由于有多个列,因此可以通过sort_values的by属性增加多个列,还可以通过ascending属性分别指定每个列是按照升序还是降序。

当然,除了求个数以外,pandas分组的聚合函数还有很多,比如求平均值等。

我们来对不同年龄的学生计算他们的平均身高:

print(frame[[‘height’]].groupby(frame[‘age’]).mean())

当然也可以组合条件分组,比如按照性别、年龄分组后统计各个组的平均身高:

print(frame[[‘height’]].groupby([frame[‘gender’], frame[‘age’]]).mean())

下面我们说一些高级的应用。Pandas的强大之处在于我们还可以自己来扩展分组条件和统计函数,并实现分组统计效果。其中,主要利用apply函数来应用这些不同的函数来扩展功能:

比如我们想按照姓名的长度来分组,并统计各组的人数:

print(frame[‘name’].groupby(frame[‘name’].apply(len)).count())

再比如统计不同身高范围的人数,我们只区分小数点后一位,这个可以使用round函数,传递1表示1位小数:

print(frame[‘name’].groupby(frame[‘height’].apply(round, args=[1])).count())

甚至还可以通过自己定义的函数扩展功能,如计算最大值和最小值之间的距离:

def peak_to_peak(arr):

    return arr.max() – arr.min()

可以想象,这个arr参数应该是个具有多个值的序列变量。

此时,就可以利用apply函数来分组调用:

print(frame[‘height’].groupby(frame[‘gender’]).apply(peak_to_peak))

其实这种自定义函数甚至可以写得更为简单,更为Pythonic(Python风格化),比如使用匿名函数来合二为一:

print(frame[‘height’].groupby(frame[‘gender’]).apply(lambda arr: arr.max() – arr.min()))

Python大数据分析5:数据的排序

常见的数据排序操作主要是指对行的排序,它是一种非常常见的操作,比如按照学生身高、成绩对记录进行排序输出等等。大家应该还能记得DataFrame通常是以追加方式来插入新的行记录,因此后续适应用户需求的行排序将变得更为重要。

排序有两种基本的方法:

第一种就是使用索引号,这个每个行自带的索引号本身就是可以排序。比如这个是我们以前的学生例子。一般而言,不指定索引号,系统会默认按照从0开始的整数依次对所有行设定索引号,并据此实现行排序。

可以通过reindex重新索引的函数方法来再次设定新的排序依据,我们可以看到操作后,数据记录已经按照新的次序排好。

frame = frame.reindex([6, 5, 4, 3, 2, 1, 0])

请注意,如果希望排序能真正存储下来,可以重新保存到原有DataFrame类变量。

第二种排序意义更大,往往是根据一些特定字段的值来进行,比如我们准备按照学生身高来对所有学生记录进行排序:

print(frame.sort_values(by=[‘height’]))

这个可以通过sort_values函数来实现,它需要设定一个或者多个列名称来表明排序依据,默认按照升序来排列。

这里需要说明下,对于数值,排序就会按照数值大小进行判断,对于字符,一般按照对应的字符编码进行,这种编码顺序一般和过去的ASCII编码基本一致。但是对于汉字信息,问题比较复杂。因为汉字有不同的编码方式,这依赖于当前电脑操作系统和数据表示等多种因素,但是不管哪一种,最终系统还是会按照当前汉字编码的顺序方式来排序

比如这是按照姓名的排序结果。其中encode函数返回汉字对应的当前系统的汉字编码。

如果想换成降序,可以增加参数ascending为假来实现:

print(frame.sort_values(by=[‘height’], ascending=False))

ascending表示上升,为假当然表示不上升即下降。

如果有多个列,则首先按照第一列来排,然后相同数值的再按照第二列再排。比如先按照年龄升序再按照身高降序:

frame = frame.sort_values(by=[‘age’, ‘height’], ascending=[True, False])

不管上述哪一种排序,可能都会对现有的行索引号带来影响,从而导致顺序失效。因此可以通过reset_index函数实现重新排序索引:

frame = frame.reset_index(drop=True)

这里drop参数为真表示删除原有的旧索引号,否则为了保持和原有数据的对应关系,原有旧索引也不会默认删除。

这个方法很有用,即使是在对数据进行删除和插入等操作后,也可以通过该方法实现对索引号的重新排序生成。

在任何时候,如果对你现有的记录做了很多排序调整,索引号也不再是过去的有序序列,都可以重新恢复下按照索引号的排序结果,方法是利用sort_index:

frame = frame.sort_values(by=[‘height’])

print(frame)

print(frame.sort_index())

此时,大家能看到,记录再次按照索引号重新排好了。

除了能对行排序外,其实列也可以排序调整次序。最为简单的方法就是利用以前介绍过的数据选择方法,重新选择数据列的次序,如:

print(frame[[‘name’, ‘height’, ‘ID’, ‘age’, ‘gender’]])

其次,刚才所说的行排序方法也适用于列的排序。一般而言,列的排序主要用在纯数字的表格数据中。比如我们这里先创建一个数字矩阵:

data = {‘col1’: [4, 2, 1, 3, 5],

        ‘col2’: [6, 4, 2, 6, 1],

        ‘col3’: [4, 2, 7, 6, 0],

        ‘col4’: [4, 3, 1, 5, 2]}

frame = pd.DataFrame(data)

现在我们准备对列来排序,其实就是指以某一行或者某几行的数值来排序。比如我们以第四行数值来对几个列排序:

print(frame.sort_values(by=[3], axis=1))

其中by参数后面的这个3是指索引号,而这个axis是指排序对象换成了列,默认为0表示对行排序。

注意,这时是以整个列为单位来排序,因此各个列次序按照第四行的大小进行了调整,我们可以从目前的列名称看的很清楚。

Python大数据分析4:数据的更新

数据的更改主要包括三种常见的操作,比如更新、插入和删除。我们下面分别来介绍下:

先来看看更新操作。所谓更新,就是指修改现有的数据。比如我们准备修改所有的身高,都增加1厘米,方法很简单:

frame[‘height’] = frame[‘height’] + 0.01

这句话的准确理解是指将现有的身高列全部拿出来,各自加上0.01后,再更新回现有的身高列。于是,所有身高就变高了些。

其实这里的关键还是数据选择问题,我们选择的数据是什么,更新数据就是什么,所以大家可以把前面我们学习过的任何一种数据选择方法拿过来,就可以修改数据。

再比如,我们准备把第二条记录的姓名改为“赵春”,方法依然建立在准确的数据选择之上:

frame[1:2][‘name’] = ‘赵春’

此时可以更新成功。

但是细心的同学会发现,这时会有一个警告信息。警告不同于错误,只是可能的错误提示。为什么会有这个警告呢?主要原因在于这种写法其实进行了两次数据选择,因此在第二次选择列时,系统提示你目前是在第一次选择后的数据上进行的操作,可能与原始数据并不是同一个数据,更新会失败。当然这里其实没有问题。

因此更好的方法就是一步到位的定位:

frame.loc[1,’name’] = ‘赵春’

再看看插入数据。所谓插入数据就是指新增数据,既可以增加一列,也可以增加一行。比如我们增加一行,也就是一个学生记录:

data1 = {‘ID’: [‘000008’],

         ‘name’: [‘方文吾’],

         ‘gender’: [False],

         ‘age’: [21],

         ‘height’: [1.75]

         }

frame1 = pd.DataFrame(data1)

print(frame.append(frame1))

这里首先需要构造一个新的DataFrame,一条多条记录都可以,只需列结构一致即可。然后通过DataFrame类变量的append函数方法实现追加,

但是请注意,这个append只是临时性追加,如果要保存,需要显式的保存在DataFrame类变量中,如:

frame = frame.append(frame1)

这里比较奇怪的是新记录的索引号,虽然看起来是0,原因是从其他DataFrame中复制过来,但是实际上的序号仍然是7。

print(frame[7:8])

如果你希望在复制数据时不复制索引号,可以增加一个属性说明,即忽略复制过来的索引:

frame.append(frame1, ignore_index=True)

其实大家不用担心行之间的次序问题,这个问题称之为排序问题,以后可以专门处理,因此在增加数据时无需考虑行之间彼此的次序。

列也可以增加,增加时只需直接给出新增的列名及其每行对应的值:

frame[‘class’] = [‘C1’, ‘C2’, ‘A1’, ‘B2’, ‘C1’, ‘C1’, ‘C2’]

这里class列并不存在,不过没关系,系统会自动在最后一列后追加新列,并填充数据。

如果想在中间插入新的列,可以使用insert函数方法

frame.insert(0, ‘class’, [‘C1’, ‘C2’, ‘A1’, ‘B2’, ‘C1’, ‘C1’, ‘C2’])

这就表示在第1列前面插入一个新的第一列,第二个和第三个参数分别指定了列名称和对应的数值。

最后我们来看看删除。同样,删除也包含行删除和列删除,它们都使用drop函数。

比如通过索引号直接删除:

frame = frame.drop(1)

这些操作也都需要存储到DataFrame类变量才能保存。

也可以删除多条记录:

frame = frame.drop([1, 2])

其实这里默认的参数名称是index,也就是说默认以索引号,而不是序号来进行删除

frame = frame.drop(index=[1, 2])

通过设定columns参数可以指定删除列的名称

frame = frame.drop(columns=’name’)

除此以外,也可以不用指定这两个参数名称,而通过另外一个参数来指定删除的是行还是列。

frame = frame.drop(‘name’, axis=1)  # 删除列

frame = frame.drop(1, axis=0)  # 删除行

看的出来,axis参数默认为0。

最为灵活的方式是根据条件来删除行列,这个再次有依赖于前面学习的数据选择方法。

比如删除所有男生记录,即性别为True的记录:

frame = frame.drop(index=(frame.loc[(frame[‘gender’] == True)].index))

其中,frame.loc[(frame[‘gender’] == True)表示数据选择,

.index表示这些数据的索引信息,它也是DataFrame类变量中的一个属性,返回当前数据框架的所有索引号集合,与之类似的还有一个columns属性返回所有列的名称集合。

说明一下,这种条件也可以结合进数据更新和数据插入。我们来看两个例子:

比如我们准备将现有学生记录中年龄大于18岁的所有学生再次批量追加进现有数据中。

frame = frame.append(frame.loc[frame[‘age’] > 17])

这里的数据选择获取的数据直接作为append函数方法的参数,产生有条件的数据插入。

再如我们准备将所有身高大于1.80的学生年龄增加1岁。

frame.loc[frame[‘height’] > 1.80, ‘age’] = frame.loc[frame[‘height’] > 1.80, ‘age’] + 1

这些操作略显复杂,但是只要大家清楚前面学习的这些规则,就能理解。建议大家多练习。

在做练习之前,要注意一个关键问题,数据更改可能并不难,但是要想恢复并不容易,因此大家在进行任何数据更改的时候,都要想一想数据是否重要以及是否可以恢复。

Python大数据分析3:数据的显示

上次我们谈到了一些基本的数据选择方法,既有对列的选择,也有对行的选择。在实际的操作中有时我们经常需要更换显示的内容,今天我来完成这些练习。

比如我们再次看看这个学生数据:

print(frame)

我们首先来看列名称的显示问题。

比如这个列的名称,通常为了方便,列名称都采用英文字母或者一些易于表示的表达方式,但是在实际显示中,考虑到用户的需求,往往需要更改为特定的语种或者格式。比如我们能否把这里的列名称换成中文呢?

当然这种替换有两种方法:

第一是真的替换,这个其实不建议做,列名称通常作为基础性的字段名称,可能使用在很多其他地方,随意更换会导致其他使用旧列名称的地方出问题,带来潜在的错误。

更换方法很简单,可以直接设置DataFrame类变量的columns属性,所谓属性就是指属性值,通常这些数值代表着一种特征,比如columns属性就表示列名称这个特征。

frame.columns = [‘学号’, ‘姓名’, ‘性别’, ‘年龄’, ‘身高’]

后面这个方括号括起来的就是一些数值集合。大家可能也感觉到了,在Python中,如果需要表达多个数值,通常都是这样形成一个组合,这个被称为列表。

第二种是表面的临时性替换,这样显然更灵活。方法也很简单:

print(frame.rename(columns={‘ID’: ‘学号’, ‘name’: ‘姓名’, ‘gender’: ‘性别’}))

这里的rename就是DataFrame类变量的函数方法,大家应该记得上一次我们谈到了loc定位函数。它们都是DataFrame类变量的函数方法,功能不一样。这里的rename就是更换列名称。但是细心的同学可能会发现有点不一样,loc参数是直接写出数值的,但是这里的rename却要说明这些数值是columns列的数值。

这是两种不同的参数使用方法,如果不说明参数的名称,那么你必须严格按照函数方法的要求,按序给出每个参数对应的数值,如果说明参数的名称,并使用等于号赋值,你就可以随意的给出哪个参数对应哪个数值。通常参数少的时候,不说明参数更方便,参数多的时候,说明下更方便。这一点大家有兴趣可以了解下Python更多的基础知识。

看的出来,由于只对前面三个列改了名称,因此后面两列依然还是原有的列名。

这里可以根据需要有选择的更换,这是完整替换的方法:

frame.rename(columns={‘ID’: ‘学号’, ‘name’: ‘姓名’, ‘gender’: ‘性别’, ‘age’: ‘年龄’, ‘height’: ‘身高’})

大家可能也感觉到了,这种以花括号括起来的集合,通常里面都有很多由冒号组成的数值对,这种结构在Python中被称为字典。第一节课里我们定义DataFrame时其实就是采用了字典和列表的组合。

我们再看看数据内容的显示问题。

在实际问题中,存储的数据内容往往都需要做进一步的处理才能更好的显示。比如我们假设要分开显示每个学生的姓和名,我们也假设学生姓都是一个字。

print(frame[‘name’].str[0:1])

这里str也是个函数方法,它是字符串变量的内含函数方法,其实每个name列的数值都是一个字符串,因此都支持字符串自己的操作。这里str表示取子字符串,0到1,不含1,因此就是第一个字符,就是单字的姓。

通过字符串相加表示连接可以形成更多的显示效果。

print(frame[‘name’].str[0:1] + ‘同学’)

也可以形成计算列,并存储下来

frame[‘newname’] = frame[‘name’].str[0:1] + ‘同学’

print(frame)

不同类型的列可以调用自己不同类型的方法,如显示每个学生身高,但是四舍五入到小数1位:

print(frame[‘height’].round(1))

其中round函数的1表示小数1位,如果是0表示四舍五入到个位,-1表示四舍五入到十位,以此类推。

比较复杂的还可以通过自定义函数来扩展功能,实现对更多数据的自定义处理。这个供有Python基础的同学了解。比如下面我们定义了根据增加输出男女的函数,并应用于性别列,产生了更为灵活的效果:

# coding:utf-8
import pandas as pd
from pandas import DataFrame


def xinbie(val):
    if val is True:
        return '男'
    else:
        return '女'


data = {'ID': ['000001', '000002', '000003', '000004', '000005', '000006', '000007'],
        'name': ['黎明', '赵怡春', '张富平', '白丽', '牛玉德', '姚华', '李南'],
        'gender': [True, False, True, False, True, False, True],
        'age': [16, 20, 18, 18, 17, 18, 16],
        'height': [1.88, 1.78, 1.81, 1.86, 1.74, 1.75, 1.76]
        }
frame = pd.DataFrame(data)

print(frame['gender'].apply(xinbie))

定义的xinbie函数接收一个参数,并判断是否为真,如果为真返回一个男,否则为女,看得出来,通过这个函数,原有的性别增加值被映射为男女汉字,解决了输出显示问题。其中,这里因为这个xinbie为自定义函数,不属于frame内置的函数,因此可以通过apply函数来调用其他函数,可以理解为通过apply函数可以调用任何其他函数。

事实上,即使是刚才的一些内置函数,也可以使用apply,比如这里也是四舍五入,通过apply调用round,不过默认round自动四舍五入到个位。

print(frame[‘height’].apply(round))

为了传递给round参数,可以使用apply的args参数,将1,这个表示1个小数位的1,传递给round,大家可以看到结果和刚才直接调用round的做法一样。

print(frame[‘height’].apply(round, args=[1]))

这个apply函数应用的讲解不做要求。

我们再来看看索引号的显示。

默认的索引号也可以更改,比如直接设定index属性即可:

frame.index = [6, 5, 4, 3, 2, 1, 0]

也可以按照前面的rename,指定改变index:

print(frame.rename(index={0: 6, 1: 5, 2: 4, 3: 3, 4: 2, 5: 1, 6: 0}))

注意这里使用了有名参数index,来说明赋值的去向。

注意即使改变后这些索引后,如果通过序号来访问仍然按照从0开始的原始顺序访问,如:

frame = frame.rename(index={0: 6, 1: 5, 2: 4, 3: 3, 4: 2, 5: 1, 6: 0})

print(frame[0:1])

这里我们采取了保存替换原有DataFrame类变量的方法将修改索引确定了下来。此时显示的仍然第一条记录,它显示的新索引号为6。

最后,我们谈谈整体格式化输出的问题,可能大家觉得目前这种文本输出并不好看,甚至连对齐都做不了。不过这个问题其实不大,因为pandas主要特点在于处理数据,它本身并不负责格式输出等其他内容。后续我们可以通过其他可视化工具包来完成真正的格式化。

即使是现在,最为简单的格式化方法可以考虑将数据全部输出为一个网页表格,直接可以看到这个数据表格:

print(frame.to_html())

具体方法是调用DataFrame类变量的to_html函数,它会生成一个网页代码。

大家可以把它复制出来,生成一个网页文件,就能看到真正的表格。

Python大数据分析2:数据的选择

对于数据选择而言,无非就是行列的选择,我们分别来看一看。

首先看看如何选择列,就是字段。比如我们想显示所有学生的姓名。方法很简单,就是利用一个方括号,直接在里面写清楚列名称即可。

print(frame[‘name’])

有时为了好看,也可以写成更为简单的形式,直接通过点加列名称就可以。但是这样写有时会出问题,尤其是列名称正好和类里面的函数或者变量重名时,就会发生歧义。

print(frame.name)

如果要显示多个列,前后加两个方括号,逗号分隔即可。这里的两个括号有具体含义,其实frame后面的方括号里只能放一个变量,因此需要再次使用方括号将两个字段名称括起来形成一个变量,了解Python的同学应该知道这其实是列表变量的意思。当然,显示的列次序不一定非要和存储的次序一致,主要依赖于这里列名称指定的先后次序来显示。

print(frame[[‘name’, ‘age’]])

我们再看看行的显示。行是通过索引来区分的,因此通过索引号我们就可以选择所需的行。行通常都会显示很多,因此常见的方法是通过范围先限定的,比如从a到b。注意,按照Python习惯,这里范围包括a,但是不包括b。

具体方法是在frame变量后增加方括号,通过冒号分隔前后的数字边界,比如0:1表示第一条记录,从0开始不到1,就是第0条即第一条。

print(frame[0:1])

补充说明下,这里的数字并非行索引号,而是指按序得到的行号。事实上,索引号也可以采取任何一种序号,甚至乱序的字符都可以。这些后文再做说明。

当然也可以选择更多。

print(frame[2:4])

甚至更为灵活,这里最后一个通过冒号分隔的2表示每2个取1个,这是Python语言常见的切片功能,有兴趣的同学可以单独去好好了解下。

print(frame[2:7:2])

有了行列的选择方法,我们就可以自由定位二维表格中的任意单元格内容,比如将选择行和选择列结合起来,两者谁在前谁在后效果一样。这是表示第二条记录的姓名。

print(frame[‘name’][1:2])

print(frame[1:2][‘name’])

除了这种常见的读取方式外,还有一种就是利用一个特殊的函数。所谓函数,就是类变量中自带的功能模块,比如loc函数,就可以直接读取行、列和单元格。

比如方括号里直接写1,表示索引号为1的第二行,从零开始嘛。

print(frame.loc[1])

也可以通过冒号选择连续的多行,注意此时1:3表示从1到3,在loc函数中,下界3要算。

print(frame.loc[1:3])

甚至通过逗号选择多个不连续的行。这里需要两个方括号,其实里面这个方括号括起来一个列表,里面有两个数值,而外面的方括号里面表示loc函数的参数。

print(frame.loc[[1,3]])

请注意这里的参数是真正的索引号,必须要有对应的索引号才能找到。

我们看一个例子:

frame = pd.DataFrame(data, index=[101, 102, 103, 104, 105, 106, 107])

print(frame[0:1])

print(frame[101:103])

print(frame.loc[0:1])

print(frame.loc[101:103])

这里我们首先在构建DataFrame时,指定了新的索引号,此时可以看出直接序号和使用loc函数的区别。其中第一条表示第一行,第二条没有结果,因为没有第101条,第三条也没有结果,因为索引号没有0、1,而第四条可以获取三条记录。其中再次能看到loc包含下界的特点。

选择列的方式需要在loc的第二个参数中表达,因此,此时需要增加一个逗号,表示第二个参数,由于第一个参数表示行,要所有行,因此可以使用冒号表示,而逗号后的第二个参数就可以使用一个或者多个列名称直接选择列。

print(frame.loc[:, [‘name’]])

print(frame.loc[:, [‘name’, ‘age’]])

借助这个函数,也可以自由选择任何单元格,比如最后两条记录的最后两列。

print(frame.loc[5:6, [‘age’, ‘height’]])

这里使用的是DataFrame类变量中的loc函数方法,所谓函数方法就是一种动作行为,通常代表着一种功能,比如这里loc就表示定位。从它的参数可以看出,它很完整的给出了定位的条件依据。

比loc更为简单有效的iloc函数,它完全根据序号来访问行和列,注意行不是索引号,和loc不一样。比如iloc[1,3]就表示第2行和第4列。

print(frame.iloc[1, 3])

还可以采用区间的选择方法,如iloc[1:2, 0:3]就表示从1到2,这里不包括2。其实这里只有loc采用了比较独特的包含下界的方法。

print(frame.iloc[1:2, 0:3])

甚至还可以任意选择,比如[3, 1], [0, 2]这种形式表示第4和第2行,同时第1和第3列。

print(frame.iloc[[3, 1], [0, 2]])

当然,行的选择一般更为常见的依据是条件,这些条件都放在DataFrame类变量后面的方括号内。比如我们要获取年龄大于17岁的学生姓名,这个显然比按照索引号来读取更有实际意义。此时选择行的方法是在方括号里表达一个列的比较条件,这里列的获取方法仍然和上面介绍的方法类似,也使用方括号表示列。

print(frame[frame[‘age’] > 17])

这些不同的条件也可以组合起来,比如采用&表示并且,| 表示或者,这个符号是反斜杠的转义字符。这里表示年龄大于17岁并且身高大于1.80。其中,请注意两个条件都要加上括号,以防止产生错误的组合。

print(frame[(frame[‘age’] > 17) & (frame[‘height’] > 1.80)])

注意对于布尔型的条件,判断是否等于真假需要使用双等于号。

print(frame[(frame[‘age’] > 17) & (frame[‘gender’] == True)])

这种条件的判断很灵活,比如我们还可以使用isin表示一个离散的范围,只要满足在这个范围中的所有数值都是符合条件。这里表示所有年龄在20或者16的学生信息。

print(frame[frame[‘age’].isin([20, 16])])

强调一下,pandas的条件选择可以任意组合,形成复杂多变的条件,如还可以把列选择、行索引号选择和条件选择综合在一起使用:

print(frame[‘name’][[2, 3]][(frame[‘height’] > 1.80)])

看的出来,方括号在这里很灵活,可以根据里面显示的列名称、行索引号、条件而显示不同的内容,大家要注意练习。

最后我们留一个练习,查询身高大于1.80的男生姓名。

print(frame[(frame[‘height’] > 1.80) & (frame[‘gender’] == True)][‘name’])