生成数据
安装matplotlib
先安装Visual Studio:Visual Studio下载
下载matplotlib:matplotlib
下载下来是一个.whl
文件,将文件放到项目的根目录,用pip
来安装matplotlib:
python -m pip install --user matplotlib-1.5.0-cp35-none-win_amd64.whl
结果:
matplotlib画廊
要查看matplotlib可以制作的各种图表,请访问http://matplot.org/的提示画廊。
绘制简单的折线图
mpl_squares.py
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25, 36, 49]
plt.plot(squares)
plt.show()
输出:
plot()
接收一个数字列表,尝试根据这些数字绘制出有意义的图形。
show()
打开matplotlib查看器,并显示绘制的图形。
修改标签文字和线条粗细
mpl_squares.py
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25, 36, 49]
# linewidth参数指定线的粗细
plt.plot(squares, linewidth=5)
# fontsize函数指定
plt.title("Squares Numbers", fontsize=24)
# 为每条轴设置标题
plt.xlabel("Numbers", fontsize=14)
plt.ylabel("Value of Squares", fontsize=14)
# 设置刻度标记的大小
# 指定的实参(axis='both')将同时映像影响x轴和y轴上的刻度
plt.tick_params(axis='both', labelsize=14)
plt.show()
校正图形
稍微有点脑子的人都会看出来,这个图像的x轴的坐标和y轴的值好像不太对应。
它默认第一个值对应的x轴的值时0,通过给plot()传参可以改变这种默认行为:
input_values = [1, 2, 3, 4, 5, 6, 7]
squares = [1, 4, 9, 16, 25, 36, 49]
# linewidth参数指定线的粗细
plt.plot(input_values, squares, linewidth=5)
输出自己脑补吧。
使用scatter()绘制散点图并设置其样式
绘制单个点,传递一对x和y坐标:
scatter_squares.py
import matplotlib.pyplot as plt
plt.scatter(6, 6)
plt.show()
标题坐标轴名称和
输出:就是一个点不想展示。
使用scatter()绘制一系列点
这个时候就要传递两个分别包含x值和y值的列表:
scatter_squares.py
import matplotlib.pyplot as plt
x_values = [1, 2, 3, 4, 5, 6, 7]
y_values = [1, 4, 9, 16, 25, 36, 49]
plt.scatter(x_values, y_values, s=200)
plt.show()
自动计算数据
其实就是我们学习的列表解析啦。
scatter_squares.py
import matplotlib.pyplot as plt
x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values]
plt.scatter(x_values, y_values, s=20)
# 设置每个坐标轴的取值范围
plt.axis([0, 1001, 0, 1100000])
plt.show()
axis()
由于这个数据集较大,我们将点设置的较小,并使用函数axis()
制定了每个坐标轴的取值范围。函数axis()
要求提供四个值:x和y坐标轴的最大值和最小值。
好多好多的点看起来就像是一条线。
删除数据点的轮廓
matplotlib允许你给散点图中的各个点指定颜色。
默认认为蓝色点和黑色轮廓,在散点图包含的数据点不多时效果很好。但绘制很多点时,黑色轮廓可能会粘连在一起。要删除数据点的轮廓,可在调用scatter()
时传递实参edgecolor='none'
:
plt.scatter(x_values, y_values, edgecolor='none', s=20)
在目前版本的matplotlib中,scatter()
函数的实参edgecolor默认为'none'
。
自定义颜色
可以向scatter()
函数传递参数c
并将其设置为要使用的颜色的名称:
plt.scatter(x_values, y_values, edgecolor='none', s=20, c='red')
参数值为表示颜色的英文单词字符串。
颜色映射
颜色映射(colormap)是一系列颜色,从起始色渐变到结束色。在可视化中。颜色映射用于突出故障的规律,例如,你可能用较浅的颜色来显示较小的值,并使用较深颜色来显示较大的值。
import matplotlib.pyplot as plt
x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values]
plt.scatter(x_values, y_values, s=10, edgecolors='none', c=y_values, cmap=plt.cm.Reds)
# 设置每个坐标轴的取值范围
plt.axis([0, 1001, 0, 1100000])
plt.show()
将参数的值设置为一个y值列表,并使用参数camp
告诉pyplot
使用哪个颜色作为映射。
将y值较小的点显示为浅红色,y’值大的显示为深红色。
自动保存图表
要让程序自动保存图表到文件中,可以将对plt.show()
的调用替换为对plt.savefig()
的调用:
plt.savefig('squares_plot.jpeg', bbox_inches='tight')
第一个实参指定图表的文件名。
第二个实参指定是否将图表多余的空白区域裁减掉,若要保留,则忽略此实参。
随机漫步
创建Randomwalk()类
Random_walk.py
from random import randint
class RandomWalk():
"""生成随机漫步数据"""
def __init__(self, num_points=5000, max_step=5):
"""初始化属性"""
# 总点数
self.num_points = num_points
# 一次移动的最大步长
self.max_step = max_step
# 所有点开始于(0, 0)
self.x_value = [0]
self.y_value = [0]
def fill_walk(self):
"""计算漫步的所有点"""
# 不断漫步,直到到达指定步数
while len(self.x_value) < self.num_points:
# 决定x和y轴运动长度
x_step = randint(-self.max_step, self.max_step)
y_step = randint(-self.max_step, self.max_step)
# 不允许原地踏步
if not x_step and not y_step:
continue
# 计算下一个点的坐标值
next_x = self.x_value[-1] + x_step
next_y = self.y_value[-1] + y_step
# 加入坐标列表
self.x_value.append(next_x)
self.y_value.append(next_y)
我用自己的方法写的,和书上的不一样,更简洁一点,更好控制。
绘制随机漫步
scatter_squares.py
import matplotlib.pyplot as plt
from random_walk import RandomWalk
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values, rw.y_values, s=15, c="red")
plt.show()
输出:
很高清,但是略显鬼畜,这个红色不如大红色好看,暗红吧。
给点着色
scatter_squares.py
import matplotlib.pyplot as plt
from random_walk import RandomWalk
rw = RandomWalk()
rw.fill_walk()
point_number = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, s=15, c=point_number, cmap=plt.cm.Reds, edgecolors='none')
plt.show()
c为一个数字列表,包含数字0~5000,因为这些点时按顺序绘制的。
之前的折线图用的是y_values,因为y的值是严格单调递增的。
上图:
好看多了,有边框太鬼畜了。
隐藏坐标轴
scatter_squares.py
# 隐藏坐标轴
plt.axes().get_xaxis().set_visible(False)
plt.axes().get_yaxis().set_visible(False)
就不演示了。
调整尺寸以适合屏幕
plt.figure(figsize=(10, 6))
函数figure()
用于指定图表的宽度,高度,分辨率和背景色。
需要给形参figsize
指定一个元组,表示绘图窗口的尺寸。
若知道分辨率,可以用形参dpi
传递该分辨率。
plt.figure(figsize=(10, 6), dpi=128)
使用Pygal模拟掷骰子
安装Pygal
Windows中:
python -m pip install --user pygal==1.7
后面的那个==1.7
可有可无,若要直接安装最新版,就不用加了,直接:
python -m pip install --user pygal
结果:
创建Die(骰子)类
die.py
from random import randint
class Die:
"""模拟骰子"""
def __init__(self, num_sides=6):
"""默认骰子为6面"""
self.num_sides = num_sides
def roll(self):
"""返回一个从 1 到 骰子总面数的随机数"""
return randint(1, self.num_sides)
掷骰子from die import Die
die_result.py
die = Die()
results = []
for roll_num in range(100):
results.append(die.roll())
print(results)
统计各个值出现的次数
die_result.py
from die import Die
die = Die()
results = []
frequency = []
for roll_num in range(120):
results.append(die.roll())
for value in range(1, die.num_sides + 1):
frequency.append(results.count(value))
print(frequency)
输出:
[18, 26, 16, 23, 17, 20]
绘制直方图
roll_die.py
import pygal
from die import Die
die = Die()
results = []
frequencies = []
for roll_num in range(120):
results.append(die.roll())
for value in range(1, die.num_sides + 1):
frequencies.append(results.count(value))
hist = pygal.Bar()
hist.title = "Result"
hist.x_labels = ['1', '2', '3', '4', '5', '6']
hist.x_title = "Result"
hist.y_title = "Frequency of Result"
# 用add()添加一系列值
hist.add('D6', frequencies)
hist.render_to_file('roll_die.svg')
不能用其他格式,那样出来的图片打不开,函数render_to_file()
将图表渲染成svg
文件,要在浏览器中打开查看,这个不用我教吧。
还是红色的,超漂亮。
同时掷两个骰子
roll_die.py
import pygal
from die import Die
die_1 = Die()
die_2 = Die()
results = []
frequencies = []
for roll_num in range(1200):
results.append(die_1.roll() + die_2.roll())
# 切记这里要从'2'开始计数
for value in range(2, die_1.num_sides + die_2.num_sides + 1):
frequencies.append(results.count(value))
hist = pygal.Bar()
hist.title = "Result of rolling two D6 1200 times"
# 切记这里要从'2'开始计数
hist.x_labels = ['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
hist.x_title = "Result"
hist.y_title = "Frequency of Result"
hist.add('D6 * 2', frequencies)
hist.render_to_file('roll_die_2.svg')
输出:
很漂亮呢,服从正态分布。
同理,举一反三,后面的多个,面数不同的骰子也就很简单了。
下载数据
CSV文件格式
绘制气温图表
highs_lows.py
import csv
from matplotlib import pyplot as plt
filename = "sitka_weather_07-2014.csv"
try:
with open(filename, 'r') as f:
reader = csv.reader(f)
# 先读取文件头
header_row = next(reader)
# 创建空列表
highs = []
# 循环从第二行开始,因为已经读取了第一行(文件头)
for row in reader:
# 将字符串变为数字,否则使用下面plot()时会报错
high = int(row[1])
highs.append(high)
plt.figure(dpi=128, figsize=(10, 6))
plt.plot(highs, c='red')
plt.show()
except FileNotFoundError:
print(filename + "not found.")
注:阅读器对象,从其停留的地方继续往下读取CSV,每次都自动返回当前所处位置的下一行。
输出:
模块datetime
import csv
from datetime import datetime as dt
from matplotlib import pyplot as plt
filename = "sitka_weather_07-2014.csv"
try:
with open(filename, 'r') as f:
reader = csv.reader(f)
header_row = next(reader)
# 创建两个空列表
dates, highs = [], []
for row in reader:
current_date = dt.strptime(row[0], "%Y-%m-%d")
dates.append(current_date)
high = int(row[1])
highs.append(high)
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red')
fig.autofmt_xdate()
plt.show()
except FileNotFoundError:
print(filename + "not found.")
调用方法strptime()
,将包含所需日期的字符串作为第一个是餐。第二个实参告诉Python如何设置日期的格式。
方法strptime()
接收不同实参,并根据它们来决定如何解读日期。
注意!是如何解读,不是直接读取然后转换,请好好理解一下。
实参 | 含义 |
---|---|
%A | 本地完整星期的名称,如Monday |
%a | 本地简写星期的名称,如Mon |
%B | 本地完整月份名,如January |
%b | 本地简写月份名,如Jan |
%m | 用数字表示的月份(01~12) |
%d | 用数字表示月份中的一天(01~31) |
%Y | 四位的年份,如2018 |
%y | 二位的年份,如18 |
%H | 24小时制的小时数(00~23) |
%I | 12小时制的小时数(00~12) |
%p | am或pm |
%M | 分钟数(00~59) |
%S | 秒数(00~59) |
原书这里写的秒数是(00~61)是错的!!
调用fig.autofmt_xdate()
来绘制斜的日期标签,以免他们彼此重叠。
输出:
涵盖更多时间并再绘制一个数据
将文件换成一整年的天气数据,再绘制一个最低气温。
highs_lows.py
import csv
from datetime import datetime as dt
from matplotlib import pyplot as plt
filename = "sitka_weather_2014.csv"
try:
with open(filename, 'r') as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs, lows = [], [], []
for row in reader:
current_date = dt.strptime(row[0], "%Y-%m-%d")
dates.append(current_date)
high = int(row[1])
highs.append(high)
low = int(row[3])
lows.append(low)
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red')
# 再次调用plot()函数便是了
plt.plot(dates, lows, c='blue')
fig.autofmt_xdate()
plt.show()
except FileNotFoundError:
print(filename + "not found.")
输出:
真好看。
给绘图表区域着色
import csv
from datetime import datetime as dt
from matplotlib import pyplot as plt
filename = "sitka_weather_2014.csv"
try:
with open(filename, 'r') as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs, lows = [], [], []
for row in reader:
current_date = dt.strptime(row[0], "%Y-%m-%d")
dates.append(current_date)
high = int(row[1])
highs.append(high)
low = int(row[3])
lows.append(low)
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red', alpha=0.5)
plt.plot(dates, lows, c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)
fig.autofmt_xdate()
plt.show()
except FileNotFoundError:
print(filename + "not found.")
plot()
的实参alpha
指定颜色的透明度。
向plt.fill_between()
传递了一个
x
x
值系列,两个值系列,facecolor
指定填充区域的颜色。
错误检查
有些气象站偶尔会出现故障,未能收集全数据。缺失数据可能会引起异常,如果不妥协处理可能会导致程序崩溃。
将文件换为death_valley_2014.csv
,其与代码同上。
报错:ValueError: invalid literal for int() with base 10: ''
。
意思就是无法将一个空字符串变成数字。
所以我们需要在程序中检查异常:
hights_lows.py
--snip--
dates, highs, lows = [], [], []
for row in reader:
try:
current_date = dt.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except ValueError:
# 注意这里是逗号,不是加号
print(current_date, 'missing date')
else:
dates.append(current_date)
highs.append(high)
lows.append(low)
--snip--
制作世界人口地图 JSON格式
在前面制作游戏的时候,已经用到了处理JSON数据的方法。
world_population.py
import json
filename = 'population_data.json'
with open(filename, 'r') as f:
pop_data = json.load(f)
for pop_dict in pop_data:
if pop_dict["Year"] == '2010':
print(pop_dict["Country Name"] + ": " + pop_dict["Value"])
这个例子打印2010年各个国家的人口数量。
将字符串转换为数字
population_data.json
中的每个键和值都是字符串,为了处理人口数据,我们需要将字符串转换为数字:
world_population.py
import json
filename = 'population_data.json'
with open(filename, 'r') as f:
pop_data = json.load(f)
for pop_dict in pop_data:
if pop_dict["Year"] == '2010':
population = int(pop_dict["Value"])
print(pop_dict["Country Name"] + ": " + str(population))
但是,也会有异常:
ValueError: invalid literal for int() with base 10: '1127437398.85751'
Python无法将有小数点的字符串直接转化为整数,所以,先把它变成浮点数就好了:
population = int(float(pop_dict["Value"]))
获取两个字母的国别码
Pygal中的地图制作工具要求数据为特定的形式:用国别码表示国家,以及用数字表示人口数量。处理地理政治数据时,经常需要用到几个标准化国别码集。而population_data.json
中包含的是三个字母的国别码。
Pygal使用的国别码存储在模块i18n
(internationalization的缩写)。字典COUNTRIES
包含的键和值分别是两个字母的国别码和国家名。
编写一个函数,获取国别码。
在此之前,先做准备工作,安装世界地图插件。
参考:官方文档
用pip
安装:
pip install pygal_maps_world
结果:
然后我们就能使用pygal.maps.world
模块了。
书上用的版本是1.7
现在是2.4
,它上面导入COUNTRIES
数组的语句from pygal.i18n import COUNTRIES
会报错,找不到模块。
在这里,进行完上面的安装后,可以使用两个语句来导入COUNTRIES
数组:
from pygal.maps.world import COUNTRIES
# 或
from pygal_maps_world.i18n import COUNTRIES
两种都行哦,亲测,虽然对于第一个语句,编译器可能会这样:
然后开始来编写函数:
country_codes.py
from pygal.maps.world import COUNTRIES
def get_country_code(country_name):
"""通过所给的国家名返回两位的国别码"""
for code, name in COUNTRIES.items():
if name == country_name:
return code
# 如果没有找到国家返回None
return None
制作世界地图
world_population.py
import json
import pygal.maps.world
from country_codes import get_country_code
filename = 'population_data.json'
with open(filename, 'r') as f:
pop_data = json.load(f)
cc_population = {}
for pop_dict in pop_data:
if pop_dict["Year"] == '2010':
population = int(float(pop_dict["Value"]))
# 获取国家的二位国别码
code = get_country_code(pop_dict["Country Name"])
# 若在数组中存在二位国别码
if code:
cc_population[code] = population
wm = pygal.maps.world.World()
wm.title = "World population of 2010, by Country"
# 向add()方法传递由国别码和人口数量组成的字典
wm.add('2010', cc_population)
wm.render_to_file('world_population.svg')
第20行和书上的不一样,应该用pygal.maps.world.World()
创建World()
实例。
输出:
又是我最喜欢的红色。
根据人口分组
将人口分为三组——少于1000万、结语1000万和10亿之间的以及超过10亿的:
world_population.py
import json
import pygal.maps.world
from country_codes import get_country_code
filename = 'population_data.json'
with open(filename, 'r') as f:
pop_data = json.load(f)
cc_population = {}
for pop_dict in pop_data:
if pop_dict["Year"] == '2010':
population = int(float(pop_dict["Value"]))
code = get_country_code(pop_dict["Country Name"])
if code:
cc_population[code] = population
# 重新创建三个空字典
cc_pop_1, cc_pop_2, cc_pop_3 = {}, {}, {}
# 需要利用之前的字典
for cc, pop in cc_population.items():
if pop < 10000000:
cc_pop_1[cc] = pop
elif pop > 1000000000:
cc_pop_3[cc] = pop
else:
cc_pop_2[cc] = pop
wm = pygal.maps.world.World()
wm.title = "World population of 2010, by Country"
# 分别调用add()方法
wm.add('0-10m', cc_pop_1)
wm.add('10m-1bn', cc_pop_2)
wm.add('>1bn', cc_pop_3)
wm.render_to_file('world_population.svg')
输出:
使用Pygal设置世界地图的样式
world_population.py
--snip--
wm_style = RotateStyle('#FF33CC')
wm = pygal.maps.world.World(style=wm_style)
--snip--
加亮颜色主题
Pygal通常使用较暗的颜色主题,使用LightColorizedStyle
加亮主题。
但不能直接使用这个类,要设置颜色,可使用RotateStyle
并且将LightColorizedStyle
作为基色(传入实参base_style
):
world_population.py
from pygal.style import RotateStyle, LightColorizedStyle
--snip--
wm_style = RotateStyle('#FF33CC', base_style=LightColorizedStyle)
--snip--
输出:
差别不大,就是从亮光到了哑光。