【Python网络爬虫与数据可视化实战案例】近15年的中国国内生产总值

需求描述

  打开东方财富网的中国国内数据总值数据页,进入如下页面。
在这里插入图片描述
  现在需要把页面上的国内生产总值数据表爬取下来,写入CSV文件以持久化存储。在这之后,将CSV文件内的数据做成折线图,实现数据可视化。
  在爬取数据的过程中需要注意:数据表在该网页中分3页显示,我们需要在Python程序中实现换页的操作。

需求分析

  任意选取数据表中的一行,单击鼠标右键检查元素(这里我使用的是Firefox浏览器,不同浏览器可能略有不同)进入查看器。我们得到了下图所示的HTML结构。
在这里插入图片描述
  这里我们很容易发现,数据表中每一列的信息都存储在table标签内的tr标签中。那么,我们只需要定位到这个标签,对该标签内的文本进行提取,即可获得数据表中的数据。
  下一个问题是如何实现换页操作。我们点击数据表下方的“下一页”,观察网址的变化。
在这里插入图片描述)在这里插入图片描述
  网址只有“p=”后的数字发生了变化,其他的地方都没有改变。不难发现,“p=”后的数字所代表的就是当前的页数。

代码实现

1.数据爬取及持久化存储

  首先导入要用到的模块。

1
2
3
from bs4 import BeautifulSoup
import csv
import requests

  对网站发起请求,获取页面的数据。

1
2
3
4
5
6
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'}

for page in range(1, 4):
url = 'http://data.eastmoney.com/cjsj/grossdomesticproduct.aspx?p=' + str(page)
response = requests.get(url=url, headers=headers)
page_text = response.text

  使用bs4对爬取到的内容进行筛选,留下有用的数据。

1
2
3
4
5
6
7
8
9
10
soup = BeautifulSoup(page_text, 'html.parser')
table = soup.find('table', id='tb')
tr_list = table.find_all('tr')
tr_list.pop(-1)
for i in range(2):
tr_list.pop(0)

for tr in tr_list:
info = tr.text.replace(' ', '').replace('\r', '').replace('\n', ' ').lstrip().rstrip()
info_list = info.split()

  持久化存储,将数据写入CSV文件。
  首先写入标题行。

1
2
3
4
5
with open('中国国内生产总值.csv', 'w', newline='') as csv_out_file:
head_list = ['季度', '国内生产总值-绝对值(亿元)', '国内生产总值-同比增长', '第一产业-绝对值(亿元)', '第一产业-同比增长',
'第二产业-绝对值(亿元)', '第二产业-同比增长', '第三产业-绝对值(亿元)', '第三产业-同比增长']
filewriter = csv.writer(csv_out_file)
filewriter.writerow(head_list)

  写入其余的数据行。

1
2
with open...:
filewriter.writerow(info_list)

  重构代码后,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3

from bs4 import BeautifulSoup
import csv
import requests

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'}

with open('中国国内生产总值.csv', 'w', newline='') as csv_out_file:
head_list = ['季度', '国内生产总值-绝对值(亿元)', '国内生产总值-同比增长', '第一产业-绝对值(亿元)', '第一产业-同比增长',
'第二产业-绝对值(亿元)', '第二产业-同比增长', '第三产业-绝对值(亿元)', '第三产业-同比增长']
filewriter = csv.writer(csv_out_file)
filewriter.writerow(head_list)

for page in range(1, 4):
url = 'http://data.eastmoney.com/cjsj/grossdomesticproduct.aspx?p=' + str(page)
response = requests.get(url=url, headers=headers)
page_text = response.text

soup = BeautifulSoup(page_text, 'html.parser')
table = soup.find('table', id='tb')
tr_list = table.find_all('tr')
tr_list.pop(-1)
for i in range(2):
tr_list.pop(0)

for tr in tr_list:
info = tr.text.replace(' ', '').replace('\r', '').replace('\n', ' ').lstrip().rstrip()
info_list = info.split()
filewriter.writerow(info_list)
2.数据可视化

  这一部分比较简单。首先导入需要的模块。

1
2
import csv
from pyecharts import Line

  读取CSV文件中的数据,并对其进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Quarter = []
GDP = []
Primary_industry = []
Secondary_industry = []
Tertiary_industry = []

with open('中国国内生产总值.csv', 'r', newline='') as csv_in_file:
filereader = csv.reader(csv_in_file)
head = next(filereader)
for row_list in filereader:
Quarter.append(row_list[0])
gdp = round(eval(row_list[2][:-1]) / 100, 3)
GDP.append(gdp)
pri = round(eval(row_list[4][:-1]) / 100, 3)
Primary_industry.append(pri)
sec = round(eval(row_list[6][:-1]) / 100, 3)
Secondary_industry.append(sec)
ter = round(eval(row_list[8][:-1]) / 100, 3)
Tertiary_industry.append(ter)

  接下来是需要注意的一点。网站上的数据是根据时间排序的,2020年的数据在最前面,2006年的数据在最后面。即网站上的数据是根据时间“由近及远”排序的。在这里我们需要将处理好的数据列表进行逆序处理。

1
2
3
4
5
Quarter = Quarter[::-1]
GDP = GDP[::-1]
Primary_industry = Primary_industry[::-1]
Secondary_industry = Secondary_industry[::-1]
Tertiary_industry = Tertiary_industry[::-1]

  最后一步是数据可视化,利用pyecharts绘制折线图。

1
2
3
4
5
6
7
8
9
line = Line('中国国内生产总值同比增长率', '时间:2006年第1季度-2020年第1季度  数据来源:东方财富网', width=1280, height=720)
line.add('国内生产总值', Quarter, GDP, is_smooth=False, mark_point=['max'], mark_line=['average'], legend_pos='right')
line.add('第一产业', Quarter, Primary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.add('第二产业', Quarter, Secondary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.add('第三产业', Quarter, Tertiary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.render('中国国内生产总值.html')

  完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3

import csv
from pyecharts import Line

Quarter = []
GDP = []
Primary_industry = []
Secondary_industry = []
Tertiary_industry = []

with open('中国国内生产总值.csv', 'r', newline='') as csv_in_file:
filereader = csv.reader(csv_in_file)
head = next(filereader)
for row_list in filereader:
Quarter.append(row_list[0])
gdp = round(eval(row_list[2][:-1]) / 100, 3)
GDP.append(gdp)
pri = round(eval(row_list[4][:-1]) / 100, 3)
Primary_industry.append(pri)
sec = round(eval(row_list[6][:-1]) / 100, 3)
Secondary_industry.append(sec)
ter = round(eval(row_list[8][:-1]) / 100, 3)
Tertiary_industry.append(ter)

Quarter = Quarter[::-1]
GDP = GDP[::-1]
Primary_industry = Primary_industry[::-1]
Secondary_industry = Secondary_industry[::-1]
Tertiary_industry = Tertiary_industry[::-1]

line = Line('中国国内生产总值同比增长率', '时间:2006年第1季度-2020年第1季度 数据来源:东方财富网', width=1280, height=720)
line.add('国内生产总值', Quarter, GDP, is_smooth=False, mark_point=['max'], mark_line=['average'], legend_pos='right')
line.add('第一产业', Quarter, Primary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.add('第二产业', Quarter, Secondary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.add('第三产业', Quarter, Tertiary_industry, is_smooth=False, mark_point=['max'], mark_line=['average'],
legend_pos='right')
line.render('中国国内生产总值.html')

结果展示

  保存有近15年中国GDP数据的CSV文件如下图所示:
在这里插入图片描述
  绘制的折线图如下图所示(注:国内生产总值、第二产业、第三产业的平均值恰好都为0.09,故平均值线重合):
在这里插入图片描述

【Python网络爬虫与数据可视化实战案例】未来15天气温走势图

需求描述

  打开天气网,点击上方城市名称,再点击“15天天气”,进入如下页面。这里以天津为例。
在这里插入图片描述)在这里插入图片描述
在这里插入图片描述
  可以看到,这里是有未来15天的天气和气温状况的。我们要做的就是把每天的最高和最低气温爬取下来,并做成折线图的形式。

需求分析

  任意选取一天的气温,单击鼠标右键检查元素(这里我使用的是Firefox浏览器,不同浏览器可能略有不同)进入查看器。我们得到了下图所示的HTML结构。
在这里插入图片描述
  可以看出,每一天的天气信息都存储在class属性为table_daydiv标签之中,而气温信息则存储在div标签内部的class属性为templi标签之中。那么,我们只需要定位到这个标签,对该标签内的文本进行提取,即可获得这15天的最高气温和最低气温。

代码实现

  首先我们需要导入相关的模块。

1
2
3
4
import re
import requests
from pyecharts import Line
from bs4 import BeautifulSoup

  对网站发起请求,获取页面的数据。即进行UA伪装后对URL发起GET请求。

1
2
3
4
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'}
url = 'https://www.tianqi.com/tianjin/15/'
response = requests.get(url=url, headers=headers)
page_text = response.text

  筛选出有关气温信息的内容。
  在筛选出class属性为templi标签的文本之后,我使用了正则表达式来分离最高气温值与最低气温值,并保存到对应的列表中。以波浪线作为切入点,波浪线前的数字即为最低气温,波浪线后的数字即为最高气温。获取正则匹配的文本之后,对字符串进行切片,然后使用eval()函数将其转化为数字形式存放在列表中。

1
2
3
4
5
6
7
8
9
10
11
soup = BeautifulSoup(page_text, 'html.parser')
div_list = soup.find_all(class_='box_day')
table_day_list = div_list[0].find_all(class_='table_day')
low_temp = []
high_temp = []
for i in range(len(table_day_list)):
li = table_day_list[i].find(class_='temp').text
low = eval(re.search(r'(\d*)~', li).group()[:-1])
low_temp.append(low)
high = eval(re.search(r'~(\d*)', li).group()[1:])
high_temp.append(high)

  最后一步,使用pyecharts模块进行数据可视化。
  这里我偷了个懒,attr列表中的内容是可以在筛选数据的过程中一并获取的。写代码的时候为了方便就自己写了attr列表。这样写有个缺点,就是在做下一次的15天气温预报的时候需要手动更改attr列表中的元素。

1
2
3
4
5
6
attr = ['2020-04-16', '2020-04-17', '2020-04-18', '2020-04-19', '2020-04-20', '2020-04-21', '2020-04-22', '2020-04-23',
'2020-04-24', '2020-04-25', '2020-04-26', '2020-04-27', '2020-04-28', '2020-04-29', '2020-04-30']
line = Line('天津未来15天的气温走势', '日期:2020-04-16至2020-04-30')
line.add('日最低气温', attr, low_temp, is_smooth=False, mark_point=['min'], mark_line=['average'])
line.add('日最高气温', attr, high_temp, is_smooth=False, mark_point=['max'], mark_line=['average'])
line.render('天津未来15天气温走势图.html')

完整代码及结果展示

  该样例的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python3

import re
import requests
from pyecharts import Line
from bs4 import BeautifulSoup

# 爬取天气网天津2020-04-16至2020-04-30的最高和最低气温
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'}
url = 'https://www.tianqi.com/tianjin/15/'
response = requests.get(url=url, headers=headers)
page_text = response.text

# 筛选出所需数据
soup = BeautifulSoup(page_text, 'html.parser')
div_list = soup.find_all(class_='box_day')
table_day_list = div_list[0].find_all(class_='table_day')
low_temp = []
high_temp = []
for i in range(len(table_day_list)):
li = table_day_list[i].find(class_='temp').text
low = eval(re.search(r'(\d*)~', li).group()[:-1])
low_temp.append(low)
high = eval(re.search(r'~(\d*)', li).group()[1:])
high_temp.append(high)

# 数据可视化
attr = ['2020-04-16', '2020-04-17', '2020-04-18', '2020-04-19', '2020-04-20', '2020-04-21', '2020-04-22', '2020-04-23',
'2020-04-24', '2020-04-25', '2020-04-26', '2020-04-27', '2020-04-28', '2020-04-29', '2020-04-30']
line = Line('天津未来15天的气温走势', '日期:2020-04-16至2020-04-30')
line.add('日最低气温', attr, low_temp, is_smooth=False, mark_point=['min'], mark_line=['average'])
line.add('日最高气温', attr, high_temp, is_smooth=False, mark_point=['max'], mark_line=['average'])
line.render('天津未来15天气温走势图.html')

  运行这个程序可以得到一个HTML文件。使用浏览器打开,得到下图所示的结果:
在这里插入图片描述

【Python网络爬虫笔记】信息标记与提取方法

学习资源中国大学MOOC Python网络爬虫与信息提取 北京理工大学计算机学院 嵩天


信息标记的三种形式

  在学习信息标记的三种形式之前,我们有必要了解一下信息标记的作用。
  1.标记后的信息可形成信息组织结构,增加了信息维度。
  2.标记的结构与信息一样具有重要价值。
  3.标记后的信息可用于通信、存储或展示。
  4.标记后的信息更利于程序理解和运用。
  信息标记的三种形式分别为XML(Extensible Markup Language,可扩展标记语言)、JSON(JavaScript Object Notation,JS对象简谱)和YAML(YAML Ain’t Markup Language,在开发这种语言时,YAML 的意思其实是”Yet Another Markup Language”,但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名)。

XML (Extensible Markup Language)

  XML有以下三种写法:<name> ... </name><name />(空元素的缩写形式)和<!-- -->(注释书写形式)。
在这里插入图片描述
在这里插入图片描述

JSON (JavaScript Object Notation)

  JSON以有类型的键值对key: value来表示,形式上非常类似于Python的字典。JSON有以下三种书写形式。
  1.单值书写形式,例如"name": "西安邮电大学"。其中""为类型,"name"为键(key),"西安邮电大学"为值(value)。
  2.多值书写形式,例如"name": ["西安邮电大学", "陕西师范大学", "西北政法大学"]。即多值用[,]组织。
  3.键值对嵌套,例如:

1
2
3
4
"name": {
"oldName": "西安邮电学院",
"newName": "西安邮电大学"
}

即键值对嵌套用{,}组织。

YAML (YAML Ain’t Markup Language)

  YAML与JSON非常相似,它是以无类型的键值对key: value来表示。
  YAML的缩进表达所属关系,例如:

1
2
3
name:
oldName: 西安邮电学院
newName: 西安邮电大学

  YAML的-表达并列关系,例如:

1
2
3
4
name:
-西安邮电大学
-陕西师范大学
-西北政法大学

  YAML的|表达整块数据,#表示注释,例如:

1
2
text: |    # 学校介绍
西安邮电大学前身是1950年成立的陕西和甘肃两省邮电人员训练班及随后的西安邮电学校。1959年经国务院批准设立西安邮电学院,是国家在西北地区布局的唯一邮电通信类普通高校。2000年划归陕西省,实行中央与地方共建,以陕西省管理为主的管理体制。2012年3月,经教育部批准更名为西安邮电大学。

三种信息标记形式的比较

  下面是三种信息标记形式的实例:
在这里插入图片描述)在这里插入图片描述)在这里插入图片描述
信息标记形式|说明|适用于
——|——|——
XML|最早的通用信息标记语言,可扩展性好,但繁琐|Internet上的信息交互与传递
JSON|信息有类型,适合程序处理(JS),较XML简洁|移动应用云端和节点的信息通信,无注释
YAML|信息无类型,文本信息比例最高,可读性好|各类系统的配置文件,有注释易读

信息提取的一般方法

  方法一:完整解析信息的标记形式,再提取关键信息。这种方法需要标记解析器(例如bs4库的标签树遍历),优点是信息解析准确,缺点是提取过程繁琐,速度慢。
  方法二:无视标记形式,直接搜索关键信息。优点是提取过程简洁,缺点是提取结果准确性与信息内容相关。
  融合方法:结合形式解析与搜索方法,提取关键信息。需要标记解析器及文本查找函数。

基于bs4的HTML内容查找方法

  常用方法:<>.find_all()。它返回一个列表类型,存储查找的结果。
在这里插入图片描述
  <tag>(...)等价于<tag>.find_all(...)soup(...)等价于soup.find_all()
  下表是一些扩展方法:
在这里插入图片描述

【Python网络爬虫笔记】BeautifulSoup模块

学习资源中国大学MOOC Python网络爬虫与信息提取 北京理工大学计算机学院 嵩天


安装BeautifulSoup模块

  和requests模块一样,如果使用Anaconda 3的话,是自带BeautifulSoup模块的。如果没有BeautifulSoup模块的话,只需要以管理员身份打开cmd,输入命令pip install beautifulsoup4即可安装。

BeautifulSoup的基本元素

  BeautifulSoup是解析、遍历、维护“标签树”的功能库。
在这里插入图片描述)在这里插入图片描述
  要想引用BeautifulSoup模块,我们需要以下面的语句来导入:

1
2
from bs4 import BeautifulSoup
import bs4

即主要是用BeautifulSoup类。
在这里插入图片描述
  下表是对BeautifulSoup库解析器的说明:
解析器|使用方法|条件
——|——|——
bs4的HTML解析器|BeautifulSoup(mk, ‘html.parser’)|安装bs4库
lxml的HTML解析器|BeautifulSoup(mk, ‘lxml’)|pip install lxml
lxml的XML解析器|BeautifulSoup(mk, ‘xml’)|pip install lxml
html5lib的解析器|BeautifulSoup(mk, ‘html5lib’)|pip install html5lib

  下面来简单说明BeautifulSoup类的基本元素:
基本元素|说明
——|——
Tag|标签,最基本的信息组织单元,分别用<>和</>标明开头和结尾
Name|标签的名字,<p>…</p>的名字是“p”,格式:<tag>.name
Attributes|标签的属性,字典形式组织,格式:<tag>.attrs
NavigableString|标签内非属性字符串,<>…</>中字符串,格式:<tag>.string
Comment|标签内字符串的注释部分,一种特殊的Comment类型

在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述

基于bs4的HTML内容遍历方法

  HTML的基本格式是:<>…</>构成了所属关系,形成了标签的树形结构,如下图所示:
在这里插入图片描述
  对标签树的遍历可以分为三种:下行遍历、上行遍历和平行遍历。
在这里插入图片描述
标签树的下行遍历
属性|说明
——|——
.contents|子节点的列表,将<tag>所有儿子节点存入列表
.children|子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
.descendants|子孙节点的迭代类型,包含所有子孙节点,用于循环遍历

  BeautifulSoup类型是标签树的根节点。

标签树的上行遍历
属性|说明
——|——
.parent|节点的父亲标签
.parents|节点先辈标签的迭代类型,用于循环遍历先辈节点

标签树的平行遍历
属性|说明
——|——
.next_sibling|返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling|返回按照HTML文本顺序的上一个平行节点标签
.next_siblings|迭代类型,返回按照HTML文本顺序的后续所有平行节点标签
.previous_siblings|迭代类型,返回按照HTML文本顺序的前续所有平行节点标签

在这里插入图片描述)在这里插入图片描述

基于bs4的HTML格式输出

prettify()方法
  prettify()为HTML文本<>及其内容增加\n。该方法可用于标签,语法为<tag>.prettify()
在这里插入图片描述
bs4的编码
  bs4将任何HTML输入都变成utf-8编码。
在这里插入图片描述

'【Python网络爬虫笔记】requests模块'

学习资源中国大学MOOC Python网络爬虫与信息提取 北京理工大学计算机学院 嵩天


安装requests模块

  我使用的是Anaconda 3,其中自带了requests模块供我们使用。如果没有requests模块的话,只需要以管理员身份打开cmd,输入命令pip install requests即可。

requests模块的7个主要方法

方法 说明
requests.request() 构造一个请求,是支撑以下各方法的基础方法
requests.get() 获取HTML网页的主要方法,对应于HTTP的GET
requests.head() 获取HTML网页头信息,对应于HTTP的HEAD
requests.post() 向HTML网页提交POST请求,对应于HTTP的POST
requests.put() 向HTML网页提交PUT请求,对应于HTTP的PUT
requests.patch() 向HTML网页提交局部修改请求,对应于HTTP的PATCH
requests.delete() 向HTML网页提交删除请求,对应于HTTP的DELETE

1.requests.request()
语法
requests.request(method, url, **kwargs)
参数说明
(1)method:请求方式,包括GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS。
(2)url:拟获取页面的URL链接。
(3)**kwargs:控制访问的参数,共13个,均为可选项。

**kwargs参数 说明
params 字典或字节序列,作为参数增加到URL中
data 字典、字节序列或文件对象,作为Request的内容
json JSON格式的数据,作为Request的内容
headers 字典,HTTP定制头
cookies 字典或CookieJar,Request中的cookie
auth 元组,支持HTTP认证功能
files 字典,传输文件
timeout 设定超时时间(单位:秒)
proxies 字典,设定访问代理服务器,可以增加登录认证
allow_redirects True(默认)或False,重定向开关
stream True(默认)或False,获取内容立即下载开关
verify True(默认)或False,认定SSL证书开关
cert 本地SSL证书路径

2.requests.get()
语法
requests.get(url, params=None, **kwargs)
参数说明
(1)url:拟获取页面的URL链接。
(2)params:URL中的额外参数,字典或字节流格式。
(3)**kwargs:12个控制访问的参数。

3.requests.head()
语法
requests.head(url, **kwargs)
参数说明
(1)url:拟获取页面的URL链接。
(2)**kwargs:12个控制访问的参数。

4.requests.post()
语法
requests.post(url, data=None, json=None, **kwargs)
参数说明
(1)url:拟更新页面的URL链接。
(2)data:字典、字节序列或文件,Request的内容。
(3)json:JSON格式的数据,Request的内容。
(4)**kwargs:12个控制访问的参数。

5.requests.put()
语法
requests.put(url, data=None, **kwargs)
参数说明
(1)url:拟更新页面的URL链接。
(2)data:字典、字节序列或文件,Request的内容。
(3)**kwargs:12个控制访问的参数。

6.requests.patch()
语法
requests.patch(url, data=None, **kwargs)
参数说明
(1)url:拟更新页面的URL链接。
(2)data:字典、字节序列或文件,Request的内容。
(3)**kwargs:12个控制访问的参数。

7.requests.delete()
语法
requests.delete(url, **kwargs)
参数说明
(1)url:拟删除页面的URL链接。
(2)**kwargs:12个控制访问的参数。

requests模块的2个重要对象

在这里插入图片描述
  Response对象包含服务器返回的所有信息,也包含请求的Request信息。下表是Response对象的属性:

属性 说明
r.status_code HTTP请求的返回状态,200表示连接成功,404表示失败
r.text HTTP响应内容的字符串形式,即url对应的页面内容
r.encoding 从HTTP header中猜测的响应内容编码方式
r.appartment_encoding 从内容中分析出的响应内容编码方式(备选编码方式)
r.content HTTP响应内容的二进制形式

  下面针对Response的编码进行说明。
  r.encoding:如果header中不存在charset,则认为编码为ISO-8859-1,r.text根据r.coding显示网页内容。
  r.apparent_encoding:根据网页内容分析出的编码方式,可以看作是r.encoding的备选。

爬取网页的通用代码框架

  首先我们要理解requests模块的异常。网络连接是有风险的,所以异常处理很重要。下表是对requests模块的异常的说明:
异常|说明
——|——
requests.ConnectionError|网络连接错误异常,如DNS查询失败、拒绝连接等
requests.HTTPError|HTTP错误异常
requests.URLRequired|URL缺失异常
requests.TooManyRedirects|超过最大重定向次数,产生重定向异常
requests.ConnectTimeout|连接远程服务器超时异常
requests.Timeout|请求URL超时,产生超时异常

  下面是爬取网页的通用代码框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests


def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status() # 如果状态不是200,引发HTTPError异常
r.encoding = r.apparent_encoding
return r.text
except:
return '产生异常'


if __name__ == '__main__':
url = 'http://www.baidu.com'
print(getHTMLText(url))

HTTP协议

  HTTP是Hypertext Transfer Protocol的缩写,中文名称为超文本传输协议。URL是通过HTTP协议存取资源的Internet路径,一个URL对应一个数据资源。
  下表是HTTP协议对资源的操作:
方法|说明
——|——
GET|请求获取URL位置的资源
HEAD|请求获取URL位置资源的响应消息报告,即获得该资源的头部信息
POST|请求向URL位置的资源后附加新的数据
PUT|请求向URL位置存储一个资源,覆盖原URL位置的资源
PATCH|请求局部更新URL位置的资源,即改变该处资源的部分内容
DELETE|请求删除URL位置存储的资源

  通过URL和命令管理资源,操作独立无状态,网络通道及服务器成为了黑盒子。
  下面来讨论PATCH和PUT的区别。我们假设URL位置有一组数据UserInfo,包括UserID、UserName等20个字段。我们的需求是用户修改UserName,其他不变。如果采用PATCH,则仅向URL提交UserName的局部更新请求。如果采用PUT,则必须将所有字段一并提交到URL,未提交字段被删除。PATCH的最主要好处是节省网络带宽。

实验室20200314数据处理任务总结

如果你想获取数据集和代码,请点这里


任务描述

基本要求
把样本文件中的数据按下面的样例格式写入输出文件。需要注意的是,输入文件中所有的暂无数据均按暂无写入输出文件,所有的None均按NULL写入输出文件。样本文件中共240条数据。
输入文件样例
样本文件ori_data的数据样例如下:

1
2
Tue Mar 19 16:23:02 2019,杭州租房网 >  萧山租房 >  钱江世纪城租房 >   佳境天城人合苑租房  , 合租·佳境天城人合苑4室1厅, 2430元/月(季付价), 公寓 独立卫生间 近地铁 押一付一 随时看房 , 合租 4室1厅2卫 16㎡ 朝南  房屋信息  基本信息 发布:12天前 入住:随时入住   租期:暂无数据 看房:随时可看   楼层:5/18层 电梯:暂无数据   车位:暂无数据 用水:暂无数据   用电:暂无数据 燃气:暂无数据   采暖:暂无数据  ,None, 地址和交通距离地铁2号线-振宁路329m, end 
Tue Mar 19 16:23:02 2019,杭州租房 > 滨江租房 > 浦沿租房 > 朗诗寓·东信大道店租房 , None, 朗诗寓·东信大道店 2550元/月起 ,None, None, None, None, 地址和交通, end

输出文件样例
输出文件dea_data的数据样例如下:

1
2
Tue Mar 19 杭州  萧山  钱江世纪城  佳境天城人合苑 2430元/月 16㎡ 4室 2卫 1厅 朝南 5/18层 NULL 随时入住 暂无 暂无 暂无 暂无 暂无 暂无
Tue Mar 19 杭州 滨江 浦沿 朗诗寓·东信大道店 2550元/月 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL

任务分析

  首先我注意到的一点是,样本文件是Unix(LF)格式的,可能需要考虑一下文件编码格式的问题。
在这里插入图片描述
  其次,不难发现每一行的数据之间都是以逗号为分隔符,这是在提醒我们使用Python的csv模块来进行处理。
  在处理之前,应当先清洗掉那些没有用处的数据,这样可以使后面的处理工作条理更加清晰。
  输出样例是有特定的格式的,我们可以导入Python的re模块,利用正则表达式来进行数据的筛选。

源代码及简单说明

  关于程序的说明已经通过注释的形式写在了下面的代码里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env python3

import csv
import re

with open('ori_data', mode='r', encoding='utf-8', newline='') as csv_in_file:
with open('dea_data_output', mode='w', newline='') as out_file:
filereader = csv.reader(csv_in_file)
for row_list in filereader:
# 创建要写入输出文件的输出字符串
out_str = ''

# 删去共有的无用信息
row_list.pop()
row_list.pop(2)
row_list.pop(3)

# 修改前三列的数据
row_list[0] = re.search(r'Tue Mar 19', row_list[0]).group()
row_list[1] = ''.join(row_list[1].split())
row_list[1] = ''.join(row_list[1].replace('>', '').replace('租房', ' ').replace('网', '').rstrip())
row_list[2] = re.search(r'(\d*)元/月', row_list[2]).group()

# 根据列表长度删去各自的无用信息
if len(row_list) == 9:
row_list.pop()
row_list.pop()
row_list.pop()
row_list.pop()
row_list.pop()
elif len(row_list) == 8:
row_list.pop()
row_list.pop()
row_list.pop()
row_list.pop()
elif len(row_list) == 7:
row_list.pop()
row_list.pop()
row_list.pop()
elif len(row_list) == 6:
row_list.pop()
row_list.pop()

# 修改最后一列的信息
if row_list[-1].strip() == 'None':
row_list[-1] = 'NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL'
else:
s = ''
# 房屋面积
s += re.search(r'(\d*㎡)', row_list[-1]).group()
s += ' '
# 室、卫、厅
s += re.search(r'(\d*室)', row_list[-1]).group()
s += ' '
s += re.search(r'(\d*卫)', row_list[-1]).group()
s += ' '
s += re.search(r'(\d*厅)', row_list[-1]).group()
s += ' '
# 房屋朝向
s += re.search(r'朝\w', row_list[-1]).group()
s += ' '
# 所在楼层
if re.search(r'(\d*/\d*层)', row_list[-1]) is None:
s += 'NULL'
else:
if re.search(r'(\d*/\d*层)', row_list[-1]).group()[0] == '/':
s += 'NULL'
else:
s += re.search(r'(\d*/\d*层)', row_list[-1]).group()
s += ' '
# 租期
if re.search(r'(\d*~\d*年)', row_list[-1]) is None:
s += 'NULL'
else:
s += re.search(r'(\d*~\d*年)', row_list[-1]).group()
s += ' '
# 入住
if re.search(r'随时入住', row_list[-1]) is None:
s += 'NULL'
else:
s += re.search(r'随时入住', row_list[-1]).group()
s += ' '
# 电梯
if re.search(r'电梯:有', row_list[-1]):
s += '有 '
elif re.search(r'电梯:无', row_list[-1]):
s += '无 '
elif re.search(r'电梯:暂无数据', row_list[-1]):
s += '暂无 '
# 车位
if re.search(r'车位:免费', row_list[-1]):
s += '免费 '
elif re.search(r'车位:租用', row_list[-1]):
s += '租用 '
elif re.search(r'车位:暂无数据', row_list[-1]):
s += '暂无 '
# 用水
if re.search(r'用水:民水', row_list[-1]):
s += '民水 '
elif re.search(r'用水:商水', row_list[-1]):
s += '商水 '
elif re.search(r'用水:暂无数据', row_list[-1]):
s += '暂无 '
# 用电
if re.search(r'用电:民电', row_list[-1]):
s += '民电 '
elif re.search(r'用电:商电', row_list[-1]):
s += '商电 '
elif re.search(r'用电:暂无数据', row_list[-1]):
s += '暂无 '
# 燃气
if re.search(r'燃气:有', row_list[-1]):
s += '有 '
elif re.search(r'燃气:无', row_list[-1]):
s += '无 '
elif re.search(r'燃气:暂无数据', row_list[-1]):
s += '暂无 '
# 采暖
if re.search(r'采暖:自采暖', row_list[-1]):
s += '自采暖'
elif re.search(r'采暖:集中供暖', row_list[-1]):
s += '集中'
elif re.search(r'采暖:暂无数据', row_list[-1]):
s += '暂无'
row_list[-1] = s

# 向输出字符串内添加信息
out_str += row_list[0] + ' ' + row_list[1] + ' ' + row_list[2] + ' ' + row_list[3] + '\n'

# 写入文件
out_file.write(out_str)

输出结果

  左侧的dea_data文件是原有的输出样例,右侧的dea_data_output文件是通过运行上面的程序得到的输出文件。
在这里插入图片描述

Python数据分析基础之按计划自动运行脚本(Windows系统)

前言

  我打算调整一下学习的路线。我的上一篇博客写的是数据分析基础的描述性统计与建模,但是由于我个人对于统计学知识的欠缺,在学习时看着书上的讲解经常会感觉一头雾水,仿佛在看天书一般。所以我决定先把书上最后一块内容——按计划自动运行脚本学完。
  我的电脑是Windows 10操作系统的。我也没用过macOS和Unix系统的电脑,所以暂时只学习基于Windows操作系统的自动运行脚本。

任务计划程序

  在Windows系统和macOS系统中,都有可以定期运行脚本和其他可执行文件的程序。微软称这个程序为Task Scheduler0(任务计划程序);在Unix系统和macOS系统中,这样的程序成为corn(定时任务)。
  我们选择之前在应用程序那一章写过的3parse_text_file.py这个脚本作为例子。下图是它的存储路径。
在这里插入图片描述
  接下来,打开控制面板,找到系统和安全→管理工具,然后双击任务计划程序
在这里插入图片描述)在这里插入图片描述  在操作菜单中点击创建基本任务,并在创建基本任务向导中填写名称和描述,然后点击下一步
在这里插入图片描述
  接下来进入任务触发器界面。我们设置为每月运行一次脚本,然后点击下一步
在这里插入图片描述
  之后,任务向导会转到每月标签页。我们在设置选择所有的月份,在设置最后一个,把开始时间设置为9:00:00,并勾选跨时区同步复选框。完成这些之后,点击下一步
在这里插入图片描述
  下面转到了操作标签页。我们选择启动程序,然后点击下一步
在这里插入图片描述
  然后我们转到了启动程序标签页。我们把py文件的路径填写在程序或脚本栏,然后在添加参数(可选)栏填写输入文件和输出文件的路径。填写完毕,点击下一步
在这里插入图片描述
  最后我们来到完成标签页。我们需要核实一下信息是否正确,如果核对无误,点击完成
在这里插入图片描述
  我们返回任务计划程序主界面,点击任务计划程序库,我们可以看到刚刚创建的任务。
在这里插入图片描述

Python数据分析基础之描述性统计与建模(1)

葡萄酒质量数据集

  葡萄酒质量数据集包括两个文件——红葡萄酒文件和白葡萄酒文件。红葡萄酒文件中包含1599条观测,白葡萄酒文件包含4898条观测。两个文件中都有1个输出变量和11个输入变量。输出变量是酒的质量,是一个从0(低质量)到10(高质量)的评分。输入变量是葡萄酒的物理化学成分和特性,包括非挥发性酸、挥发性酸、柠檬酸、残余糖分、氯化物、游离二氧化硫、总二氧化硫、密度、pH值、硫酸盐和酒精含量。
  我们把这两个数据集合成一个数据集,保存在文件winequality-both.csv中。这个数据集中应该包括一个标题行和6497条。另外,还应该再添加一列,用来区分这行数据是红葡萄酒还是白葡萄酒的数据。
  想要下载数据集?点我
在这里插入图片描述

描述性统计

  我们来对这个数据集进行分析。首先,我们要计算出每列的总体描述性统计量、质量列中的唯一值以及和这个唯一值对应的观测数量。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.formula.api import ols, glm

# 将数据集读入到pandas数据框中
wine = pd.read_csv('winequality-both.csv', sep=',', header=0)
wine.columns = wine.columns.str.replace(' ', '_')
print(wine.head())

# 显示所有变量的描述性统计量
print(wine.describe())

# 找出唯一值
print(sorted(wine.quality.unique()))

# 计算值的频率
print(wine.quality.value_counts())

  在使用pandas模块的read_csv()方法将文本文件读入一个pandas数据框之后,我们使用head()函数检查一下标题行和前五行数据,确保数据被正确加载。第17行代码使用pandas的describe()函数打印出数据集中每个数值型变量的摘要统计量。第20行代码使用unique()识别出质量列中的唯一值,并以升序打印在屏幕上。最后,第23行代码计算出质量列中每个唯一值在数据集中出现的次数,并把它们以降序打印到屏幕上。
  这段代码的运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  type  fixed_acidity  volatile_acidity  ...  sulphates  alcohol  quality
0 red 7.4 0.70 ... 0.56 9.4 5
1 red 7.8 0.88 ... 0.68 9.8 5
2 red 7.8 0.76 ... 0.65 9.8 5
3 red 11.2 0.28 ... 0.58 9.8 6
4 red 7.4 0.70 ... 0.56 9.4 5

[5 rows x 13 columns]
fixed_acidity volatile_acidity ... alcohol quality
count 6497.000000 6497.000000 ... 6497.000000 6497.000000
mean 7.215307 0.339666 ... 10.491801 5.818378
std 1.296434 0.164636 ... 1.192712 0.873255
min 3.800000 0.080000 ... 8.000000 3.000000
25% 6.400000 0.230000 ... 9.500000 5.000000
50% 7.000000 0.290000 ... 10.300000 6.000000
75% 7.700000 0.400000 ... 11.300000 6.000000
max 15.900000 1.580000 ... 14.900000 9.000000

[8 rows x 12 columns]
[3, 4, 5, 6, 7, 8, 9]
6 2836
5 2138
7 1079
4 216
8 193
3 30
9 5
Name: quality, dtype: int64

  输出显示,质量评分中有6497个观测,评分范围从3到9,平均质量评分为5.8,标准差为0.87;质量列中的唯一值是3、4、5、6、7、8和9;有2836个观测的质量评分为6,2138个观测的质量评分为5,1079个观测的质量评分为7,216个观测的质量评分为4,193个观测的质量评分为8,30个观测的质量评分为3,5个观测的质量评分为9。

分组、直方图与t检验

  下面我们分别分析红葡萄酒数据和白葡萄酒数据,看看统计量是否会保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...

# 按照葡萄酒类型显示质量的描述性统计量
print(wine.groupby('type')[['quality']].describe().unstack('type'))

# 按照葡萄酒类型显示质量的特定分位数值
print(wine.groupby('type')[['quality']].quantile([0.25, 0.75]).unstack('type'))

# 按照葡萄酒类型查看质量分布
red_wine = wine.loc[wine['type'] == 'red', 'quality']
white_wine = wine.loc[wine['type'] == 'white', 'quality']
sns.set_style("dark")
print(sns.distplot(red_wine, norm_hist=True, kde=False, color="red", label="Red wine"))
print(sns.distplot(white_wine, norm_hist=True, kde=False, color="white", label="White wine"))
sns.utils.axlabel("Quality Score", "Density")
plt.title("Distribution of Quality by Wine Type")
plt.legend()
plt.show()

# 检验红葡萄酒和白葡萄酒的平均质量是否有所不同
print(wine.groupby(['type'])[['quality']].agg(['std']))
tstat, pvalue, df = sm.stats.ttest_ind(red_wine, white_wine)
print('tstat: %.3f pvalue: %.4f' % (tstat, pvalue))

  groupby()函数使用type列中的两个值将数据分为两组。方括号可以生成一个列表,列表中的元素是用来生成输出的列。在这里我们只对质量列应用describe()函数。这些命令的结果就是生成一列统计量,来自红葡萄酒数据的计算结果和白葡萄酒数据的计算结果是相互垂直地堆叠在一起的。unstack()函数将结果重新排列,这样红葡萄酒和白葡萄酒的统计量就会显示在并排的两列中。quantile()函数对质量列计算第25百分位数和第75百分位数。
  接下来,我们使用seaborn创建直方图,红条表示红葡萄酒,白条表示白葡萄酒。因为白葡萄酒数据比红葡萄酒多,所以直方图显示密度分布而不是频率分布。
  最后,进行一下t检验,判断红葡萄酒和白葡萄酒的平均评分是否有区别。在这里我们想知道红葡萄酒和白葡萄酒评分的标准差是否相同,所以在t检验中可以使用合并方差。
  下面是这段代码的运行结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
                type 
quality count red 1599.000000
white 4898.000000
mean red 5.636023
white 5.877909
std red 0.807569
white 0.885639
min red 3.000000
white 3.000000
25% red 5.000000
white 5.000000
50% red 6.000000
white 6.000000
75% red 6.000000
white 6.000000
max red 8.000000
white 9.000000
dtype: float64
quality
type red white
0.25 5.0 5.0
0.75 6.0 6.0
AxesSubplot(0.125,0.11;0.775x0.77)
AxesSubplot(0.125,0.11;0.775x0.77)
quality
std
type
red 0.807569
white 0.885639
tstat: -9.686 pvalue: 0.0000

在这里插入图片描述
  由绘制的密度分布直方图和输出结果可以得出结论:两种葡萄酒的评分都近似正态分布;t检验统计量为-9.686,p值为0.000,这说明白葡萄酒的平均质量评分在统计意义上大于红葡萄酒的平均质量评分。

成对变量之间的关系和相关性

  前面已经检查了输出变量,下面简单研究一下输入变量。让我们计算一下输入变量两两之间的相关性,并为一些输入变量创建带有回归直线的散点图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...

# 计算所有变量的相关矩阵
print(wine.corr())


# 从红葡萄酒和白葡萄酒的数据中取出一个“小”样本来进行绘图
def take_sample(data_frame, replace=False, n=200):
return data_frame.loc[np.random.choice(data_frame.index, replace=replace, size=n)]


reds_sample = take_sample(wine.loc[wine['type'] == 'red', :])
whites_sample = take_sample(wine.loc[wine['type'] == 'white', :])
wine_sample = pd.concat([reds_sample, whites_sample])
wine['in_sample'] = np.where(wine.index.isin(wine_sample.index), 1., 0.)
print(pd.crosstab(wine.in_sample, wine.type, margins=True))

# 查看成对变量之间的关系
sns.set_style("dark")
g = sns.pairplot(wine_sample, kind='reg', plot_kws={"ci": False, "x_jitter": 0.25, "y_jitter": 0.25}, hue='type',
diag_kind='hist', diag_kws={"bins": 10, "alpha": 1.0}, palette=dict(red="red", white="white"),
markers=["o", "s"], vars=['quality', 'alcohol', 'residual_sugar'])
print(g)
plt.suptitle('Histograms and Scatter Plots of Quality, Alcohol, and Residual Sugar', fontsize=14,
horizontalalignment='center', verticalalignment='top', x=0.5, y=0.999)
plt.show()

  corr()函数可以计算出数据集中所有变量两两之间的线性相关性。
  数据集中有6000多个点,所以如果将它们都画在统计图中,就很难分辨出清楚的点。我们定义了一个函数take_sample(),用来抽取在统计图中使用的样本点。这个函数使用pandas数据框索引和numpy的random.choice()函数随机选择一个行的子集。我们是用这个函数对红葡萄酒和白葡萄酒分别进行抽样,并将抽样所得的两个数据框连接成一个数据框。然后,在wine数据框中创建一个新列in_sample,并使用numpy的where()函数和pandas的isin()函数对这个新列进行填充,填充的值根据此行的索引值是否在抽样数据的索引值中分别设为1和0.最后,我们使用pandas的crosstab()函数来确认in_sample列中包含400个1(200条红葡萄酒数据和200条白葡萄酒数据)和6097个0。
  seaborn的pairplot()函数可以创建一个统计图矩阵。主对角线上的图以直方图或密度图的形式显示了每个变量的单变量分布,对角线之外的图以散点图的形式显示了每两个变量之间的双变量分布,散点图中可以有回归直线,也可以没有。因为质量评分都是整数,所以加上一点振动可以更容易看出数据在何处集中。
  这段代码的运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
                      fixed_acidity  volatile_acidity  ...   alcohol   quality
fixed_acidity 1.000000 0.219008 ... -0.095452 -0.076743
volatile_acidity 0.219008 1.000000 ... -0.037640 -0.265699
citric_acid 0.324436 -0.377981 ... -0.010493 0.085532
residual_sugar -0.111981 -0.196011 ... -0.359415 -0.036980
chlorides 0.298195 0.377124 ... -0.256916 -0.200666
free_sulfur_dioxide -0.282735 -0.352557 ... -0.179838 0.055463
total_sulfur_dioxide -0.329054 -0.414476 ... -0.265740 -0.041385
density 0.458910 0.271296 ... -0.686745 -0.305858
pH -0.252700 0.261454 ... 0.121248 0.019506
sulphates 0.299568 0.225984 ... -0.003029 0.038485
alcohol -0.095452 -0.037640 ... 1.000000 0.444319
quality -0.076743 -0.265699 ... 0.444319 1.000000

[12 rows x 12 columns]
type red white All
in_sample
0.0 1399 4698 6097
1.0 200 200 400
All 1599 4898 6497
<seaborn.axisgrid.PairGrid object at 0x00000249B4D11E48>

在这里插入图片描述
  根据相关系数的符号,从输出中可以知道酒精含量、硫酸盐、pH值、游离二氧化硫和柠檬酸这些指标与质量是正相关的,相反,非挥发性酸、挥发性酸、残余糖分、氯化物、总二氧化硫和密度这些指标与质量是负相关的。
  从统计图中可以看出,对于红葡萄酒和白葡萄酒来说,酒精含量的均值和标准差是大致相同的,但是,白葡萄酒残余糖分的均值和标准差却大于红葡萄酒残余糖分的均值和标准差。从回归直线可以看出,对于两种类型的葡萄酒,酒精含量增加时,质量评分也随之提高,相反,残余糖分增加时,质量评分则随之降低。这两个变量对白葡萄酒的影响都要大于对红葡萄酒的影响。

使用最小二乘估计进行线性回归

  相关系数和两两变量之间的统计图有助于对两个变量之间的关系进行量化和可视化,但是它们不能测量出每个自变量在其他自变量不变时与因变量之间的关系。线性回归可以解决这个问题。
  线性回归模型如下:$$y_i\sim N(\mu_i,\sigma^2),$$$$\mu_i=\beta_0+\beta_1x_{i1}+\beta_2x_{i2}+…+\beta_px_{ip}$$对于$i=1,2,…,n$个观测和$p$个自变量。
  这个模型表示观测$y_i$服从均值为$\mu_i$方差为$\sigma^2$的正态分布(高斯分布),其中$\mu_i$依赖于自变量,$\sigma^2$为一个常数。也就是说,给定了自变量的值之后,我们就可以得到一个具体的质量评分,但在另一天,给定同样的自变量值,我们可能会得到一个和前面不同的质量评分。但是,经过很多天自变量取同样的值(也就是一个很长的周期),质量评分会落在$\mu_i±\sigma$这个范围内。
  下面我们使用statsmodel包来进行线性回归:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my_formula = 'quality ~ alcohol + chlorides + citric_acid + density +fixed_acidity + free_sulfur_dioxide + pH' \
'+ residual_sugar + sulphates + total_sulfur_dioxide + volatile_acidity'

lm = ols(my_formula, data=wine).fit()

# 或者,也可以使用广义线性模型(glm)语法进行线性回归
# lm = glm(my_formula, data=wine, family=sm.families.Gaussian()).fit()

print(lm.summary())
print("\nQuantities you can extract from the result:\n%s" % dir(lm))
print("Coefficients:\n%s" % lm.params)
print("Coefficient Std Errors:\n%s" % lm.bse)
print("\nAdj. R-squared:\n%.2f" % lm.rsquared_adj)
print("\nF-statistic: %.1f P-value: %.2f" % (lm.fvalue, lm.f_pvalue))
print("\nNumber of obs: %d Number of fitted values: %d" % (lm.nobs, len(lm.fittedvalues)))

  运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
                            OLS Regression Results                            
==============================================================================
Dep. Variable: quality R-squared: 0.292
Model: OLS Adj. R-squared: 0.291
Method: Least Squares F-statistic: 243.3
Date: Thu, 27 Feb 2020 Prob (F-statistic): 0.00
Time: 23:03:10 Log-Likelihood: -7215.5
No. Observations: 6497 AIC: 1.445e+04
Df Residuals: 6485 BIC: 1.454e+04
Df Model: 11
Covariance Type: nonrobust
========================================================================================
coef std err t P>|t| [0.025 0.975]
----------------------------------------------------------------------------------------
Intercept 55.7627 11.894 4.688 0.000 32.447 79.079
alcohol 0.2670 0.017 15.963 0.000 0.234 0.300
chlorides -0.4837 0.333 -1.454 0.146 -1.136 0.168
citric_acid -0.1097 0.080 -1.377 0.168 -0.266 0.046
density -54.9669 12.137 -4.529 0.000 -78.760 -31.173
fixed_acidity 0.0677 0.016 4.346 0.000 0.037 0.098
free_sulfur_dioxide 0.0060 0.001 7.948 0.000 0.004 0.007
pH 0.4393 0.090 4.861 0.000 0.262 0.616
residual_sugar 0.0436 0.005 8.449 0.000 0.033 0.054
sulphates 0.7683 0.076 10.092 0.000 0.619 0.917
total_sulfur_dioxide -0.0025 0.000 -8.969 0.000 -0.003 -0.002
volatile_acidity -1.3279 0.077 -17.162 0.000 -1.480 -1.176
==============================================================================
Omnibus: 144.075 Durbin-Watson: 1.646
Prob(Omnibus): 0.000 Jarque-Bera (JB): 324.712
Skew: -0.006 Prob(JB): 3.09e-71
Kurtosis: 4.095 Cond. No. 2.49e+05
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 2.49e+05. This might indicate that there are
strong multicollinearity or other numerical problems.

Quantities you can extract from the result:
['HC0_se', 'HC1_se', 'HC2_se', 'HC3_se', '_HCCM', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cache', '_data_attr', '_get_robustcov_results', '_is_nested', '_wexog_singular_values', 'aic', 'bic', 'bse', 'centered_tss', 'compare_f_test', 'compare_lm_test', 'compare_lr_test', 'condition_number', 'conf_int', 'conf_int_el', 'cov_HC0', 'cov_HC1', 'cov_HC2', 'cov_HC3', 'cov_kwds', 'cov_params', 'cov_type', 'df_model', 'df_resid', 'diagn', 'eigenvals', 'el_test', 'ess', 'f_pvalue', 'f_test', 'fittedvalues', 'fvalue', 'get_influence', 'get_prediction', 'get_robustcov_results', 'initialize', 'k_constant', 'llf', 'load', 'model', 'mse_model', 'mse_resid', 'mse_total', 'nobs', 'normalized_cov_params', 'outlier_test', 'params', 'predict', 'pvalues', 'remove_data', 'resid', 'resid_pearson', 'rsquared', 'rsquared_adj', 'save', 'scale', 'ssr', 'summary', 'summary2', 't_test', 't_test_pairwise', 'tvalues', 'uncentered_tss', 'use_t', 'wald_test', 'wald_test_terms', 'wresid']
Coefficients:
Intercept 55.762750
alcohol 0.267030
chlorides -0.483714
citric_acid -0.109657
density -54.966942
fixed_acidity 0.067684
free_sulfur_dioxide 0.005970
pH 0.439296
residual_sugar 0.043559
sulphates 0.768252
total_sulfur_dioxide -0.002481
volatile_acidity -1.327892
dtype: float64
Coefficient Std Errors:
Intercept 11.893899
alcohol 0.016728
chlorides 0.332683
citric_acid 0.079619
density 12.137473
fixed_acidity 0.015573
free_sulfur_dioxide 0.000751
pH 0.090371
residual_sugar 0.005156
sulphates 0.076123
total_sulfur_dioxide 0.000277
volatile_acidity 0.077373
dtype: float64

Adj. R-squared:
0.29

F-statistic: 243.3 P-value: 0.00

Number of obs: 6497 Number of fitted values: 6497

  字符串变量my_formula包含的是类似R语言语法的回归公式定义。波浪线左侧的变量quality是因变量,波浪线右侧的变量是自变量。
  下一行代码使用公式和数据你喝一个普通最小二乘回归模型,并将结果赋给变量lm。被注释掉的那一行代码使用广义线性模型(glm)的语法代替普通最小二乘语法也可以拟合同样的模型。

Tips:
dir():不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表
lm.params:以一个序列的形式返回模型系数
lm.rsquared_adj:返回修正R方
lm.fvalue:返回F统计量
lm.f_pvalue:返回F统计量的p值
lm.fittedvalues:返回拟合值

系数解释

  在这个模型中,某个自变量系数的意义是,在其他自变量保持不变的情况下,这个自变量发生1个单位的变化时,导致葡萄酒质量评分发生的平均变化。
  并不是所有的系数都需要解释。例如,截距系数的意义是当所有自变量的值都为0时的期望评分。因为没有任何一种葡萄酒的各种成分都为0,所以截距系数没有具体意义。

自变量标准化

  关于这个模型,普通最小二乘回归是通过使残差平方和最小化来估计未知的$\beta$参数值的,这里的残差是指自变量观测值与拟合值之间的差别。因为残差大小是依赖于自变量的测量单位的,所以如果自变量的测量单位相差很大的话,那么将自变量标准化后,就可以更容易对模型进行解释了。对自变量进行标准化的方法是,先从自变量的每个观测值中减去均值,然后再除以这个自变量的标准差。自变量标准化完成以后,它的均值为0,标准差为1。

Data Analysis Using Regression and Multilevel/Hierarchical Models (Cambridge University Press, 2007, p.56) 中,Gelman and Hill建议在数据集中既有连续型自变量又有二值型自变量的情况下,用两杯标准差去除,而不是用一倍标准差。这样的话,标准化自变量一个单位的变化就对应于均值上下一个标准差的变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...

# 创建一个名为dependent_variable的序列来保存质量数据
dependent_variable = wine['quality']

# 创建一个名为independent_variables的数据框来保存初始的葡萄酒数据集中除quality、type和in_sample之外的所有变量
independent_variables = wine[wine.columns.difference(['quality', 'type', 'in_sample'])]

# 对自变量进行标准化
# 对每个变量,在每个观测中减去变量的均值
# 并且使用结果除以变量的标准差
independent_variables_standardized = (independent_variables
- independent_variables.mean()) / independent_variables.std()

# 将因变量quality作为一列添加到自变量数据框中
# 创建一个带有标准化自变量的新数据集
wine_standardized = pd.concat([dependent_variable, independent_variables_standardized], axis=1)

# 重新进行线性回归,并查看一下摘要统计
lm_standardized = ols(my_formula, data=wine_standardized).fit()
print(lm_standardized.summary())

  运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
                            OLS Regression Results                            
==============================================================================
Dep. Variable: quality R-squared: 0.292
Model: OLS Adj. R-squared: 0.291
Method: Least Squares F-statistic: 243.3
Date: Fri, 28 Feb 2020 Prob (F-statistic): 0.00
Time: 16:31:15 Log-Likelihood: -7215.5
No. Observations: 6497 AIC: 1.445e+04
Df Residuals: 6485 BIC: 1.454e+04
Df Model: 11
Covariance Type: nonrobust
========================================================================================
coef std err t P>|t| [0.025 0.975]
----------------------------------------------------------------------------------------
Intercept 5.8184 0.009 637.785 0.000 5.800 5.836
alcohol 0.3185 0.020 15.963 0.000 0.279 0.358
chlorides -0.0169 0.012 -1.454 0.146 -0.040 0.006
citric_acid -0.0159 0.012 -1.377 0.168 -0.039 0.007
density -0.1648 0.036 -4.529 0.000 -0.236 -0.093
fixed_acidity 0.0877 0.020 4.346 0.000 0.048 0.127
free_sulfur_dioxide 0.1060 0.013 7.948 0.000 0.080 0.132
pH 0.0706 0.015 4.861 0.000 0.042 0.099
residual_sugar 0.2072 0.025 8.449 0.000 0.159 0.255
sulphates 0.1143 0.011 10.092 0.000 0.092 0.137
total_sulfur_dioxide -0.1402 0.016 -8.969 0.000 -0.171 -0.110
volatile_acidity -0.2186 0.013 -17.162 0.000 -0.244 -0.194
==============================================================================
Omnibus: 144.075 Durbin-Watson: 1.646
Prob(Omnibus): 0.000 Jarque-Bera (JB): 324.712
Skew: -0.006 Prob(JB): 3.09e-71
Kurtosis: 4.095 Cond. No. 9.61
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

  自变量标准化会改变对模型系数的解释。现在每个自变量系数的含义是,不同的葡萄酒在其他自变量均相同的情况下,某个自变量相差1个标准差,会使葡萄酒的质量评分平均相差多少个标准差。
  自变量标准化同样会改变对截距的解释。当解释变量被标准化后,截距表示的就是当所有自变量取值为均值时因变量的均值。

预测

1
2
3
4
5
6
7
8
9
10
11
12
...

# 使用葡萄酒数据集中的前10个观测创建10个“新”观测
# 新观测中只包含模型中使用的自变量
new_observations = wine.loc[wine.index.isin(range(10)), independent_variables.columns]

# 基于新观测中的葡萄酒特性预测质量评分
y_predicted = lm.predict(new_observations)

# 将预测值保留两位小数并打印到屏幕上
y_predicted_rounded = [round(score, 2) for score in y_predicted]
print(y_predicted_rounded)

  运行结果如下:

1
[5.0, 4.92, 5.03, 5.68, 5.0, 5.04, 5.02, 5.3, 5.24, 5.69]

Python数据分析基础之图与图表(ggplot番外篇)

ggplot简介

  ggplot是一个Python绘图包,它基于R语言的ggplot2包和图形语法。ggplot与其他绘图包的关键区别是它的语法将数据与实际绘图明确地分离开来。为了对数据进行可视化表示,ggplot提供了几种基本元素:几何对象、图形属性和标度。除此之外,为了进行更高级的绘图,ggplot还提供一些附加元素:统计变换、坐标系、子窗口和可视化主题。
  Python的ggplot库不像R语言的ggplot2库那样成熟,所以它不具备ggplot2的所有功能。也就是说,它没有那么多的几何对象、统计变换和标度,也没有坐标系、注释和增强功能。在与ggplot相关的包进行了升级与修改之后,使用ggplot也可能会遇到问题。

安装ggplot模块

  打开Anaconda Prompt(如果你没有安装Anaconda的话,打开命令行窗口即可),输入命令pip install ggplot,等待安装完成。

参考书的示例代码及问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python3

from ggplot import *

print(mtcars.head())
plt1 = ggplot(aes(x='mpg'), data=mtcars) +\
geom_histogram(fill='darkblue', binwidth=2) +\
xlim(10, 35) + ylim(0, 10) +\
xlab("MPG") + ylab("Frequency") +\
ggtitle("Histogram of MPG") +\
theme_matplotlib()
print(plt1)

print(meat.head())
plt2 = ggplot(aes(x='date', y='beef'), data=meat) +\
geom_line(color='purple', size=1.5, alpha=0.75) +\
stat_smooth(colour='blue', size=2.0, span=0.15) +\
xlab("Year") + ylab("Head of Cattle Slaughtered") +\
ggtitle("Beef Consumption Over Time") +\
theme_seaborn()
print(plt2)

print(diamonds.head())
plt3 = ggplot(diamonds, aes(x='carat', y='price', colour='cut')) +\
geom_point(alpha=0.5) +\
scale_color_gradient(low='#05D9F6', high='#5011D1') +\
xlim(0, 6) + ylim(0, 20000) +\
xlab("Carat") + ylab("Price") +\
ggtitle("Diamond Price by Carat and Cut") +\
theme_gray()
print(plt3)

ggsave(plt3, "ggplot_plots.png")

  我在运行这个脚本的时候,PyCharm报错信息为:AttributeError: module 'pandas' has no attribute 'tslib'。我当时就懵了:这是什么情况???
  后来我在网上查找了一下相关资料,以下是对这个问题的解答。

抱歉,看起来ggplot(又称ggpy)是一个官方不再维护的项目了,github上的最近一次提交还是三年前。而AttributeError: module 'pandas' has no attribute 'tslib'也是已知的由于 pandas 版本更新,API改变带来的bug。

  解答者还在回答中提供了ggplot模块在GitHub上的issue和PR,但是我英语水平一般,就没有仔细地阅读这些,而是打算自己动手解决。

解决方案

  我们找到smoothers.py和utils.py这两个文件,如下图所示修改红框中的内容。

Tips:
smoothers.py的路径:D:\Anaconda3\Lib\site-packages\ggplot\stats\smoothers.py
utils.py的路径:D:\Anaconda3\Lib\site-packages\ggplot\utils.py
由于Anaconda3文件夹保存位置不同,路径可能会有差异。

在这里插入图片描述)在这里插入图片描述
  修改完成之后,我们再次运行上面的代码。
  运行结果显示报错信息:NameError: name 'theme_matplotlib' is not defined。此时我的内心是崩溃的:我的天,怎么还是有问题啊!!!
  好在我很快地冷静了下来,开始着手分析这个问题。
  我打开了ggplot.py文件,按下Ctrl+F组合键,输入“theme”进行搜索,得到如下图所示的搜索信息。

Tips:
ggplot.py的路径:D:\Anaconda3\Lib\site-packages\ggplot\ggplot.py
由于Anaconda3文件夹保存位置不同,路径可能会有差异。

在这里插入图片描述
  接下来,我打开了ggplot文件夹中的themes文件夹,发现……emmmm……它好像根本就没有theme_matplotlibtheme_seaborn啊!!!
在这里插入图片描述
  我的内心再一次崩溃。
  算了,没有就没有吧,大不了我不用就是了。
  于是,经过了一番折腾,代码变成了下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

from ggplot import *

print(diamonds.head())
plt = ggplot(diamonds, aes(x='carat', y='price', colour='cut')) + \
geom_point(alpha=0.5) + \
scale_color_gradient(low='#05D9F6', high='#5011D1') + \
xlim(0, 6) + ylim(0, 20000) + \
xlab("Carat") + ylab("Price") + \
ggtitle("Diamond Price by Carat and Cut") + \
theme_gray()
print(plt)

ggsave(plt, "ggplot_plots.png")

  再运行一次。你们猜怎么样?
  又报错了。
在这里插入图片描述
  好在之前已经折腾了那么多,再报错我也见怪不怪了。我再次打开ggplot.py文件,以“save”为关键字搜索一下。
在这里插入图片描述
  原来它不叫ggsave而是叫save啊。这样一来就好办多了,我们再修改一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

from ggplot import *

print(diamonds.head())
plt = ggplot(diamonds, aes(x='carat', y='price', colour='cut')) + \
geom_point(alpha=0.5) + \
scale_color_gradient(low='#05D9F6', high='#5011D1') + \
xlim(0, 6) + ylim(0, 20000) + \
xlab("Carat") + ylab("Price") + \
ggtitle("Diamond Price by Carat and Cut") + \
theme_gray()
print(plt)

ggplot.save(plt, "ggplot_plots.png", dpi=400)

  这次,它终于没有报错!没有报错!没有报错!!!
在这里插入图片描述
  不过它保存出来的这个png格式文件……红框里面的内容不太一样,图上的点的颜色也有些许不同……这我就不是很清楚了。

写在最后

  我觉得,正是由于Python的ggplot模块没有R语言的ggplot2模块那么成熟,再加上官方已经有三年多的时间没有对其进行维护和升级了,这期间它所依赖的模块有所更新(比如pandas),才会导致这一系列问题。所以,如果想使用Python进行统计图表的绘制,相较于ggplot模块,matplotlib和seaborn会更加友好一些。

Python数据分析基础之图与图表(2)

  上一节主要学习了使用matplotlib模块绘制统计图。这一节主要学习使用pandas, seaborn等模块绘制统计图。

使用pandas绘制统计图

  pandas模块提供了一个可以作用于序列和数据框的函数plot(),简化了基于序列和数据框中的数据创建图表的过程。plot()函数默认创建折线图,我们可以通过设置参数kind来创建其他类型的图表。
  除了使用matplotlib模块创建标准统计图,还可以使用pandas模块可以创建其他类型的统计图,例如六边箱图(hexagonal bin plot)、矩阵散点图、密度图、Andrews曲线图、平行坐标图、延迟图、自相关图和自助抽样图。如果要向统计图中添加第二y轴、误差棒和数据表,使用pandas模块可以很直接地实现。
  下面的代码是应用pandas模块创建一对条形图和箱线图的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python3

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')

# 准备数据
fig, axes = plt.subplots(nrows=1, ncols=2) # 创建两个并排放置的子图
ax1, ax2 = axes.ravel() # 将两个子图分别赋给变量ax1和ax2
data_frame = pd.DataFrame(np.random.rand(5, 3),
index=['Customer 1', 'Customer 2', 'Customer 3', 'Customer 4', 'Customer 5'],
columns=pd.Index(['Metric 1', 'Metric 2', 'Metric 3'], name='Metrics'))

# 绘制条形图
data_frame.plot(kind='bar', ax=ax1, alpha=0.75, title='Bar Plot') # 创建条形图
plt.setp(ax1.get_xticklabels(), rotation=45, fontsize=10)
plt.setp(ax1.get_yticklabels(), rotation=0, fontsize=10)
ax1.set_xlabel('Customer')
ax1.set_ylabel('Value')
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')

# 绘制箱线图
colors = dict(boxes='DarkBlue', whiskers='Gray', medians='Red', caps='Black') # 为箱线图创建颜色字典
data_frame.plot(kind='box', color=colors, sym='r.', ax=ax2, title='Box Plot') # 创建箱线图
plt.setp(ax2.get_xticklabels(), rotation=45, fontsize=10)
plt.setp(ax2.get_yticklabels(), rotation=0, fontsize=10)
ax2.set_xlabel('Metric')
ax2.set_ylabel('Value')
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')

# 保存图片
plt.savefig('pandas_plots.png', dpi=400, bbox_inches='tight')
plt.show()

  运行结果如下图所示。
在这里插入图片描述

使用seaborn绘制统计图

  seaborn模块简化了在Python中创建信息丰富的统计图表的过程。它可以创建标准统计图,包括直方图、密度图、条形图、箱线图和散点图。它可以对成对变量之间的相关性、线性与非线性回归模型以及统计估计的不确定性进行可视化。它可以用来在评估变量时检查变量之间的关系,并可以建立统计图矩阵来显示复杂的关系。它有内置的主题和调色板,可以用来制作精美的图标。最后,因为它是建立在matplotlib上的,所以我们可以使用matplotlib的命令来对图形进行更深入的定制。
  下面的代码演示了如何使用seaborn创建各种统计图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python3

import seaborn as sns
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

sns.set(color_codes=True)

# 直方图
x = np.random.normal(size=100)
sns.distplot(x, bins=20, kde=False, rug=True, label="Histogram w/o Density")
sns.utils.axlabel("Value", "Frequency")
plt.title("Histogram of a Random Sample from a Normal Distribution")
plt.legend()
plt.savefig("seaborn_plot_1.png", dpi=400, bbox_inches='tight')
plt.show()

# 带有回归直线的散点图与单变量直方图
mean, cov = [5, 10], [(1, .5), (.5, 1)]
data = np.random.multivariate_normal(mean, cov, 200)
data_frame = pd.DataFrame(data, columns=["x", "y"])
sns.jointplot(x="x", y="y", data=data_frame, kind="reg").set_axis_labels("x", "y")
plt.suptitle("Joint Plot of Two Variables with Bivariate and Univariate Graphs")
plt.savefig("seaborn_plot_2.png", dpi=400, bbox_inches='tight')
plt.show()

# 成对变量之间的散点图与单变量直方图
iris = sns.load_dataset("iris")
sns.pairplot(iris)
plt.savefig("seaborn_plot_3.png", dpi=400, bbox_inches='tight')
plt.show()

# 按照某几个变量生成的箱线图
tips = sns.load_dataset("tips")
sns.catplot(x="time", y="total_bill", hue="smoker", col="day", data=tips, kind="box", height=4, aspect=.5)
plt.savefig("seaborn_plot_4.png", dpi=400, bbox_inches='tight')
plt.show()

# 带有bootstrap置信区间的线性回归模型
sns.lmplot(x="total_bill", y="tip", data=tips)
plt.savefig("seaborn_plot_5.png", dpi=400, bbox_inches='tight')
plt.show()

# 带有bootstrap置信区间的逻辑斯蒂回归模型
tips["big_tip"] = (tips.tip / tips.total_bill) > .15
sns.lmplot(x="total_bill", y="big_tip", data=tips, logistic=True, y_jitter=.03).set_axis_labels("Total Bill", "Big Tip")
plt.title("Logistic Regression of Big Tip vs.Total Bill")
plt.savefig("seaborn_plot_6.png", dpi=400, bbox_inches='tight')
plt.show()

  运行结果如下图所示。
在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述