Merge branch 'master' of https://github.com/datawhalechina/team-learning-program
|
@ -0,0 +1,120 @@
|
||||||
|
# 0.前言
|
||||||
|
|
||||||
|
本文中带*的部分为选学部分,对学有余力的同学进行相关法律风险的讲解
|
||||||
|
- [0.前言](#0前言)
|
||||||
|
- [0.1课程背景](#01课程背景)
|
||||||
|
- [0.2八爪鱼数据采集器简介](#02八爪鱼数据采集器简介)
|
||||||
|
- [0.3软件安装](#03软件安装)
|
||||||
|
- [0.4爬虫的"盗亦有道"*](#04爬虫的盗亦有道)
|
||||||
|
|
||||||
|
## 0.1课程背景
|
||||||
|
|
||||||
|
随着数字化转型的推动与发展,数据的分析与挖掘在各行各业都变得尤为重要,而数据的获取是诸如大数据、机器学习等领域至关重要的一个环节。在进行相关的项目时,我们可能需要从互联网上获取相关的数据,人工获取数据工作量大,工作流程复杂,不利于大批量的数据获取。爬虫为数据获取提供了一个较好的解决方案,通过解析网页的信息从而进行数据的获取。
|
||||||
|
|
||||||
|
Datawhale组队学习也提供了爬虫的相关课程:
|
||||||
|
|
||||||
|
https://github.com/datawhalechina/team-learning-program/tree/master/WebSpider
|
||||||
|
|
||||||
|
https://github.com/datawhalechina/team-learning-program/tree/master/OfficeAutomation
|
||||||
|
|
||||||
|
对于python而言,虽然有封装的比较好的爬虫库,但是对于小批量数据的获取仍然有较高的门槛,在这样的背景下,软件八爪鱼应运而生,让大家能够便捷的从互联网上获取数据,而不用写一行代码。
|
||||||
|
|
||||||
|
## 0.2八爪鱼数据采集器简介
|
||||||
|
|
||||||
|
八爪鱼数据采集器在客户端中内置了浏览器,通过类似于selenium这样的浏览器驱动来控制浏览器,从而达到数据采集的目的,和python+selenium采集数据的原理比较类似。
|
||||||
|
|
||||||
|
八爪鱼数据采集的优势:
|
||||||
|
|
||||||
|
- 可视化操作流程,上手门槛低,学习曲线平滑
|
||||||
|
- 通过驱动浏览器进行数据采集,能适应绝大多数网页的数据采集
|
||||||
|
- 页面识别相对智能,大幅度减少数据采集开发时间
|
||||||
|
|
||||||
|
八爪鱼数据采集的劣势:
|
||||||
|
|
||||||
|
- 页面定制化操作(如:验证码识别)相对困难
|
||||||
|
- 不适合大批量数据采集,当采集数据量超过1万及以上需要增值服务
|
||||||
|
- 采集速度慢,不适合高并发数据采集
|
||||||
|
|
||||||
|
基于八爪鱼软件的相应特点,本课程适合不会爬虫代码、有小批量数据采集需求的同学进行学习与数据采集训练。
|
||||||
|
|
||||||
|
## 0.3软件安装
|
||||||
|
|
||||||
|
八爪鱼官网:https://www.bazhuayu.com/
|
||||||
|
|
||||||
|
Windows操作系统下载链接:https://www.bazhuayu.com/download/windows
|
||||||
|
|
||||||
|
Mac操作系统下载链接:https://www.bazhuayu.com/download/mac
|
||||||
|
|
||||||
|
以Windows操作系统安装为例:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
选择目标文件夹,点击安装即可完成软件的安装
|
||||||
|
|
||||||
|
Q&A
|
||||||
|
|
||||||
|
1.**八爪鱼安装的系统要求**:
|
||||||
|
|
||||||
|
Win7/Win8/Win8.1/Win10(x64位)选择八爪鱼8版本安装,即上文所提到的链接
|
||||||
|
|
||||||
|
XP系统和32位系统,选择八爪鱼7版本安装,安装链接:https://www.bazhuayu.com/blog/7azsm
|
||||||
|
|
||||||
|
2.**安装过程中提示【安装已终止,安装程序并未成功地运行完成】**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
上述问题出现原因:之前安装过老版本,没有卸载干净,有残留
|
||||||
|
|
||||||
|
解决方法① :删除八爪鱼8缓存文件夹。找到\AppData\Roaming\Octopus8 文件夹,将Octopus8 文件夹删除。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
解决方法②:打开【控制面板】-【程序】,将之前安装过的版本卸载干净。
|
||||||
|
|
||||||
|
点击控制面板-->点击卸载程序-->找到Octopus-->右键点击卸载
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 0.4爬虫的"盗亦有道"*
|
||||||
|
|
||||||
|
做网络爬虫的同时,我们也要注意到爬虫的法律风险,当然正常的爬虫使用是不会造成相应的法律风险,不过我们仍然要了解相应的法律风险。
|
||||||
|
|
||||||
|
**1、如果爬虫程序干扰了被访问的网站或系统正常运营**
|
||||||
|
|
||||||
|
在法律责任方面,如果爬虫程序干扰了被访问的网站或系统正常运营,根据《刑法》第二百八十六条的规定,**违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,将构成“破坏计算机信息系统罪”。**
|
||||||
|
|
||||||
|
而近日出台的《数据安全管理办法》(征求意见稿)中,则专门针对数据爬取的条款进行了首次规定。根据《管理办法》,网络运营者采取自动化手段访问收集网站数据,不得妨碍网站正常运行;此类行为严重影响网站运行,如自动化访问收集流量超过网站**日均流量三分之一**,网站要求停止自动化访问收集时,应当停止。
|
||||||
|
|
||||||
|
**2、** **如果爬虫程序规避网站经营者设置的反爬虫措施或者破解服务器防抓取措施**
|
||||||
|
|
||||||
|
在法律责任方面,如果爬虫程序规避网站经营者设置的反爬虫措施或者破解服务器防抓取措施,非法获取相关信息,根据《刑法》二百八十五条的规定, **违反国家规定,侵入除国家事务、国防建设、尖端科学技术领域以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,将构成非法侵入计算机信息系统罪。**
|
||||||
|
|
||||||
|
**3、** **如果爬虫程序采集的内容涉及商业机密或公民个人信息**
|
||||||
|
|
||||||
|
根据《刑法》第二百八十七条的规定, **利用计算机实施金融诈骗、盗窃、贪污、挪用公款、窃取国家秘密或者其他犯罪的,依照本法有关规定定罪处罚。**
|
||||||
|
|
||||||
|
如果爬虫程序采集了商业机密或公民个人信息(包括但不限于公民的姓名、身份证件号码、通信通讯联系方式、住址、账号密码、财产状况、行踪轨迹等),情节严重的,则可能分别构成“侵犯商业秘密罪”和“侵犯公民个人信息罪”
|
||||||
|
|
||||||
|
对于爬虫的使用者而言,我们应该要注意爬虫的使用规范
|
||||||
|
|
||||||
|
- 要明确自己制作的爬虫程序是否涉及抓取对象的个人信息,如社交信息、财产信息、联系信息等;
|
||||||
|
- 要明确自己爬取的数据是否涉及国防安全、商业机密以及其它相关敏感信息
|
||||||
|
- 要明确自己的爬虫程序是否会对对方服务器的的正常运营产生相应的影响
|
||||||
|
|
||||||
|
参考链接:
|
||||||
|
|
||||||
|
https://www.bazhuayu.com/tutorial8/81azwthz
|
||||||
|
|
||||||
|
https://zhuanlan.zhihu.com/p/88872696
|
||||||
|
|
||||||
|
**Task0 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
|
@ -0,0 +1,129 @@
|
||||||
|
# 1.初识八爪鱼
|
||||||
|
|
||||||
|
- [1.初识八爪鱼](#1初识八爪鱼)
|
||||||
|
- [1.1界面初识](#11界面初识)
|
||||||
|
- [1.2快速上手](#12快速上手)
|
||||||
|
- [1.3数据导出限制](#13数据导出限制)
|
||||||
|
- [1.4练习与思考](#14练习与思考)
|
||||||
|
|
||||||
|
## 1.1界面初识
|
||||||
|
|
||||||
|
安装好八爪鱼的软件后,我们能在桌面上看到八爪鱼的图标
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击图标后运行相关程序,进入程序后会进入到一个登陆页面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果没有账号的话,可以按照程序的要求先注册一个,注册完成后登陆到八爪鱼的界面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
当你看到这个界面的时候,表示前面的软件安装工作已经全部完成了
|
||||||
|
|
||||||
|
## 1.2快速上手
|
||||||
|
|
||||||
|
> 本项目以36kr快讯作为案例
|
||||||
|
|
||||||
|
八爪鱼程序启动有2个入口,一个是首页的网页地址入口,另一个是软件左上角的+新建
|
||||||
|
|
||||||
|
八爪鱼的软件是如何运行的呢?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
作为某公众号的运营人员,你需要实时关注互联网方向的动态,于是你想去36kr网站爬取快讯,你去官网上找到了快讯的网站
|
||||||
|
|
||||||
|
36kr快讯:https://36kr.com/newsflashes
|
||||||
|
|
||||||
|
打开八爪鱼采集器,在主页的搜索框中输入36kr快讯的地址,点击开始采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在屏幕右侧的操作提示中,点击自动识别网页,点击后会进入到识别网页的过程
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
等网页识别完成后我们就看到了八爪鱼自动识别的结果
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击生成采集设置按钮
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击保存并开始采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
数据采集的方式有2种,一个是本地采集,也就是程序运行在你的电脑上,一个是云采集,也就是用八爪鱼的服务器进行采集,其中云采集为八爪鱼的增值服务,是属于收费项目,因此在本次课程中我们选择本地采集
|
||||||
|
|
||||||
|
点击后,我们的数据采集工作就开始了
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击停止采集,就可以停止数据采集的工作
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
选择导出数据
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
我们可以看到,八爪鱼的数据导出支持很多种形式,比如excel、csv、HTML以及json,甚至可以将数据导入到数据库种中,因此数据的存储形式相当的方便
|
||||||
|
|
||||||
|
我们选择导出到excel的形式
|
||||||
|
|
||||||
|
导出完成后有相应的提示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
这个时候我们就看到了我们采集的相关数据
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
打开八爪鱼,在我的任务种就可以看到我们保存的相关任务
|
||||||
|
|
||||||
|
点击我的任务
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1.3数据导出限制
|
||||||
|
|
||||||
|
八爪鱼数据采集器对数据的导出有一定的限制,毕竟是个商用软件,增值服务收费也是能理解的,目前八爪鱼提供了几个版本的的选择
|
||||||
|
|
||||||
|
具体收费方案可以查看链接:https://www.bazhuayu.com/plan
|
||||||
|
|
||||||
|
免费版的数据导出上限是1万条,每次启动任务导出的最大数据量是1万,这个数据量对于小批量数据采集来说也是非常大了,如果我们采集的数据量超过了1万条,可以选择购买增值服务,或者将采集任务分多个批次完成,从这个角度来看八爪鱼对于日常使用体验还是极佳的。
|
||||||
|
|
||||||
|
八爪鱼也提供了教育公益版本,进行在校师生认证可以获得专业版12个月的免费使用权限,可以导出10万的数据量,这点还是很棒的。
|
||||||
|
|
||||||
|
公益板块链接:https://www.bazhuayu.com/edu
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1.4练习与思考
|
||||||
|
|
||||||
|
前面的操作步骤你学会了吗?
|
||||||
|
|
||||||
|
如果还不是很清楚那就上手试一试吧,完成36kr快讯的数据采集,并将数据导出为excel形式
|
||||||
|
|
||||||
|
思考部分
|
||||||
|
|
||||||
|
1.体验了八爪鱼采集后,你觉得它能够用在学习和工作中的哪些方面?、
|
||||||
|
|
||||||
|
2.推测一下,你觉得八爪鱼的工作原理是什么?
|
||||||
|
|
||||||
|
3.和python爬虫相比,八爪鱼的优势和劣势是什么?
|
||||||
|
|
||||||
|
**Task1 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
|
@ -0,0 +1,194 @@
|
||||||
|
# 2.自动识别初体验
|
||||||
|
|
||||||
|
> 本节课程所涉及到的问题均为Datawhale读者所遇到的实际问题,将八爪鱼的操作知识点融合在实际问题中,本节中的方案均为作者整理和思考后的原创方案,在学习输出成笔记的过程中请附带本课程链接作为参考链接,感谢你对原创工作的支持!
|
||||||
|
|
||||||
|
完成了前面的课程学习,我们已经成功的安装了八爪鱼软件以及体验了简单的数据采集过程,在采集过程中我们使用了智能识别,在本节我们会实际的案例来学习八爪鱼操作中的自定义数据采集部分。
|
||||||
|
|
||||||
|
本节的知识点有:如何使用八爪鱼的智能识别功能,并完成登陆、翻页等爬虫任务需求。其中带*为选学部分。
|
||||||
|
|
||||||
|
- [2.自动识别初体验](#2自动识别初体验)
|
||||||
|
- [2.1微博数据抓取(登陆Cookie设置)](#21微博数据抓取登陆cookie设置)
|
||||||
|
- [2.2豆瓣图书数据抓取(翻页与循环)](#22豆瓣图书数据抓取翻页与循环)
|
||||||
|
- [2.3采集流程逻辑*](#23采集流程逻辑)
|
||||||
|
- [2.4练习与思考](#24练习与思考)
|
||||||
|
|
||||||
|
## 2.1微博数据抓取(登陆Cookie设置)
|
||||||
|
|
||||||
|
你是Datawhale的一个读者,在Datawhale的公众号上看到了相关文章[《我用“觉醒年代”做数据分析》](https://mp.weixin.qq.com/s/f_euOxrMKEh5Db2ixVhTjw),在文章中使用爬虫爬取了微博关键词下的相关信息,你也想爬取微博的相关数据,那么如何用八爪鱼来爬取数据呢?
|
||||||
|
|
||||||
|
我们在八爪鱼页面打开微博的官网
|
||||||
|
|
||||||
|
https://www.weibo.com/
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
通过观察发现,想要搜索微博的相关信息需要先登陆微博的账号,我们先进入到浏览模式进行账号的登陆。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
进入浏览模式之后,会像进入微博网页版,点击右上角的登陆进入到微博账号的登陆界面。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
登陆完成后我们就进入到了全新的微博界面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
想一下如果每次数据抓取都需要登陆岂不是相当的麻烦,能不能让页面保持这个登陆状态呢?
|
||||||
|
|
||||||
|
在这之前我们需要了解一下Cookie
|
||||||
|
|
||||||
|
Cookie是某些网站为了辨别用户身份在用户本地终端上的数据(通常经过加密),由用户计算机暂时或永久保存的信息。
|
||||||
|
|
||||||
|
也就是如果我们能在打开网页时使用指定的Cookie,这样网页就能辨别我们的身份,从而避免了每次登陆网站需要登陆的问题,那么如何设置呢?
|
||||||
|
|
||||||
|
首先,找到左边的流程图,设置打开网页的信息,设置获取当前Cookie,点击应用
|
||||||
|
|
||||||
|
Cookie设置完成后,退出浏览模式进入到数据爬取规则设置阶段
|
||||||
|
|
||||||
|
点击微博的搜索按钮,会出现智能提示,按照智能提示进行相关操作
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
通过关键词的输入以及点击,我们进入到了觉醒年代话题下的相关微博,使用**自动识别**网页功能对网页进行识别
|
||||||
|
|
||||||
|
识别完成后,默认采集了14个字段
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果字段有些不是我们想要的,可以将其删除
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
字段设置完成以后,点击生成采集设置,就可以对微博的数据进行爬取了
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
对于部分笔记本电脑,会出现采集卡顿的情况,可以在采集的过程中将采集的数据隐藏掉,可以提升采集的速度。
|
||||||
|
|
||||||
|
最后将任务修改名称后保存,可以在我的任务中找到设置的相应任务。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 2.2豆瓣图书数据抓取(翻页与循环)
|
||||||
|
|
||||||
|
你是某高校的学生,你需要爬取豆瓣图书的相关信息进行做数据统计分析,需要知道每本书评价人数是多少,各个等级评价比例是多少
|
||||||
|
|
||||||
|
豆瓣图书的网址:https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4
|
||||||
|
|
||||||
|
首先是登陆八爪鱼,进入到豆瓣的采集页面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
使用自动识别网页的功能对豆瓣的数据进行采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
我们发现自动采集无法满足我们数据采集的相关需求,我们需要对豆瓣的每个链接点击进入,然后去采集打分的人数以及比例,比如小说《活着》,有607608个人评价,同时我们需要采集不同星的比例,那么如何操作呢?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
聪明的你想到了2个思路
|
||||||
|
|
||||||
|
第一个是先采集各个书的链接,然后再依次打开各个书的链接进行数据采集
|
||||||
|
|
||||||
|
第二个是打开豆瓣的网站,然后点击第一个链接进入采集数据,然后返回到链接列表然后采集第二个链接
|
||||||
|
|
||||||
|
这里我们选择第一个思路作为示例
|
||||||
|
|
||||||
|
**第一步就是采集各个图书的链接**
|
||||||
|
|
||||||
|
我们使用八爪鱼的自动识别功能对豆瓣网页进行识别
|
||||||
|
|
||||||
|
通过前面的测试我们看到自动识别采集了11个字段,而我们只要书的名称以及链接这个2个字段,其它的都删除。
|
||||||
|
|
||||||
|
可以不删除嘛?也是可以的,不过需要注意的是,采集的字段越多,采集的速度越慢,减少采集的字段可以提升采集速度,因此将不需要的字段删除
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击生成采集设置,点击保存并开始采集,点击启动本地采集。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
接下来就是漫长的采集等待...
|
||||||
|
|
||||||
|
实际的测试速度是25条/分钟,虽然这个速度对于无界面爬虫来说不算快,但是比手动快很多了
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
经过漫长的采集(一个午觉的时间)终于采集完成了...
|
||||||
|
|
||||||
|
我们看看采集的数据
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**第二步就是循环打开各个网页采集相关信息**
|
||||||
|
|
||||||
|
将前面采集的网页导入到八爪鱼中,如果数量超过了1万条可以分批导入
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
对网页的数据进行采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
点击采集开始对最终数据进行抓取
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 2.3采集流程逻辑*
|
||||||
|
|
||||||
|
**八爪鱼的工作原理**
|
||||||
|
|
||||||
|
模拟人的行为,通过内置Chrome浏览器浏览网页数据,所以采集数据的第一步永远是找到目标网址并输入。这跟通过普通浏览器访问网页完全一样。在普通浏览器中需要点击链接进入详情、点击翻页按钮查看更多数据,在八爪鱼中也需如此操作。
|
||||||
|
|
||||||
|
**八爪鱼的流程逻辑**
|
||||||
|
|
||||||
|
八爪鱼通过【采集流程】全自动采集数据。【采集流程】执行逻辑遵循2个原则:**先从上至下、再由内而外**
|
||||||
|
|
||||||
|
【采集流程】由【蓝色步骤】和【灰色框】两大部分组成。【蓝色步骤】是会执行的步骤,八爪鱼与网页发生互动。【灰色框】起记录网页的作用。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
相关案例:
|
||||||
|
|
||||||
|
案例1
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
案例2
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
案例3
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 2.4练习与思考
|
||||||
|
|
||||||
|
前面的操作步骤你学会了吗?
|
||||||
|
|
||||||
|
如果还不是很清楚那就上手试一试吧,完成微博与豆瓣图书的数据采集,并将数据导出为excel形式
|
||||||
|
|
||||||
|
思考部分
|
||||||
|
|
||||||
|
1.抓取的微博数据算不算是个人隐私数据,在使用中需要注意什么?
|
||||||
|
|
||||||
|
2.设置Cookie登陆的优势是什么?Cookie在手机和电脑使用中体现在哪些方面?
|
||||||
|
|
||||||
|
3.豆瓣图书数据采集中选择第一个思路的优势是什么?选择第二个思路的优势是什么?
|
||||||
|
|
||||||
|
参考链接:https://www.bazhuayu.com/tutorial8/81xsrm9
|
||||||
|
|
||||||
|
**Task2 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
# 3.自定义数据采集
|
||||||
|
|
||||||
|
> 本节课程所涉及到的问题均为作者经过思考后将八爪鱼的操作知识点融合在实际问题中,在学习输出成笔记的过程中请附带参考课程链接,感谢你对原创工作的支持!
|
||||||
|
|
||||||
|
完成了前面的课程学习,我们已经成功的安装了八爪鱼软件以及体验了简单的数据采集过程,但是在实际的数据采集中我们还是会遇到一些采集难度比较大的数据,因此本节对八爪鱼的特殊数据采集进行介绍。其中带*为选学部分。
|
||||||
|
|
||||||
|
- [3.自定义数据采集](#3自定义数据采集)
|
||||||
|
- [3.1京东关键词循环与特殊字段](#31京东关键词循环与特殊字段)
|
||||||
|
- [3.2豆瓣数据格式化](#32豆瓣数据格式化)
|
||||||
|
- [3.3正则表达式*](#33正则表达式)
|
||||||
|
- [3.4练习与思考](#34练习与思考)
|
||||||
|
|
||||||
|
## 3.1京东关键词循环与特殊字段
|
||||||
|
|
||||||
|
你是某电商平台的运营,你需要对京东部分商品的数据进行竞品相关数据分析,分析的商品有多个,前面课程我们完成了对单个关键词数据的采集,但是对于多个关键词循环并未涉及到,那么如何完成多个关键词任务采集呢?
|
||||||
|
|
||||||
|
京东链接:https://www.jd.com
|
||||||
|
|
||||||
|
进入八爪鱼打开京东的链接,找到流程图在【打开网页的下方】点击+号,增加文本循环,将提取准备好的关键词输入到循环列表中。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
设置完循环然后设置网页访问,进行数据采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
当然在设置的过程中我们也遇到了京东要求登陆的情况,按照第二节所学习的Cookie设置完成相应的页面登录设置。
|
||||||
|
|
||||||
|
到这里循环搜索关键词就设置成功了,接下来就是采集需要的数据或者使用自动识别网页对网页数据进行采集。
|
||||||
|
|
||||||
|
在这里有读者可能就有疑问了,对于商品的数据采集就这样完成了。但是当我们想要采集一些特殊数据,比如页面标题时,应该如何进行采集呢?
|
||||||
|
|
||||||
|
八爪鱼也能够很好的满足这一需求
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 3.2豆瓣数据格式化
|
||||||
|
|
||||||
|
在实际的采集过程中,采集到的数据可能不满足我们的需求,因此需要对数据进行格式化。本节内容以豆瓣图书为例,讲解如何使用八爪鱼进行数据采集。(本节内容来源于八爪鱼官方教程)
|
||||||
|
|
||||||
|
在第2节中我们采集了豆瓣图书的相关信息
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
采集中我们发现作者和出版社等信息都混到了一起,如果我们只想要其中的一部分怎么办?
|
||||||
|
|
||||||
|
字段提取完成以后,鼠标移动到目标字段上,然后点击 【...】按钮,选择【格式化数据】,就会进入【格式化数据】配置页面。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
原始值:原始字段
|
||||||
|
|
||||||
|
结果:经过格式化步骤后,输出的字段结果
|
||||||
|
|
||||||
|
添加步骤:提供8个数据格式化选项:替换、正则表达式替换、正则表达式匹配、去除空格、添加前缀、添加后缀、日期时间格式化、Html。
|
||||||
|
|
||||||
|
以下将配合具体案例,详解这8个选项如何使用
|
||||||
|
|
||||||
|
**1.替换**
|
||||||
|
|
||||||
|
将字段中的部分或全部内容替换为其他内容,支持文字、数字、符号、空格、换行符的替换
|
||||||
|
|
||||||
|
例如:如果我们想将字段【436665人评价】中的文本【人评价】去掉,只留下数字【43665】。
|
||||||
|
|
||||||
|
具体步骤为:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**2、正则表达式替换**
|
||||||
|
|
||||||
|
用正则表达式将字段中的部分或全部内容找出来,然后将其替换为其他内容,支持文字、数字、符号、空格、换行符的替换。相比简单的【替换】,【正则表达式替换】更为强大灵活。
|
||||||
|
|
||||||
|
在提取书籍信息时,我们发现采集到的数据中有很多空格,我们想将空格去除掉
|
||||||
|
|
||||||
|
【正则表达式】:\s+(这条正则表达式的意思是,找到字段中所有的空格)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**3、正则表达式匹配**
|
||||||
|
|
||||||
|
用正则表达式将字段中的部分或全部内容提取出来
|
||||||
|
|
||||||
|
实例:在采集数据中,我们只需要作者这一行的信息
|
||||||
|
|
||||||
|
首先使用正则表达式替换,将空格全部替换为空
|
||||||
|
|
||||||
|
接着使用正则表达式匹配将作者的相关信息提取出来
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**4、去除空格**
|
||||||
|
|
||||||
|
包括三种,分别是【去除开头空格】、【去除结尾空格】、【去除两头空格】
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**5、添加前缀**
|
||||||
|
|
||||||
|
增加前缀,就是在采集的字段前增加相关信息,如下图所示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**6、添加后缀**
|
||||||
|
|
||||||
|
怎么理解增加前缀,就是在采集的字段后增加相关信息
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**7、日期时间格式化**
|
||||||
|
|
||||||
|
选中时间字段,选择【日期时间格式化】,将日期转化成需要的格式或者仅提取日期时间中的某一部分。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**8、HTML转码**
|
||||||
|
|
||||||
|
html页面中会有html带有相关的特殊标记,需要将这些特殊的标记进行转换,比如gt;转化为>,nbsp;转化为空格等等,当然这种类型也能用替换来解决。
|
||||||
|
|
||||||
|
## 3.3正则表达式*
|
||||||
|
|
||||||
|
**1、正则表达式简介**
|
||||||
|
|
||||||
|
正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个【规则字符串】,这个【规则字符串】用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
|
||||||
|
|
||||||
|
**2、正则表达式的用途**
|
||||||
|
|
||||||
|
字符串匹配(字符匹配)
|
||||||
|
|
||||||
|
字符串查找
|
||||||
|
|
||||||
|
字符串替换
|
||||||
|
|
||||||
|
**3、常用元字符及描述**
|
||||||
|
|
||||||
|
\d 匹配一个数字字符。等价于[0-9]
|
||||||
|
|
||||||
|
\D 匹配一个非数字字符。等价于[^0-9]
|
||||||
|
|
||||||
|
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
|
||||||
|
|
||||||
|
\S 匹配任何非空白字符。
|
||||||
|
|
||||||
|
\w 匹配构成单词的字符(字母、数字、下划线)。等价于[A-Za-z0-9_]
|
||||||
|
|
||||||
|
\W 匹配非构成单词的字符。等价于[^A-Za-z0-9_]
|
||||||
|
|
||||||
|
\p{Lower} 英文的小写字母
|
||||||
|
|
||||||
|
. 任意一个字符
|
||||||
|
|
||||||
|
X* 任意次数
|
||||||
|
|
||||||
|
X+ 一次或多次
|
||||||
|
|
||||||
|
X? 最多一次
|
||||||
|
|
||||||
|
X{n} 正好n次
|
||||||
|
|
||||||
|
X{n,} 最少n次,上不封顶
|
||||||
|
|
||||||
|
X{n,m} 最少n次,最多m次
|
||||||
|
|
||||||
|
[] 表示一个范围
|
||||||
|
|
||||||
|
[a-z]|[A-Z] [a-zA-Z] [a-z[A-Z]] a到z 或A到Z
|
||||||
|
|
||||||
|
[A-Z&&[RFG]] A到Z并且RFG (交集的意思)
|
||||||
|
|
||||||
|
“” 正则 “a?” 零宽度匹配,出现零次
|
||||||
|
|
||||||
|
**边界匹配**
|
||||||
|
|
||||||
|
^开头和$结尾
|
||||||
|
|
||||||
|
^h.* .*ir$ ^h[a-z]{1,3}\b
|
||||||
|
|
||||||
|
\b 一个单词的边界,空格、换行……
|
||||||
|
|
||||||
|
^[\s&&[^\n]]*\n$ 空白行,开头是空白字符但不是换行符,出现0次或多次,且紧跟着是结束的换行符
|
||||||
|
|
||||||
|
常用的正则表达式:https://www.cnblogs.com/hsinfo/p/13584432.html
|
||||||
|
|
||||||
|
当你对你写的正则表达式不确定时,可以使用正则表达在线测试,以确定正则表达式正确
|
||||||
|
|
||||||
|
正则表达式测试:https://c.runoob.com/front-end/854
|
||||||
|
|
||||||
|
## 3.4练习与思考
|
||||||
|
|
||||||
|
前面的操作步骤你学会了吗?
|
||||||
|
|
||||||
|
如果还不是很清楚那就上手试一试吧,完成京东与豆瓣图书的数据采集,并将数据导出为excel形式
|
||||||
|
|
||||||
|
思考部分
|
||||||
|
|
||||||
|
1.在数据采集过程中,数据格式化作用是什么?为什么要进行数据格式化?
|
||||||
|
|
||||||
|
2.除了在数据采集中,正则表达式能用在学习和生活中的哪些方面?
|
||||||
|
|
||||||
|
参考链接:
|
||||||
|
|
||||||
|
https://www.bazhuayu.com/tutorial8/81srgjc
|
||||||
|
|
||||||
|
https://www.bazhuayu.com/tutorial8/81gshsj
|
||||||
|
|
||||||
|
**Task3 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
|
@ -0,0 +1,76 @@
|
||||||
|
# 4.综合实践
|
||||||
|
|
||||||
|
> 本节课程所涉及到的问题均为Datawhale读者所遇到的实际问题,将八爪鱼的操作知识点与实际问题相结合,本节中的方案均为作者整理和思考后的原创方案,在学习输出成笔记的过程中请课程参考链接,感谢你对原创工作的支持!
|
||||||
|
|
||||||
|
学习完前面的课程后,已经初步掌握了使用八爪鱼进行数据采集的相关流程,是时候来检验一下学习成果了。
|
||||||
|
|
||||||
|
在设计本节课程内容的过程中作者经过漫长的思考与讨论,注意到爬虫技能是低频但是紧急的技能,有需要的时候能顺利完成数据采集即可
|
||||||
|
|
||||||
|
我们课程的优点是无代码数据采集,能进行日常的数据采集,提升学习者的自动化工作的能力,从而提升工作效率。
|
||||||
|
|
||||||
|
因此课程最后选择了2个容易上手的数据采集的项目,希望学习者能在项目中掌握基本的数据采集能力。
|
||||||
|
|
||||||
|
综合实践的项目不做详细的介绍,只提供相关的操作思路,学习者需要自己完成相关的实践过程,如果在学习过程中遇到了相关的问题可以在学习群中与助教进行讨论。
|
||||||
|
|
||||||
|
## 4.1综合实践背景
|
||||||
|
|
||||||
|
小张是某高校管理学研究生,在毕业课题中需要采集boss直聘武汉地区的相关数据,而小张同学因为是人文科学背景没有接触过爬虫,在网上搜索很多资料后,现在她找到你希望你帮她采集boss直聘网的相关数据,你应该怎么做呢?
|
||||||
|
|
||||||
|
boss直聘武汉地区网站:https://www.zhipin.com/wuhan/
|
||||||
|
|
||||||
|
## 4.2综合实践操作
|
||||||
|
|
||||||
|
首先打开八爪鱼到打开boss直聘武汉的相关页面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在职务关键词中输入【数据分析】关键词进行搜索
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
首先是对页面进行自动网页识别
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
这是自动识别完成后的界面,我们可以看到系统抓取的数据比较全面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果需要获得更加详细的职位页面数据,相关数据采集完成后可以进入到单独的职位页面进行数据采集
|
||||||
|
|
||||||
|
采集完成后 将采集的职位的链接导入到八爪鱼后
|
||||||
|
|
||||||
|
对后续的职位信息进行相关采集
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
循环打开采集的网页,对职位的岗位需求进行采集
|
||||||
|
|
||||||
|
针对Boss直聘对ip进行验证的情况,可以对打开网页这一步骤中的重试部分进行设置,从而在一定程度上对反爬虫的绕过
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 4.3练习与思考
|
||||||
|
|
||||||
|
前面的操作步骤你学会了吗?
|
||||||
|
|
||||||
|
如果还不是很清楚那就上手试一试吧,完成boss直聘的数据采集,并将数据导出为excel形式
|
||||||
|
|
||||||
|
思考部分
|
||||||
|
|
||||||
|
1.在数据采集过程中,频繁访问网页会引发反爬虫机制,如何规避这样的问题?
|
||||||
|
|
||||||
|
2.对于boss直聘的数据采集中,你觉得哪些信息字段是比较重要的?
|
||||||
|
|
||||||
|
**Task4 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
# 5.综合实践
|
||||||
|
|
||||||
|
> 本节课程所涉及到的问题均为Datawhale读者所遇到的实际问题,将八爪鱼的操作知识点与实际问题相结合,本节中的方案均为作者整理和思考后的原创方案,在学习输出成笔记的过程中请附带课程参考链接,感谢你对原创工作的支持!
|
||||||
|
|
||||||
|
在完成了上一节的boss直聘的数据抓取后,我们根据数据采集中所遇到的实际问题,增加了微博个人信息抓取的相关实践部分,该部分针对Xpath数据提取的情况进行了详细的介绍,以期学习者能够在日常的学习和工作中能够实地使用。
|
||||||
|
|
||||||
|
## 5.1综合实践背景
|
||||||
|
|
||||||
|
小李是某高校的大二的学生,在学校的课程项目中需要采集微博某个主题广场上相关用户的信息,在Datawhale的公众号上看到了相关文章[《我用“觉醒年代”做数据分析》](https://mp.weixin.qq.com/s/f_euOxrMKEh5Db2ixVhTjw)后想了解如何采集个人用户信息。
|
||||||
|
|
||||||
|
基于这个问题,本项目就详细的介绍如何使用八爪鱼进行微博个人信息的采集。
|
||||||
|
|
||||||
|
## 5.2综合实践
|
||||||
|
|
||||||
|
通过第二节内容的学习,我们已经掌握了如何就微博某一话题下的相关信息采集
|
||||||
|
|
||||||
|
首先导入我们需要采集的相关个人链接
|
||||||
|
|
||||||
|
这里以我自己的微博账号为例子:https://weibo.com/u/6001346718
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.1用户id
|
||||||
|
|
||||||
|
通过观察我们发现用户的id就是网址中的一串数字,以我的账号为例,我的用户id号是6001346718
|
||||||
|
|
||||||
|
那么如何把这个数据采集下来呢?
|
||||||
|
|
||||||
|
首先,我们采集当前的网页地址
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
那么如何提取用户的id数字呢?
|
||||||
|
|
||||||
|
我们想到了正则表达式匹配,需要匹配数字的正则表达式为【[0-9]+】因此你完成了如下操作
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.2用户名称
|
||||||
|
|
||||||
|
点击用户名称,八爪鱼会提示相关的操作,选择采集文本
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.3用户性别
|
||||||
|
|
||||||
|
用户性别采集相对比较困难,为什么呢?
|
||||||
|
|
||||||
|
因为用户性别是一个图标,因此我们需要读取这个图标是什么东西
|
||||||
|
|
||||||
|
因此我们采用了这样的操作
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
通过上述操作后 我们采集了图标的outer html
|
||||||
|
|
||||||
|
采集后我们发现采集的信息如下
|
||||||
|
|
||||||
|
```
|
||||||
|
<i class="W_icon icon_pf_male" style=""></i>
|
||||||
|
```
|
||||||
|
|
||||||
|
通过观察我们发现
|
||||||
|
|
||||||
|
icon_pf_male代表男生,icon_pf_female代表女生
|
||||||
|
|
||||||
|
因此我们使用正则表达式匹配这个信息,使用的正则表达式为【(?<=class="W_icon)(.+?)(?=")】
|
||||||
|
|
||||||
|
这个正则表达式是什么意思呢?
|
||||||
|
|
||||||
|
(?<=class="W_icon) 表示以class="W_icon这个开头
|
||||||
|
|
||||||
|
(.+?)表示中间的所有字符串
|
||||||
|
|
||||||
|
(?=") 表示以"结尾
|
||||||
|
|
||||||
|
匹配完成后我们发现生成的结果是icon_pf_male
|
||||||
|
|
||||||
|
因此我们再增加替换,将icon_pf_male替换为男,将icon_pf_female替换为女
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.4用户个人简介
|
||||||
|
|
||||||
|
采集方式与用户名称相似,点击用户个人简历,选择采集该文本
|
||||||
|
|
||||||
|
不过需要注意的是,因为有的用户没有写个人简介,因此我们采集不到该元素时,我们可以留空
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.5用户关注、粉丝、微博数
|
||||||
|
|
||||||
|
采集方式与用户名称相似,点击用户个人简历,选择采集该文本
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2.5用户地理位置、毕业学校、生日
|
||||||
|
|
||||||
|
采集方式与用户名称相似,点击用户个人简历,选择采集该文本
|
||||||
|
|
||||||
|
对于时间信息,八爪鱼也提供了相关的时间格式化方式
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 5.3练习与思考
|
||||||
|
|
||||||
|
前面的操作步骤你学会了吗?
|
||||||
|
|
||||||
|
如果还不是很清楚那就上手试一试吧,完成微博个人信息数据采集,并将数据导出为excel形式
|
||||||
|
|
||||||
|
思考部分
|
||||||
|
|
||||||
|
1.对于微博个人信息数据的采集,这部分数据是属于个人隐私数据吗?如果是,在使用中应该做怎样的操作?如果不是,请说明理由。
|
||||||
|
|
||||||
|
**Task5 END.**
|
||||||
|
|
||||||
|
--- By: 牧小熊
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
>
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
关于Datawhale: Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结,详情可关注Datawhale:
|
||||||
|
|
||||||
|
[](https://camo.githubusercontent.com/8578ee173c78b587d5058439bbd0b98fa39c173def229a8c3d957e62aac0b649/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323032303039313330313032323639382e706e67237069635f63656e746572)
|
After Width: | Height: | Size: 438 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 5.5 MiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 3.0 MiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 3.3 MiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 591 KiB |
After Width: | Height: | Size: 166 KiB |
After Width: | Height: | Size: 362 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 646 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 565 KiB |
After Width: | Height: | Size: 312 KiB |
After Width: | Height: | Size: 888 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 382 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 573 KiB |
After Width: | Height: | Size: 585 KiB |
After Width: | Height: | Size: 5.9 MiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 563 KiB |
After Width: | Height: | Size: 518 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 153 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 217 KiB |
After Width: | Height: | Size: 151 KiB |
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 188 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 342 KiB |
After Width: | Height: | Size: 165 KiB |
After Width: | Height: | Size: 463 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 594 KiB |
After Width: | Height: | Size: 430 KiB |
After Width: | Height: | Size: 385 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 297 KiB |
After Width: | Height: | Size: 521 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 554 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 866 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 824 KiB |
After Width: | Height: | Size: 229 KiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 219 KiB |
After Width: | Height: | Size: 356 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -1,2 +1,103 @@
|
||||||
# 数据采集从入门到精通
|
# 课程简介
|
||||||
|
|
||||||
|
**课程背景**:随着数字化的不断推进,数据采集在数据分析的任务中占据了一定的重要性。Python爬虫作为数据采集的代表方向在这段时间不断发展,但是我们也注意到,爬虫对于数据从业者来说是一个低频但紧急的需求。
|
||||||
|
|
||||||
|
因此,基于这个需求,我们推出了我们的全新课程《数据采集从入门到精通》,基于八爪鱼数据采集器对数据采集过程的封装,通过对数据采集任务的可视化,从而降低爬虫的门槛,提升数据从业者的工作效率。
|
||||||
|
|
||||||
|
本课程的核心亮点是数据采集的简单化、可视化以及无代码化,通过将数据采集的相关操作融入到实际的数据采集项目中,以实践学习入手理论知识从而增强学习者对数据采集学习的信心与学习热情。
|
||||||
|
|
||||||
|
我们在课程中设置了选学部分,也介绍了爬虫中的一些概念以及涉及到的相关法律知识,整体课程设计系统,学习难度具有一定的梯度,因此可以满足不同人群对课程的需求,希望在完成本次课程的学习后,能将数据采集应用到学习和工作中。
|
||||||
|
|
||||||
|
**课程基本信息**
|
||||||
|
|
||||||
|
- 学习周期:14天,每天平均花费时间1小时-3小时不等,根据个人学习接受能力强弱有所浮动。
|
||||||
|
- 学习形式:理论学习 + 练习
|
||||||
|
- 人群定位:对数据采集有需求的同学
|
||||||
|
- 难度系数:⭐
|
||||||
|
|
||||||
|
# 课程大纲
|
||||||
|
|
||||||
|
课程中带有*标识的为选学课程
|
||||||
|
|
||||||
|
**Task00:熟悉规则与课程背景(1天)**
|
||||||
|
|
||||||
|
- 组队、修改群昵称。
|
||||||
|
- 熟悉打卡规则。
|
||||||
|
- 学习课程的相关背景
|
||||||
|
|
||||||
|
**Task01 认识八爪鱼 (2天)**
|
||||||
|
|
||||||
|
- 八爪鱼界面认识
|
||||||
|
- 八爪鱼快速上手
|
||||||
|
- 相关数据导出
|
||||||
|
|
||||||
|
**Task02 自动识别初体验 (3天)**
|
||||||
|
|
||||||
|
- 微博数据抓取
|
||||||
|
- 豆瓣图书输出抓取
|
||||||
|
- 采集逻辑与流程*
|
||||||
|
|
||||||
|
**Task03 自定义数据采集 (3天)**
|
||||||
|
|
||||||
|
- 京东关键词抓取
|
||||||
|
- 豆瓣图书数据格式化
|
||||||
|
- 正则表达式*
|
||||||
|
|
||||||
|
**Task04 综合实践-boss直聘信息采集(2天)**
|
||||||
|
|
||||||
|
- boss直聘职位数据抓取
|
||||||
|
|
||||||
|
**Task05 综合实践-微博个人信息采集(3天)**
|
||||||
|
|
||||||
|
- 微博个人信息数据抓取
|
||||||
|
|
||||||
|
**致谢**
|
||||||
|
|
||||||
|
感谢以下成员对项目推进作出的贡献(排名不分先后):
|
||||||
|
|
||||||
|
<table align="center" style="width:100%;">
|
||||||
|
<caption><b>内容贡献者名单</b></caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>成员</th>
|
||||||
|
<th>个人简介及贡献</th>
|
||||||
|
<th>个人主页</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">聂雄伟(牧小熊)</span></td>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">华中农业大学研究生,Datawhale成员,项目负责人,内容构建 </td>
|
||||||
|
<td><a href="https://www.zhihu.com/people/muxiaoxiong">知乎</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">杨石雄</span></td>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">电子科技大学研究生,课程测评 </td>
|
||||||
|
<td><a href="https://blog.csdn.net/Eric___Young?spm=1011.2124.3001.5343">CSDN</a></td>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">李云龙</span></td>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">中国科学技术大学研究生,课程测评 </td>
|
||||||
|
<td><a href="https://blog.csdn.net/li_kin?type=blog">CSDN</a></td>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">叶前坤(荞麦)</span></td>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">中国传媒大学研究生,课程测评 </td>
|
||||||
|
<td><a href="https://purebuckwheat.github.io/">github</a></td>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">武润琦</span></td>
|
||||||
|
<td><span style="font-weight:normal;font-style:normal;text-decoration:none">悉尼大学研究生,课程测评 </td>
|
||||||
|
<td><a href="https://github.com/Allonsy-ops">github</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
> "Datawhale是一个专注AI领域的开源组织,以“for the learner,和学习者一起成长”为愿景,构建对学习者最有价值的开源学习社区。关注我们,一起学习成长。"
|
||||||
|
|
||||||
|

|
||||||
|
|
|
@ -26,5 +26,31 @@ public class BMIexponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### 练习1:试着实现将37摄氏度转换为整型的华氏度。(提示∶ 华氏度 =32+摄氏度 ×1.8)
|
#### 练习1:试着实现将37摄氏度转换为整型的华氏度。(提示∶ 华氏度 =32+摄氏度 ×1.8)
|
||||||
#### 练习2:一个圆柱形粮仓,底面值径为 10米,高为 3米,该粮仓体积为多少立方米?如果每立方米屯粮 750 千克,该粮仓一共可储存多少千克粮食?
|
```java
|
||||||
|
public class CelsiusToFahrenheit {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int celsius = 37;
|
||||||
|
System.out.println("要转换的摄氏度 = " + celsius);
|
||||||
|
double fahrenheit = 9.0/5 * celsius + 32;
|
||||||
|
int intfahrenheit = (int)fahrenheit;
|
||||||
|
System.out.println("37摄氏度 = " + fahrenheit + "华氏度(未转换成int型)");
|
||||||
|
System.out.println("37摄氏度 = " + intfahrenheit + "华氏度(转换成int型)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
#### 练习2:一个圆柱形粮仓,底面值径为 10米,高为 3米,该粮仓体积为多少立方米?如果每立方米屯粮 750 千克,该粮仓一共可储存多少千克粮食?
|
||||||
|
```java
|
||||||
|
public class Granary {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
final double PI = 3.14;
|
||||||
|
int diameter = 10;
|
||||||
|
int height = 3;
|
||||||
|
double volume = diameter/2 * diameter/2 * PI * height;
|
||||||
|
System.out.println("该粮仓的体积 = " + volume + "立方米");
|
||||||
|
int weight = 750;
|
||||||
|
System.out.println("该粮仓一共可储存" + weight * volume + "千克粮食");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
228
Java/练习题:异常处理.md
|
@ -37,9 +37,77 @@ public class Tomato { // 创建西红柿类
|
||||||
|
|
||||||
#### 练习1
|
#### 练习1
|
||||||
在控制台上简述一个整型数组,如int a[] ={1,2,3,4 }; 数组遍历的过程,并体现出当i的值为多少时,会产生异常,异常的种类是什么?
|
在控制台上简述一个整型数组,如int a[] ={1,2,3,4 }; 数组遍历的过程,并体现出当i的值为多少时,会产生异常,异常的种类是什么?
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Array {// 创建一个Array类
|
||||||
|
public static void main(String args[]) {
|
||||||
|
int a[] = { 1, 2, 3, 4 };// 定义一个int类型的数组
|
||||||
|
for (int i = 0; i < 5; i++) {// 遍历数组
|
||||||
|
try {// try块
|
||||||
|
System.out.print("当 i = " + i + "," + i + " < 5 时,a[" + i + "] = " + a[i] + ";");// 控制台输出
|
||||||
|
} catch (ArrayIndexOutOfBoundsException e) {// catch块
|
||||||
|
System.out.println("当 i = " + i + "," + i + " < 5 时,a[" + i + "]不存在,会引起"
|
||||||
|
+ e.toString().substring(10, e.toString().indexOf(':'))
|
||||||
|
+ "异常,\n该异常为数组越界异常,主要是由于索引超出了数组的长度范围引起的");// 控制台输出
|
||||||
|
}
|
||||||
|
if (i != 4) {// 当i不等于4的时候
|
||||||
|
System.out.println("执行i++," + "i = " + (i + 1) + "。");// 控制台输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习2
|
#### 练习2
|
||||||
模拟一个简单的整数计算器(只能计算两个整数之间的加、减、乘、除的运算),并用try-catch捕捉InputMismatchException(控制台输入的不是整数)异常。
|
模拟一个简单的整数计算器(只能计算两个整数之间的加、减、乘、除的运算),并用try-catch捕捉InputMismatchException(控制台输入的不是整数)异常。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Calculator {// 创建一个Number类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in); // 控制台输入
|
||||||
|
try {// try块
|
||||||
|
System.out.println("请输入第一个整数:"); // 提示信息
|
||||||
|
int num1 = sc.nextInt(); // 声明一个int类型的变量num1接收控制台输入的信息
|
||||||
|
|
||||||
|
System.out.println("请输入运算符号(+、-、*、/):"); // 提示信息
|
||||||
|
String symbol = sc.next(); // 声明一个String类型的变量symbol接收控制台输入的信息
|
||||||
|
|
||||||
|
System.out.println("请输入第二个整数:"); // 提示信息
|
||||||
|
int num2 = sc.nextInt(); // 声明一个int类型的变量num1接收控制台输入的信息
|
||||||
|
|
||||||
|
int result = 0; // 定义并初始化“运算结果”
|
||||||
|
|
||||||
|
switch (symbol) {
|
||||||
|
case "+":
|
||||||
|
result = num1 + num2;
|
||||||
|
break;
|
||||||
|
case "-":
|
||||||
|
result = num1 - num2;
|
||||||
|
break;
|
||||||
|
case "*":
|
||||||
|
result = num1 * num2;
|
||||||
|
break;
|
||||||
|
case "/":
|
||||||
|
if (num2 != 0) {
|
||||||
|
result = num1 / num2;
|
||||||
|
} else {
|
||||||
|
System.out.println("除数怎么可能是“0”呢?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
System.out.println("运算结果:" + result); // 输出运算结果
|
||||||
|
} catch (InputMismatchException ime) {// catch块
|
||||||
|
System.out.println("对不起!您输入的不是整数,已经引起了" + ime.toString() + "异常;\n" + "即:用户输入的信息与规定的参数类型不符时出现的异常"); // 输出异常的信息
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## 实例2: 捕捉控制台输入西红柿单价后的异常
|
## 实例2: 捕捉控制台输入西红柿单价后的异常
|
||||||
西红柿的单价不是一成不变的,修改上面的实例,首先实现控制台输入单价的功能,然后在finally代码块中关闭控制台输入对象。
|
西红柿的单价不是一成不变的,修改上面的实例,首先实现控制台输入单价的功能,然后在finally代码块中关闭控制台输入对象。
|
||||||
|
@ -86,9 +154,69 @@ public class Tomato { // 创建西红柿类
|
||||||
|
|
||||||
#### 练习3
|
#### 练习3
|
||||||
银行账号中现有余额 1023.79 元。模拟取款,当在控制台上输入的取款金额不是整数时,会引起数字格式转换异常。
|
银行账号中现有余额 1023.79 元。模拟取款,当在控制台上输入的取款金额不是整数时,会引起数字格式转换异常。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Account { // 创建一个Account类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
double leftMoney = 1023.79; // 初始化“账户余额”
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.println("请输入取款金额:");
|
||||||
|
try { // try块
|
||||||
|
int drawMoney = sc.nextInt();
|
||||||
|
double result = leftMoney - drawMoney; // 建立变量间的关系
|
||||||
|
if(result >= 0) { // 当余额大于取款金额时
|
||||||
|
System.out.println("您账号上的余额:" + (float)result + "元");
|
||||||
|
} else { // 当取款金额超出余额时
|
||||||
|
System.out.println("您账号上的余额不足!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InputMismatchException e) { // catch块
|
||||||
|
System.out.println("发生数字格式转换异常:输入的“取款金额”不是整数!");
|
||||||
|
} finally {
|
||||||
|
sc.close(); // 关闭控制台输入
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习4
|
#### 练习4
|
||||||
新买了一台电脑,这台电脑与其他的电脑不一样,无法正常启动开机(电脑品牌未声明)。使用继承来体现这个事件,并尝试利用"电脑品牌"引出空指针异常。
|
新买了一台电脑,这台电脑与其他的电脑不一样,无法正常启动开机(电脑品牌未声明)。使用继承来体现这个事件,并尝试利用"电脑品牌"引出空指针异常。
|
||||||
|
```java
|
||||||
|
|
||||||
|
class Computer { // 创建一个Computer类
|
||||||
|
public void powerUp() { // 创建一个普通的方法powerUp()
|
||||||
|
System.out.println("电脑正常开机启动"); // 控制台输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewComputer extends Computer { // 创建一个NewComputer类继承Computer类
|
||||||
|
private String brand; // 定义“电脑品牌”
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void powerUp() {// 重写父类Computer中的powerUp()方法
|
||||||
|
System.out.println("刚买回来的新电脑不能正常开机启动\n新电脑的品牌是:" + brand); // 控制台输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PowerOn {// 创建一个Test类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("模拟场景:用户新买了台电脑,这台电脑与其他的电脑不一样,无法正常启动开机 \n 经检测,是由于电脑的品牌不确定造成的……\n");
|
||||||
|
try { // try块
|
||||||
|
NewComputer newComputer = new NewComputer(); // 创建对象newComputer
|
||||||
|
newComputer.powerUp(); // 对象newComputer调用powerUp()方法
|
||||||
|
} catch (NullPointerException npe) { // catch块
|
||||||
|
System.out.println("引起空指针异常:" + npe.toString().substring(npe.toString().lastIndexOf('.') + 1)); // 控制台输出
|
||||||
|
} finally { // finally块
|
||||||
|
System.out.println("异常出现的原因:\n新机器newComputer对象中的品牌(brand)为null"); // 控制台输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## 实例3: 规定西红柿单价不得超过7元
|
## 实例3: 规定西红柿单价不得超过7元
|
||||||
|
@ -187,9 +315,109 @@ public class Tomato {// 创建西红柿类
|
||||||
|
|
||||||
#### 练习5
|
#### 练习5
|
||||||
模拟老师上课前的点名过程,并将旷课的学生作为异常抛出∶张三、李四、王五(老师在点名册上记下了“王五旷课”)。
|
模拟老师上课前的点名过程,并将旷课的学生作为异常抛出∶张三、李四、王五(老师在点名册上记下了“王五旷课”)。
|
||||||
|
```java
|
||||||
|
|
||||||
|
class Person { // 创建一个Person类
|
||||||
|
private String job; // 定义“职称”
|
||||||
|
|
||||||
|
public String getJob() { // 获得“职称”
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJob(String job) { // 设置“职称”
|
||||||
|
this.job = job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void say(String words) { // 创建有参的方法say()
|
||||||
|
System.out.println(job + ":" + words); // 输出结果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CallTheRoll { // 创建一个CallTheRoll类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try { // try块
|
||||||
|
Person person1 = new Person(); // 创建对象person1
|
||||||
|
person1.setJob("老师"); // 设置对象person1的“职称”
|
||||||
|
person1.say("同学们早上好!现在我们开始点名..."); // 设置对象person1说话的内容
|
||||||
|
|
||||||
|
Person person2 = new Person(); // 创建对象person2
|
||||||
|
person2.setJob("班长"); // 设置对象person2的“职称”
|
||||||
|
person2.say("大家保持安静!准备上课..."); // 设置对象person2说话的内容
|
||||||
|
|
||||||
|
person1.say("张三!"); // 设置对象person1说话的内容
|
||||||
|
|
||||||
|
Person person3 = new Person(); // 创建对象person3
|
||||||
|
person3.setJob("同学张三"); // 设置对象person3的“职称”
|
||||||
|
person3.say("到!"); // 设置对象person3说话的内容
|
||||||
|
|
||||||
|
person1.say("李四!"); // 设置对象person1说话的内容
|
||||||
|
|
||||||
|
Person person4 = new Person(); // 创建对象person4
|
||||||
|
person4.setJob("同学李四"); // 设置对象person4的“职称”
|
||||||
|
person4.say("Here!"); // 设置对象person4说话的内容
|
||||||
|
|
||||||
|
person1.say("下次说中文。下一个!\n 王五!"); // 设置对象person1说话的内容
|
||||||
|
|
||||||
|
Person person5 = new Person(); // 创建对象person5
|
||||||
|
person5.setJob("同学王五"); // 设置对象person5的“职称”
|
||||||
|
person5.say("...."); // 设置对象person5说话的内容
|
||||||
|
|
||||||
|
person1.say("王五!"); // 设置对象person1说话的内容
|
||||||
|
|
||||||
|
person5.say("...."); // 设置对象person5说话的内容
|
||||||
|
|
||||||
|
throw new Exception("抛出异常:老师在点名册上记下了“王五旷课”!!!"); // 抛出异常
|
||||||
|
} catch (Exception e) { // catch块
|
||||||
|
System.out.println(e.getMessage()); // 输出异常的信息
|
||||||
|
} finally {
|
||||||
|
System.out.println("老师:今天点名到此结束。现在开始上课!"); // 控制台输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习6
|
#### 练习6
|
||||||
超市经常会对定价较市场价低的产品实施限购∶ 超市里的鲜鸡蛋每500 克 3.98 元,每人限购1500克。现将超过1500克的作为异常抛出,而对于满足条件的,计算出应付款。
|
超市经常会对定价较市场价低的产品实施限购∶ 超市里的鲜鸡蛋每500 克 3.98 元,每人限购1500克。现将超过1500克的作为异常抛出,而对于满足条件的,计算出应付款。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
class OverloadException extends Exception {// 自定义一个异常类OverloadException继承Exception类
|
||||||
|
public OverloadException(double weight) {// 构造有参的方法
|
||||||
|
// 出现异常时控制台输出的信息
|
||||||
|
System.out.println("异常提示:这份鲜鸡蛋的重量为" + weight + "斤,超过3斤了,超重了!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BuyEggs {// 创建一个BuyEggs类
|
||||||
|
public static void pay(double weight) throws OverloadException {// 创建一个静态的、有参的pay(double
|
||||||
|
if (weight > 3.0) {// 如果鸡蛋的重量超过三斤
|
||||||
|
throw new OverloadException(weight); // 抛出异常
|
||||||
|
}
|
||||||
|
float money = (float) (weight * 3.98); // 如果鸡蛋的重量没有超过三斤,计算“应付款”
|
||||||
|
System.out.println("应付款:" + money + "元"); // 输出“应付款”
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.print("请输入鲜鸡蛋的重量(斤):");
|
||||||
|
Scanner sc = new Scanner(System.in); // 控制台输入
|
||||||
|
double weight = sc.nextDouble(); // 声明一个double类型的变量weight接收控制台输入的值
|
||||||
|
try {// try块
|
||||||
|
pay(weight); // 调用pay()方法,并传递参数weight
|
||||||
|
} catch (OverloadException ole) {// catch块
|
||||||
|
/*
|
||||||
|
* 注意: 这个方法体之所以为“空”是因为在自定义异常的类OverloadException中, 我们通过构造的有参方法public
|
||||||
|
* OverloadException(double weight),已将出现异常时的信息在控制台上输出了;
|
||||||
|
* 所以,此处就不必再写“System.out.println(ole);”这条控制台输出语句。
|
||||||
|
*/
|
||||||
|
} finally {// finally块
|
||||||
|
sc.close(); // 关闭控制台输入
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
##### 知识补充:
|
##### 知识补充:
|
||||||
|
|
|
@ -46,10 +46,133 @@ public class GoShopping {
|
||||||
|
|
||||||
#### 练习1:
|
#### 练习1:
|
||||||
创建 Shape(图形)类,该类中有一个计算面积的方法。圆形和矩形都继承自图形类,输出圆形和矩形的面积。
|
创建 Shape(图形)类,该类中有一个计算面积的方法。圆形和矩形都继承自图形类,输出圆形和矩形的面积。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Circle extends Shape {
|
||||||
|
double radius;
|
||||||
|
|
||||||
|
public Circle(double radius) {
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getArea() {
|
||||||
|
return Math.PI * this.radius * this.radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class Rectangle extends Shape {
|
||||||
|
double length;
|
||||||
|
double width;
|
||||||
|
|
||||||
|
public Rectangle(double length, double width) {
|
||||||
|
this.length = length;
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getArea() {
|
||||||
|
return this.length * this.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class Shape {
|
||||||
|
String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract double getArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Circle circle = new Circle(1.5);
|
||||||
|
circle.name = "圆形";
|
||||||
|
System.out.println(circle.name + "面积:" + circle.getArea());
|
||||||
|
Rectangle rectangle = new Rectangle(5.5, 2);
|
||||||
|
rectangle.name = "矩形";
|
||||||
|
System.out.println(rectangle.name + "面积:" + rectangle.getArea());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习2:
|
#### 练习2:
|
||||||
创建工厂类,工厂类中有一个抽象的生产方法,创建汽车厂和鞋厂类,重写工厂类中的抽象生产方法,输出汽车厂生产的是汽车,鞋厂生产的是鞋。
|
创建工厂类,工厂类中有一个抽象的生产方法,创建汽车厂和鞋厂类,重写工厂类中的抽象生产方法,输出汽车厂生产的是汽车,鞋厂生产的是鞋。
|
||||||
|
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class AutoPlant extends Factory {
|
||||||
|
String productsName;
|
||||||
|
|
||||||
|
public AutoPlant(String productsName) {
|
||||||
|
this.productsName = productsName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String produce() {
|
||||||
|
return this.productsName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class Factory {
|
||||||
|
String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String produce();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class ShoeFactory extends Factory {
|
||||||
|
String productsName;
|
||||||
|
|
||||||
|
public ShoeFactory(String productsName) {
|
||||||
|
this.productsName = productsName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String produce() {
|
||||||
|
return this.productsName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
AutoPlant autoPlant = new AutoPlant("汽车");
|
||||||
|
autoPlant.setName("汽车厂");
|
||||||
|
System.out.println(autoPlant.getName() + "生产的是" + autoPlant.productsName);
|
||||||
|
ShoeFactory shoeFactory = new ShoeFactory("鞋");
|
||||||
|
shoeFactory.setName("鞋厂");
|
||||||
|
System.out.println(shoeFactory.getName() + "生产的是" + shoeFactory.productsName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
215
Java/练习题:控制流程.md
|
@ -23,7 +23,54 @@ public class Restaurant {
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 练习1:将一、二年级学生的学生成绩划分等级,等级划分标准如下∶①"优秀",大于等于 90分;②"良好",大于等于 80 分,小于 90分;③"合格",大于等于 60分,小于80分;④"不合格",小于 60 分。使用 if…else if语句实现根据控制台输入的成绩,输出与该成绩对应等级的功能。
|
#### 练习1:将一、二年级学生的学生成绩划分等级,等级划分标准如下∶①"优秀",大于等于 90分;②"良好",大于等于 80 分,小于 90分;③"合格",大于等于 60分,小于80分;④"不合格",小于 60 分。使用 if…else if语句实现根据控制台输入的成绩,输出与该成绩对应等级的功能。
|
||||||
|
```java
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class ScoreLevel {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.print("请输入您的分数:");
|
||||||
|
double score = sc.nextDouble();
|
||||||
|
if (score >= 90) {
|
||||||
|
System.out.println("您的分数" + score + "对应的等级为“优秀”");
|
||||||
|
} else if (score >= 80) {
|
||||||
|
System.out.println("您的分数" + score + "对应的等级为“良好”");
|
||||||
|
} else if (score >= 60) {
|
||||||
|
System.out.println("您的分数" + score + "对应的等级为“合格”");
|
||||||
|
} else if (score < 60) {
|
||||||
|
System.out.println("您的分数" + score + "对应的等级为“不合格”");
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习2:BMI 身体质量指数的等级划分标准如下∶①"偏轻",BMI 小于18.5;②"正常",BMI大于等于 18.5,小于25;③"偏重",BMI 大于等于25,小于30;④"肥胖",BMI大于等于30。根据控制台输入的身高( 单位∶ 来)、体重(单位∶ 千克),输出 BMI指数以及与该指数对应的等级。
|
#### 练习2:BMI 身体质量指数的等级划分标准如下∶①"偏轻",BMI 小于18.5;②"正常",BMI大于等于 18.5,小于25;③"偏重",BMI 大于等于25,小于30;④"肥胖",BMI大于等于30。根据控制台输入的身高( 单位∶ 来)、体重(单位∶ 千克),输出 BMI指数以及与该指数对应的等级。
|
||||||
|
```java
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class BMIexponent {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.print("请输入您的身高(单位:米):");
|
||||||
|
double height = sc.nextDouble();
|
||||||
|
System.out.print("请输入您的体重(单位:公斤):");
|
||||||
|
double weight = sc.nextDouble();
|
||||||
|
double exponent = weight/(height * height);
|
||||||
|
if (exponent < 18.5) {
|
||||||
|
System.out.println("您的身体质量指数(BMI)为" + exponent + ",偏轻。");
|
||||||
|
} else if (exponent < 25) {
|
||||||
|
System.out.println("您的身体质量指数(BMI)为" + exponent + ",正常。");
|
||||||
|
} else if (exponent < 30) {
|
||||||
|
System.out.println("您的身体质量指数(BMI)为" + exponent + ",偏重。");
|
||||||
|
} else if (exponent >= 30) {
|
||||||
|
System.out.println("您的身体质量指数(BMI)为" + exponent + ",肥胖。");
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## 实例2: 用 switch 多分支语句判断成绩等级
|
## 实例2: 用 switch 多分支语句判断成绩等级
|
||||||
创建 Grade类,使用 Scanner 类在控制台输入成绩,然后用 switch 多分支语句判断输入的分数属于哪个等级。10分和9分属于优,8分属于良,7分和6分属于中,5分、4分、3分、2分、1分以及 0 分均为差,实例代码如下∶
|
创建 Grade类,使用 Scanner 类在控制台输入成绩,然后用 switch 多分支语句判断输入的分数属于哪个等级。10分和9分属于优,8分属于良,7分和6分属于中,5分、4分、3分、2分、1分以及 0 分均为差,实例代码如下∶
|
||||||
|
@ -66,7 +113,77 @@ public class Grade {
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 练习3:使用 switch多分支语句判断控制台输入的某个月份属于哪个季节。
|
#### 练习3:使用 switch多分支语句判断控制台输入的某个月份属于哪个季节。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Seasons {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.print("请输入某个月份(1~12):");
|
||||||
|
int month = sc.nextInt();
|
||||||
|
if (month < 1 || month > 12) {
|
||||||
|
System.out.println("警告:您在非法操作……");
|
||||||
|
} else {
|
||||||
|
switch (month) {
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
System.out.println(month + "月正值春季");
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
System.out.println(month + "月正值夏季");
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
System.out.println(month + "月正值秋季");
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
System.out.println(month + "月正值冬季");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习4:使用 switch多分支语句判断控制台输入的某个月份属于哪个季节。(光盘\Code\Try\O3\05)(2)某大型商超为答谢新老顾客,当累计消费金额达到一定数额时,顾客可享受不同的折扣∶①尚未超过 200 元,顾客须按照小票价格支付全款;不少于 200元但尚未超过 600元,顾客全部的消费金额可享 8.5 折优惠;③不少于 600 元但尚未超过 1000 元,顾客全部的消费金额可享 7折优惠;④不少于 1000 元,顾客全部的消费金额可享 6折优惠;
根据顾客购物小票上的消费金额,在控制台上输出该顾客将享受的折扣与打折后需支付的金额。
|
#### 练习4:使用 switch多分支语句判断控制台输入的某个月份属于哪个季节。(光盘\Code\Try\O3\05)(2)某大型商超为答谢新老顾客,当累计消费金额达到一定数额时,顾客可享受不同的折扣∶①尚未超过 200 元,顾客须按照小票价格支付全款;不少于 200元但尚未超过 600元,顾客全部的消费金额可享 8.5 折优惠;③不少于 600 元但尚未超过 1000 元,顾客全部的消费金额可享 7折优惠;④不少于 1000 元,顾客全部的消费金额可享 6折优惠;
根据顾客购物小票上的消费金额,在控制台上输出该顾客将享受的折扣与打折后需支付的金额。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Discount {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.print("请输入您的消费金额:");
|
||||||
|
int money = sc.nextInt();
|
||||||
|
switch (money / 200) {
|
||||||
|
case 0:
|
||||||
|
System.out.println("您已消费:" + money + "元,尚未超过200元,须按照小票价格支付全款,即" + money + "元RMB");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
System.out.println("您已消费:" + money + "元,不少于200元但尚未超过600元,全部的消费金额可享8.5折优惠,即" + (money * 0.85) + "元RMB");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
System.out.println("您已消费:" + money + "元,不少于600元但尚未超过1000元,全部的消费金额可享7折优惠,即" + (money * 0.7) + "元RMB");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println("您已消费:" + money + "元,不少于1000元,全部的消费金额可享6折优惠,即" + (money * 0.6) + "元RMB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## 实例3:使用 for 循环完成 1~ 100的相加运算
|
## 实例3:使用 for 循环完成 1~ 100的相加运算
|
||||||
|
@ -86,7 +203,54 @@ public class AdditiveFor {
|
||||||
$思考:可不可以改写用while实现相加运算呢?$
|
$思考:可不可以改写用while实现相加运算呢?$
|
||||||
|
|
||||||
#### 练习5:有一组数∶ 1、1、2、3、5、8、13、21、34…,请用for循环算出这组数的第n个数是多少?
|
#### 练习5:有一组数∶ 1、1、2、3、5、8、13、21、34…,请用for循环算出这组数的第n个数是多少?
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Arithmetic {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int num1 = 1, num2 = 1, temp;//定义3个变量,num1用来记录
|
||||||
|
Scanner sc = new Scanner(System.in);// 创建扫描器对象,用于输入
|
||||||
|
System.out.print("请输入要查看第n个数中的n值:");
|
||||||
|
int num = sc.nextInt();// 记录用户输入的数字
|
||||||
|
for (int i = 2; i < num; i++) {
|
||||||
|
num1 += num2;//后一个数字是前两个数字之和
|
||||||
|
//交换num1和num2的值
|
||||||
|
temp = num1;
|
||||||
|
num1 = num2;
|
||||||
|
num2 = temp;
|
||||||
|
}
|
||||||
|
System.out.println("第" + num + "个数为" + num2);//输出指定位数上的数据
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习6:一个球从80米高度自由落下,每次落地后反弹的高度为原高度的一半,第6次落地时共经过多少米?第6次反弹多高?
|
#### 练习6:一个球从80米高度自由落下,每次落地后反弹的高度为原高度的一半,第6次落地时共经过多少米?第6次反弹多高?
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Fall {// 创建一个Fall类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
double high = 80;// 声明double类型变量high
|
||||||
|
double sum = 0;// 声明double类型变量sum
|
||||||
|
// i = 0代表小球第一次落地时,i = 10代表第六次落地时
|
||||||
|
for (int i = 0; i < 11; i++) {
|
||||||
|
// 小球开始下落到第六次落地时经过的总路程
|
||||||
|
sum += high;
|
||||||
|
// 判断小球是否完成了一次完整地落地、反弹过程
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
// 每次落地之后反弹高度为原来的一半
|
||||||
|
high = high / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 控制台输出小球第六次落地时经过的总路程
|
||||||
|
System.out.println("第六次落地时共经过" + sum + "米");
|
||||||
|
// 控制台输出小球第六次落地后反弹的高度
|
||||||
|
System.out.println("第六次落地后反弹:" + high + "米");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## 实例4:使用嵌套的 for 循环输出乘法口诀表
|
## 实例4:使用嵌套的 for 循环输出乘法口诀表
|
||||||
|
@ -108,8 +272,56 @@ public class Multiplication {
|
||||||
|
|
||||||
|
|
||||||
#### 练习7:5文钱可以买一只公鸡,3 文钱可以买一只母鸡,1文钱可以买三只雏鸡,现在用 100文钱买 100 只鸡,那么公鸡、母鸡、雏鸡各有多少只?
|
#### 练习7:5文钱可以买一只公鸡,3 文钱可以买一只母鸡,1文钱可以买三只雏鸡,现在用 100文钱买 100 只鸡,那么公鸡、母鸡、雏鸡各有多少只?
|
||||||
#### 练习8:根据用户控制输入 * 的行数,在控制台中输出相应行数的等腰三角形。
|
```java
|
||||||
|
|
||||||
|
public class BuyChicken {// 百钱买百鸡
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int cock, hen, chick;// 公鸡、母鸡、小鸡
|
||||||
|
for (cock = 0; cock <= 20; cock++) {// 最多买20只公鸡
|
||||||
|
for (hen = 0; hen <= 33; hen++) {// 最多买33只母鸡
|
||||||
|
for (chick = 3; chick <= 99; chick += 3) {// 最多买99只小鸡(有“百鸡”的限制)
|
||||||
|
if (5 * cock + 3 * hen + chick / 3 == 100)// 百钱
|
||||||
|
{
|
||||||
|
if (cock + hen + chick == 100)// 百鸡
|
||||||
|
{
|
||||||
|
System.out.println("公鸡:" + cock + "\t母鸡:" + hen + "\t小鸡:" + chick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
#### 练习8:根据用户控制输入 * 的行数,在控制台中输出相应行数的等腰三角形。
|
||||||
|
```java
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class OutputTriangle {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.print("等腰三角形由多行“*”号组成,请输入“*”的行数:");
|
||||||
|
int line = sc.nextInt();
|
||||||
|
if (line <= 1) {
|
||||||
|
System.out.println("须输入大于1的自然数!");
|
||||||
|
} else {
|
||||||
|
for (int i = 1; i <= line; i++) {// 控制“*”的行数
|
||||||
|
for (int j = 1; j <= line - i; j++) {// 每行添加空格的个数
|
||||||
|
System.out.print(" ");
|
||||||
|
}
|
||||||
|
for (int j = 1; j <= 2 * i - 1; j++) {// 在空格后添加“*”
|
||||||
|
System.out.print("*");
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## 实例5:找出1 ~ 19 之间所有的偶数
|
## 实例5:找出1 ~ 19 之间所有的偶数
|
||||||
使用一个for循环输出1~19之间所有的值,如果输出的值是奇数,则使用 continue 语句跳过本次循环,实例代码如下∶
|
使用一个for循环输出1~19之间所有的值,如果输出的值是奇数,则使用 continue 语句跳过本次循环,实例代码如下∶
|
||||||
|
@ -126,4 +338,3 @@ public class ContinueTest {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 练习:找出1-1000数字种的素数和水花仙数。
|
|
||||||
|
|
|
@ -34,6 +34,65 @@ public class ExplicitConversion {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### 练习1:使用 char型声明'a'~'g',然后输出它们相加后的结果。
|
#### 练习1:使用 char型声明'a'~'g',然后输出它们相加后的结果。
|
||||||
|
```java
|
||||||
|
public class PlusChar {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
char a = 'a';
|
||||||
|
char b = 'b';
|
||||||
|
char c = 'c';
|
||||||
|
char d = 'd';
|
||||||
|
char e = 'e';
|
||||||
|
char f = 'f';
|
||||||
|
char g = 'g';
|
||||||
|
System.out.println("’a’~’g’相加后的结果:" + (a + b + c + d + e + f + g));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习2:IP地址每段数字的最大值可以由byte型的最大值与 short型128相加后得到,使用隐式转换控制台输出 IP 地址每段数字的最大值。
|
#### 练习2:IP地址每段数字的最大值可以由byte型的最大值与 short型128相加后得到,使用隐式转换控制台输出 IP 地址每段数字的最大值。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MaxIP {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
byte maxvalue = 127;
|
||||||
|
short s = 128;
|
||||||
|
System.out.println("IP中的每段数字的最大值 = " + (maxvalue + s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习3:将 65~71显式转换为 char 型并输出。
|
#### 练习3:将 65~71显式转换为 char 型并输出。
|
||||||
#### 练习4:一辆货车运输箱子,载货区宽2米,长 4米,一个箱子宽 1.5 米,长 1.5米,请问载货区一层可以放多少个箱子?
|
```java
|
||||||
|
public class IntToChar {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int i_a = 65;
|
||||||
|
int i_b = 66;
|
||||||
|
int i_c = 67;
|
||||||
|
int i_d = 68;
|
||||||
|
int i_e = 69;
|
||||||
|
int i_f = 70;
|
||||||
|
int i_g = 71;
|
||||||
|
System.out.println("65转换为char型的结果为" + (char)i_a);
|
||||||
|
System.out.println("66转换为char型的结果为" + (char)i_b);
|
||||||
|
System.out.println("67转换为char型的结果为" + (char)i_c);
|
||||||
|
System.out.println("68转换为char型的结果为" + (char)i_d);
|
||||||
|
System.out.println("69转换为char型的结果为" + (char)i_e);
|
||||||
|
System.out.println("70转换为char型的结果为" + (char)i_f);
|
||||||
|
System.out.println("71转换为char型的结果为" + (char)i_g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 练习4:一辆货车运输箱子,载货区宽2米,长 4米,一个箱子宽 1.5 米,长 1.5米,请问载货区一层可以放多少个箱子?
|
||||||
|
```java
|
||||||
|
public class GoodsVan {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int vanWidth = 2;
|
||||||
|
int vanLength = 4;
|
||||||
|
double boxWidth = 1.5;
|
||||||
|
double boxLength = 1.5;
|
||||||
|
int boxNumber = (int)(vanWidth/boxWidth) * (int)(vanLength/boxLength);
|
||||||
|
System.out.println("载货区一层可以放" + boxNumber + "箱子!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
130
Java/练习题:数组.md
|
@ -1,4 +1,132 @@
|
||||||
#### 练习1:遍历二维数组int a[][] = {{23.65,43,68},{45,99,86,80},{76,81,34,45},{88,64,48,25}}后,再通过循环计算该二维数组的两条对角线之和。
|
#### 练习1:遍历二维数组int a[][] = {{23.65,43,68},{45,99,86,80},{76,81,34,45},{88,64,48,25}}后,再通过循环计算该二维数组的两条对角线之和。
|
||||||
|
```java
|
||||||
|
public class GetSum {// 创建GetSum类
|
||||||
|
public static void main(String args[]) {
|
||||||
|
// 初始化int类型的二维数组
|
||||||
|
int a[][] = {{ 23, 65, 43, 68 },
|
||||||
|
{ 45, 99, 86, 80 },
|
||||||
|
{ 76, 81, 34, 45 },
|
||||||
|
{ 88, 64, 48, 25 }};
|
||||||
|
System.out.println("原始数组如下:");// 控制台输出提示信息
|
||||||
|
for (int i = 0; i < a.length; i++) {// 遍历二维数组
|
||||||
|
for (int j = 0; j < a[i].length; j++) {
|
||||||
|
System.out.print(a[i][j] + " ");
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
int sum1 = 0;// 声明int类型的变量sum1(求一条对角线的和)
|
||||||
|
int sum2 = 0;// 声明int类型的变量sum2(求另一条对角线的和)
|
||||||
|
// 通过循环,获得元素的下标,并分别求出两条对角线的和
|
||||||
|
for (int i = 0, j = a[i].length - 1; i < a.length; i++, j--) {
|
||||||
|
sum1 += a[i][i];
|
||||||
|
sum2 += a[i][j];
|
||||||
|
}
|
||||||
|
// 把两条对角线的和相加求和并输出
|
||||||
|
System.out.println("对角线总和为:" + (sum1 + sum2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习2:一个3×3的网格,将从1到9的数字放入方格,达到能够使得每行每列以及每个对角线的值相加都相同。
|
#### 练习2:一个3×3的网格,将从1到9的数字放入方格,达到能够使得每行每列以及每个对角线的值相加都相同。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class NineGrids {// 创建NineGrids类
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 声明int类型的数组并分配内存(三阶方阵)
|
||||||
|
int arr[][] = new int[3][3];
|
||||||
|
// 确定数字“1”的位置
|
||||||
|
int a = 2;// 第3行的下标
|
||||||
|
int b = 3 / 2;// 第2列的下标
|
||||||
|
for (int i = 1; i <= 9; i++) {// 给数组赋值
|
||||||
|
arr[a++][b++] = i;// 避免数组下标越界
|
||||||
|
if (i % 3 == 0) {// 如果i是3的倍数
|
||||||
|
a = a - 2;
|
||||||
|
b = b - 1;
|
||||||
|
} else {// 如果i不是3的倍数
|
||||||
|
a = a % 3;
|
||||||
|
b = b % 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("九宫格:");
|
||||||
|
// 遍历数组
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < arr.length; j++) {
|
||||||
|
System.out.print(arr[i][j] + " ");// 输出数组中的数据
|
||||||
|
}
|
||||||
|
System.out.println();// 换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习3:交换二维数组 int array[][] = {{8,75,23},{21,534},{15,23,20 }};的行、列数据。
|
#### 练习3:交换二维数组 int array[][] = {{8,75,23},{21,534},{15,23,20 }};的行、列数据。
|
||||||
#### 练习4:使用二维数组实现杨辉三角算法。
|
```java
|
||||||
|
|
||||||
|
public class SwapRC {// 交换二维数组的行列数据
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int i, j;// 定义两个变量,分别用来作为行和列的循环变量
|
||||||
|
// 初始化一个静态的int型二维数组
|
||||||
|
int[][] array = { { 8, 75, 23 }, { 21, 55, 34 }, { 15, 23, 20 } };
|
||||||
|
System.out.println("—————原始数组—————");// 提示信息
|
||||||
|
// 遍历原始的二维数组
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
for (j = 0; j < 3; j++) {
|
||||||
|
System.out.print(array[i][j] + "\t");// 输出原始数组中的元素
|
||||||
|
}
|
||||||
|
System.out.println();// 换行
|
||||||
|
}
|
||||||
|
int temp;// 临时变量
|
||||||
|
// 通过循环调换元素的位置
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
for (j = 0; j < i; j++) {
|
||||||
|
temp = array[i][j];// 把数组元素赋给临时变量
|
||||||
|
// 交换行列数据
|
||||||
|
array[i][j] = array[j][i];
|
||||||
|
array[j][i] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("——调换位置之后的数组——");// 提示信息
|
||||||
|
// 遍历调换位置之后的二维数组
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
for (j = 0; j < 3; j++) {
|
||||||
|
System.out.print(array[i][j] + "\t");// 输出调换位置后的数组元素
|
||||||
|
}
|
||||||
|
System.out.println();// 换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
#### 练习4:使用二维数组实现杨辉三角算法。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class YangHui {// 杨辉三角算法的实现
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 定义一个长度为10的二维数组
|
||||||
|
int[][] Array_int = new int[10][];
|
||||||
|
// 向数组中记录杨辉三角形的值
|
||||||
|
for (int i = 0; i < Array_int.length; i++) {// 遍历行数
|
||||||
|
Array_int[i] = new int[i + 1];// 定义二维数组的列数
|
||||||
|
// 遍历二维数组的列数
|
||||||
|
for (int j = 0; j < Array_int[i].length; j++) {
|
||||||
|
if (i <= 1) {// 如果是数组的前两行
|
||||||
|
Array_int[i][j] = 1;// 将其设置为1
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// 如果是行首或行尾
|
||||||
|
if (j == 0 | j == Array_int[i].length - 1)
|
||||||
|
Array_int[i][j] = 1;// 将其设置为1
|
||||||
|
else// 根据杨辉算法进行计算
|
||||||
|
Array_int[i][j] = Array_int[i - 1][j - 1] + Array_int[i - 1][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < Array_int.length; i++) {// 输出杨辉三角
|
||||||
|
for (int j = 0; j < Array_int[i].length; j++)
|
||||||
|
System.out.print(Array_int[i][j] + "\t");
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
127
Java/练习题:类与方法.md
|
@ -31,14 +31,139 @@ public class EggCake { // 创建鸡蛋灌饼EggCake类
|
||||||
定义了两个构造方法,在无参构造方法中可以使用this关键字调用有参的构造方法。但是要注意,this()语句之前不可以有其他代码。
|
定义了两个构造方法,在无参构造方法中可以使用this关键字调用有参的构造方法。但是要注意,this()语句之前不可以有其他代码。
|
||||||
|
|
||||||
#### 练习1:设计加油站类和汽车类,加油站提供一个给车加油的方法,参数为剩余汽油数量。每次执行加油方法,汽车的剩余汽油数量都会加 2。
|
#### 练习1:设计加油站类和汽车类,加油站提供一个给车加油的方法,参数为剩余汽油数量。每次执行加油方法,汽车的剩余汽油数量都会加 2。
|
||||||
|
```java
|
||||||
|
|
||||||
|
class GasStation {
|
||||||
|
public int addOil(int oilVolume) {
|
||||||
|
oilVolume += 2;
|
||||||
|
return oilVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AutoMobile {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int leftOilVolume = 10;
|
||||||
|
GasStation gs = new GasStation();
|
||||||
|
for (int i = 1; i <= 5; i++) {
|
||||||
|
leftOilVolume = gs.addOil(leftOilVolume);
|
||||||
|
}
|
||||||
|
System.out.println("该车现有油量:" + leftOilVolume + "L。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### 练习2:智能手机的默认语言为英文。,但制造手机时可以将默认语言设置为中文。编写手机类, 无,参构造方法使用默认语言设计,利用有参构造方法修改手机的默认语言。
|
#### 练习2:智能手机的默认语言为英文。,但制造手机时可以将默认语言设置为中文。编写手机类, 无,参构造方法使用默认语言设计,利用有参构造方法修改手机的默认语言。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Cellphone {
|
||||||
|
|
||||||
|
public Cellphone() {
|
||||||
|
System.out.println("智能手机的默认语言为英文");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cellphone(String defaultLanguage) {
|
||||||
|
System.out.println("将智能手机的默认语言设置为" + defaultLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Cellphone cellphone1 = new Cellphone();
|
||||||
|
Cellphone cellphone2 = new Cellphone("中文");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习3:张三去KFC买可乐,商家默认不加冰块。但是张三可以要求加 3 个冰块。请利用有参构造方法实现上述功能。
|
#### 练习3:张三去KFC买可乐,商家默认不加冰块。但是张三可以要求加 3 个冰块。请利用有参构造方法实现上述功能。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class IceBlock {
|
||||||
|
|
||||||
|
public IceBlock() {
|
||||||
|
System.out.println("商家默认可乐里没有冰块……");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IceBlock(String name, int number) {
|
||||||
|
System.out.println(name + "要求向可乐里放入" + number + "个冰块。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
IceBlock block = new IceBlock();
|
||||||
|
IceBlock iceBlock = new IceBlock("张三", 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习4:创建教师类,类中有姓名、性别和年龄三个属性,在构造方法中使用 this 关键字分别为这三个成员属性赋值。
|
#### 练习4:创建教师类,类中有姓名、性别和年龄三个属性,在构造方法中使用 this 关键字分别为这三个成员属性赋值。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Teacher {
|
||||||
|
String name;
|
||||||
|
char sex;
|
||||||
|
int age;
|
||||||
|
|
||||||
|
public Teacher(String name, char sex, int age) {
|
||||||
|
this.name = name;
|
||||||
|
this.sex = sex;
|
||||||
|
this.age = age;
|
||||||
|
System.out.println("教师姓名:" + name + "\n教师性别:" + sex + "\n教师年龄:" + age);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Teacher chinese = new Teacher("张三", '男', 38);
|
||||||
|
Teacher math = new Teacher("李四", '男', 45);
|
||||||
|
Teacher english = new Teacher("王五", '女', 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### 练习5:一只大熊猫,长 1.3 米,重 90千克。在自定义方法中使用this关键字调用类的成员变量并在控制台输出这只大熊猫的信息。
|
#### 练习5:一只大熊猫,长 1.3 米,重 90千克。在自定义方法中使用this关键字调用类的成员变量并在控制台输出这只大熊猫的信息。
|
||||||
|
```java
|
||||||
|
|
||||||
#### 练习6:创建信用卡类,有两个成员变量分别是卡号和密码,如果用户开户时没有设置初始密码,则使用"123456"作为默认密码。设计两个不同的构造方法,分别用于用户设置密码和用户未设置密码两种构造场景。
|
public class Panda {
|
||||||
|
private double length = 1.3;
|
||||||
|
private double weight = 90.0;
|
||||||
|
|
||||||
|
public String getMessages() {
|
||||||
|
return "熊猫体长" + this.length + "米,体重" + this.weight + "KG。";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Panda panda = new Panda();
|
||||||
|
System.out.println(panda.getMessages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 练习6:创建信用卡类,有两个成员变量分别是卡号和密码,如果用户开户时没有设置初始密码,则使用"123456"作为默认密码。设计两个不同的构造方法,分别用于用户设置密码和用户未设置密码两种构造场景。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Credit {
|
||||||
|
String cardNum;
|
||||||
|
String password;
|
||||||
|
|
||||||
|
public Credit(String cardNum, String password) {
|
||||||
|
this.cardNum = cardNum;
|
||||||
|
this.password = password;
|
||||||
|
if (password.equals("123456")) {
|
||||||
|
System.out.println("信用卡" + cardNum + "的默认密码为" + password);
|
||||||
|
} else {
|
||||||
|
System.out.println("重置信用卡" + cardNum + "的密码为" + password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Credit(String cardNum) {
|
||||||
|
this(cardNum, "123456");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Credit initialCredit = new Credit("4013735633800642");
|
||||||
|
Credit resetedCredit = new Credit("4013735633800642", "168779");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
|
@ -26,6 +26,57 @@ public class Pad extends Computer { // 子类:平板电脑
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 练习1:创建银行卡类,并设计银行卡的两个子类∶ 储蓄卡与信用卡。
|
#### 练习1:创建银行卡类,并设计银行卡的两个子类∶ 储蓄卡与信用卡。
|
||||||
|
```java
|
||||||
|
public class BankCard {
|
||||||
|
String cardNum;
|
||||||
|
|
||||||
|
public void saveMoney() {
|
||||||
|
System.out.println("卡号为" + cardNum + "银行卡可以存钱。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class CreditCard extends BankCard{
|
||||||
|
public void drawMoney() {
|
||||||
|
System.out.println("卡号为" + cardNum + "银行卡可以取钱。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void borrowMoney() {
|
||||||
|
System.out.println("卡号为" + cardNum + "银行卡可以借钱。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class SavingCard extends BankCard{
|
||||||
|
public void drawMoney() {
|
||||||
|
System.out.println("卡号为" + cardNum + "银行卡可以取钱。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("--------------银行卡的特点--------------");
|
||||||
|
BankCard bankCard = new BankCard();
|
||||||
|
bankCard.cardNum = "6224 4569 2421 3677";
|
||||||
|
bankCard.saveMoney();
|
||||||
|
System.out.println("--------------储蓄卡的特点--------------");
|
||||||
|
SavingCard savingCard = new SavingCard();
|
||||||
|
savingCard.cardNum = "6212 2636 0001 2739";
|
||||||
|
savingCard.saveMoney();
|
||||||
|
savingCard.drawMoney();
|
||||||
|
System.out.println("--------------信用卡的特点--------------");
|
||||||
|
CreditCard creditCard = new CreditCard();
|
||||||
|
creditCard.cardNum = "4013 7356 3380 0642";
|
||||||
|
creditCard.saveMoney();
|
||||||
|
creditCard.drawMoney();
|
||||||
|
creditCard.borrowMoney();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## 实例2:让平板电脑调用台式机的功能
|
## 实例2:让平板电脑调用台式机的功能
|
||||||
|
@ -54,8 +105,52 @@ public class Pad3 extends Computer3 { // 子类:平板电脑
|
||||||
|
|
||||||
#### 练习3:
|
#### 练习3:
|
||||||
设计火车类和高铁类,高铁类继承火车类,不管火车类的行进速度是多少,高铁的行进速度永远是火车的二倍。
|
设计火车类和高铁类,高铁类继承火车类,不管火车类的行进速度是多少,高铁的行进速度永远是火车的二倍。
|
||||||
|
```java
|
||||||
|
|
||||||
|
class Train {
|
||||||
|
public double getSpeed() {
|
||||||
|
return 145.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HighSpeedRail extends Train {
|
||||||
|
public double getSpeed() {
|
||||||
|
return super.getSpeed() * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Train train = new Train();
|
||||||
|
System.out.println("火车的速度为" + train.getSpeed() + "公里/小时");
|
||||||
|
HighSpeedRail highSpeedRail = new HighSpeedRail();
|
||||||
|
System.out.println("高铁的速度为" + highSpeedRail.getSpeed() + "公里/小时");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习4:
|
#### 练习4:
|
||||||
设计人类,有二个自我介绍的方法,输出"我是 XXX";设计博士类,继承人类,博士类自我介绍时输出"我是XXX 博士"。
|
设计人类,有二个自我介绍的方法,输出"我是 XXX";设计博士类,继承人类,博士类自我介绍时输出"我是XXX 博士"。
|
||||||
|
```java
|
||||||
|
|
||||||
|
class Person {
|
||||||
|
public String introduce() {
|
||||||
|
return "我是XXX";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Doctor extends Person {
|
||||||
|
public String introduce() {
|
||||||
|
return super.introduce() + "博士";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Person person = new Person();
|
||||||
|
System.out.println(person.introduce());
|
||||||
|
Doctor doctor = new Doctor();
|
||||||
|
System.out.println(doctor.introduce());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
**扩展知识:**
|
**扩展知识:**
|
||||||
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Obiect类。Obiect类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类时,除非已经指定要从其他类继承,否则都是从java.lang.Object类继承而来的,所以Java中的每个类都源于java.lang.Object类,如 String、Integer等类都是继承于 Object类。除此之外,自定义的类也都继承于Object类。由于所有类都是Obiect子类,所以在定义类时,省略了extends Object语句。
|
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Obiect类。Obiect类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类时,除非已经指定要从其他类继承,否则都是从java.lang.Object类继承而来的,所以Java中的每个类都源于java.lang.Object类,如 String、Integer等类都是继承于 Object类。除此之外,自定义的类也都继承于Object类。由于所有类都是Obiect子类,所以在定义类时,省略了extends Object语句。
|
||||||
|
@ -117,8 +212,43 @@ public class OverLoadTest {
|
||||||
|
|
||||||
#### 练习5:
|
#### 练习5:
|
||||||
使用方法的重载描述所有的超市都支持现金付款,但大型商超还支持刷卡付款。
|
使用方法的重载描述所有的超市都支持现金付款,但大型商超还支持刷卡付款。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Market {
|
||||||
|
public static String pay(String payStyle) {
|
||||||
|
return "所有的超市都支持" + payStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String pay(String marketStyle, String payStyle) {
|
||||||
|
return marketStyle + "还支持" + payStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(pay("现金付款"));
|
||||||
|
System.out.println("但" + pay("大型商超", "刷卡付款"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
#### 练习6:
|
#### 练习6:
|
||||||
使用方法的重载描述所有的汽车都至少有两块脚踏板,但手动挡汽车有三块脚踏板。
|
使用方法的重载描述所有的汽车都至少有两块脚踏板,但手动挡汽车有三块脚踏板。
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class Car {
|
||||||
|
public static Integer havePedalNum(int pedalNum) {
|
||||||
|
return pedalNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String havePedalNum(String name, int pedalNum) {
|
||||||
|
return name + "有" + pedalNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("所有的汽车至少有" + havePedalNum(2) + "块脚踏板");
|
||||||
|
System.out.println("但" + havePedalNum("手动挡汽车", 3) + "块脚踏板");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
自增、自减运算符是单目运算符,可以放在变量之前,也可以放在变量之后。自增、自减运算符的作用是使变量的值加 1或减1。语法如下∶
|
||||||
|
```java
|
||||||
|
a++; // 先输出a的原值,后做+1运算
|
||||||
|
++a; // 先做+1运算,再输出a计算之后的值
|
||||||
|
a--; // 先输出a的原值,后做-1运算
|
||||||
|
--a; // 先做-1运算,再输出a计算之后的值
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实例1:对同一个变量做自增、自减运算
|
||||||
|
创建 AutolncrementDecreasing类,对一个整型变量先做自增运算,再做自减运算,实例代码如下:
|
||||||
|
```java
|
||||||
|
public class AutoIncrementDecreasing {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int a = 1; // 创建整型变量a,初始值为1
|
||||||
|
System.out.println("a = " + a); // 输出此时a的值
|
||||||
|
a++; // a自增1
|
||||||
|
System.out.println("a++ = " + a); // 输出此时a的值
|
||||||
|
a++; // a自增1
|
||||||
|
System.out.println("a++ = " + a); // 输出此时a的值
|
||||||
|
a++; // a自增1
|
||||||
|
System.out.println("a++ = " + a); // 输出此时a的值
|
||||||
|
a--; // a自减1
|
||||||
|
System.out.println("a-- = " + a); // 输出此时a的值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 练习1:使用赋值运算符输出银行的年利率 (2.95%)以及存款额 15000元后,计算并输出3年后的本金和利息的总和。
|
||||||
|
```java
|
||||||
|
public class CorpusAndInterest {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
double interest = 0.0295;
|
||||||
|
int corpus = 15000;
|
||||||
|
double sum = corpus * (1 + interest) * (1 + interest) * (1 + interest);
|
||||||
|
System.out.println("存款额15000元,3年后的本息和 = " + sum + "元RMB");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 练习2:规定 1美元=6.8995元人民币后,计算10000元人民币可兑换多少美元。
|
||||||
|
```java
|
||||||
|
public class RMBToDollar {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
double dollarToRMB = 6.8995;
|
||||||
|
int RMB = 10000;
|
||||||
|
double dollar = RMB/dollarToRMB;
|
||||||
|
System.out.println("10000元人民币可兑换" + dollar + "美元");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 实例2:逻辑运算符结合关系运算符进行运算
|
||||||
|
创建 LogicalAndRelational类,首先利用关系运算符计算出布尔结果,再用逻辑运算符做二次计算,实例代码如下∶
|
||||||
|
```java
|
||||||
|
|
||||||
|
public class LogicalAndRelational {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int a = 2; // 声明int型变量a
|
||||||
|
int b = 5; // 声明int型变量b
|
||||||
|
// 声明boolean型变量,用于保存应用逻辑运算符“&&”后的返回值
|
||||||
|
boolean result = ((a > b) && (a != b));
|
||||||
|
// 声明boolean型变量,用于保存应用逻辑运算符“||”后的返回值
|
||||||
|
boolean result2 = ((a > b) || (a != b));
|
||||||
|
System.out.println(result); // 将变量result输出
|
||||||
|
System.out.println(result2); // 将变量result2输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 练习3:有两名男性应聘者∶ 一位25岁,一位32 岁。该公司招聘信息中有一个要求,即男性应聘者的年龄在 23~30 岁之间,判断这两名应聘者是否满足这个要求。
|
||||||
|
```java
|
||||||
|
public class Interview {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int employeeAge_A = 25;
|
||||||
|
int employeeAge_B = 32;
|
||||||
|
System.out.println("25岁的应聘者是否满足这个要求:" + ((employeeAge_A >= 23) && (employeeAge_A <= 30)));
|
||||||
|
System.out.println("32岁的应聘者是否满足这个要求:" + ((employeeAge_B >= 23) && (employeeAge_B <= 30)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 练习4:我们在登录一个网站时,可以使用账户名登录,也可以使用手机号登录,还可以使用电子邮箱地址登录。请判断某用户是否可以登录。(已知服务器中有如下记录,账户名∶ 张三,手机号∶ 1234567890,电子邮箱∶zhangsan@163.com)
|
||||||
|
```java
|
||||||
|
public class Login {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("某用户是否可以登录此网站:" +
|
||||||
|
(("张三" == "张三") || ("1234567890" == "1234567890") || ("zhangsan@163.com" == "zhangsan@163.com")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,184 @@
|
||||||
|
# (PART) 准备工作 {-}
|
||||||
|
|
||||||
|
# 熟悉规则与R语言入门 {#task-00 .unnumbered}
|
||||||
|
|
||||||
|
{width=100%}
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### R
|
||||||
|
|
||||||
|
* R 语言是一门用于统计计算与绘图的编程语言和开源软件([The R Foundation](https://www.r-project.org/))。
|
||||||
|
* R 语言是当今应用最多的统计软件之一。
|
||||||
|
* 截止到这份文档编写时,在 CRAN ([the Comprehensive R Archive Network](https://cran.r-project.org/)) 上总共发行了`r format(ifelse((nn <- nrow(available.packages(contrib.url("https://cran.rstudio.com/", "both")))) < 17619, 17619, nn), scientisfic = FALSE)`个R语言程辑包。
|
||||||
|
* 除了 CRAN 以外,R 语言的程辑包作者也在其他线上代码托管与研发协作平台发布了数不尽的作品。这样的平台包括 [GitHub](https://github.com/)、[GitLab](https://about.gitlab.com)、[Gitee](https://gitee.com/) 等。
|
||||||
|
|
||||||
|
你可以从 CRAN 的网站上下载 R:https://cloud.r-project.org/。
|
||||||
|
你也可以在[这里](https://cran.r-project.org/mirrors.html)选择任意一个镜像网站来下载 R.
|
||||||
|
|
||||||
|
### RStudio
|
||||||
|
|
||||||
|
[RStudio](https://rstudio.com/products/rstudio/) 是针对 R 语言设计的集成开发环境。如果没有 RStudio 的话,R 本身只提供一个简单的文本编辑器。如果把R语言本身比喻成飞机的话,那么 RStudio 便是飞机场:你不需要它就可以飞,但是有了它会极大增加效率。它包括一个控制台、语法突出显示的编辑器、直接执行代码的支持,以及用于绘图、历史记录、调试和工作区管理的工具。
|
||||||
|
|
||||||
|
你可以从其官网下载开源版本:https://rstudio.com/products/rstudio/
|
||||||
|
|
||||||
|
在本文档中,我们会介绍 RStudio 的用户界面和部分功能,帮助你尽快上手使用 RStudio 的数据分析。
|
||||||
|
|
||||||
|
### R语言程辑包(R Package)
|
||||||
|
|
||||||
|
R 语言程辑包是 R 语言必不可少的部分。R语言能有他今天在统计学里的位置正是归功于其程辑包在统计计算方面的发展。一个程辑包为用户提供函数(function)、数据(data)或者插件(addins)。除了 R 本身自带的基础程辑包(base、utils、stats等)以外,你还可以用以下代码来从 CRAN 上下载并安装额外的程辑包:
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
install.packages("tidyverse")
|
||||||
|
```
|
||||||
|
|
||||||
|
我们将会在这次组队学习中多次用到`tidyverse`。它其实是一系列程辑包的组合,主要提供数据清洗与处理的工具。
|
||||||
|
|
||||||
|
#### 进阶的安装方法
|
||||||
|
|
||||||
|
当你应用 R 语言的能力到一定阶段之后,你会发现自己需要安装不在 CRAN 上发布的程辑包,或者你需要最新版本的程辑包( CRAN 上的包为了保证代码的可靠性,发布前需要经过一系列的检查与测试,这就导致 CRAN 上的版本往往不是最新的开发版本)。以安装 GitHub 上发布的程辑包为例,你可以使用以下代码:
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
# 安装 remotes 包
|
||||||
|
install.packages("remotes")
|
||||||
|
# 使用 remotes 从 GitHub 上安装 username 名下的 repo 包
|
||||||
|
remotes::install_github("username/repo")
|
||||||
|
```
|
||||||
|
|
||||||
|
我们需要先安装 `remotes` 包,并使用其中的 `install_github` 函数来完成操作。注意这里是从源代码安装,在本地编译。Windows 用户需要使用 Rtools 作为背后的编译工具。关于 Rtools 的安装信息见 https://cran.r-project.org/bin/windows/Rtools/
|
||||||
|
|
||||||
|
#### R与其程辑包的更新
|
||||||
|
|
||||||
|
在本文档编写之时,R 语言已更新到版本`r R.version$version.string`。当新的版本发布时,你可以使用 `installr` 包中的 `installr` 函数来完成R的更新(你当然也可以手动下载更新,如果不嫌麻烦的话)。代码如下:
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
# 安装 installr 包
|
||||||
|
install.packages(installr)
|
||||||
|
# 更新 R
|
||||||
|
installr::installr()
|
||||||
|
```
|
||||||
|
|
||||||
|
根据对话窗口中的提示完成整个安装过程即可。
|
||||||
|
|
||||||
|
你也可以使用以下代码来更新R的程辑包:
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
# 手动确认是否将各个更新到最新版本,或者
|
||||||
|
update.packages()
|
||||||
|
# 更新所有包到最新版本
|
||||||
|
update.packages(ask = FALSE)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 环境配置
|
||||||
|
|
||||||
|
### 项目(Project)
|
||||||
|
|
||||||
|
在RStudio中一个项目(Project)本质上是一个把项目相关的文件储存在一个地方的文件夹。如果使用项目相关的功能的话,你不需要担心使用的文件是否在当前的工作目录(Working Directory)。 项目功能提供了一个将不同目的的文件分隔开的方式,同时自动保存上次相应的工作进度。
|
||||||
|
|
||||||
|
#### 练习 {-}
|
||||||
|
|
||||||
|
为这次组队学习建立一个新的项目。
|
||||||
|
|
||||||
|
每次进行组队学习的时候,不要忘记去打开这个项目。在结束工作退出R,或者切换到另一个项目的时候,为了下次打开 RStudio 的时候有一个干净的工作环境,建议不去保存“工作空间镜像” (Workspace image,即在当前进程中加载的数据、函数)。
|
||||||
|
|
||||||
|
创建新的项目,可以在下拉菜单 __File__ (或者RStudio界面的右上角)找到 __New Project__ 选项。在弹出的对话框中,如果你想创建一个新的文件夹作为项目文件夹,选择 __New Directory__ ;如果你想用一个已经存在的文件夹作为项目文件夹,选择 __Existing Directory__ 。
|
||||||
|
|
||||||
|
![RStudio 界面右上角的项目设置^[小提示:如果想要改变 RStudio 的主题颜色,可以通过 __Tools__ >> __Global Options..__ >> __Appearance__]](./image/task00_project.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 用户界面
|
||||||
|
|
||||||
|
接下来让我们关注一下 RStudio 用户界面里的各种面板和标签。在这一部分有四个基础面板值得注意。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* __Console__ 控制台位于界面左侧。你可以在这里运行命令、浏览输出结果。
|
||||||
|
* __Environment__ 位于界面右上方。环境面板会总结出当前加载的数据、定义的函数等。现在在你看来可能是空的,因为我们还没有做任何事情。
|
||||||
|
* __Files__ 位于界面右下方。文件面板展示当前文件夹的信息。
|
||||||
|
* __Help__ 帮助面板也位于界面右下方。在这里你可以找到相应数据或者函数的帮助信息。
|
||||||
|
|
||||||
|
### R Markdown
|
||||||
|
|
||||||
|
你可以控制台(Concole)直接运行命令,但是这样跑出来的指令不会被保存下来。一般来说,我们更建议将命令写在叫做 R script 的脚本中,或者写在一个叫做 R Markdown 的文件中。
|
||||||
|
|
||||||
|
#### 练习 {-}
|
||||||
|
|
||||||
|
创建一个脚本 R script.
|
||||||
|
|
||||||
|
* __File__ >> __New File__ >> __R Script__
|
||||||
|
|
||||||
|
#### 练习 {-}
|
||||||
|
|
||||||
|
创建一个 R Markdown 文件。
|
||||||
|
|
||||||
|
* __File__ >> __New File__ >> __R Markdown..__
|
||||||
|
|
||||||
|
|
||||||
|
在脚本或者 R Markdown 的界面中,界面上方可以找到一个运行所有代码的按键。
|
||||||
|
|
||||||
|
一个 R Markdown 文件是一个可以将代码与 _markdown_ 标准文本(一种纯文本的格式语法)结合在一起的文本文档。使用 R Markdown 文件可以很容易地生成 pdf 文件或者 html 文件,其中不止包含了你的文本,还有代码以及运行代码所生成的结果。点击界面上方的 __Knit__ 按键^[如果你想要生成 pdf 文件,你需要安装 [LaTeX](https://www.latex-project.org/)。可以看看很好地兼容了 R 的[TinyTex](https://yihui.org/tinytex/)。]即可。再也不需要复制粘贴、屏幕截图输出结果到 Word 了。R Markdown 文档的一个主要优势是可复现。只要有了同样的代码和数据,你可以获得与其他人一模一样的结果,只要生成文档就可以了。
|
||||||
|
|
||||||
|
在 R Markdown 文件里写代码,需要使用特定的代码块(code chunks)来告诉 R Markdown 这部分是需要运行的代码而不只是文本。^[R Markdown 的更多语法可以看看 [R Markdown cheatsheet](https://rstudio.com/wp-content/uploads/2016/03/rmarkdown-cheatsheet-2.0.pdf)。]
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
`r ''````{r}
|
||||||
|
|
||||||
|
# 在这里写你的代码
|
||||||
|
# 使用三个反引号和 {r} 起始,三个反引号结束来构建代码块
|
||||||
|
# 在代码块里使用井号 # 写评论(纯文本)
|
||||||
|
|
||||||
|
`r ''````
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 帮助
|
||||||
|
|
||||||
|
R 能够发展到其今天的地位,很大的一个因素是他提供了相对详细的帮助文档,对初学者相对友好。一个相对完整的 R 包最低标准便是有函数的帮助文档。需要查看一个具体函数或者数据的帮助时可以用 `?fun`(等同于 `help(fun)`),该函数 `fun` 的帮助文档便会出现在之前提到过的帮助面板里。这个是已经知道需要什么函数了之后查找具体函数的用法的方式,如果你不记得具体的函数名字,可以使用两个问号加关键字来搜索:`??keyword`。
|
||||||
|
|
||||||
|
其次R包会有一个或多个 vignette。vignette 文档的目的主要是当使用者不知道用什么函数,对这个包不了解的时候提供一份入门简介一样的东西,一般会对常用的函数做出说明和演示,以及一些理论的阐述。这个包如果是哪一篇论文的副产品,vignette 甚至有可能是这篇论文。根据包的大小不同,vignette 的数量也不一样。如果是针对于一个问题写出的精炼的小包的话会只有一个 vignette 。如果包的用途比较广泛或者作者想说的话比较多,会针对每个问题有一个单独的 vignette。浏览所有已安装的 vignette 用 `browseVignettes()` ,查看具体包的用`browseVignettes("packagename")`。以上两个是通过 CRAN 发行的包的标配。
|
||||||
|
|
||||||
|
如果这个包没有在 CRAN 上发行,只在 GitHub 上,或者 GitHub 上有开发版本的话,一般会有一个 `README.md` 的文档。这个文档相对于 vignette 来说更加简短,一般都只写明如何安装,以及最主要的命令的演示,没有太多的说明。文档最后有可能会说明这个包用的是什么许可证。如果有这么一个文件的话,就可以很快速的知道这个包最主要的命令是什么。这个文档就需要到相对应的R包的资源库搜索了。
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
## 总结
|
||||||
|
# 查看具体函数的帮助文档
|
||||||
|
?fun
|
||||||
|
help(fun)
|
||||||
|
# 在帮助文档中搜索关键词
|
||||||
|
??keyword
|
||||||
|
# 浏览所有已安装的vignette
|
||||||
|
browseVignettes()
|
||||||
|
# 查看具体包的vignette
|
||||||
|
browseVignettes("packagename")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Happy Coding!
|
||||||
|
|
||||||
|
这次的R语言数据分析组队学习的入门篇便到这里了。接下里请移步组队学习的正篇第一部分:数据结构与数据集。
|
||||||
|
|
||||||
|
玩得开心!
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
__Fin__
|
||||||
|
|
||||||
|
> https://yangzhuoranyang.com
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale 是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注 Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,886 @@
|
||||||
|
# (PART) 开始干活 {-}
|
||||||
|
|
||||||
|
# 数据结构与数据集 {#task-01}
|
||||||
|
|
||||||
|
{width=100%}
|
||||||
|
|
||||||
|
## 准备工作
|
||||||
|
|
||||||
|
这节组队学习的目的主要是帮助你上手R的基本编程逻辑,了解一些R编程的基本概念,包括各个数据类型和数据集的读取与保存。
|
||||||
|
|
||||||
|
在开始我们的学习之前,不要忘记在 R Studio 中切换到组队学习专属的项目,打开一个 R script 文件或者 R Markdown 文件(详见入门篇)。
|
||||||
|
|
||||||
|
## 编码基础
|
||||||
|
|
||||||
|
首先我们来了解一些基本的编码操作。在 R Studio 中想要运行代码可以在控制台 Console 中键入代码后点击回车。这样运行的代码会被保存在当前项目的 `.Rhistory` 文件中,也可以在 R Studio 界面右上角的 History 面板中找到,但是不会被明确地保存下来作为一个脚本文件。一般只有在我们想要运行一些简单的指令或者计算的时候才会采取这种方式。更常见的是将代码写在脚本文件中,选中相应的代码后点击界面上方的Run或者快捷键(`Ctrl` + `Enter`)来运行。
|
||||||
|
|
||||||
|
### 算术
|
||||||
|
|
||||||
|
你可以直接运行计算命令。计算符号包括加`+`、减`-`、乘`*`、除`/`、求幂`^`以及求余数`%%`等。值得一提的是开平方根有他自己单独的函数`sqrt`。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
1 + 1
|
||||||
|
1 - 1
|
||||||
|
1 * 2
|
||||||
|
1 / 2
|
||||||
|
3 %% 2
|
||||||
|
2^(1 / 2)
|
||||||
|
sqrt(2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 赋值
|
||||||
|
|
||||||
|
在 R 里,我们可以为一个“东西”取一个名字,这个“东西”可以是一个值、一个向量、或者一个函数等,这样我们就可以之后再获取存储在这个名字下面的信息。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 将数字42赋予名叫x的变量
|
||||||
|
x <- 42
|
||||||
|
# 在R中运行一个物体的名字
|
||||||
|
# R将会打印出(print)该物体的值
|
||||||
|
x
|
||||||
|
```
|
||||||
|
|
||||||
|
在 R 中基础赋值的符号有三种:
|
||||||
|
|
||||||
|
1. 一个向左的箭头`<-`表示将箭头右方的值取名叫做箭头左侧的名字,或者将箭头右侧的值存储在箭头左侧的名字里;
|
||||||
|
2. 一个向右的箭头`->`表示将箭头左侧的值储存在箭头右侧的名字里;
|
||||||
|
3. 一个等号`=`表示将箭头右侧的值存储在箭头左侧的名字里(同1)。
|
||||||
|
|
||||||
|
在早期的键盘中有一个单独的按键就是向左的箭头,虽然后来的键盘不再设立这个按键,但是使用箭头的编程习惯流传了下来。赋值符号的选择取决于个人习惯,但是我们大多数情况下都推荐使用箭头(尤其是向左的箭头)作为赋值的符号。这是R语言于其他语言不同的地方,有以下原因:
|
||||||
|
|
||||||
|
1. 箭头明确了赋值方向,这是等号做不到的;
|
||||||
|
2. 等号用在顶层环境中的时候是赋值,用在函数中则是设参(或者叫做在函数层面赋值)。这种二义性不小心区分则可能会引发错误。而等号即使用在函数中也是赋值;
|
||||||
|
3. 箭头可以做到多次赋值(`a <- b <- 42`)甚至是不同方向上多次赋值(`a <- 42 -> b`)(尽量避免!);
|
||||||
|
4. 虽然这次组队学习中不会学到,但是更高级的赋值工具包括 `<<-` 和 `->>` 对应向左或向右的箭头;
|
||||||
|
5. 同时使用`=`与`==`(判断是否相等)会降低可读性(`a <- 1 == 2` vs `a = 1 == 2`)。
|
||||||
|
|
||||||
|
总结,__凡是赋值都用`<-`__,凡是设参(之后会说到)都用`=`。
|
||||||
|
|
||||||
|
在 R Studio 中可以使用快捷键`Alt` + `-`来输入`<-`,这样就不用每次都点两次键啦。这样还有一个好处,就是 R Studio 会自动识别当前的输入语言,从而选择最佳的赋值符号。大多数情况下这也就是说他会输入R的`<-`,如果你在 R Studio 里用 Python 的话就会自动变成`=`啦。
|
||||||
|
|
||||||
|
原来作用于单纯数字上的算术运算现在即可用变量名称代替具体的数值。
|
||||||
|
```{r}
|
||||||
|
y <- 21
|
||||||
|
x + y
|
||||||
|
x <- x + y
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 函数
|
||||||
|
|
||||||
|
R是一个非纯函数式编程(Functional Programming)的语言,与你平时可能所熟悉的面向对象程序设计(Object-Oriented Programming)的编程语言(比如 Python)不一样。这意味着在R中,相对于以类(Class)与对象(Object)的思路思考问题,我们要更多地定义函数(Function)以及考虑函数的输入与输出来进行运算。(如果你不知道我在这里说什么,请忽略这段话。)
|
||||||
|
|
||||||
|
在R中,所有的运算都是通过函数来达成的。我们可以用和之前一样的赋值方法(`<-`)来将一个函数存储在一个名字下。请看以下示例:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
addone <- function(x = 0) {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这里我创建了一个名为`addone`的函数,这个函数的作用就是将输入值在函数内部存储在名为`x`的参数里,在名为`x`的值上加一,再返回结果。如果没有输入值的话,`x`的默认值是`x = 0`。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 作用在数字上
|
||||||
|
addone(42)
|
||||||
|
# 作用在变量上
|
||||||
|
y <- 42
|
||||||
|
addone(y)
|
||||||
|
```
|
||||||
|
|
||||||
|
如你可见,调用函数的方法就是在函数的名字后边加小括号,再在小括号中输入参数(arguments)。当函数有多个可选参数的时候,建议输入参数的时候使用等号`=`明确函数名称。这便是之前提到过的等号的设参用法。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
addone(x = 42)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你没有使用小括号而是直接在控制台中运行函数的名字的话,像以前一样,R会直接打印出这个函数的内容,即源代码:
|
||||||
|
```{r}
|
||||||
|
addone
|
||||||
|
```
|
||||||
|
|
||||||
|
当你完成了一个复杂的计算,不要忘记把结果储存在一个名字下,否则结果不会保存下来,只会在控制台中一闪而过。
|
||||||
|
```{r}
|
||||||
|
y <- 42
|
||||||
|
y_plusone <- addone(y)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 循环(loop)
|
||||||
|
|
||||||
|
使用代码很重要的一个原因是可以重复进行多次相同或有规律的操作,也就是循环了。
|
||||||
|
|
||||||
|
R 中的循环函数包括`for`,`while`,和`repeat`。在这里我们简单用一个例子来介绍一下最灵活的`for`循环:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
x <- 0
|
||||||
|
for(i in 1:3){
|
||||||
|
x <- x + i
|
||||||
|
print(x)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在最开始的时候,我们让`x`等于0。在接下来进行的循环操作中,紧跟在`for`之后的小括号里给出了每个回合当中会变化的参数,叫做`i`。`i`后边的`in`之后给出的是参数`i`在回合中的可能取值,也就是从1到3的正整数。最后大括号中给出每个回合的操作,在`x`上加上`i`的值,重新取名为`x`,再打印出来。
|
||||||
|
|
||||||
|
整个流程下来:
|
||||||
|
|
||||||
|
第一个回合`x`一开始是0,在第一个回合中`i`是1,经过计算赋值`x`变成了1,打印后进入第二个回合;
|
||||||
|
第二个回合`x`一开始是1,在第二个回合中`i`是2,经过计算赋值`x`变成了3,打印后进入第二个回合;
|
||||||
|
第三个回合`x`一开始是3,在第三个回合中`i`是3,经过计算赋值`x`变成了6,打印后结束循环。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 管道(pipe)
|
||||||
|
|
||||||
|
如果我们想要对一个对象进行多个函数的操作,比如说想要使用我们刚刚定义的`addone`函数,还有新定义的`addtwo`,`addthree`,我们可以按照普通调用函数的方法一个套一个:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
addone <- function(x) x+1
|
||||||
|
addtwo <- function(x) x+2
|
||||||
|
addthree <- function(x) x+3
|
||||||
|
|
||||||
|
x <- 0
|
||||||
|
addthree(addtwo(addone(x)))
|
||||||
|
```
|
||||||
|
|
||||||
|
在这种常规的方法下,函数运行的顺序,和我们读函数的顺序,都是从内到外的。比如在上边的操作中,我们先用了`addone`给0加1,又用了`addtwo`,最后用了`addthree`。这样的坏处也是显而易见的,即可读性很差。想象一下你要对一个数据列表连续使用十几个函数,每个函数里都有其自己不同的参数,这么一系列操作如果用这个常规方法的话必然会使代码变成一个很难读的庞然大物。
|
||||||
|
|
||||||
|
`magrittr`包提供了另一种使用函数的办法,即使用`%>%`这个符号函数进行方法链(method chain)的操作。你可以把这个符号叫做管道。如果我们用管道来重写之前的一连串操作,代码会变成:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# tidyverse也包含了管道符号
|
||||||
|
library(tidyverse)
|
||||||
|
|
||||||
|
x %>%
|
||||||
|
addone() %>%
|
||||||
|
addtwo() %>%
|
||||||
|
addthree()
|
||||||
|
```
|
||||||
|
|
||||||
|
这个符号的具体含义简单来说是“将上一步运行的结果放在下一步运行的函数的第一个参数的位置上”。在这个例子中,`x`被当作`addone`的第一个参数被加一。`addone`的运行结果被当成下一步`addtwo`的第一个参数被加二,其运行结果最后被当成`addthree`的第一个参数被加三,最终得到结果。
|
||||||
|
|
||||||
|
经过了管道的改写之后,函数的可读性得到了大幅上升。从常规的“从内到外”读法,变成了“从上到下,从左到右”。虽然需要运行额外R包,但是由于符合阅读习惯和数据清洗流程的特点,管道在数据分析的领域被普遍使用。
|
||||||
|
|
||||||
|
关于管道符号的具体使用规则详见`` ?`%>%` ``。
|
||||||
|
|
||||||
|
## 数据类型
|
||||||
|
|
||||||
|
### 基础数据类型
|
||||||
|
|
||||||
|
在R中有五种基础数据类型,包括三个数值型、一个逻辑型和一个字符型。
|
||||||
|
|
||||||
|
#### 数值型
|
||||||
|
|
||||||
|
数值型数据包括三种,分别是默认的实数数值型数据(double)、整数类型(integer)和复数类型(complex):
|
||||||
|
```{r}
|
||||||
|
# numeric
|
||||||
|
a <- 132.2345
|
||||||
|
# Inf
|
||||||
|
# integer
|
||||||
|
b <- 132L
|
||||||
|
# complex
|
||||||
|
c <- 2 + 3i
|
||||||
|
```
|
||||||
|
|
||||||
|
实数数值型数据每个值占用8个字节(bytes),是最常见的数值型数据。如果没有做特别处理,我们平时见到的数字都是这个类型的——单纯的数字罢了。
|
||||||
|
|
||||||
|
整数类型,正如它的名字一样,只包含整数而没有小数部分。我们可以在整数末尾加上一个大写的L来表示这个数字是一个整数类型的数据。如果没有加大写的L的话,虽然只输入了一个整数,但是这个整数是实数数值类型的整数,而不是整数类型。他们的区别在于实数数值类型的整数和和非整数一样都占用8个字节,而整数类型只占用4个字节。平时用起来区别不大,但是如果数据量比较大且都是整数的话推荐使用整数类型来节约空间。
|
||||||
|
|
||||||
|
复数类型便是包含复数部分的数值类型了。只要在实数部分后边加上虚数部分并且用小写字母i来代表虚数单位,这个数值便是复数类型。鉴于数据分析领域基本不会涉及复数,我们在这次组队学习不去讨论复数类型。
|
||||||
|
|
||||||
|
判断一个数值是什么类型,可以用`typeof()`:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
typeof(a)
|
||||||
|
typeof(b)
|
||||||
|
typeof(c)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 逻辑型
|
||||||
|
|
||||||
|
逻辑型(logical)数据只包括两个值,`TRUE`(`T`) 和 `FALSE`(`F`):
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
TRUE
|
||||||
|
T
|
||||||
|
FALSE
|
||||||
|
F
|
||||||
|
```
|
||||||
|
尽管一个字母的缩写和全拼效果是一样的,但是一个好的编程习惯是始终使用大写全拼的`TRUE`和`FALSE`。这样可以增加可读性,也会减小因为命名产生的使用错误。比如,有些时候涉及到时间序列时,一些用户喜欢将最大时序上限命名为`T`,这个时候就不能用`T`来代表`TRUE`了。
|
||||||
|
|
||||||
|
说到逻辑型数据,就不得不说到逻辑算符。这里我们只考虑三个,分别是“和”(and)`&`、“或”(or)`|`、“否”(not)`!`。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
TRUE & FALSE
|
||||||
|
TRUE | FALSE
|
||||||
|
!TRUE
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 字符型
|
||||||
|
|
||||||
|
字符型数据(character)可以总结为“任何带引号的值”。它可以是一个字母、一个单词、一句话、或者任何用引号框起来的数值或逻辑。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
string_a <- "A"
|
||||||
|
string_b <- "letter"
|
||||||
|
string_c <- "This is a sentence."
|
||||||
|
string_d <- "42"
|
||||||
|
string_e <- "TRUE"
|
||||||
|
```
|
||||||
|
|
||||||
|
在输入的时候,即使是数字或者逻辑型的`TRUE`和`FALSE`,只要加上了引号,他们就变成了字符型的数据,而不再带有数值型或逻辑型的特性。要注意区分。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
typeof(42)
|
||||||
|
typeof("42")
|
||||||
|
typeof(TRUE)
|
||||||
|
typeof("TRUE")
|
||||||
|
```
|
||||||
|
字符型是最“自由”的数据类型,因为它的内容可以是任何字符,任何其他的数据类型也可以转化为字符型数据。比如你可以把一个数值型数据加上引号来当作字符型数据来解读。但是反过来却不可以:你没有办法把一个字符当作数字来解读。数据之间的转化我们会在第3.2.2节讲到。
|
||||||
|
|
||||||
|
在R中,单引号(`'`)和双引号(`"`)是等效的,但是我们推荐大多数情况下使用双引号,只有在引号内有双引号的时候使用单引号去引双引号(比如`' This is an "example". '`)。这主要是为了帮助其他语言(C, C++, Java等)的用户区分单双引号的细微区别。在C语言里,单双引号不是等效的。R语言中的(单)双引号大致是与C语言中的双引号等效的。
|
||||||
|
|
||||||
|
|
||||||
|
### 向量(vector)
|
||||||
|
|
||||||
|
这里说到的向量主要指基础向量类型(atomic vector)。
|
||||||
|
向量是由一组相同类型的值组成的一维序列。根据值的类型不同,我们会有不同类型的向量。
|
||||||
|
相对应之前的数值、逻辑和字符型的基础数据类型,这里我们也有数值、逻辑和字符型的向量类型。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec_num <- c(1, 2, 3)
|
||||||
|
vec_log <- c(TRUE, FALSE, TRUE)
|
||||||
|
vec_cha <- c("A", "B", "middle school")
|
||||||
|
```
|
||||||
|
|
||||||
|
使用函数`c()`来构建向量。可以进行向量上的运算,而不用一个一个值地单独去计算。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec_A <- c(1, 2, 3)
|
||||||
|
vec_B <- c(3, 5, 6)
|
||||||
|
vec_A + vec_B # 等同于 c(1 + 3, 2 + 5, 3 + 6)
|
||||||
|
|
||||||
|
!vec_log
|
||||||
|
```
|
||||||
|
|
||||||
|
也有相应的作用于向量上的函数,可以计算相应的统计量。比如求和的`sum`、求方差的`var`、平均值的`mean`等:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
sum(vec_A)
|
||||||
|
var(vec_A)
|
||||||
|
mean(vec_A)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 因子(factor)
|
||||||
|
|
||||||
|
除了之前提到的基础数据类型组成的向量外,还有一类重要的的向量类型便是因子,可以使用函数`factor`和`c`组合来创建。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec_fac <- factor(c("male", "female", "male", "female", "female"))
|
||||||
|
vec_fac
|
||||||
|
```
|
||||||
|
|
||||||
|
从表面上看,一个因子向量和字符向量很相似,都是一系列带引号的字符组成的。它与字符向量的主要区别在于因子向量的独特值(levels)是有限个数的。因子向量的所有元素都是由这些有限个数的独特值组成的。比如在以上的例子中,虽然`vec_fac`由五个元素组成,但是只包括了两个独特值“male”和“female”。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 查看因子向量的独特值
|
||||||
|
levels(vec_fac)
|
||||||
|
```
|
||||||
|
|
||||||
|
你也可以用函数`ordered`或者`factor`里的`ordered = TRUE`参数(argument)创造一个有内在顺序的因子向量,内在顺序可以用`levels`参数来手动设定:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
educ <- ordered(
|
||||||
|
c("kindergarten", "primary school", "middle school",
|
||||||
|
"primary school", "middle school", "kindergarten"),
|
||||||
|
levels = c("kindergarten", "primary school", "middle school")
|
||||||
|
)
|
||||||
|
# 等同于
|
||||||
|
educ <- factor(
|
||||||
|
c("kindergarten", "primary school", "middle school",
|
||||||
|
"primary school", "middle school", "kindergarten"),
|
||||||
|
ordered = TRUE,
|
||||||
|
levels = c("kindergarten", "primary school", "middle school")
|
||||||
|
)
|
||||||
|
|
||||||
|
educ
|
||||||
|
```
|
||||||
|
|
||||||
|
实质上,R 把因子向量当作整数型数值向量来对待。这也就意味着用因子向量替代字符向量可以剩下很多字节。
|
||||||
|
|
||||||
|
#### 数值之间的转换 {#transform}
|
||||||
|
|
||||||
|
不同的向量/数据类型之间是可以互相转换的。相互转换的可行性取决于数据类型的复杂程度(或者说自由程度)。按照自由程度将已经提到的几种向量以从高到低的排序可得
|
||||||
|
|
||||||
|
> 字符>数值>逻辑
|
||||||
|
|
||||||
|
在数值型内的排序从自由度高到低为
|
||||||
|
|
||||||
|
> 复数>实数>整数
|
||||||
|
|
||||||
|
越靠近字符的类型越“自由”,自由度低的类型可以(随意)转化为同层或自由度更高的类型。字符型向量是最自由的:它可以包含任何原始值,其他任何类型都可以转化为它。我们以一个最受限制的逻辑向量为例,在这里展示如何根据这个排序使用几个常见的类型转换函数:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec_loc <- c(TRUE, FALSE, TRUE)
|
||||||
|
# 从逻辑型到数值型
|
||||||
|
vec_num <- as.numeric(vec_loc)
|
||||||
|
vec_num
|
||||||
|
# 从数值型到字符型
|
||||||
|
vec_cha <- as.character(vec_num)
|
||||||
|
vec_cha
|
||||||
|
# 从逻辑型到字符型
|
||||||
|
vec_cha2 <- as.character(vec_loc)
|
||||||
|
vec_cha2
|
||||||
|
|
||||||
|
## 倒序
|
||||||
|
# 从字符型到数值型
|
||||||
|
as.numeric(vec_cha)
|
||||||
|
# 从字符型到逻辑型
|
||||||
|
as.logical(vec_cha2)
|
||||||
|
# 从数值型到逻辑型
|
||||||
|
as.logical(vec_num)
|
||||||
|
```
|
||||||
|
这里我们可以看到逻辑型的`TRUE`和`FALSE`实际上对应数值型的1和0。
|
||||||
|
|
||||||
|
从一个低自由的类型可以随便转化到高自由的类型,但是反过来,从一个高自由的类型要转化到一个低自由的类型必须要符合一些特定值。比如:
|
||||||
|
|
||||||
|
1. 从字符型转化到数值型的时候,字符的值一定要符合数字的格式;
|
||||||
|
2. 从数值型转化到逻辑型,0会转化为`FALSE`,其他数值会转化为`TRUE`;
|
||||||
|
3. 从字符型转化到逻辑型,字符的值只能是`TRUE`和`FALSE`。
|
||||||
|
|
||||||
|
如果不符合这个规则的话,会得到`NA`。`NA`是“Not Available”的缩写,即所谓的缺失值。缺失值的处理在下一篇《数据清洗与准备》会讲到。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 产生缺失值
|
||||||
|
as.logical(c("some", "random", "strings"))
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
因子型是一个相对特殊的类型,它可以和数值型与字符型相互转换。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec_fac <- factor(c("male", "female", "male", "female", "female"))
|
||||||
|
# 从因子型到数值型
|
||||||
|
vec_num <- as.numeric(vec_fac)
|
||||||
|
vec_num
|
||||||
|
# 从因子型到字符型
|
||||||
|
vec_cha <- as.character(vec_fac)
|
||||||
|
vec_cha
|
||||||
|
|
||||||
|
# 从字符型到因子型
|
||||||
|
as.factor(vec_cha)
|
||||||
|
# 从整数型到字符型
|
||||||
|
as.factor(vec_num)
|
||||||
|
|
||||||
|
```
|
||||||
|
正如之前所说,R内部将因子变量当作整数变量来处理,这也就是为什么一个看上去像是字符的东西可以被变成数值。
|
||||||
|
需要注意的是,把因子型转化为其他类型的时候会丢失一定的信息:
|
||||||
|
|
||||||
|
1. 因子向量变成字符向量会丢失独特值的信息;
|
||||||
|
2. 因子向量变成数值型的时候会丢失字面信息,只会保留独特值的编码,即根据独特值排序的正整数。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 向量命名
|
||||||
|
|
||||||
|
除了向量自己的名字,我们也可以给向量里的每个元素一个名字。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 先命名向量
|
||||||
|
# 再命名向量的元素
|
||||||
|
vec <- c(1, 2, 3)
|
||||||
|
names(vec) <- c("A", "B", "C")
|
||||||
|
vec
|
||||||
|
|
||||||
|
# 或者
|
||||||
|
# 创造向量的时候命名向量的元素
|
||||||
|
vec <- c(A = 1, B = 2, C = 3)
|
||||||
|
vec
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 访问向量的子集
|
||||||
|
|
||||||
|
三种截取子集的符号:`[`、`[[` 和 `$`(其中`$`不能用在基础向量上)。
|
||||||
|
|
||||||
|
六种截取向量子集的方法:
|
||||||
|
|
||||||
|
1. 正整数:根据元素的序号提取元素;
|
||||||
|
2. 负整数:根据元素的序号去除元素;
|
||||||
|
3. 和向量长度一样的逻辑向量:将逻辑向量的元素与向量元素一一对应,`TRUE` 选择该元素,`FALSE`去除该元素;
|
||||||
|
4. Nothing:选择原向量;
|
||||||
|
5. 零(0):什么都不选择;
|
||||||
|
5. 字符向量:选择根据元素名字选择元素。
|
||||||
|
|
||||||
|
使用`[` 作为选取符号的示例:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
vec <- c(a = 1.2, b = 5.6, c = 8.4, d = 9.5)
|
||||||
|
# 1. 正整数
|
||||||
|
vec[c(1,3)]
|
||||||
|
# 2. 负整数
|
||||||
|
vec[c(-1,-3)]
|
||||||
|
# 3. 逻辑向量
|
||||||
|
vec[c(TRUE, FALSE, FALSE, TRUE)]
|
||||||
|
# 4. Nothing
|
||||||
|
vec[]
|
||||||
|
# 5. 零
|
||||||
|
vec[0]
|
||||||
|
# 6. 字符向量
|
||||||
|
vec[c("a", "c")]
|
||||||
|
```
|
||||||
|
|
||||||
|
`[[` 在向量的场景里只能选择一个元素,而不是像`[` 一样选择一个子集:
|
||||||
|
|
||||||
|
```{r, error = TRUE}
|
||||||
|
# 可以
|
||||||
|
vec[[1]]
|
||||||
|
vec[1]
|
||||||
|
vec[c(1, 3)]
|
||||||
|
# 不可以
|
||||||
|
vec[[c(1, 3)]]
|
||||||
|
```
|
||||||
|
正是因为这个原因,我们提倡在只选择一个元素的时候多使用`[[`而不是`[`。这样在函数产生预期外的行为,选择多余一个元素的时候可以及时被错误信息提醒。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 特殊数据类型
|
||||||
|
|
||||||
|
#### 日期(date)
|
||||||
|
|
||||||
|
R中有蕴含日期的特殊类型`Date`,有日期-时间类型的`POSIXct`和`POSIXlt`。但在这一节我主要想介绍一下专注于日期处理的包`lubridate`。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
library(lubridate)
|
||||||
|
```
|
||||||
|
|
||||||
|
日期的本质实质上只是数字罢了,但是日期也有特殊的计算方式,特殊的进制。比如一个月有可能有30天或31一天,多少天进一个月也需要相应变化。`lubridate`包中的年月日`ymd`函数就是用来帮助解决这个问题的:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
sevenseven <- ymd("2021-07-07")
|
||||||
|
|
||||||
|
sevenseven
|
||||||
|
typeof(sevenseven)
|
||||||
|
class(sevenseven)
|
||||||
|
```
|
||||||
|
注意这里打印出来的日期是符合阅读习惯的年月日,但是属于`Date`的class,又是`double`的类别,也就意味着可以把这个日期当作一个单纯的数来计算。比如七月七日加上一天就是七月八日:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
sevenseven + 1
|
||||||
|
```
|
||||||
|
|
||||||
|
七月七日加上一个月就是八月七日:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
sevenseven + months(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
年月日`ymd`函数所做的只是把输入的字符串自动识别并输出日期格式的数值,只要输入的字符串符合“年月日”的类似格式顺序。如果字符串不是年月日的格式,也没关系,`lubridate`也提供相应的 月年日`myd`,日月年`dmy`,月日年`mdy`,日年月`dym`,甚至是 年季`yq` 的函数。
|
||||||
|
|
||||||
|
`lubridate`的更多用法详见[`lubridate`主页](https://lubridate.tidyverse.org/)。
|
||||||
|
|
||||||
|
#### 时间序列(ts)
|
||||||
|
|
||||||
|
时间序列作为一种有自相关性质的特殊数据类型,在R中也是可以分开处理的。制造时间序列的函数叫做`ts`,也就是 time series 的缩写:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
xts <- ts(rnorm(12), start = c(2021, 1), frequency = 4)
|
||||||
|
xts
|
||||||
|
```
|
||||||
|
在这里创造的序列便拥有了时间序列的性质。`ts`函数的`start`参数设定了时间序列开始的时间,`frequency`参数设定了时间序列的周期性。在上面的例子中,我们创造了一个从2021年第一季度开始的,具有季节性的时间序列,跨度三年。我们也有相应的函数可以提取这些时间序列的信息:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 起始日期
|
||||||
|
start(xts)
|
||||||
|
# 结束日期
|
||||||
|
end(xts)
|
||||||
|
# 周期性
|
||||||
|
frequency(xts)
|
||||||
|
```
|
||||||
|
|
||||||
|
使用时间序列的好处在于我们可以用一些很简单的命令来使用时间序列的模型,比如使用`forecast`包来用一个 ARIMA 模型对澳大利亚燃气月生产量进行预测:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
library(forecast)
|
||||||
|
gas %>%
|
||||||
|
auto.arima() %>%
|
||||||
|
forecast(36) %>%
|
||||||
|
autoplot()
|
||||||
|
```
|
||||||
|
|
||||||
|
关于时间序列的分析与预测的更多信息可见[`tidyverts`系列包](https://tidyverts.org/), [`forecast`包](https://pkg.robjhyndman.com/forecast/)等。
|
||||||
|
|
||||||
|
## 多维数据类型
|
||||||
|
|
||||||
|
之前我们讨论的数据类型都是一个序列(向量),都是一维的数据。在这章里我们会学习二维甚至多于二维的数据类型。
|
||||||
|
|
||||||
|
### 矩阵(matrix)
|
||||||
|
|
||||||
|
在R中的矩阵和数学概念上的矩阵很相似。在数学概念里,矩阵是一个按照长方阵列排列的数字集合,它有着固定的行数和列数。在R里,矩阵是一个按照长方阵列排列的、有着固定行数和列数的、包含同一类型数据的集合。你可以使用函数`matrix`来创建一个矩阵:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
matrix(1:9, nrow = 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
在这里,第一个参数是矩阵中数据的具体内容。`1:9` 是 `c(1, 2, 3, 4, 5, 6, 7, 8, 9)` 的一个缩写,用于创建间隔为1的整数序列。
|
||||||
|
|
||||||
|
第二个参数告诉R这个矩阵应该有多少行。你也可以使用`ncol`来告诉R这个矩阵有多少列。默认状态下,R会把数值按照从上到下、从左到右的顺序填充在这个固定行数列数的矩阵里。如果你想让R先从左到右填充(横向按照行填充),则需要将`byrow`参数设置为`TRUE`:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
matrix(1:9, ncol = 3, byrow = TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
R中的矩阵不局限于数值型矩阵,它只要求包含的数据从属于同一类型:如果是数值型,那每一个格子里都是数值型;如果是字符型,所有值都是字符型数据。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
mat_month <- matrix(month.name, nrow = 4, byrow = TRUE)
|
||||||
|
mat_month
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 矩阵命名
|
||||||
|
|
||||||
|
对于一个矩阵来说,主要的命名集中于行名`rownames`和列名`colnames`:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 你可以用这两个函数去更改行名和列名
|
||||||
|
rownames(mat_month) <- c("Quarter1", "Quarter2", "Quarter3", "Quarter4")
|
||||||
|
colnames(mat_month) <- c("Month1", "Month2", "Month3")
|
||||||
|
|
||||||
|
mat_month
|
||||||
|
|
||||||
|
# 也可以用这两个函数去获取行名和列名
|
||||||
|
rownames(mat_month)
|
||||||
|
colnames(mat_month)
|
||||||
|
|
||||||
|
# 或者用一个函数获取所有维度的名称
|
||||||
|
dimnames(mat_month)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 访问矩阵子集
|
||||||
|
|
||||||
|
和在向量里一样,访问矩阵的子集也可以用`[`或者`[[`。区别在于矩阵中我们有两个维度,所以需要同时给定两个维度的坐标:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 访问矩阵中第1行第2列格子的元素
|
||||||
|
mat_month[[1, 2]]
|
||||||
|
|
||||||
|
# 在逗号前不输入数字的时候
|
||||||
|
# 根据列号截取整列
|
||||||
|
mat_month[, 2]
|
||||||
|
|
||||||
|
# 在逗号后不输入数字的时候
|
||||||
|
# 根据行号截取整行
|
||||||
|
mat_month[1, ]
|
||||||
|
|
||||||
|
# 如果有行名和列名的话
|
||||||
|
# 也可以用字符串来截取特定范围
|
||||||
|
mat_month[["Quarter1", "Month3"]]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 列表(list)
|
||||||
|
|
||||||
|
列表是R中比较基础的数据类型中最灵活的类型。它和向量或者矩阵不一样,在一个列表中可以储存各种不同的基本数据类型。你既可以存三个数字,也可以把数值型、字符型、逻辑型混合:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
list(1, 2, 3)
|
||||||
|
list(1, "lol", TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
列表甚至可以储存列表本身,也就意味着你可以一层套一层地设置列表。夹杂其他各种类型,就可以创造一个庞然大物:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
stuff <- list(
|
||||||
|
list(
|
||||||
|
1:12,
|
||||||
|
"To be or not to be",
|
||||||
|
c(TRUE, FALSE)),
|
||||||
|
42,
|
||||||
|
list(
|
||||||
|
list(
|
||||||
|
ymd("2021-07-07"),
|
||||||
|
"remembrance"),
|
||||||
|
2L+3i)
|
||||||
|
)
|
||||||
|
stuff
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 访问子列表
|
||||||
|
|
||||||
|
|
||||||
|
列表同样可以用中括号来访问子列表。单个中括号`[`和两个中括号`[[`的区分在列表中特别重要。简单来说,单个中括号返回的列表元素类型还是列表,双中括号返回的列表元素是它本身的类型。想要返回多个子列表,就只能用单括号了,因为元素本身的类型不允许多个类型在一个序列中保存。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 返回前两个子列表
|
||||||
|
stuff[1:2]
|
||||||
|
|
||||||
|
# 返回第一个子列表中的第二个子列表
|
||||||
|
stuff[[1]][[2]]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 列表命名
|
||||||
|
|
||||||
|
列表的维度,或者说层数可是比矩阵多多了,这也就意味着,列表中可以命名的地方多多了。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 给列表的顶层三个列表命名
|
||||||
|
names(stuff) <- c("I", "II", "III")
|
||||||
|
|
||||||
|
# 给列表的第一个列表里的三个子列表命名
|
||||||
|
names(stuff[[1]]) <- c("I", "II", "III")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
如果列表有名字,自然可以用包含名字的字符串获取子列表。
|
||||||
|
```{r}
|
||||||
|
# 访问名为“I”的列表中名为“II”的子列表
|
||||||
|
stuff[["I"]][["II"]]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
之前我们一直没有使用过美元符号`$`来获取子集,列表提供了一个最佳的展示场景,以下这行代码可以起到于上一行代码一样的效果,而不用加各种括号和引号:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
stuff$I$II
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 数据表(data frame 与 tibble)
|
||||||
|
|
||||||
|
|
||||||
|
数据表将是进行数据分析的时候接触的最多的数据类型了。一个数据表(data frame)的本质是一个列表(list),但是采取了矩阵(matrix)的展示形式:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
df <- data.frame(x = 1:12, y = month.abb, z = month.name)
|
||||||
|
df
|
||||||
|
```
|
||||||
|
|
||||||
|
数据表的每一列是一个子列表。将几个长度相同的子列表并排放在一起,就组成了一个长方形的矩阵形式。这种特殊的处理使得数据表包含了两种数据形式的优势。列与列之间可以使用不同的基础数据类型,也就是说一列的数据是数值型的数据,下一列数据可以是字符型的数据。长方形的形状保证了列与列之间的数值是一一对应的,每一行都是一个观察量。这很符合日常会遇到的数据的形式。
|
||||||
|
|
||||||
|
`tibble`是`tidyverse`系列包中的`tibble`包提供的一种数据形式。使用`tibble`比较明显的好处是,当你把`tibble`打印在控制台里的时候,它有一个更干净直观的打印方式。与 data frame 试图打印所有的行、一股脑把所有信息扔给你不同,`tibble` 默认只会打印前几行给你一个数据长什么样的感觉,还会告诉你每一列的数据是什么类型的:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
tb <- tibble(a = 1:100, b = 101:200)
|
||||||
|
tb
|
||||||
|
```
|
||||||
|
|
||||||
|
除了看起来好看以外,`tibble`在原始数据表的基础上保留了有用的功能,去除了多余的功能。它干得更少,比如它不会自发修改变量类型或变量名字,也不会做部分匹配;同时它抱怨得更多,比如当一个变量不存在的时候就会触发错误信息。这样用户就能及早发现错误,不会等到代码堆成小`r emo::ji("poop")`山。
|
||||||
|
|
||||||
|
#### 访问数据表内容
|
||||||
|
|
||||||
|
既然看上去像矩阵,听起来像列表,那就应该可以用适用于矩阵和列表的方法访问数据表元素。事实上也的确是这样:
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 访问数据表名为x的列
|
||||||
|
df[["x"]]
|
||||||
|
df$x
|
||||||
|
|
||||||
|
# 访问第一行第二个数值
|
||||||
|
df[1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
# 访问tibble第2列
|
||||||
|
tb[, 2]
|
||||||
|
|
||||||
|
# 访问tibble第1行第2列的数值
|
||||||
|
tb[1, 2]
|
||||||
|
|
||||||
|
```
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
tb$a
|
||||||
|
#太长了还是不显示了吧
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
tibble 的另一个特性是其访问的子列表也是`tibble`类型的数据表,即使是用单引号返回一个格子的元素。
|
||||||
|
|
||||||
|
|
||||||
|
关于`tibble`更多信息,详见[`tibble`主页](https://tibble.tidyverse.org/)。
|
||||||
|
|
||||||
|
## 读写数据
|
||||||
|
|
||||||
|
这一章我们主要讨论根据不同数据保存方式区分的读写数据的方法。
|
||||||
|
|
||||||
|
### 内置数据集
|
||||||
|
|
||||||
|
R本身和一些R包都会有内置的数据集。使用`data`命令来查看、使用可用数据集。
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
# 查看R本身自带的数据集
|
||||||
|
data()
|
||||||
|
|
||||||
|
# 查看某一R包自带的数据集
|
||||||
|
data(package = "dplyr")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
# 载入AirPassengers数据集
|
||||||
|
data("AirPassengers")
|
||||||
|
|
||||||
|
glimpse(AirPassengers)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表格类型数据(csv, excel)
|
||||||
|
|
||||||
|
h1n1 流感问卷数据储存在名为 “h1n1_flu.csv” 的文件中,我们会在下一篇《数据清洗与准备》中用到。假设 “h1n1_flu" 有不同的储存类型,我们列举一些读写数据表类型数据的方法。
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
|
||||||
|
# 读取csv文件
|
||||||
|
library(readr)
|
||||||
|
h1n1_flu <- read_csv("h1n1_flu.csv")
|
||||||
|
# 保存csv文件
|
||||||
|
write_csv(h1n1_flu, "h1n1_flu.csv")
|
||||||
|
|
||||||
|
|
||||||
|
# 读取excel文件
|
||||||
|
library(readxl)
|
||||||
|
# 自动识别文件后缀
|
||||||
|
h1n1_flu <- read_excel("h1n1_flu.xls")
|
||||||
|
# 读取xls文件
|
||||||
|
h1n1_flu <- read_xls("h1n1_flu.xls")
|
||||||
|
# 读取xlsx文件
|
||||||
|
h1n1_flu <- read_xlsx("h1n1_flu.xlsx")
|
||||||
|
```
|
||||||
|
|
||||||
|
不建议在R中直接编辑 excel 文件,csv 文件应该满足日常所需了。如果有编辑 excel 文件的需求,可以看看`openxlsx`包。
|
||||||
|
|
||||||
|
|
||||||
|
### R的专属类型数据(RData, rds)
|
||||||
|
|
||||||
|
有一些数据存储方式是R中独有的。我们在这里讨论两类。一类是 rds 文件,一类是 RData 文件。
|
||||||
|
|
||||||
|
1. rds 文件储存一个R中的对象。这个对象不一定是四四方方的数据表,而可以是任何形式,包括复杂的列表等。因为他储存的是一个对象,所以读取的时候也是读取出来一个对象,需要被保存在一个名字下。
|
||||||
|
1. RData 储存的是一个或多个、任意结构的、带有自己名字的对象。读取的时候会将储存的对象直接载入当前的环境中,使用的是对象自己的名字,所以不需要再额外起名字。
|
||||||
|
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
# 读取
|
||||||
|
h1n1_flu <- read_rds("h1n1_flu.rds")
|
||||||
|
# 存储
|
||||||
|
write_rds(h1n1_flu, "h1n1_flu.rds")
|
||||||
|
|
||||||
|
# 读取
|
||||||
|
load("h1n1_flu.RData")
|
||||||
|
# 存储
|
||||||
|
save(h1n1_flu, file = "h1n1_flu.RData")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 其他软件(SPSS, Stata, SAS)
|
||||||
|
|
||||||
|
R也可以直接读取其他软件的数据类型。这里列举使用`haven`包读写 SPSS 的 sav 和 zsav、 Stata 的 dta、SAS 的 sas7bdat 和 sas7bcat。
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
library(haven)
|
||||||
|
|
||||||
|
# SPSS
|
||||||
|
read_spss()
|
||||||
|
write_spss()
|
||||||
|
|
||||||
|
# Stata
|
||||||
|
read_dta()
|
||||||
|
write_dta()
|
||||||
|
|
||||||
|
# SAS
|
||||||
|
read_sas()
|
||||||
|
write_sas()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 练习题
|
||||||
|
|
||||||
|
### 了解数据集
|
||||||
|
|
||||||
|
请使用之前读取的`h1n1_flu`完成以下任务。
|
||||||
|
|
||||||
|
#### 常用数据探查函数
|
||||||
|
|
||||||
|
请尝试使用以下常用的数据探查函数,挑出两个你最喜欢的描述他们的功能。别忘了可以用`?fun`查看帮助文档。
|
||||||
|
|
||||||
|
```{r, eval = FALSE}
|
||||||
|
glimpse(h1n1_flu)
|
||||||
|
str(h1n1_flu)
|
||||||
|
head(h1n1_flu)
|
||||||
|
tail(h1n1_flu)
|
||||||
|
View(h1n1_flu)
|
||||||
|
summary(h1n1_flu)
|
||||||
|
nrow(h1n1_flu)
|
||||||
|
length(h1n1_flu$sex)
|
||||||
|
class(h1n1_flu$sex)
|
||||||
|
summary(h1n1_flu)
|
||||||
|
table(h1n1_flu$sex)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 分组计算统计量
|
||||||
|
|
||||||
|
|
||||||
|
```{r, include = FALSE}
|
||||||
|
h1n1_flu <- read_csv("./datasets/h1n1_flu.csv")
|
||||||
|
library(tidyverse)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```{r, echo=TRUE, eval = FALSE}
|
||||||
|
h1n1_flu %>%
|
||||||
|
group_by(sex, employment_status) %>%
|
||||||
|
summarise(n())
|
||||||
|
```
|
||||||
|
```{r, echo=FALSE}
|
||||||
|
h1n1_flu %>%
|
||||||
|
group_by(sex, employment_status) %>%
|
||||||
|
dplyr::summarise(n())
|
||||||
|
```
|
||||||
|
|
||||||
|
请问上边这几行代码在计算什么?你可不可以使用同样的方法计算一些其他的统计量?别忘了看看帮助文档`?summarise`。
|
||||||
|
|
||||||
|
|
||||||
|
### 创造数据集
|
||||||
|
|
||||||
|
我们说过数据表的本质是将列表排列在一起,所以数据表就会有列表的性质。而我们又知道列表可以包含任何类型的数据,无论是单个的数值或者是向量、矩阵等。
|
||||||
|
|
||||||
|
1. 请你创造一个数据表,其中的某一列变量的每一个格子包含的不再是常规的单个数值或者字符串,而是一个向量或者矩阵等多维的数据类型;
|
||||||
|
1. 请你描述一个可以在数据分析时运用此类特性的使用场景。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
__Fin__
|
||||||
|
|
||||||
|
> https://yangzhuoranyang.com
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale 是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注 Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,615 @@
|
||||||
|
|
||||||
|
# 数据清洗与准备 {#task-02}
|
||||||
|
|
||||||
|
{width=100%}
|
||||||
|
|
||||||
|
Task 02共计6个知识点,预计需学习5~8小时,请安排好学习任务。
|
||||||
|
|
||||||
|
## 环境配置 {-}
|
||||||
|
```{r, message=FALSE}
|
||||||
|
library(mlbench) # 将会使用到包中的BostonHousing数据集
|
||||||
|
library(funModeling) # 探索性数据分析工具包,本节内容中将会使用到它的status()函数,打印整体数据质量
|
||||||
|
library(tidyverse) # 数据转化工具包,本节内容中将会使用它包含的dplyr中的管道函数 %>%
|
||||||
|
library(VIM) # 缺失值可视化工具包,本节内容中将会使用到它的aggr()函数
|
||||||
|
library(mice) # 缺失值处理工具包,本节内容会使用它来进行多重插补
|
||||||
|
library(Rlof) # 用于LOF异常值检测方法,本节内容将会使用到它的lof()函数
|
||||||
|
library(fastDummies) # 用于生成dummy的包,本节内容将会使用到它的dummy_cols()函数
|
||||||
|
library(sjmisc) # 用于生成dummy的包,本节内容将会使用到它的to_dummy()函数
|
||||||
|
library(MASS) # 基于此包进行box-cox转换
|
||||||
|
library(dlookr) # 本节内容将会使用到它的transform()函数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 案例数据 {-}
|
||||||
|
|
||||||
|
本节内容将会使用到两个数据集。
|
||||||
|
|
||||||
|
### 数据集1 h1n1流感问卷数据集 {-}
|
||||||
|
|
||||||
|
#### 数据说明 {-}
|
||||||
|
|
||||||
|
目前提供的数据集来自关于h1n1流感调查问卷的部分内容,可以从这个网站上看到具体字段的详细说明:https://www.drivendata.org/competitions/66/flu-shot-learning/page/211/
|
||||||
|
|
||||||
|
数据集包含26,707个受访者数据,共有32个特征+1个标签(是否接种h1n1疫苗)。
|
||||||
|
|
||||||
|
#### 加载并查看部分数据 {-}
|
||||||
|
|
||||||
|
首先加载数据,了解数据集大小。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
h1n1_data <- read.csv("./datasets/h1n1_flu.csv", header = TRUE)
|
||||||
|
dim(h1n1_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
注:为了简化本章的示例,我们在这32个特征中,筛选出了10个特征,作为一个子集,来学习如何使用R做数据清洗与准备。如有兴趣,可以把下面这块筛选去掉,自己用完整数据集做一次探索。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data <- h1n1_data[, c(1, 3, 11, 12, 15, 16, 19, 20, 22, 23, 33)]
|
||||||
|
head(h1n1_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据集2 波士顿房价数据集 {-}
|
||||||
|
|
||||||
|
#### 数据说明 {-}
|
||||||
|
数据集来自`mlbench`包,请提前装好。数据字段说明可从网址查看:https://blog.csdn.net/weixin_46027193/article/details/112238597
|
||||||
|
|
||||||
|
数据集包含506条房价信息,共有13个特征+1个预测字段(房屋价格)。
|
||||||
|
|
||||||
|
#### 加载并查看部分数据 {-}
|
||||||
|
```{r }
|
||||||
|
data(BostonHousing)
|
||||||
|
dim(BostonHousing)
|
||||||
|
head(BostonHousing)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 重复值处理
|
||||||
|
|
||||||
|
在某些情况下,我们需要对数据进行去重处理。`unique()`函数可以对数据进行整体去重,`distinct()`函数可以针对某些列去重。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 整体去重
|
||||||
|
h1n1_data_de_dup1 <- unique(h1n1_data)
|
||||||
|
|
||||||
|
# 指定根据列respondent_id,h1n1_knowledge去重,并保留所有列
|
||||||
|
h1n1_data_de_dup2 <- distinct(h1n1_data, respondent_id, h1n1_knowledge, .keep_all = T)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 缺失值识别与处理
|
||||||
|
|
||||||
|
现实环境中,由于数据来源及搜集过程,可能有各种不规范,导致数据往往会存在缺失。缺失值识别与处理,无论是在统计还是数据管理中,往往是数据清洗的第一步。
|
||||||
|
|
||||||
|
### 缺失值识别
|
||||||
|
|
||||||
|
#### 常用识别方法
|
||||||
|
|
||||||
|
在R语言中,惯用会把缺失值表示为NA,一般可使用`is.na(a)`,`!complete.cases(a)`来识别`a`是否为缺失值。
|
||||||
|
```{r }
|
||||||
|
# 假设定义的一个变量中存在缺失值
|
||||||
|
y <- c(1, 2, 3, NA)
|
||||||
|
|
||||||
|
# 用is.na在识别是否为缺失值
|
||||||
|
is.na(y)
|
||||||
|
|
||||||
|
# 用!complete.cases()在识别是否为缺失值
|
||||||
|
!complete.cases(y)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 缺失值统计
|
||||||
|
|
||||||
|
统计缺失值总数。
|
||||||
|
```{r }
|
||||||
|
# 数据集中总缺失数据量
|
||||||
|
sum(is.na(h1n1_data))
|
||||||
|
|
||||||
|
# 数据集中某一列缺失数据量
|
||||||
|
sum(is.na(h1n1_data["h1n1_knowledge"]))
|
||||||
|
```
|
||||||
|
|
||||||
|
如果想按行或按列统计,可以写函数。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
pMiss <- function(x) {
|
||||||
|
sum(is.na(x)) / length(x) * 100
|
||||||
|
}
|
||||||
|
apply(h1n1_data, 2, pMiss) # 按列统计缺失比率%
|
||||||
|
# apply(h1n1_data,1,pMiss) #按行统计缺失比率%
|
||||||
|
```
|
||||||
|
|
||||||
|
或调用一些现成的包。比如,我们可以使用`funModeling`包中的`status()`函数,直接观测案例数据中包含的0值,缺失值(NA),在每个特征中的分布情况。以h1n1 flu数据集为例:
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
data_quality <- status(h1n1_data)
|
||||||
|
data_quality %>% mutate(across(where(is.numeric), ~ round(., 3))) # 保留4位小数
|
||||||
|
```
|
||||||
|
|
||||||
|
结合案例数据h1n1 flu来看,存在缺失值的有5个特征字段。
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
missing_Value <- data_quality[which(data_quality$p_na > 0), ]
|
||||||
|
missing_Value$variable
|
||||||
|
```
|
||||||
|
#### 缺失值机制与分析
|
||||||
|
|
||||||
|
统计学家通常将缺失数据分为3类,为了更好的处理缺失值,我们可以基于缺失值机制来识别以下3种缺失模式:
|
||||||
|
|
||||||
|
* MCAR(完全随机缺失):如果数据的缺失与任何值(观察或缺失)之间没有关系,则为MCAR。
|
||||||
|
* MAR(随机缺失):考虑MAR与MCAR有何不同,如果缺失和观测值之间存在系统关系,则为MAR。例如-男性比女性更容易告诉自己的体重,因此体重就是MAR。“ Weight”变量的缺失取决于变量"Sex"的观测值。
|
||||||
|
* MNAR(非随机缺失):若缺失数据不属于MCAR和MAR,数据的缺失依赖于不完全变量本身,则数据为非随机缺失。例如,抑郁程度高的人更不容易填写抑郁调查问卷。
|
||||||
|
|
||||||
|
MNAR是最复杂的情况,处理 MNAR的策略是找到更多有关缺失原因的数据,或者执行假设分析,查看结果在各种情况下的敏感程度。大部分处理缺失数据的方法都假定数据是MCAR或MAR,此时,可以忽略缺失数据的生成机制,在替换或删除缺失数据后,直接对感兴趣的关系进行建模。
|
||||||
|
|
||||||
|
以下介绍几种可视化分析缺失数据关联的方法:
|
||||||
|
|
||||||
|
我们用`VIM`包里的`aggr()`函数,直观看一下具体的缺失情况。
|
||||||
|
```{r }
|
||||||
|
aggr(h1n1_data, cex.axis = .6, oma = c(9, 5, 5, 1)) # cex.axis调整轴字体大小,oma调整外边框大小
|
||||||
|
```
|
||||||
|
|
||||||
|
通过用`VIM`包里的矩阵图`matrixplot()`函数,可以检查某些变量的缺失值模式是否与其他变量的真实值有关联。矩阵图中,观测数据以黑白色阶显示(颜色越深,数值越高),缺失值会被标记为红色。我们对某一个存在缺失值的变量进行排序,来找寻含缺失值变量与其他变量的关系。
|
||||||
|
|
||||||
|
在此案例中,我们按照`health_insurance`进行分组排序。可以看到是否有慢性病`chronic_med_condition`的缺失,与`opinion_h1n1_vacc_effective`的缺失相对较集中。除此之外,也可以看到有慢性病的人年龄普遍较大。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 先简单处理一下一些类别变量的顺序
|
||||||
|
h1n1_data_matplt <- h1n1_data
|
||||||
|
h1n1_data_matplt$age_group <- factor(h1n1_data_matplt$age_group)
|
||||||
|
h1n1_data_matplt$education <- factor(h1n1_data_matplt$education, levels = c("", "< 12 Years", "12 Years", "Some College", "College Graduate"))
|
||||||
|
h1n1_data_matplt$sex <- factor(h1n1_data_matplt$sex)
|
||||||
|
h1n1_data_matplt$income_poverty <- factor(h1n1_data_matplt$income_poverty, levels = c("18 - 34 Years", "<= $75,000, Above Poverty", "> $75,000"))
|
||||||
|
# levels(h1n1_data_matplt$age_group) # 查看顺序
|
||||||
|
|
||||||
|
# 矩阵图可视化
|
||||||
|
par(mar = c(9, 4.1, 2.1, 2.1)) # x轴标签太长,调用par()函数调整外边框的大小
|
||||||
|
matrixplot(h1n1_data_matplt, sortby = "chronic_med_condition", cex.axis = 0.7) # cex.axis为调整坐标轴字体大小
|
||||||
|
```
|
||||||
|
|
||||||
|
用相关性探索缺失值。首先生成一个影子矩阵,用指示变量替代数据集中的数据(1表示缺失,0表示存在)。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
shadow_mat <- as.data.frame(abs(is.na(h1n1_data[, -1])))
|
||||||
|
head(shadow_mat)
|
||||||
|
```
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 可提取含缺失值的变量
|
||||||
|
shadow_mat <- shadow_mat[which(apply(shadow_mat, 2, sum) > 0)]
|
||||||
|
|
||||||
|
# 计算相关系数
|
||||||
|
cor(shadow_mat)
|
||||||
|
|
||||||
|
# 相关系数热力图
|
||||||
|
heatmap(cor(shadow_mat))
|
||||||
|
```
|
||||||
|
|
||||||
|
根据缺失相关性矩阵,`opinion_h1n1_vacc_effective ` 与 `chronic_med_condition` 缺失相关性较大。
|
||||||
|
|
||||||
|
综上,在案例中,变量之间的存在部分相关性,考虑为MAR。
|
||||||
|
|
||||||
|
其他数据缺失关系分析,可参考附录`数据的预处理基础`。
|
||||||
|
|
||||||
|
### 缺失值处理
|
||||||
|
|
||||||
|
缺失值一般有三种方式:
|
||||||
|
|
||||||
|
* 将缺失值作为变量值使用。比如在民意调查中,当选民不投票时,可以将缺失值处理为"无法确定"。
|
||||||
|
* 删除数据。主要有删除样本值和删除特征值。但可能会损失掉一些有用信息。
|
||||||
|
* 插补法。如均值/中位数/同类均值插补(数值变量),众数插补(类别变量),手动插补(根据主观理解),多重插补等。
|
||||||
|
|
||||||
|
以下我们主要介绍删除法和插补法:
|
||||||
|
|
||||||
|
#### 删除法
|
||||||
|
|
||||||
|
行删除,可以直接用`complete.cases()`或`na.omit()`来过滤掉数据集中所有缺失行。
|
||||||
|
```{r }
|
||||||
|
h1n1_data_row_del1 <- h1n1_data[!complete.cases(h1n1_data), ]
|
||||||
|
h1n1_data_row_del2 <- na.omit(h1n1_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
列删除,一般对于缺失率极高又没有太大作用的特征值,我们直接删除,如可以用`dataset[,-5]`去掉第五列,或` subset(dataset, select = -c(col1, col2))`去掉列col1和列col2。
|
||||||
|
|
||||||
|
比如,我们把`health_insurance`变量删除。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_col_del1 <- subset(h1n1_data, select = -c(health_insurance))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 简单插补法
|
||||||
|
|
||||||
|
注意在空值插补的时候,要区分类别变量与数值变量,均值插补不适用于类别变量。我们这里随机选择了一个变量演示`impute()`函数用法,在实际插补的时候,请大家根据情况进行选择。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_sim_imp <- h1n1_data
|
||||||
|
h1n1_data_sim_imp$h1n1_knowledge <- impute(h1n1_data_sim_imp$h1n1_knowledge, 1) # 填充特定值
|
||||||
|
h1n1_data_sim_imp$h1n1_knowledge <- impute(h1n1_data_sim_imp$h1n1_knowledge, median) # 插补中位数
|
||||||
|
h1n1_data_sim_imp$h1n1_knowledge <- impute(h1n1_data_sim_imp$h1n1_knowledge, mean) # 插补均值
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 拟合插补法
|
||||||
|
|
||||||
|
利用有监督的机器学习方法,比如回归、最邻近、随机森林、支持向量机等模型,对缺失值作预测。
|
||||||
|
|
||||||
|
|
||||||
|
#### 多重插补法
|
||||||
|
|
||||||
|
多重插补(MI)是一种基于重复模拟的处理缺失值的方法。其思想来源于贝叶斯估计,认为待插补的值是随机的,它的值来自于已观测到的值。具体实践上通常是估计出待插补的值,然后再加上不同的噪声,形成多组可选插补值(通常是3到10个)。根据某种选择依据,选取最合适的插补值。与单个插补(例如均值)相比,创建多个插补可解决缺失值的不确定性。 R中可利用`Amelia`、`mice`和`mi`包来执行这些操作。
|
||||||
|
|
||||||
|
本节中,我们将用案例介绍mice包(通过链式方程进行的多元插补)提供的方法。使用mice生成m个完整的插补数据集。然后利用`with-pool`的方法来评估选择哪一个数据集。首先使用`with()`函数依次对每个完整数据集应用统计模型如lm,glm等,用`summary()`输出数据集检验,看某数据集是否合格。接下来`pool()`函数把5个回归模型汇总,用`summary()`输出汇总数据集检验,查看整体插补方法是否合格。检验结果分析可参考附录`mice检验结果解释`
|
||||||
|
|
||||||
|
{width=60%}
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 先处理下数据,把数据集中一些类别变量转换回来
|
||||||
|
|
||||||
|
# imp是一个包含m个插补数据集的列表对象,同时还含有完成插补过程的信息。
|
||||||
|
# 参数m的默认值为5,这里我们将m设为4,生成4个无缺失数据集
|
||||||
|
# 参数method, 对于每个变量的拟合,可以指定所用的拟合方法,method传入的参数可以是一个具体方法,也可以为不同列指定具体方法,具体方法选择可参考附录mice使用文档。这里我们使用默认值。
|
||||||
|
imp <- mice(h1n1_data, m = 4, seed = 122, printFlag = FALSE)
|
||||||
|
|
||||||
|
# 查看变量h1n1_knowledge在几个插补数据集中的插补结果
|
||||||
|
# imp$imp$h1n1_knowledge
|
||||||
|
|
||||||
|
# 查看每个变量所用的插补方法
|
||||||
|
# imp$method
|
||||||
|
|
||||||
|
# 设定应用于m个插补数据集的统计分析方法。方法包括做线性回归模型的lm()函数、做广义线性模型的glm()函数、做广义可加模型的gam(),做负二项模型的nbrm()函数
|
||||||
|
fit <- with(imp, lm(h1n1_vaccine ~ h1n1_knowledge + doctor_recc_h1n1 + chronic_med_condition + health_insurance + opinion_h1n1_vacc_effective))
|
||||||
|
|
||||||
|
# 输出每个数据集检验
|
||||||
|
print.data.frame(summary(fit), digits = 4)
|
||||||
|
|
||||||
|
# 包含m个统计分析平均结果的列表对象
|
||||||
|
pooled <- pool(fit)
|
||||||
|
|
||||||
|
# 这是一个总体评估结果
|
||||||
|
pooled
|
||||||
|
|
||||||
|
# 这里修改action的参数(范围1-m),选择一个数据集作为我们已填充完成的数据集
|
||||||
|
h1n1_data_complete <- complete(imp, action = 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 异常值识别与处理
|
||||||
|
|
||||||
|
### 异常值识别
|
||||||
|
|
||||||
|
本节的异常值指离群点。为了让数据统计或数据建模更加准确,我们通常会识别并对处理一些离群点。有些模型会对异常值较敏感,参考附录`什么样的模型对缺失值更敏感?`。
|
||||||
|
总的来说,有几种常用方法,包括可视化图形分布识别(箱线图)、z-score识别、局部异常因子法(LOF法)、聚类法等。
|
||||||
|
|
||||||
|
我们这里用波士顿房价数据集来演示一下异常值识别的处理过程。
|
||||||
|
|
||||||
|
### 可视化图形分布
|
||||||
|
|
||||||
|
首先是可视化图形分布识别,将数值型变量筛选出来,用boxlpot看看分布。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 提取数值字段
|
||||||
|
nums <- unlist(lapply(BostonHousing, is.numeric))
|
||||||
|
nums_data <- BostonHousing[, nums]
|
||||||
|
|
||||||
|
# 数据变形
|
||||||
|
nums_data.new <- nums_data %>%
|
||||||
|
as.data.frame() %>%
|
||||||
|
mutate(Cell = rownames(.)) %>%
|
||||||
|
gather(., key = colname, value = "value", -Cell)
|
||||||
|
|
||||||
|
# 用ggplot画出箱线图
|
||||||
|
ggplot(data = nums_data.new, aes(x = colname, y = value)) +
|
||||||
|
geom_boxplot(aes(1)) +
|
||||||
|
facet_wrap(~colname, scales = "free") +
|
||||||
|
theme_grey() +
|
||||||
|
labs(title = "Outlier Detection On Numeric Data By Boxplot", x = "Numeric Columns", y = "") +
|
||||||
|
theme(legend.position = "top") +
|
||||||
|
theme_bw()
|
||||||
|
```
|
||||||
|
|
||||||
|
通过可视化分布,可以选择剔除一些不合理的离群值,比如在数据集中将dis>10.0的数据剔除。
|
||||||
|
|
||||||
|
### z-score
|
||||||
|
|
||||||
|
z-score是一种一维或低维特征空间中参数异常检测方法。它假定数据是高斯分布,异常值是分布尾部的数据点,因此远离数据的平均值。一般将z-score低于-3或高于3的数据看成是异常值。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 定义一个识别异常点的函数,x是输入数据(matrix或df),zs是异常临界值,z-score超过zs的被识别为异常点
|
||||||
|
outliers <- function(x, zs) {
|
||||||
|
temp <- abs(apply(x, 1, scale))
|
||||||
|
return(x[temp > zs])
|
||||||
|
}
|
||||||
|
# 打印出z-score<3的值
|
||||||
|
outliers(nums_data, 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 局部异常因子法
|
||||||
|
|
||||||
|
局部异常因子法(LOF),是一种无监督的离群检测方法,是基于密度的离群点检测方法中一个比较有代表性的算法。适用于在中等高维数据集上执行异常值检测。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# k是计算局部异常因子所需要判断异常点周围的点的个数
|
||||||
|
outlier_score <- lof(data = nums_data, k = 5)
|
||||||
|
|
||||||
|
# 绘制异常值得分的直方分布图
|
||||||
|
hist(outlier_score, col = "#8ac6d1")
|
||||||
|
|
||||||
|
# 排序,挑出得分排前五的数据(找到索引)作为异常值
|
||||||
|
names(outlier_score) <- 1:nrow(nums_data)
|
||||||
|
sort(outlier_score, decreasing = TRUE)[1:5]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异常值处理
|
||||||
|
|
||||||
|
首先需要确定是否是真的异常值,有些值虽然离群,但其实并不是异常值,处理掉反而会影响后续任务的准确性。 如果确定需要处理,可以参考缺失值的处理方式进行处理。
|
||||||
|
|
||||||
|
## 特征编码
|
||||||
|
|
||||||
|
为什么要进行特征编码?我们拿到的原始数据中,一般会有一些类别变量,但是在统计或机器学习中,我们通常需要把类别变量转化为数值型变量,才能应用于一些方法中。
|
||||||
|
|
||||||
|
### 独热编码/哑编码
|
||||||
|
|
||||||
|
One-hot encoding 和 dummy,是将类别变量扩充为多个只显示1,0的变量,每个变量代表原类别变量中的一个类。 注意他们之间的区别:https://www.cnblogs.com/lianyingteng/p/7792693.html
|
||||||
|
|
||||||
|
* 优点:解决了分类器不好处理分类数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
|
||||||
|
* 缺点:当类别的数量很多时,特征空间会变得非常大,容易造成维度灾难。(为避免维度灾难,后续可以考虑降维处理)
|
||||||
|
|
||||||
|
R里面有很多现成的转化编码的包,我们这里使用了`dummy_cols()`函数做演示,可以看到原来的类别类型字段,已经扩充为多个0,1编码的字段。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_dummy <- dummy_cols(subset(h1n1_data_complete, select = c(age_group)), select_columns = c("age_group"))
|
||||||
|
head(h1n1_data_dummy)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 标签编码
|
||||||
|
|
||||||
|
标签编码(Label Encoder)是将类别变量转换成连续的数值型变量,通常对有序的变量进行标签编码,既保留了顺序信息,也节约了空间(不会扩充变量)
|
||||||
|
|
||||||
|
R里有一个特殊的结构factor(factor是有序的分类变量),我们这里可以利用factor来做标签编码。首先根据实际情况设置factor的类别顺序,然后直接用`as.numeric()`转化为数字。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_complete_lab_encoder <- h1n1_data_complete
|
||||||
|
h1n1_data_complete_lab_encoder$income_poverty_lab_encoder <- as.numeric(factor(h1n1_data_complete_lab_encoder$income_poverty, levels = c("Below Poverty", "<= $75,000, Above Poverty", "> $75,000")))
|
||||||
|
head(subset(h1n1_data_complete_lab_encoder, select = c(income_poverty, income_poverty_lab_encoder)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动编码
|
||||||
|
|
||||||
|
比如,当某一个特征中有很多类别,我们认为某些类别可以合为一类,可以用`case_when()`函数手动处理。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_manual <- subset(h1n1_data_complete, select = c(age_group))
|
||||||
|
h1n1_data_manual$age_group_manual <- case_when(
|
||||||
|
h1n1_data_manual$age_group %in% c("18 - 34 Years") ~ 1,
|
||||||
|
h1n1_data_manual$age_group %in% c("35 - 44 Years", "45 - 54 Years", "55 - 64 Years") ~ 2,
|
||||||
|
h1n1_data_manual$age_group %in% c("65+ Years") ~ 3
|
||||||
|
)
|
||||||
|
head(h1n1_data_manual)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日期特征转换
|
||||||
|
|
||||||
|
参考附录`R语言日期时间处理`
|
||||||
|
|
||||||
|
|
||||||
|
## 规范化与偏态数据
|
||||||
|
|
||||||
|
为什么要数据规范化?简单来说是为了去除数据量纲和数据大小的差异,确保数据是在同一量纲或者同一数量级下进行比较,一般用在机器学习算法之前。数据规范化又可以使用0-1规范化,Z-score等方法。
|
||||||
|
为什么要处理偏态数据?。很多模型会假设数据或参数服从正态分布。例如线性回归(linear regression),它假设误差服从正态分布。
|
||||||
|
|
||||||
|
提示:注意在测试数据与训练数据分布差别很大的情况下,对测试数据运用一些规范化方法时,可能因为数据分布不匹配而带来误差。
|
||||||
|
|
||||||
|
这里我们使用波士顿房价数据集来做演示。可以看到图中数据的偏态分布及量纲差别。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
BostonHousing %>%
|
||||||
|
keep(is.numeric) %>%
|
||||||
|
gather() %>%
|
||||||
|
ggplot(aes(value)) +
|
||||||
|
facet_wrap(~key, scales = "free") +
|
||||||
|
geom_density(color = "#348498", fill = "#8ac6d1") +
|
||||||
|
theme_bw()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 0-1规范化
|
||||||
|
|
||||||
|
0-1规范化是将原始数据缩放到[0,1]区间内,一般方法是最小最大规范的方法,公式如下:
|
||||||
|
|
||||||
|
{width=20%}
|
||||||
|
|
||||||
|
这里用循环计算出每一列的最大最小值,再根据公式求出缩放后的数据。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
nums_data_norm1 <- nums_data
|
||||||
|
for (col in names(nums_data_norm1))
|
||||||
|
{
|
||||||
|
xmin <- min(nums_data_norm1[col])
|
||||||
|
xmax <- max(nums_data_norm1[col])
|
||||||
|
nums_data_norm1[col] <- (nums_data_norm1[col] - xmin) / (xmax - xmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
head(nums_data_norm1)
|
||||||
|
```
|
||||||
|
|
||||||
|
转换完再看一下分布,已经缩放到0-1之间了。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
nums_data_norm1 %>%
|
||||||
|
keep(is.numeric) %>%
|
||||||
|
gather() %>%
|
||||||
|
ggplot(aes(value)) +
|
||||||
|
facet_wrap(~key, scales = "free") +
|
||||||
|
geom_density(color = "#348498", fill = "#8ac6d1") +
|
||||||
|
theme_bw()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
此外可以用dlookr包里的`transform()`函数。
|
||||||
|
```{r }
|
||||||
|
nums_data_norm2 <- nums_data
|
||||||
|
nums_data_norm2$crim <- dlookr::transform(nums_data$crim, method = "minmax")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Z-score标准化
|
||||||
|
|
||||||
|
Z-score标准化是原数据减去期望再除以标准差,将数据按比例缩放,使其落入到一个小的区间内,标准化后的数据可正可负,但是一般绝对值不会太大。
|
||||||
|
|
||||||
|
{width=15%}
|
||||||
|
|
||||||
|
R里面可以用`scale()`函数来计算z-score。也可以dlookr包里的中`transform()`函数。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
nums_data_zscore <- nums_data
|
||||||
|
nums_data_zscore <- scale(nums_data_zscore)
|
||||||
|
head(nums_data_zscore)
|
||||||
|
```
|
||||||
|
|
||||||
|
转换完再看一下分布,数据缩放后在0周围的一个小区间了。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
data.frame(nums_data_zscore) %>%
|
||||||
|
keep(is.numeric) %>%
|
||||||
|
gather() %>%
|
||||||
|
ggplot(aes(value)) +
|
||||||
|
facet_wrap(~key, scales = "free") +
|
||||||
|
geom_density(color = "#348498", fill = "#8ac6d1") +
|
||||||
|
theme_bw()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 对数转换(log transform)
|
||||||
|
|
||||||
|
使用对数转换也是一种常见的处理偏斜特征的方法,但要注意原数据中不能含有负值。此外为了避免0值,我们通常使用log1p,公式为`lg(x+1)`。可以直接用dlookr包里的`transform()`函数,一般结合mutate函数一起使用。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 直接公式转换
|
||||||
|
nums_data_log1p1 <- log(nums_data + 1)
|
||||||
|
|
||||||
|
# 用transform()函数
|
||||||
|
nums_data_log1p2 <- nums_data
|
||||||
|
nums_data_log1p2$b <- dlookr::transform(nums_data_log1p2$b, method = "log+1")
|
||||||
|
```
|
||||||
|
|
||||||
|
转换完再看一下分布,大多变量转换后接近正态分布了。但是这里要特别注意离散数据。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
nums_data_log1p1 %>%
|
||||||
|
keep(is.numeric) %>%
|
||||||
|
gather() %>%
|
||||||
|
ggplot(aes(value)) +
|
||||||
|
facet_wrap(~key, scales = "free") +
|
||||||
|
geom_density(color = "#348498", fill = "#8ac6d1") +
|
||||||
|
theme_bw()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Box-Cox
|
||||||
|
|
||||||
|
Box-Cox变换是Box和Cox在1964年提出的一种广义幂变换方法,在变换后可以一定程度上减小不可观测的误差和预测变量的相关性,在机器学习中经常用来处理偏态分布。其一个显著优点是通过求变换参数来确定变换形式,而这个过程完全基于数据本身而无须任何先验信息,这无疑比凭经验或通过尝试而选用对数、平方根等变换方式要客观和精确。计算公式如下:
|
||||||
|
|
||||||
|
{width=40%}
|
||||||
|
|
||||||
|
示例参考附录`基于R语言进行Box-Cox变换`
|
||||||
|
|
||||||
|
|
||||||
|
## 小拓展
|
||||||
|
|
||||||
|
R语言中,mutate 类似于SQL中,根据表的现有变量,生成新变量。使用mutate集中处理变量转换,代码显示较整洁。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
h1n1_data_de <- h1n1_data_complete %>%
|
||||||
|
to_dummy(education, suffix = "label") %>%
|
||||||
|
bind_cols(h1n1_data_complete) %>%
|
||||||
|
mutate(
|
||||||
|
# 标签编码(label encoder)
|
||||||
|
sex = as.factor(as.numeric(factor(sex))),
|
||||||
|
income_poverty = (as.numeric(factor(
|
||||||
|
income_poverty,
|
||||||
|
levels = c(
|
||||||
|
"Below Poverty",
|
||||||
|
"<= $75,000, Above Poverty",
|
||||||
|
"> $75,000"
|
||||||
|
)
|
||||||
|
))),
|
||||||
|
# 手动编码
|
||||||
|
age_group = as.factor(
|
||||||
|
case_when(
|
||||||
|
age_group %in% c("18 - 34 Years") ~ 1,
|
||||||
|
age_group %in% c("35 - 44 Years", "45 - 54 Years", "55 - 64 Years") ~ 2,
|
||||||
|
age_group %in% c("65+ Years") ~ 3
|
||||||
|
)
|
||||||
|
),
|
||||||
|
# 标准化
|
||||||
|
across(
|
||||||
|
c(
|
||||||
|
"h1n1_knowledge",
|
||||||
|
"doctor_recc_h1n1",
|
||||||
|
"chronic_med_condition",
|
||||||
|
"opinion_h1n1_vacc_effective",
|
||||||
|
"age_group",
|
||||||
|
"income_poverty"
|
||||||
|
),
|
||||||
|
~ scale(as.numeric(.x))
|
||||||
|
)
|
||||||
|
) %>%
|
||||||
|
dplyr::select(-one_of("education", "education_"))
|
||||||
|
|
||||||
|
head(h1n1_data_de)
|
||||||
|
```
|
||||||
|
|
||||||
|
注意在机器学习中,尽量在数据集划分后,分别在训练集与验证集、测试集上进行数据清洗,避免数据泄露。R中的数据集划分方法参考附录`R中数据集分割`。
|
||||||
|
|
||||||
|
## 思考与练习
|
||||||
|
|
||||||
|
看完了本节数据清洗与准备,尝试着选取一个完整的数据集(从本节中选取或使用自己的数据集),来做一次清洗吧!
|
||||||
|
|
||||||
|
|
||||||
|
## 附录:参考资料 {-}
|
||||||
|
|
||||||
|
### 理论资料 {-}
|
||||||
|
|
||||||
|
**数据的预处理基础:** 如何处理缺失值 https://cloud.tencent.com/developer/article/1626004
|
||||||
|
|
||||||
|
**多重插补法:** 处理缺失值之多重插补(Multiple Imputation)https://zhuanlan.zhihu.com/p/36436260
|
||||||
|
|
||||||
|
**异常值检测:** R语言--异常值检测 https://blog.csdn.net/kicilove/article/details/76260350
|
||||||
|
|
||||||
|
**异常值检测之LOF:** 异常检测算法之局部异常因子算法-Local Outlier Factor(LOF) https://blog.csdn.net/BigData_Mining/article/details/102914342
|
||||||
|
|
||||||
|
**规范化:** 规范化、标准化、归一化、正则化 https://blog.csdn.net/u014381464/article/details/81101551
|
||||||
|
|
||||||
|
**什么样的模型对缺失值更敏感?:** https://blog.csdn.net/zhang15953709913/article/details/88717220
|
||||||
|
|
||||||
|
|
||||||
|
### R语言函数用法示例 {-}
|
||||||
|
|
||||||
|
`funModeling`用法示例:https://cran.r-project.org/web/packages/funModeling/vignettes/funModeling_quickstart.html
|
||||||
|
|
||||||
|
`tidyverse`官方文档:https://www.tidyverse.org/
|
||||||
|
|
||||||
|
`VIM`教学网页:https://www.datacamp.com/community/tutorials/visualize-data-vim-package
|
||||||
|
|
||||||
|
`mice`使用文档(Multivariate Imputation by Chained Equations):https://cran.r-project.org/web/packages/mice/mice.pdf
|
||||||
|
|
||||||
|
`mice`使用中文解释:https://blog.csdn.net/sinat_26917383/article/details/51265213
|
||||||
|
|
||||||
|
`mice`检验结果解释:http://blog.fens.me/r-na-mice/
|
||||||
|
|
||||||
|
`caret`包数据预处理:https://www.cnblogs.com/Hyacinth-Yuan/p/8284612.html
|
||||||
|
|
||||||
|
R语言日期时间处理:https://zhuanlan.zhihu.com/p/83984803
|
||||||
|
|
||||||
|
基于R语言进行Box-Cox变换:https://ask.hellobi.com/blog/R_shequ/18371
|
||||||
|
|
||||||
|
R中数据集分割:https://zhuanlan.zhihu.com/p/45163182
|
||||||
|
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
|
||||||
|
__June__
|
||||||
|
|
||||||
|
> 悉尼大学研究生,Datawhale成员
|
||||||
|
> https://blog.csdn.net/Yao_June
|
||||||
|
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale 是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注 Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
|
@ -0,0 +1,150 @@
|
||||||
|
# 基本统计分析 {#task-03}
|
||||||
|
|
||||||
|
{width=100%}
|
||||||
|
|
||||||
|
|
||||||
|
## 准备工作 {-}
|
||||||
|
|
||||||
|
如果没有相关的包,则使用`install.packages('package_name')`进行安装以下包。
|
||||||
|
```{r}
|
||||||
|
library(pastecs)
|
||||||
|
library(psych)
|
||||||
|
library(ggm)
|
||||||
|
```
|
||||||
|
|
||||||
|
读取数据,使用H1N1流感数据集和波士顿房价数据集。
|
||||||
|
```{r}
|
||||||
|
flu <- read.table("./datasets/h1n1_flu.csv", header = TRUE, sep = ",")
|
||||||
|
housing <- read.csv("./datasets/BostonHousing.csv", header = TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多种方法获取描述性统计量
|
||||||
|
|
||||||
|
### 基础方法
|
||||||
|
|
||||||
|
通过summary计算数值型变量的最大值、最小值、分位数以及均值,类别变量计算频数统计。
|
||||||
|
```{r}
|
||||||
|
summary(flu[c("household_children", "sex")])
|
||||||
|
```
|
||||||
|
```{r}
|
||||||
|
summary(flu[c("h1n1_concern", "h1n1_knowledge")])
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
通过 sapply() 计算描述性统计量,先定义统计函数,在进行聚合计算。
|
||||||
|
```{r}
|
||||||
|
mystats <- function(x, na.omit = FALSE) {
|
||||||
|
if (na.omit) {
|
||||||
|
x <- x[!is.na(x)]
|
||||||
|
}
|
||||||
|
m <- mean(x)
|
||||||
|
n <- length(x)
|
||||||
|
s <- sd(x)
|
||||||
|
skew <- sum((x - m)^3 / s^3) / n
|
||||||
|
kurt <- sum((x - m)^4 / s^4) / n - 3
|
||||||
|
return(c(n = n, mean = m, stdev = s, skew = skew, kurtosis = kurt))
|
||||||
|
}
|
||||||
|
|
||||||
|
sapply(flu[c("h1n1_concern", "h1n1_knowledge")], mystats)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 拓展包方法
|
||||||
|
|
||||||
|
通过pastecs包中的 stat.desc()函数计算描述性统计量,可以得到中位数、平均数、平均数的标准误、平均数置信度为95%的置信区间、方差、标准差以及变异系数。
|
||||||
|
```{r}
|
||||||
|
stat.desc(flu[c("household_children", "sex")])
|
||||||
|
```
|
||||||
|
通过psych包中的describe()计算描述性统计量。
|
||||||
|
```{r}
|
||||||
|
describe(flu[c("household_children", "sex")])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分组计算描述性统计
|
||||||
|
|
||||||
|
### 基础方法
|
||||||
|
|
||||||
|
#### 使用aggregate()分组获取描述性统计 {-}
|
||||||
|
|
||||||
|
1. 分组计算不同性别收入贫困计数。
|
||||||
|
2. 是否属于查尔斯河的房价中位数平均值。
|
||||||
|
```{r}
|
||||||
|
aggregate(flu[c("income_poverty")], by = list(sex = flu$sex), length)
|
||||||
|
aggregate(housing$medv, by = list(medv = housing$chas), FUN = mean)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 by() 分组计算描述性统计量 {-}
|
||||||
|
```{r}
|
||||||
|
by(flu[c("income_poverty", "sex")], flu$sex, length)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 频数表和列联表
|
||||||
|
```{r}
|
||||||
|
table(flu$sex)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关
|
||||||
|
|
||||||
|
### 相关的类型
|
||||||
|
|
||||||
|
#### Pearson、Spearman和Kendall相关 {-}
|
||||||
|
|
||||||
|
R可以计算多种相关系数,包括Pearson相关系数、Spearman相关系数、Kendall相关系数、偏相关系数、多分格(polychoric)相关系数和多系列(polyserial)相关系数。
|
||||||
|
1. 计算房价数据的相关系数,默认是Pearson相关系数。
|
||||||
|
```{r}
|
||||||
|
cor(housing)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 指定计算Spearman相关系数
|
||||||
|
```{r}
|
||||||
|
cor(housing, method = "spearman")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 城镇人均犯罪率与房价的相关系数
|
||||||
|
```{r}
|
||||||
|
x <- housing
|
||||||
|
y <- housing[c("medv")]
|
||||||
|
cor(x, y)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 偏相关 {-}
|
||||||
|
|
||||||
|
偏相关是指在控制一个或多个定量变量时,另外两个定量变量之间的相互关系。使用ggm 包中的 pcor() 函数计算偏相关系数。
|
||||||
|
|
||||||
|
### 相关性的显著性检验
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
cor.test(housing[, c("crim")], housing[, c("medv")])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 方差分析
|
||||||
|
方差分析(ANOVA)又称“变异数分析”或“F检验”,用于两个及两个以上样本均数差别的显著性检验。
|
||||||
|
|
||||||
|
### 单因素方差分析
|
||||||
|
从输出结果的F检验值来看,p<0.05比较显著,说明是否在查尔斯河对房价有影响。
|
||||||
|
```{r}
|
||||||
|
fit <- aov(housing$medv ~ housing$chas)
|
||||||
|
summary(fit)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多因素方差分析
|
||||||
|
构建多因素方差分析,查看因子对房价的影响是否显著。
|
||||||
|
```{r}
|
||||||
|
fit <- aov(housing$medv ~ housing$crim * housing$b)
|
||||||
|
summary(fit)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
__杨佳达__
|
||||||
|
|
||||||
|
> 数据挖掘师,Datawhale成员,目前在国内某第三方数据服务公司做数据分析挖掘及数据产品
|
||||||
|
> https://github.com/yangjiada
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale 是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注 Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
|
@ -0,0 +1,449 @@
|
||||||
|
|
||||||
|
# 数据可视化 {#task-04}
|
||||||
|
|
||||||
|
|
||||||
|
{width=60%}
|
||||||
|
|
||||||
|
## ggplot2包介绍 {-}
|
||||||
|
|
||||||
|
ggplot2包由Hadley Wickham编写,提供了一种基于Wilkinson所述图形语法的图形系统。ggplot2包的目标是提供一个全面的、基于语法的、连贯一致的图形生成系统,允许用户创建新颖的、有创新性的数据可视化图形。
|
||||||
|
|
||||||
|
总的来说有以下几点:
|
||||||
|
|
||||||
|
- ggplot2的核心理念是将绘图与数据分离,数据相关的绘图与数据无关的绘图分离
|
||||||
|
- ggplot2保有命令式作图的调整函数,使其更具灵活性
|
||||||
|
- ggplot2将常见的统计变换融入到了绘图中。
|
||||||
|
- ggplot2是按图层作图
|
||||||
|
|
||||||
|
ggplot2图像的三个基本构成:数据、图形属性映射、几何对象
|
||||||
|
|
||||||
|
按照ggplot2的绘图理念,Plot(图)= data(数据集)+ Aesthetics(美学映射)+ Geometry(几何对象)。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```{r example}
|
||||||
|
# ggplot(data,aes(x=x,y=y))+geom_point()
|
||||||
|
```
|
||||||
|
|
||||||
|
- 数据:用于绘制图形的数据
|
||||||
|
- 映射:aes()函数是ggplot2中的映射函数, 所谓的映射即为数据集中的数据关联到相应的图形属性过程中一种对应关系, 图形的颜色,形状,分组等都可以通过通过数据集中的变量映射。
|
||||||
|
- 几何对象:我们在图中实际看到的图形元素,如点、线、多边形等。
|
||||||
|
|
||||||
|
ggplot2绘图代码如同数据公式一般,只需要套相应的公式即可绘制出丰富的图形,后续的讲解也会按照此方法。
|
||||||
|
|
||||||
|
ggplot2参考链接:
|
||||||
|
|
||||||
|
- https://ggplot2.tidyverse.org/reference/
|
||||||
|
- https://ggplot2-book.org/
|
||||||
|
|
||||||
|
ggplot2的安装方法
|
||||||
|
|
||||||
|
```{r cars}
|
||||||
|
# install.packages("ggplot2")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境配置
|
||||||
|
``` {r}
|
||||||
|
library(ggplot2) # 画图工具ggplot2
|
||||||
|
library(ggpubr) # 将多个图形拼接
|
||||||
|
library(plyr) # 数据处理包
|
||||||
|
```
|
||||||
|
|
||||||
|
在本讲中会用到ggpubr中的ggrrange这个多图拼接工具,详细使用方法参见:
|
||||||
|
http://www.sthda.com/english/articles/24-ggpubr-publication-ready-plots/81-ggplot2-easy-way-to-mix-multiple-graphs-on-the-same-page/
|
||||||
|
|
||||||
|
### 案例数据 {-}
|
||||||
|
本节内容将会使用到两个数据集
|
||||||
|
|
||||||
|
**1.1h1n1流感问卷数据集**
|
||||||
|
|
||||||
|
h1n1流感问卷数据集是关于h1n1流感问卷调查的一个数据,属于外部数据
|
||||||
|
数据集包含26,707个受访者数据,共有32个特征+1个标签(是否接种h1n1疫苗)
|
||||||
|
|
||||||
|
读取相关的数据集
|
||||||
|
```{r }
|
||||||
|
h1n1_data <- read.csv("./datasets/h1n1_flu.csv", header = TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.2波士顿房价数据集**
|
||||||
|
|
||||||
|
波士顿房价数据集属于R语言自带数据集,也可以通过外部读取
|
||||||
|
|
||||||
|
读取相关的数据集
|
||||||
|
```{r boston}
|
||||||
|
boston_data <- read.csv("./datasets/BostonHousing.csv", header = TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 散点图
|
||||||
|
|
||||||
|
散点图是指在数理统计回归分析中,数据点在直角坐标系平面上的分布图,散点图表示因变量随自变量而变化的大致趋势,由此趋势可以选择合适的函数进行经验分布的拟合,进而找到变量之间的函数关系。
|
||||||
|
|
||||||
|
散点图的优势:
|
||||||
|
|
||||||
|
- 数据用图表来展示,显然比较直观,在工作汇报等场合能起到事半功倍的效果,让听者更容易接受,理解你所处理的数据。
|
||||||
|
- 散点图更偏向于研究型图表,能让我们发现变量之间隐藏的关系为我们决策作出重要的引导作用。
|
||||||
|
- 散点图核心的价值在于发现变量之间的关系,包括线性与非线性之间的关系。
|
||||||
|
|
||||||
|
|
||||||
|
```{r plot1}
|
||||||
|
# 读取数据
|
||||||
|
boston_data <- read.csv("./datasets/BostonHousing.csv", header = TRUE)
|
||||||
|
# 绘制简单的散点图 x轴选择的是lstat ,y轴选择的是medv
|
||||||
|
ggplot(data = boston_data, aes(x = lstat, y = medv)) +
|
||||||
|
geom_point()
|
||||||
|
```
|
||||||
|
|
||||||
|
上图选择的是lstat为x轴,medv为y轴绘制的散点图,x轴表示弱势群体人口所占比例,y轴表示房屋的平均价格,通过图上的数据可以看到,弱势人群的比例增加会影响房价,这2个变量呈现一定的负相关。
|
||||||
|
|
||||||
|
ggplot2可以修改散点图的性状和大小,R语言中存储了一些相关的形状
|
||||||
|
{width=50%}
|
||||||
|
|
||||||
|
size参数修改点的大小,color参数修改点的颜色
|
||||||
|
```{r plot2}
|
||||||
|
# 使用第17号形状
|
||||||
|
p1 <- ggplot(data = boston_data, aes(x = lstat, y = medv)) +
|
||||||
|
geom_point(shape = 17)
|
||||||
|
# size参数修改点的大小,color参数修改点的颜色
|
||||||
|
p2 <- ggplot(data = boston_data, aes(x = lstat, y = medv)) +
|
||||||
|
geom_point(size = 3, color = "red")
|
||||||
|
ggarrange(p1, p2, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
可将数据集的其它属性映射到散点图的颜色属性中
|
||||||
|
```{r plot4}
|
||||||
|
p3 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = factor(rad))) +
|
||||||
|
geom_point()
|
||||||
|
p4 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = rad)) +
|
||||||
|
geom_point()
|
||||||
|
ggarrange(p3, p4, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
ggplot2关于散点图的相关做法有很详细的介绍,相关参考链接:https://ggplot2.tidyverse.org/reference/geom_point.html
|
||||||
|
|
||||||
|
## 直方图
|
||||||
|
|
||||||
|
直方图是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。
|
||||||
|
直方图可以很好的查看数据的分布情况,是常用的数据可视化展示图形。
|
||||||
|
|
||||||
|
我们对rad变量进行直方图分析
|
||||||
|
```{r plot6}
|
||||||
|
ggplot(data = boston_data, aes(x = rad)) +
|
||||||
|
geom_histogram()
|
||||||
|
```
|
||||||
|
|
||||||
|
可以看到ggplot2可以自动对数据进行直方图的统计
|
||||||
|
|
||||||
|
我们给直方图填充颜色,同时改变直方图类型color表示直方图的边框,fill表示直方图中的填充颜色,ggplot2支持RGB颜色表的配色方案,linetype表示直方图线的类型
|
||||||
|
|
||||||
|
RGB颜色表可以参考:http://www.mgzxzs.com/sytool/se.htm
|
||||||
|
|
||||||
|
```{r plot7}
|
||||||
|
p5 <- ggplot(data = boston_data, aes(x = rad)) +
|
||||||
|
geom_histogram(color = "black", fill = "#69b3a2")
|
||||||
|
p6 <- ggplot(data = boston_data, aes(x = rad)) +
|
||||||
|
geom_histogram(color = "black", fill = "#69b3a2", linetype = "dashed")
|
||||||
|
ggarrange(p5, p6, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
ggplot2也支持在直方图上添加平均线和密度图
|
||||||
|
|
||||||
|
```{r plot8}
|
||||||
|
p7 <- p5 + geom_vline(aes(xintercept = mean(rad)), color = "blue", linetype = "dashed", size = 1)
|
||||||
|
p8 <- ggplot(data = boston_data, aes(x = rad)) +
|
||||||
|
geom_histogram(color = "black", fill = "#69b3a2", aes(y = ..density..)) +
|
||||||
|
geom_density(alpha = .2, fill = "#FF6666")
|
||||||
|
ggarrange(p7, p8, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
ggplot2关于直方图的相关做法有很详细的介绍,相关参考链接:https://ggplot2.tidyverse.org/reference/geom_histogram.html
|
||||||
|
|
||||||
|
## 柱状图
|
||||||
|
柱状图是一种常用的数据可视化图形,根据翻译的不同,柱状图又叫长条图、柱状统计图、条状图、棒形图
|
||||||
|
柱状图图用来比较两个或以上的价值(不同时间或者不同条件),只有一个变量,通常利用于较小的数据集分析。长条图亦可横向排列,或用多维方式表达。需要注意的是柱状图与直方图是不同的数据可视化方法,不要弄混淆了。
|
||||||
|
|
||||||
|
对h1n1数据集中填写人的受教育情况进行可视化展示,使用pylr包中的count对edcation进行计数统计
|
||||||
|
|
||||||
|
```{r plot9}
|
||||||
|
data <- count(h1n1_data["race"])
|
||||||
|
p <- ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity")
|
||||||
|
# 也可以进行水平放置
|
||||||
|
p1 <- p + coord_flip()
|
||||||
|
ggarrange(p, p1)
|
||||||
|
```
|
||||||
|
|
||||||
|
可以看到左边的柱状图文字有点挡住了,我们把文字旋转45°
|
||||||
|
|
||||||
|
```{r plot9a}
|
||||||
|
data <- count(h1n1_data["race"])
|
||||||
|
ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity") +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
对柱状图的样式进行修改
|
||||||
|
|
||||||
|
```{r plot10}
|
||||||
|
# 更改条的宽度和颜色:
|
||||||
|
# 更改条的宽度
|
||||||
|
p2 <- ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", width = 0.5) +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
# 改变颜色
|
||||||
|
p3 <- ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", color = "blue", fill = "white") +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
# 最小主题+蓝色填充颜色
|
||||||
|
p4 <- ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", fill = "steelblue") +
|
||||||
|
theme_minimal() +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
# 选择要显示的项目
|
||||||
|
p5 <- p + scale_x_discrete(limits = c("White", "Black")) + theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
ggarrange(p2, p3, p4, p5)
|
||||||
|
```
|
||||||
|
|
||||||
|
对柱状图进行标签显示
|
||||||
|
```{r plot11}
|
||||||
|
p6 <- ggplot(data = data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", fill = "steelblue") +
|
||||||
|
geom_text(aes(label = freq), vjust = -0.3, size = 3.5) +
|
||||||
|
theme_minimal() +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
# 条形内部标签
|
||||||
|
p7 <- ggplot(data = data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", fill = "steelblue") +
|
||||||
|
geom_text(aes(label = freq), vjust = 1.6, color = "white", size = 3.5) +
|
||||||
|
theme_minimal() +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
ggarrange(p6, p7, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果觉得柱状图的顺序不是你想要的,可以对柱状图的顺序进行修改
|
||||||
|
```{r plot12}
|
||||||
|
data <- within(data, {
|
||||||
|
race <- factor(race, levels = c("White", "Black", "Hispanic", "Other or Multiple"))
|
||||||
|
})
|
||||||
|
ggplot(data, aes(x = race, y = freq)) +
|
||||||
|
geom_bar(stat = "identity", fill = "steelblue") +
|
||||||
|
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
ggplot2关于柱状图的相关做法有很详细的介绍,相关参考链接:
|
||||||
|
https://ggplot2.tidyverse.org/reference/geom_bar.html
|
||||||
|
|
||||||
|
## 饼状图
|
||||||
|
|
||||||
|
饼状图作为常用的数据可视化图形之一,广泛的使用在各个领域,能够很清楚展示数据的所占的百分比。
|
||||||
|
ggplot2并没有类似于geom_pie()这样的函数实现饼图的绘制,但ggplot2有一个理念,就是通过极坐标变换绘制饼图
|
||||||
|
|
||||||
|
饼图在ggplot2中就是通过极坐标变换获得,在绘制饼图之前需要绘制堆叠的条形图,通过将条形图进行极坐标变换后,就能实现饼图绘制了。
|
||||||
|
|
||||||
|
对h1n1问卷表中race数据进行数据展示
|
||||||
|
```{r plot13}
|
||||||
|
data <- count(h1n1_data["race"])
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity")
|
||||||
|
```
|
||||||
|
|
||||||
|
堆叠的条形图绘制完后,接下来就需要进行极坐标变换了,ggplot2中coord_polar()函数可以非常方便的实现极坐标变换。
|
||||||
|
```{r plot14}
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity") +
|
||||||
|
coord_polar(theta = "y")
|
||||||
|
```
|
||||||
|
|
||||||
|
看起来像饼图了,但是饼图周围还有多余的数字,如何清除呢?
|
||||||
|
这里的标签其实就是坐标轴的标签,可以通过labs()函数将其清除。
|
||||||
|
|
||||||
|
```{r plot15}
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity") +
|
||||||
|
coord_polar(theta = "y") +
|
||||||
|
labs(x = "", y = "", title = "") +
|
||||||
|
theme(axis.text = element_blank())
|
||||||
|
```
|
||||||
|
|
||||||
|
接下来就是显示各个所占的比例
|
||||||
|
第一种方法,将百分比直接显示在图例中,这种方式适合分类较多的情况。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
label_value <- paste("(", round(data$freq / sum(data$freq) * 100, 1), "%)", sep = "")
|
||||||
|
label_value
|
||||||
|
```
|
||||||
|
将计算的百分比和race匹配
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
label <- paste(data$race, label_value, sep = "")
|
||||||
|
label
|
||||||
|
```
|
||||||
|
|
||||||
|
接下来就是将这些百分比标签放到图例中
|
||||||
|
```{r }
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity") +
|
||||||
|
coord_polar(theta = "y") +
|
||||||
|
labs(x = "", y = "", title = "") +
|
||||||
|
theme(axis.text = element_blank()) +
|
||||||
|
scale_fill_discrete(labels = label)
|
||||||
|
```
|
||||||
|
|
||||||
|
看起来就很不错~
|
||||||
|
|
||||||
|
第二种方法,直接将百分比放到各自的饼区中。
|
||||||
|
|
||||||
|
首先是去掉饼图中的图例
|
||||||
|
```{r }
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity") +
|
||||||
|
coord_polar(theta = "y") +
|
||||||
|
labs(x = "", y = "", title = "") +
|
||||||
|
theme(axis.text = element_blank()) +
|
||||||
|
theme(legend.position = "none")
|
||||||
|
```
|
||||||
|
|
||||||
|
将标签放置在饼图中
|
||||||
|
```{r }
|
||||||
|
ggplot(data = data, aes(x = "", y = freq, fill = race)) +
|
||||||
|
geom_bar(stat = "identity", width = 1) +
|
||||||
|
coord_polar(theta = "y") +
|
||||||
|
labs(x = "", y = "", title = "") +
|
||||||
|
theme(axis.text = element_blank(), legend.position = "none") +
|
||||||
|
geom_text(aes(label = label), size = 3, position = position_stack(vjust = 0.5))
|
||||||
|
```
|
||||||
|
|
||||||
|
## 折线图
|
||||||
|
折线图作为反映数据变化的趋势是常用的数据可视化图形之一,在ggplot2中通过geom_line()这个函数进行绘制。
|
||||||
|
|
||||||
|
对波士顿房价中rad进行可视化展示,使用pylr包中的count对edcation进行计数统计
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
data <- count(boston_data["rad"])
|
||||||
|
data
|
||||||
|
```
|
||||||
|
|
||||||
|
把rad为24的数据去除掉
|
||||||
|
```{r }
|
||||||
|
data <- data[1:8, ]
|
||||||
|
ggplot(data, aes(x = rad, y = freq)) +
|
||||||
|
geom_line()
|
||||||
|
```
|
||||||
|
|
||||||
|
有时候我们需要在折线图上显示对应x轴的点数据,从而可以更加清晰的辨别原始数据,这特别适合数据比较稀疏的情况
|
||||||
|
```{r }
|
||||||
|
ggplot(data, aes(x = rad, y = freq)) +
|
||||||
|
geom_line() +
|
||||||
|
geom_point(size = 4)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
我们调整横坐标的显示刻度
|
||||||
|
```{r }
|
||||||
|
ggplot(data, aes(x = rad, y = freq)) +
|
||||||
|
geom_line() +
|
||||||
|
geom_point(size = 4) +
|
||||||
|
scale_x_continuous(breaks = c(1:8))
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以修改线的类型和颜色
|
||||||
|
```{r }
|
||||||
|
ggplot(data, aes(x = rad, y = freq)) +
|
||||||
|
geom_line(linetype = "dashed", color = "red") +
|
||||||
|
geom_point(size = 4) +
|
||||||
|
scale_x_continuous(breaks = c(1:8))
|
||||||
|
```
|
||||||
|
|
||||||
|
ggplt2关于折线图的相关做法的参考链接:
|
||||||
|
https://ggplot2.tidyverse.org/reference/geom_abline.html
|
||||||
|
|
||||||
|
|
||||||
|
## ggplot2扩展包主题
|
||||||
|
R语言中的ggplot2包里面的风格固定,在需要特殊的图形时,需要更改甚至自定义设置主题。
|
||||||
|
ggplot2内置了8种风格的主题
|
||||||
|
|
||||||
|
|主题函数|效果|
|
||||||
|
|---|---|
|
||||||
|
|theme_bw()|网格白色主题|
|
||||||
|
|theme_classic()|经典主题|
|
||||||
|
|theme_dark()| 暗色主题,可用于对比|
|
||||||
|
|theme_gray()|默认主题|
|
||||||
|
|theme_light()|浅色坐标带网格|
|
||||||
|
|theme_linedraw()|黑色网格线|
|
||||||
|
|theme_minimal()|极简主题|
|
||||||
|
|theme_void()|空白主题|
|
||||||
|
|
||||||
|
|
||||||
|
我们来试一试不同的主题
|
||||||
|
|
||||||
|
```{r}
|
||||||
|
p <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = rad)) +
|
||||||
|
geom_point()
|
||||||
|
p1 <- p + theme_bw() + labs(title = "网格白色主题") + theme(legend.position = "none")
|
||||||
|
p2 <- p + theme_classic() + labs(title = "经典主题") + theme(legend.position = "none")
|
||||||
|
p3 <- p + theme_dark() + labs(title = "暗色主题") + theme(legend.position = "none")
|
||||||
|
p4 <- p + theme_gray() + labs(title = "默认主题") + theme(legend.position = "none")
|
||||||
|
p5 <- p + theme_light() + labs(title = "浅色坐标带网格") + theme(legend.position = "none")
|
||||||
|
p6 <- p + theme_linedraw() + labs(title = "黑色网格线") + theme(legend.position = "none")
|
||||||
|
p7 <- p + theme_minimal() + labs(title = "极简主题") + theme(legend.position = "none")
|
||||||
|
p8 <- p + theme_void() + labs(title = "空白主题") + theme(legend.position = "none")
|
||||||
|
|
||||||
|
ggarrange(p1, p2, p3, p4, p5, p6, p7, p8, ncol = 4, nrow = 2, heights = 1.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
除了ggplot2自带的主题外,还有许多拓展主题包,比如:ggthemes、ggthemr
|
||||||
|
ggthemes在cran上发布,因此推荐使用这个
|
||||||
|
ggthemr 色彩很好看,因此推荐这个
|
||||||
|
|
||||||
|
ggthemes相关链接:https://github.com/jrnold/ggthemes
|
||||||
|
|
||||||
|
ggthemr相关链接:https://github.com/Mikata-Project/ggthemr
|
||||||
|
|
||||||
|
因为ggthemr没有上cran,因此需要通过github安装
|
||||||
|
```{r}
|
||||||
|
# devtools::install_github('Mikata-Project/ggthemr')
|
||||||
|
```
|
||||||
|
|
||||||
|
使用方法也是非常简单,这里用我比较喜欢的greyscale主题方案
|
||||||
|
```{r}
|
||||||
|
library(ggthemr)
|
||||||
|
ggthemr("greyscale")
|
||||||
|
p3 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = factor(rad))) +
|
||||||
|
geom_point()
|
||||||
|
p4 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = rad)) +
|
||||||
|
geom_point()
|
||||||
|
ggarrange(p3, p4, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
试一试light这个主题,配色非常的温柔
|
||||||
|
```{r}
|
||||||
|
library(ggthemr)
|
||||||
|
ggthemr("light")
|
||||||
|
p3 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = factor(rad))) +
|
||||||
|
geom_point()
|
||||||
|
p4 <- ggplot(data = boston_data, aes(x = lstat, y = medv, colour = rad)) +
|
||||||
|
geom_point()
|
||||||
|
ggarrange(p3, p4, nrow = 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
实战部分:
|
||||||
|
对提供的数据集我们可以试一试ggthemr中的不同主题,同时对波士顿房价进行其它的数据可视化的探索。
|
||||||
|
|
||||||
|
ggplot2是一个非常经典的数据可视化R包,内容非常丰富,由于篇幅的原因没办法将ggplot2中的各种方法全部讲述,因此选择了几个常见的图形进行相关的讲解,以期达到抛砖引玉的效果。如果对ggplot2感兴趣的同学,可以去官网进行更加详细的学习,也非常期待大家的数据可视化作品~
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
|
||||||
|
__牧小熊__
|
||||||
|
|
||||||
|
> 华中农业大学研究生,Datawhale成员, Datawhale优秀原创作者
|
||||||
|
> 知乎:https://www.zhihu.com/people/muxiaoxiong
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
|
@ -0,0 +1,224 @@
|
||||||
|
# 模型 {#task-05}
|
||||||
|
|
||||||
|
|
||||||
|
{width=100%}
|
||||||
|
|
||||||
|
Task05共计3个知识点,预计需学习2-3小时,请安排好学习任务。
|
||||||
|
|
||||||
|
## 前言
|
||||||
|
为了帮助大家更好的使用R语言进行建模分析,本章节将借助波士顿房价数据集来展示常见的模型。本章节学习的目的是帮助大家了解模型的适用范围以及如何建模,不会对模型的底层原理进行深入的研究。并且迫于时间和精力有限,本章节仅介绍部分模型的实现。
|
||||||
|
|
||||||
|
- 回归模型: 回归模型是一种有监督的、预测性的建模技术,它研究的是因变量和自变量之间的关系。
|
||||||
|
|
||||||
|
- 分类模型: 分类模型也是一种有监督的机器学习模型。与回归模型不同的是,其标签(因变量)通常是有限个数的定类变量。最常见的是二分类模型。
|
||||||
|
|
||||||
|
|
||||||
|
我们主要使用波士顿房价数据集来实现各种模型。因此我们使用2021作为种子值生成70%的数据作为训练集,其余数据作为测试集。下面展示来各个数据集的大小。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 导入BostonHousing数据
|
||||||
|
library(mlbench)
|
||||||
|
data(BostonHousing)
|
||||||
|
|
||||||
|
# 设置种子值,方便复现
|
||||||
|
set.seed(2021)
|
||||||
|
|
||||||
|
# 生成训练集的索引,用来划分训练集和测试集
|
||||||
|
train_index <- sample(dim(BostonHousing)[1], 0.7 * dim(BostonHousing)[1])
|
||||||
|
BostonHousingTrain <- BostonHousing[train_index, ]
|
||||||
|
BostonHousingTest <- BostonHousing[-train_index, ]
|
||||||
|
|
||||||
|
# 查看数据集的size
|
||||||
|
dim(BostonHousing)
|
||||||
|
dim(BostonHousingTrain)
|
||||||
|
dim(BostonHousingTest)
|
||||||
|
|
||||||
|
# 查看数据集包含的变量名称
|
||||||
|
names(BostonHousing)
|
||||||
|
```
|
||||||
|
|
||||||
|
##回归模型
|
||||||
|
回归模型有很多主要有Linear Regression、Logistic Regression、Polynomial Regression、Stepwise Regression、Ridge Regression、Lasso Regression、ElasticNet等。
|
||||||
|
|
||||||
|
本部分主要介绍有Linear Regression、以及Stepwise Regression三种回归模型的实现。
|
||||||
|
|
||||||
|
### Linear Regression
|
||||||
|
|
||||||
|
多元线性回归是一种最为基础的回归模型,其使用多个自变量和一个因变量利用OLS完成模型训练。下面我们将使用`medv`作为因变量,剩余变量作为自变量构建模型。
|
||||||
|
|
||||||
|
多元线性回归模型使用`lm()`命令, 其中`medv~.`是回归公式,`data=BostonHousingTrain`是回归数据。对回归公式的构建进行一些补充,`~`左侧表示因变量,`~`右侧表示自变量,多个自变量使用`+`依次叠加。这里右侧使用了`.`,该符号的含义是除左侧变量外所有的变量。因此,`medv~.`等价于`medv~crim + zn + indus + chas + nox + rm + age + dis + rad + tax + ptratio + b + medv`。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 构建模型,medv~.表示回归方程
|
||||||
|
lr_model <- lm(medv ~ ., data = BostonHousingTrain)
|
||||||
|
|
||||||
|
# summary输出模型汇总
|
||||||
|
summary(lr_model)
|
||||||
|
```
|
||||||
|
运用plot命令对模型进行诊断,各图含义参考 https://www.cnblogs.com/lafengdatascientist/p/5554167.html
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
plot(lr_model)
|
||||||
|
```
|
||||||
|
|
||||||
|
`predict`命令能够基于已经训练好的模型进行预测。
|
||||||
|
```{r }
|
||||||
|
# 根据模型对新数据进行预测
|
||||||
|
BostonHousingTest$lr_pred <- predict(lr_model, newdata = BostonHousingTest)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stepwise Regression
|
||||||
|
利用逐步回归分析可以对模型中的变量进行优化。R语言中的`step()`命令,是以AIC信息统计量为准则,通过选择最小的AIC信息统计量来达到提出或添加变量的目的。
|
||||||
|
|
||||||
|
对于逐步回归,一般有前向、后向、双向等逐步方式。本部分将基于已经实现的`lr_model`进行双向逐步回归。前向和后向回归只需要更改`step()`命令行中的`direstion`参数即可。具体内容参照 https://blog.csdn.net/qq_38204302/article/details/86567356
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# both逐步回归
|
||||||
|
step_model <- step(lr_model, direction = "both")
|
||||||
|
summary(step_model)
|
||||||
|
```
|
||||||
|
|
||||||
|
对于分类模型还有较为常用的Lasso Regression 和 Ridge Regression,我们将会在进阶教程中来更加具体的讲解模型知识。
|
||||||
|
|
||||||
|
## 分类模型
|
||||||
|
|
||||||
|
在进行分类模型前,我们需要构建分类标签。我们使用`medv`的中位数进行划分,其中1表示高房价,0表示低房价。通过这样的转化将原本的数值型变量转化为二元标签。并使用相同的种子值划分测试集和训练集。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 将连续变量转化成二分类变量
|
||||||
|
BostonHousing$medv <- as.factor(ifelse(BostonHousing$medv > median(BostonHousing$medv), 1, 0))
|
||||||
|
# 查看两种变量类别的数量
|
||||||
|
summary(BostonHousing$medv)
|
||||||
|
|
||||||
|
# 使用相同的种子值,复现训练集合测试集的划分
|
||||||
|
set.seed(2021)
|
||||||
|
train_index <- sample(dim(BostonHousing)[1], 0.7 * dim(BostonHousing)[1])
|
||||||
|
BostonHousingTrain <- BostonHousing[train_index, ]
|
||||||
|
BostonHousingTest <- BostonHousing[-train_index, ]
|
||||||
|
```
|
||||||
|
|
||||||
|
同时引入两个计算函数,用来计算AUC指标值。
|
||||||
|
```{r }
|
||||||
|
# 引入auc计算函数
|
||||||
|
library("ROCR")
|
||||||
|
calcAUC <- function(predcol, outcol) {
|
||||||
|
perf <- performance(prediction(predcol, outcol == 1), "auc")
|
||||||
|
as.numeric(perf@y.values)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Logistics Regression
|
||||||
|
|
||||||
|
逻辑回归是一种广义的线性回归分析模型,利用sigmode将线性回归结果转化成概率的形式。下面展示了利用`glm()`构建逻辑回归的过程。通过计算,训练集上的auc取值为0.9554211,测试集上的auc取值为0.9506969,说明模型效果整体不错。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 逻辑回归模型构建
|
||||||
|
lr_model <- glm(medv ~ ., data = BostonHousingTrain, family = binomial(link = "logit"))
|
||||||
|
summary(lr_model)
|
||||||
|
|
||||||
|
# 分别对训练集和测试集进行预测
|
||||||
|
lr_pred_train <- predict(lr_model, newdata = BostonHousingTrain, type = "response")
|
||||||
|
lr_pred_test <- predict(lr_model, newdata = BostonHousingTest, type = "response")
|
||||||
|
|
||||||
|
# 计算训练集和测试集的auc
|
||||||
|
calcAUC(lr_pred_train, BostonHousingTrain$medv)
|
||||||
|
calcAUC(lr_pred_test, BostonHousingTest$medv)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### KNN
|
||||||
|
|
||||||
|
KNN模型是一种简单易懂、可以用于分类和回归的模型。其中 K 表示在新样本点附近(距离)选取 K 个样本数据,通过在 K 个样本进行投票来判断新增样本的类型。
|
||||||
|
|
||||||
|
KNN模型较难的一点是确定超参数K,目前有一些指标和经验方法帮助确定最优K的取值。这部分内容会在后续进行讲解,这里使用k=25进行建模。KNN模型在测试集上的auc值为0.8686411,相比于逻辑回归效果较差。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 导入knn模型的包
|
||||||
|
library(kknn)
|
||||||
|
|
||||||
|
# 构建knn模型
|
||||||
|
knn <- kknn(medv ~ ., BostonHousingTrain, BostonHousingTest, k = 25)
|
||||||
|
|
||||||
|
# 预测并计算测试集上的auc取值
|
||||||
|
knn_pred_test <- predict(knn, newdata = BostonHousingTest)
|
||||||
|
calcAUC(as.numeric(knn_pred_test), BostonHousingTest$medv)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Decision Tree
|
||||||
|
|
||||||
|
决策树是一种基于树模型进行划分的分类模型,通过一系列if then决策规则的集合,将特征空间划分成有限个不相交的子区域,对于落在相同子区域的样本,决策树模型给出相同的预测值。下面构建了决策树的分类模型
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 导入包
|
||||||
|
library(tree)
|
||||||
|
|
||||||
|
# 构建决策树模型函数,medv~.是决策树公式,用来表明变量。
|
||||||
|
# summary输出模型汇总信息
|
||||||
|
dt_model <- tree(medv ~ ., BostonHousingTrain)
|
||||||
|
summary(dt_model)
|
||||||
|
|
||||||
|
# plot可以对树模型进行绘制,但可能会出现书分支过多的情况。
|
||||||
|
plot(dt_model)
|
||||||
|
text(dt_model)
|
||||||
|
```
|
||||||
|
|
||||||
|
在构建决策树模型的基础上,分别对训练集和测试集进行预测并计算auc取值。该模型在训练集上的auc取值为0.9281874,在测试集上的auc取值为0.8789199。训练集和测试集间存在抖动,说明该模型可能出现过拟合。我们需要引入剪枝的操作来降低模型的过拟合,这部分供同学们自学。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 预测
|
||||||
|
dt_pred_train <- predict(dt_model, newdata = BostonHousingTrain, type = "class")
|
||||||
|
dt_pred_test <- predict(dt_model, newdata = BostonHousingTest, type = "class")
|
||||||
|
|
||||||
|
# 计算auc取值
|
||||||
|
calcAUC(as.numeric(dt_pred_train), BostonHousingTrain$medv)
|
||||||
|
calcAUC(as.numeric(dt_pred_test), BostonHousingTest$medv)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Random Forest
|
||||||
|
|
||||||
|
随机森林是一个包含多个决策树的分类器,可以用于分类和回归问题。在解决分类问题是,其输出的类别是由个别树输出的类别的众数而定。相比于单树模型,随机森林具有更好地泛化能力。
|
||||||
|
|
||||||
|
使用`randomForest()`构建模型的过程中,可以通过`ntree`设定随机森林中包含的决策树数量。由于随机森林是对样本和变量的随机,因此可以通过`important`展示变量的重要性排序。通过模型预测,随机森林模型在训练集上的auc为0.9615975,在测试集上的auc为0.9247387。
|
||||||
|
|
||||||
|
```{r }
|
||||||
|
# 导入随机森林包
|
||||||
|
library(randomForest)
|
||||||
|
|
||||||
|
# 随机森林模型
|
||||||
|
rf_model <- randomForest(medv ~ ., BostonHousingTrain, ntree = 100, nodesize = 10, importance = T)
|
||||||
|
# 展示模型变量的重要性
|
||||||
|
importance(rf_model)
|
||||||
|
|
||||||
|
# 预测
|
||||||
|
rf_pred_train <- predict(rf_model, newdata = BostonHousingTrain, type = "class")
|
||||||
|
rf_pred_test <- predict(rf_model, newdata = BostonHousingTest, type = "class")
|
||||||
|
|
||||||
|
# 计算auc取值
|
||||||
|
calcAUC(as.numeric(rf_pred_train), BostonHousingTrain$medv)
|
||||||
|
calcAUC(as.numeric(rf_pred_test), BostonHousingTest$medv)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 思考与练习 {-}
|
||||||
|
|
||||||
|
本章节仅对模型进行简单介绍,更多详细、复杂的模型将在后面的进阶课程中展开。
|
||||||
|
|
||||||
|
学习完本章节,希望你能够尝试一些模型调优工作。如决策树剪枝,如尝试搜索KNN模型中最佳K取值等。
|
||||||
|
|
||||||
|
|
||||||
|
## 本章作者 {-}
|
||||||
|
|
||||||
|
__张晋__
|
||||||
|
|
||||||
|
> Datawhale成员,算法竞赛爱好者
|
||||||
|
> https://blog.csdn.net/weixin_44585839/
|
||||||
|
|
||||||
|
## 关于Datawhale {-}
|
||||||
|
|
||||||
|
Datawhale 是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。 本次数据挖掘路径学习,专题知识将在天池分享,详情可关注 Datawhale:
|
||||||
|
|
||||||
|
```{r, echo = FALSE}
|
||||||
|
insert_logo()
|
||||||
|
```
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |