数据可视化 指的是通过可视化表示来探索数据,它与数据挖掘 紧密相关,而数据挖掘指的是使用代码来探索数据集的规律和关联。数据集可以是用一行代码就能表
示的小型数字列表,也可以是数以吉字节的数据。
使用Pygal模拟掷骰子
在本节中,我们将使用Python可视化包Pygal来生成可缩放的矢量图形文件。对于需要在尺寸不同的屏幕上显示的图表,这很有用,因为它们将自动缩放,以适合观看者的屏幕。
如果你打算以在线方式使用图表,请考虑使用Pygal来生成它们,这样它们在任何设备上显示时都会很美观。
在这个项目中,我们将对掷骰子的结果进行分析。掷6面的常规骰子时,可能出现的结果为1~6点,且出现每种结果的可能性相同。然而,如果同时掷两个骰子,某些点数出现的
可能性将比其他点数大。为确定哪些点数出现的可能性最大,我们将生成一个表示掷骰子结果的数据集,并根据结果绘制出一个图形。
在数学领域,常常利用掷骰子来解释各种数据分析,但它在赌场和其他博弈场景中也得到了实际应用,在游戏《大富翁》以及众多角色扮演游戏中亦如此。
安装Pygal
请使用pip 来安装Pygal(如果还未使用过pip )。
在Linux和OS X系统中,应执行的命令类似于下面这样:
pip install --user pygal
在Windows系统中,命令类似于下面这样:
python -m pip install --user pygal
注意 你可能需要使用命令pip3 而不是pip ,如果这还是不管用,你可能需要删除标志–user 。
Pygal画廊
要了解使用Pygal可创建什么样的图表,请查看图表类型画廊:访问http://www.pygal.org/ ,单击Documentation,再单击Chart types。每个示例都包含源代码,让你知道这些图表是如何
生成的。
创建Die 类
下面的类模拟掷一个骰子:
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)
方法__init__() 接受一个可选参数。创建这个类的实例时,如果没有指定任何实参,面数默认为6;如果指定了实参,这个值将用于设置骰子的面数(见❶)。骰子是根据面
数命名的,6面的骰子名为D6,8面的骰子名为D8,以此类推。
方法roll() 使用函数randint() 来返回一个1和面数之间的随机数(见❷)。这个函数可能返回起始值1、终止值num_sides 或这两个值之间的任何整数。
掷骰子
使用这个类来创建图表前,先来掷D6骰子,将结果打印出来,并检查结果是否合理:
from die import Die
# 创建一个D6
❶ die = Die()
# 掷几次骰子,并将结果存储在一个列表中
results = []
❷ for roll_num in range(100):
result = die.roll()
results.append(result)
print(results)
在❶处,我们创建了一个Die 实例,其面数为默认值6。在❷处,我们掷骰子100次,并将每次的结果都存储在列表results 中。下面是一个示例结果集:
[4, 6, 5, 6, 1, 5, 6, 3, 5, 3, 5, 3, 2, 2, 1, 3, 1, 5, 3, 6, 3, 6, 5, 4,
1, 1, 4, 2, 3, 6, 4, 2, 6, 4, 1, 3, 2, 5, 6, 3, 6, 2, 1, 1, 3, 4, 1, 4,
3, 5, 1, 4, 5, 5, 2, 3, 3, 1, 2, 3, 5, 6, 2, 5, 6, 1, 3, 2, 1, 1, 1, 6,
5, 5, 2, 2, 6, 4, 1, 4, 5, 1, 1, 1, 4, 5, 3, 3, 1, 3, 5, 4, 5, 6, 5, 4,
1, 5, 1, 2]
通过快速扫描这些结果可知,Die 类看起来没有问题。我们见到了值1和6,这表明返回了最大和最小的可能值;我们没有见到0或7,这表明结果都在正确的范围内。我们还看到
了1~6的所有数字,这表明所有可能的结果都出现了。
分析结果
为分析掷一个D6骰子的结果,我们计算每个点数出现的次数:
--snip--
# 掷几次骰子,并将结果存储在一个列表中
results = []
❶ for roll_num in range(1000):
result = die.roll()
results.append(result)
# 分析结果
frequencies = []
❷ for value in range(1, die.num_sides+1):
❸ frequency = results.count(value)
❹ frequencies.append(frequency)
print(frequencies)
由于我们将使用Pygal来进行分析,而不是将结果打印出来,因此可以将模拟掷骰子的次数增加到1000(见❶)。为分析结果,我们创建了空列表frequencies ,用于存储每种
点数出现的次数。在❷处,我们遍历可能的点数(这里为1~6),计算每种点数在results 中出现了多少次(见❸),并将这个值附加到列表frequencies 的末尾(见❹)。
接下来,我们在可视化之前将这个列表打印出来:
[155, 167, 168, 170, 159, 181]
结果看起来是合理的:我们看到了6个值——掷D6骰子时可能出现的每个点数对应一个;我们还发现,没有任何点数出现的频率比其他点数高很多。下面来可视化这些结果。
绘制直方图
有了频率列表后,我们就可以绘制一个表示结果的直方图。直方图 是一种条形图,指出了各种结果出现的频率。创建这种直方图的代码如下:
import pygal
--snip--
# 分析结果
frequencies = []
for value in range(1, die.num_sides+1):
frequency = results.count(value)
frequencies.append(frequency)
# 对结果进行可视化
❶ hist = pygal.Bar()
hist.title = "Results of rolling one D6 1000 times."
❷ hist.x_labels = ['1', '2', '3', '4', '5', '6']
hist.x_title = "Result"
hist.y_title = "Frequency of Result"
❸ hist.add('D6', frequencies)
hist.render_to_file('die_visual.svg')
为创建条形图,我们创建了一个pygal.Bar() 实例,并将其存储在hist 中(见❶)。接下来,我们设置hist 的属性title (用于标示直方图的字符串),将掷D6骰子的可
能结果用作 x 轴的标签(见❷),并给每个轴都添加了标题。在❸处,我们使用add() 将一系列值添加到图表中(向它传递要给添加的值指定的标签,还有一个列表,其中包含
将出现在图表中的值)。最后,我们将这个图表渲染为一个SVG文件,这种文件的扩展名必须为.svg。
要查看生成的直方图,最简单的方式是使用Web浏览器。为此,在任何Web浏览器中新建一个标签页,再在其中打开文件die_visual.svg(它位于die_visual.py所在的文件夹中)。你
将看到一个类似于图15-11所示的图表(为方便印刷,我稍微修改了这个图表;默认情况下,Pygal生成的图表的背景比你在图15-11中看到的要暗)。
注意,Pygal让这个图表具有交互性:如果你将鼠标指向该图表中的任何条形,将看到与之相关联的数据。在同一个图表中绘制多个数据集时,这项功能显得特别有用。
同时掷两个骰子
同时掷两个骰子时,得到的点数更多,结果分布情况也不同。下面来修改前面的代码,创建两个D6骰子,以模拟同时掷两个骰子的情况。每次掷两个骰子时,我们都将两个骰子
的点数相加,并将结果存储在results 中。请复制die_visual.py并将其保存为dice_visual.py,再做如下修改:
import pygal
from die import Die
# 创建两个D6骰子
die_1 = Die()
die_2 = Die()
# 掷骰子多次,并将结果存储到一个列表中
results = []
for roll_num in range(1000):
❶ result = die_1.roll() + die_2.roll()
results.append(result)
# 分析结果
frequencies = []
❷ max_result = die_1.num_sides + die_2.num_sides
❸ for value in range(2, max_result+1):
frequency = results.count(value)
frequencies.append(frequency)
# 可视化结果
hist = pygal.Bar()
❹ hist.title = "Results of rolling two D6 dice 1000 times."
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 + D6', frequencies)
hist.render_to_file('dice_visual.svg')
创建两个Die 实例后,我们掷骰子多次,并计算每次的总点数(见❶)。可能出现的最大点数12为两个骰子的最大可能点数之和,我们将这个值存储在了max_result 中(见
❷)。可能出现的最小总点数2为两个骰子的最小可能点数之和。分析结果时,我们计算2到max_result 的各种点数出现的次数(见❸)。我们原本可以使用range(2, 13)
,但这只适用于两个D6骰子。模拟现实世界的情形时,最好编写可轻松地模拟各种情形的代码。前面的代码让我们能够模拟掷任何两个骰子的情形,而不管这些骰子有多少面。
创建图表时,我们修改了标题、x 轴标签和数据系列(见❹)。(如果列表x_labels 比这里所示的长得多,那么编写一个循环来自动生成它将更合适。)
运行这些代码后,在浏览器中刷新显示图表的标签页。
这个图表显示了掷两个D6骰子时得到的大致结果。正如你看到的,总点数为2或12的可能性最小,而总点数为7的可能性最大,这是因为在6种情况下得到的总点数都为7。这6种
情况如下:1和6、2和5、3和4、4和3、5和2、6和1。
同时掷两个面数不同的骰子
下面来创建一个6面骰子和一个10面骰子,看看同时掷这两个骰子50 000次的结果如何:
from die import Die
import pygal
# 创建一个D6和一个D10
die_1 = Die()
❶ die_2 = Die(10)
# 掷骰子多次,并将结果存储在一个列表中
results = []
for roll_num in range(50000):
result = die_1.roll() + die_2.roll()
results.append(result)
# 分析结果
--snip--
# 可视化结果
hist = pygal.Bar()
❷ hist.title = "Results of rolling a D6 and a D10 50,000 times."
hist.x_labels = ['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12',
'13', '14', '15', '16']
hist.x_title = "Result"
hist.y_title = "Frequency of Result"
hist.add('D6 + D10', frequencies)
hist.render_to_file('dice_visual.svg')
为创建D10骰子,我们在创建第二个Die 实例时传递了实参10 (见❶)。我们还修改了第一个循环,以模拟掷骰子50 000次而不是1000次。可能出现的最小总点数依然是2,但现
在可能出现的最大总点数为16,因此我们相应地调整了标题、x 轴标签和数据系列标签(见❷)。
可能性最大的点数不是一个,而是5个,这是因为导致出现最小点数和最大点数的组合都只有一种(1和1以及6和10),但面数较小的骰子限制了得到中间点数的组合数:得到总点数7、8、9、10和11的组合数都是6种。因此,这些总点数是最常见的结果,它们出现的可能性相同。
通过使用Pygal来模拟掷骰子的结果,能够非常自由地探索这种现象。只需几分钟,就可以掷各种骰子很多次。