数据可视化指的是通过可视化表示来探索数据,它与数据挖掘紧密相关,而数据挖掘指的是使用代码来探索数据集的规律和关联。数据集可以是用一行代码就能表示的小型数字列表,也可以是数以G字节的数据。
学习内容:
系统自带的Python版本:
$ sudo apt-get install python3-matplotlib
Python 2.7:
$ sudo apt-get install python-matplotlib
如果安装了较新的Python版本,就必须安装matplotlib依赖的一些库:
$ sudo apt-get install python3.5-dev python3.5-tk tk-dev
$ sudo apt-get install libfreetype6-dev g++
$ pip install --user matplotlib
首先需要安装Visual Studio
接下来,需要下载matplotlib安装程序。为此,请访问https://pypi.python.org/pypi/matplotlib/,
并查找与你使用的Python版本匹配的wheel文件(扩展名为.whl的文件)
将这个.whl文件复制到你的项目文件夹,打开一个命令窗口,并切换到该项目文件夹
再使用pip来安装matplotlib:
> cd python_work
python_work> python -m pip install --user matplotlib-1.4.3-cp35-none-win32.whl
import matplotlib
要查看使用matplotlib可制作的各种图表,请访问http://matplotlib.org/的示例画廊。单击画廊中的图表,就可查看用于生成图表的代码。
import matplotlib.pyplot as plt
'''导入了模块pyplot,定了别名plt'''
squares = [1, 4, 9, 16, 25]
plt.plot(squares)
plt.show() # 打开matplotlib查看器,并显示绘制的图形
import matplotlib.pyplot as plt
squares = [1, 4, 9, 16, 25]
plt.plot(squares, linewidth=5) # linewidth:线条粗细参数
plt.title("Square Numbers", fontsize=24) # 标题及字体
plt.xlabel("Value", fontsize=14) # x轴标题及字体
plt.ylabel("Square of Value", fontsize=14) # y轴标题及字体
plt.tick_params(axis='both', labelsize=14) # 刻度大小
plt.show()
'''横坐标的平方对应纵坐标的值'''
import matplotlib.pyplot as plt
input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.plot(input_values, squares, linewidth=5) # 横坐标的平方对应纵坐标的值
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.tick_params(axis='both', labelsize=14)
plt.show()
可能想以一种颜色显示较小的值,而用另一种颜色显示较大的值。绘制大型数据集时,你还可以对每个点都设置同样的样式,再使用不同的样式选项重新绘制某些点,以突出它们。
import matplotlib.pyplot as plt
plt.scatter(2, 4) # 单点:传递x,y坐标
plt.show()
import matplotlib.pyplot as plt
plt.scatter(2, 4, s=200) # 参数s决定绘制点的尺寸
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.show()
import matplotlib.pyplot as plt
x_values = [1, 2, 3, 4, 5]
y_values = [1, 4, 9, 16, 25]
plt.scatter(x_values, y_values, s=100) # 一系列点传入
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.show()
import matplotlib.pyplot as plt
x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values] # x的平方值:解析列表
plt.scatter(x_values, y_values, s=40)
plt.axis([0, 1100, 0, 1100000]) # axis指定x,y的取值范围
plt.show()
plt.scatter(x_values, y_values, edgecolor='none', s=40)
plt.show()
plt.scatter(x_values, y_values, c='red', edgecolor='none', s=40)
plt.show()
plt.scatter(x_values, y_values, c=(0, 0, 0.8), edgecolor='none', s=40)
plt.show() # 表示红色,绿色,蓝色的分量。输出是深蓝色,越接近1颜色越浅
颜色映射(colormap)是一系列颜色,它们从起始颜色渐变到结束颜色。在可视化中,颜色映射用于突出数据的规律,例如,你可能用较浅的颜色来显示较小的值,并使用较深的颜色来显示较大的值。
import matplotlib.pyplot as plt
x_values = list(range(1001))
y_values = [x**2 for x in x_values]
plt.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues,
edgecolor='none', s=40) # cmap表示哪种渐变色,y值小的颜色浅大的颜色深
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.show()
要让程序自动将图表保存到文件中,可将对plt.show()的调用替换为对plt.savefig()的调用
plt.savefig('squares_plot.png', bbox_inches='tight')
# 第一个参数表示文件名和路径,第二个参数表示将空白区域减掉,默认是留存
将使用Python来生成随机漫步数据,再使用matplotlib以引人瞩目的方式将这些数据呈现出来。随机漫步是这样行走得到的路径:
为模拟随机游走,创建一个名为RandomWalk的类,它随机地选择前进方向。
这个类需要三个属性:
其中一个是存储随机游走次数的变量,其他两个是列表,分别存储随机游走经过的每个点的x和y坐标。
RandomWalk类只包含两个方法:
__init__ ()
和fill_walk()
,其中后者计算随机漫步经过的所有点。
from random import choice
class RandomWalk():
"""一个生成随机漫步数据的类"""
def __init__(self, num_points=5000): # 默认点数为500
"""初始化随机漫步的属性"""
self.num_points = num_points
# 所有随机漫步都始于(0, 0)
self.x_values = [0]
self.y_values = [0]
# 将其加入到上面的类中
def fill_walk(self):
"""计算随机漫步包含的所有点"""
# 不断漫步,直到列表达到指定的长度
while len(self.x_values) < self.num_points:
# 决定前进方向以及沿这个方向前进的距离
x_direction = choice([1, -1]) # x的选择,又走1或左走1
x_distance = choice([0, 1, 2, 3, 4]) # 沿着方向走的距离0~4
x_step = x_direction * x_distance # 沿着x轴走的距离
y_direction = choice([1, -1])
y_distance = choice([0, 1, 2, 3, 4])
y_step = y_direction * y_distance
# 拒绝原地踏步
if x_step == 0 and y_step == 0:
continue # 接着执行下一个循环
# 计算下一个点的x和y值,x_values最后一个值和x_step相加
next_x = self.x_values[-1] + x_step
next_y = self.y_values[-1] + y_step
self.x_values.append(next_x) # 下一个点追加到x值的末尾
self.y_values.append(next_y)
import matplotlib.pyplot as plt
from random_walk import RandomWalk # 模块random_walk,类RandomWalk
rw = RandomWalk() # 创建一个RandomWalk实例
rw.fill_walk() # 随机游走的5000个点
plt.scatter(rw.x_values, rw.y_values, s=10)
plt.show()
每次随机漫步都不同,因此探索可能生成的各种模式很有趣。要在不多次运行程序的情况下使用前面的代码模拟多次随机漫步,一种办法是将这些代码放在一个 while 循环中
import matplotlib.pyplot as plt
from random_walk import RandomWalk
# 只要程序处于活动状态,就不断地模拟随机漫步
while True:
# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values, rw.y_values, s=15)
plt.show()
keep_running = input("Make another walk? (y/n): ")
if keep_running == 'n':
break
import matplotlib.pyplot as plt
from random_walk import RandomWalk
rw = RandomWalk()
rw.fill_walk()
# 参数c,并将其设置为一个列表,其中包含各点的先后顺序。
point_numbers = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues,
edgecolor='none', s=15)
plt.show()
import matplotlib.pyplot as plt
from random_walk import RandomWalk
rw = RandomWalk()
rw.fill_walk()
point_numbers = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, c=point_numbers,
cmap=plt.cm.Blues,edgecolor='none', s=15)
plt.scatter(0, 0, c='green', edgecolors='none', s=100)
plt.scatter(rw.x_values[-1], rw.y_values[-1], c='red',
edgecolors='none',s=100)
plt.show()
下面来隐藏这个图表中的坐标轴,以免我们注意的是坐标轴而不是随机漫步路径。要隐藏坐标轴
import matplotlib.pyplot as plt
from random_walk import RandomWalk
rw = RandomWalk()
rw.fill_walk()
point_numbers = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, c=point_numbers,
cmap=plt.cm.Blues,edgecolor='none', s=15)
plt.scatter(0, 0, c='green', edgecolors='none', s=100)
plt.scatter(rw.x_values[-1], rw.y_values[-1], c='red',
edgecolors='none',s=100)
# 隐藏坐标轴
plt.axes().get_xaxis().set_visible(False) # 串联的方式
plt.axes().get_yaxis().set_visible(False)
plt.show()
rw = RandomWalk(50000) # 即可
函数figure()用于指定图表的宽度、高度、分辨率和背景色。你需要给形参figsize指定一个元组,向matplotlib指出绘图窗口的尺寸,单位为英寸
plt.figure(figsize=(10, 6)) # 加入该命令
plt.figure(dpi=128, figsize=(10, 6))
要在文本文件中存储数据,最简单的方式是将数据作为一系列以逗号分隔的值(CSV)写入文件。这样的文件称为CSV文件。
csv 模块包含在Python标准库中,可用于分析CSV文件中的数据行,让我们能够快速提取感兴趣的值。
import csv
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader) # next只调用一次,返回的是第一行
print(header_row)
[‘AKDT’, ‘Max TemperatureF’, ‘Mean TemperatureF’, ‘Min TemperatureF’, ‘Max Dew PointF’, ‘MeanDew PointF’, ‘Min DewpointF’, ‘Max Humidity’, ’ Mean Humidity’, ’ Min Humidity’, ’ Max Sea Level PressureIn’, ’ Mean Sea Level PressureIn’, ’ Min Sea Level PressureIn’, ’ Max VisibilityMiles’, ’ Mean VisibilityMiles’, ’ Min VisibilityMiles’, ’ Max Wind SpeedMPH’, ’ Mean Wind SpeedMPH’, ’ Max Gust SpeedMPH’, ‘PrecipitationIn’, ’ CloudCover’, ’ Events’, ’ WindDirDegrees’]
import csv
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader) # 文件第一行是一个列表
for index, column_header in enumerate(header_row): # for循环索引列表
print(index, column_header) # enumerate():获取每个元素的索引及其值
0 AKDT
1 Max TemperatureF
2 Mean TemperatureF
3 Min TemperatureF
…
# 取第二列的数据
import csv
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader) # 读完之后reader已无第一行
highs = [] # 空列表
for row in reader: # 遍历行,实际上从第二行开始
high = int(row[1]) # 第二列数据由字符串类型转为整型
highs.append(high) # 索引1加入最高气温里
print(highs)
[64, 71, 64, 59, 69, 62, 61, 55, 57, 61, 57, 59, 57, 61, 64, 61, 59, 63, 60, 57, 69, 63, 62, 59, 57, 57, 61, 59, 61, 61, 66]
import csv
from matplotlib import pyplot as plt # 画廊模块
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
highs = []
for row in reader:
high = int(row[1])
highs.append(high)
print(highs)
# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6)) # figure像素和长宽
plt.plot(highs, c='red')
# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()
读取该数据时,获得的是一个字符串,因为我们需要想办法将字符串’2014-7-1’转换为一个表示相应日期的对象。为创建一个表示2014年7月1日的对象,可使用模块datetime中的方法strptime()
方法 strptime():
from datetime import datetime # 导入模块datetime模块的datetime类
first_date = datetime.strptime('2014-7-1', '%Y-%m-%d') # 转换日期格式
print(first_date) # 2014-07-01 00:00:00
模块datetime中设置日期和时间格式的实参:
日期和时间格式 | 解释 |
---|---|
%A | 星期的名称,如Monday |
%B | 月份名,如January |
%m | 用数字表示的月份(01~12) |
%d | 用数字表示月份中的一天(01~31) |
%Y | 四位的年份,如2015 |
%y | 两位的年份,如15 |
%H | 24小时制的小时数(00~23) |
%I | 12小时制的小时数(01~12) |
%p | am或pm |
%M | 分钟数(00~59) |
%S | 秒数(00~61) |
import csv
from datetime import datetime
from matplotlib import pyplot as plt
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs = [], [] # 创建两个空列表
for row in reader:
current_date = datetime.strptime(row[0], "%Y-%m-%d") # 日期在row的第一列
dates.append(current_date) # 储存在dates列表中
high = int(row[1]) # 最高气温在第二列
highs.append(high) # 储存在highs列表中
# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red') # 绘制图形
# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate() # 来绘制斜的日期标签,以免它们彼此重叠
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()
添加上最低气温
import csv
from datetime import datetime
from matplotlib import pyplot as plt
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/sitka_weather_2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs, lows = [], [], [] # 创建三个空列表
for row in reader:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
dates.append(current_date) # 日期列表储存在dates列表中
high = int(row[1])
highs.append(high) # 最高气温列表储存在dates列表中
low = int(row[3])
lows.append(low) # 最低气温列表储存在dates列表中
# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red') # 绘制最高温度-日期图,红色
plt.plot(dates, lows, c='blue') # 绘制最低温度-日期图,蓝色
# 设置图形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate() # 来绘制斜的日期标签,以免它们彼此重叠
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()
使用方法fill_between(),它接受一个x值系列和两个y值系列,并填充两个y值系列之间的空间。
实参alpha指定颜色的透明度:
alpha值为0表示完全透明,1(默认设置)表示完全不透明。
fill_between():
传递两个关于x轴的序列值,实参facecolor指定了填充区域的颜色
--snip--
# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red', alpha=0.5) # alpha=0,无色。点的透明度
plt.plot(dates, lows, c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)
# 设置图形的格式
--snip--
某个数据缺失或为空字符串
import csv
from datetime import datetime
from matplotlib import pyplot as plt
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/death_valley_2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs, lows = [], [], []
for row in reader: # 循环来发现缺失数据
try:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except ValueError:
print(current_date, 'missing data') # 输出缺失数据的日期
else: # 没有发生错误则运行else代码
dates.append(current_date)
highs.append(high)
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)
# 设置图形的格式
title = "Daily high and low temperatures - 2014\nDeath Valley, CA"
plt.title(title, fontsize=20)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()
列表,元素是字典,每个字典包含四个键值对
[
{
"Country Name": "Arab World",
"Country Code": "ARB",
"Year": "1960",
"Value": "96388069"
},
--snip--
]
import json
# 将数据加载到一个列表中
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/population_data.json'
with open(filename) as f:
pop_data = json.load(f) # 将数据储存在此
# 打印每个国家2010年的人口数量
for pop_dict in pop_data: # 遍历每个元素,元素都是字典
if pop_dict['Year'] == '2010': # 是不是2010年的人数
country_name = pop_dict['Country Name']
population = pop_dict['Value']
print(country_name + ": " + population)
Arab World: 357868000
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
…
Python不能直接将包含小数点的字符串’1127437398.85751’转换为整数(这个小数值可能是人口数据缺失时通过插值得到的)。
为消除这种错误,我们先将字符串转换为浮点数,再将浮点数转换为整数
import json
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/population_data.json'
with open(filename) as f:
pop_data = json.load(f)
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = int(float(pop_dict['Value']))
print(country_name + ": " + str(population))
Arab World: 357868000
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
…
from pygal_maps_world.i18n import COUNTRIES
for country_code in sorted(COUNTRIES.keys()): # 按国别码排序
print(country_code, COUNTRIES[country_code]) # cn China
# 在COUNTRIES中查找并返回国别码
from pygal_maps_world.i18n import COUNTRIES
def get_country_code(country_name): # 函数接收国名
"""根据指定的国家,返回Pygal使用的两个字母的国别码"""
for code, name in COUNTRIES.items():
if name == country_name: # 如果实参国家名称=国别码的国家名称则返回国别码
return code
# 如果没有找到指定的国家,就返回None
return None
print(get_country_code('Andorra')) # ad
print(get_country_code('United Arab Emirates')) # ae
print(get_country_code('China')) # cn
# 在world_population.py中导入上面函数
import json
from pygal_maps_world.i18n import COUNTRIES
def get_country_code(country_name):
for code, name in COUNTRIES.items():
if name == country_name:
return code
return None
# 打印每个国家2010年的人口数量
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/population_data.json'
with open(filename) as f:
pop_data = json.load(f) # pop_data文件名
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country_name = pop_dict['Country Name']
population = int(float(pop_dict['Value']))
code = get_country_code(country_name) # code储存国别码
if code: # 如果文件里国家名称对应有国别码输出国别码和人口数量
print(code + ": " + str(population)) # cn: 1338300000
else: # 否则输出错误,看看是那个国家
print('ERROR - ' + country_name) # ERROR - Comoros
pygal_maps_world提供了图表类型Worldmap,可帮助你制作呈现各国数据的世界地图。
from pygal_maps_world import maps
wm = maps.World() # 创建实例
wm.title = 'North, Central, and South America' # 标题
wm.add('North America', ['ca', 'mx', 'us']) # 参数:标签、国别码
wm.add('Central America', ['bz', 'cr', 'gt', 'hn', 'ni', 'pa', 'sv'])
wm.add('South America', ['ar', 'bo', 'br', 'cl', 'co', 'ec', 'gf',
'gy', 'pe', 'py', 'sr', 'uy', 've'])
wm.render_to_file('americas.svg') # 浏览器打开
from pygal_maps_world import maps
# 先看看北美三个国家的人口数量
wm = maps.World() # 创建地图实例
wm.title = 'Populations of Countries in North America'
wm.add('North America', {'ca': 34126000, 'us': 309349000, 'mx': 113423000})
# 第二个参数传递的是字典
wm.render_to_file('na_populations.svg')
import json
from pygal_maps_world import maps # 画图使用
from pygal_maps_world.i18n import COUNTRIES # 国别码使用
# 定义get_country_code函数输入国家名称返回国别码
def get_country_code(country_name):
for code, name in COUNTRIES.items():
if name == country_name:
return code
return None
# 读取文件
filename = '/home/deyouli/PythonCrashCourse/pcc-master/chapter_16/population_data.json'
with open(filename) as f:
pop_data = json.load(f)
# 根据文件里国家名称利用get_country_code函数得到国别码,二者放在一个字典里
cc_populations = {}
for pop_dict in pop_data:
if pop_dict['Year'] == '2010':
country = pop_dict['Country Name']
population = int(float(pop_dict['Value']))
code = get_country_code(country) # 利用前面的函数得到国别码
if code:
cc_populations[code] = population # 键值对:国别码:人口数量
wm = maps.World() # 创建地图实例
wm.title = 'World Population in 2010, by Country'
wm.add('2010', cc_populations) # 循环后的世界各国国别码和人口数量
wm.render_to_file('world_population.svg')
但只有大约3亿。下面不将所有国家都作为一个编组,而是根据人口数量分成三组——少于1000万的、介于1000万和10亿之间的以及超过10亿的
--snip--
# 根据人口数量将所有的国家分成三组
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_populations.items(): # 国别码和人口数量
if pop < 10000000:
cc_pops_1[cc] = pop
elif pop < 1000000000:
cc_pops_2[cc] = pop
else:
cc_pops_3[cc] = pop
# 看看每组分别包含多少个国家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))
wm = maps.World()
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bn', cc_pops_2)
wm.add('>1bn', cc_pops_3)
wm.render_to_file('world_population.svg')
from pygal.style import RotateStyle # 样式
--snip--
wm_style = RotateStyle('#336699') # 十六进制的RGB颜色
wm = maps.World(style=wm_style) # 使用上面的样式
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bn', cc_pops_2)
wm.add('>1bn', cc_pops_3)
wm.render_to_file('world_population.svg')
十六进制格式的RGB颜色是一个以井号(#)打头的字符串,后面跟着6个字符,其中前两个字符表示红色分量,接下来的两个表示绿色分量,最后两个表示蓝色分量。每个分量的取值范围为00(没有相应的颜色)~FF(包含最多的相应颜色)。
RotateStyle返回一个样式对象,我们将其存储在wm_style中。
为使用这个样式对象,我们在创建Worldmap实例时以关键字实参的方式传递它。
使用 LightColorizedStyle 加亮了地图的颜色。
这个类修改整个图表的主题,包括背景色、标签以及各个国家的颜色。
from pygal.style import LightColorizedStyle
wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)
Web API是网站的一部分,用于与使用非常具体的URL请求特定信息的程序交互。这种请求称为API调用。请求的数据将以易于处理的格式(如JSON或CSV)返回。依赖于外部数据源的大多数应用程序都依赖于API调用,如集成社交媒体网站的应用程序。
本章的可视化将基于来自GitHub的信息,这是一个让程序员能够协作开发项目的网站。我们将使用GitHub的API来请求有关该网站中Python项目的信息,然后使用Pygal生成交互式可视化,以呈现这些项目的受欢迎程度。
GitHub(https://github.com/)的名字源自Git,Git是一个分布式版本控制系统,让程序员团队能够协作开发项目。Git帮助大家管理为项目所做的工作,避免一个人所做的修改影响其他人所做的修改。你在项目中实现新功能时,Git将跟踪你对每个文件所做的修改。确定代码可行后,你提交所做的修改,而Git将记录项目最新的状态。如果你犯了错,想撤销所做的修改,可轻松地返回以前的任何可行状态(要更深入地了解如何使用Git进行版本控制,请参阅附录D)。GitHub上的项目都存储在仓库中,后者包含与项目相关联的一切:代码、项目参与者的信息、问题或bug报告等。
对于喜欢的项目,GitHub用户可给它加星(star)以表示支持,用户还可跟踪他可能想使用的项目。在本章中,我们将编写一个程序,它自动下载GitHub上星级最高的Python项目的信息,并对这些信息进行可视化。
GitHub的API让你能够通过API调用来请求各种信息。要知道API调用是什么样的,请在浏览器的地址栏中输入如下地址并按回车键:
https://api.github.com/search/repositories?q=language:python&sort=stars
这个调用返回GitHub当前托管了多少个Python项目,还有有关最受欢迎的Python仓库的信息。下面来仔细研究这个调用。第一部分( https://api.github.com/ )将请求发送到GitHub网站中响应API调用的部分;接下来的一部分( search/repositories )让API搜索GitHub上的所有仓库。
repositories 后面的问号指出我们要传递一个实参。 q 表示查询,而等号让我们能够开始指定查询( q= )。通过使用 language:python ,我们指出只想获取主要语言为Python的仓库的信息。最后一部分( &sort=stars )指定将项目按其获得的星级进行排序。
从第二行输出可知,编写本书时, GitHub总共有3874583个Python项目。“incomplete_results"的值为 false ,据此我们知道请求是成功的(它并非不完整的)。倘若GitHub无法全面处理该API,它返回的这个值将为 true 。接下来的列表中显示了返回的"items” ,其中包含GitHub上最受欢迎的Python项目的详细信息。
requests包让Python程序能够轻松地向网站请求信,息以及检查返回的响应。要安装requests,请执行类似于下面的命令:
$ pip install --user requests
来编写一个程序,它执行API调用并处理结果,找出GitHub上星级最高的Python项目
import requests
# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url) # 使用requests执行调用,调用get将URL传递
print("Status code:", r.status_code) # 属性status_code告诉请求是否成功了是的话200
# Status code: 200
# 将API响应存储在一个变量中,API返回JSON格式的信息
response_dict = r.json() # 使用json将json格式转换为字典
# 处理结果
print(response_dict.keys()) # dict_keys(['total_count', 'incomplete_results', 'items'])
将API调用返回的信息存储到字典中后,就可以处理这个字典中的数据了。下面来生成一些概述这些信息的输出。这是一种不错的方式,可确认收到了期望的信息,进而可以开始研究感兴趣的信息:
import requests
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url) # 使用requests执行调用,调用get将URL传递
print("Status code:", r.status_code) # 属性status_code告诉请求是否成功了是的话200
response_dict = r.json()
print("Total repositories:", response_dict['total_count']) # 3847989
# 探索有关仓库的信息
repo_dicts = response_dict['items']
print("Repositories returned:", len(repo_dicts)) # 30
# 研究第一个仓库
repo_dict = repo_dicts[0]
print("\nKeys:", len(repo_dict)) # 第一个包含74个键
print("\nSelected information about first repository:")
print('Name:', repo_dict['name'])
print('Owner:', repo_dict['owner']['login'])
print('Stars:', repo_dict['stargazers_count'])
print('Repository:', repo_dict['html_url'])
print('Created:', repo_dict['created_at'])
print('Updated:', repo_dict['updated_at'])
print('Description:', repo_dict['description'])
'''
从输出可知:
项目名称:awesome-python
项目所有者:vinta
星评级:68296
项目在GitHub仓库的URL: https://github.com/vinta/awesome-python
项目创建时间:2014-06-27T21:00:06Z
最近更新时间:2019-05-13T05:45:00Z
仓库描述:A curated list of awesome Python frameworks, libraries, software and resources
'''
for key in sorted(repo_dict.keys()):
print(key)
对这些数据进行可视化时,我们需要涵盖多个仓库。下面就来编写一个循环,打印API调用返回的每个仓库的特定信息,以便能够在可视化中包含所有这些信息:
--snip--
print("\nSelected information about each repository:")
for repo_dict in repo_dicts: # 30个仓库
print('\nName:', repo_dict['name']) # 项目名称
print('Owner:', repo_dict['owner']['login']) # 所有者
print('Stars:', repo_dict['stargazers_count']) # 星级
print('Repository:', repo_dict['html_url']) # url
print('Description:', repo_dict['description']) # 描述
大多数API都存在速率限制,即你在特定时间内可执行的请求数存在限制。要获悉你是否接近了GitHub的限制,请在浏览器中输入https://api.github.com/rate_limit
很多API都要求你注册获得API密钥后才能执行API调用。编写本书时,GitHub没有这样
的要求,但获得API密钥后,配额将高得多。
有了一些有趣的数据后,我们来进行可视化,呈现GitHub上Python项目的受欢迎程度。我们将创建一个交互式条形图:条形的高度表示项目获得了多少颗星。
import requests
import pygal # 导入pygal及样式
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url)
print("Status code:", r.status_code)
response_dict = r.json()
print("Total repositories:", response_dict['total_count']) # 3790192
repo_dicts = response_dict['items']
names, stars = [], [] # 创建两个空列表储存项目名和星级
for repo_dict in repo_dicts:
names.append(repo_dict['name'])
stars.append(repo_dict['stargazers_count'])
# 可视化
my_style = LS('#333366', base_style=LCS) # 定义样式,使用LightColorizedStyle
chart = pygal.Bar(style=my_style, x_label_rotation=45, show_legend=False) # 45°,隐藏图例
chart.title = 'Most-Starred Python Projects on GitHub'
chart.x_labels = names
chart.add('', stars) # 不需要标签
chart.render_to_file('python_repos.svg')
进行多个方面的定制,因此先来稍微调整代码的结构,创建一个配置对象,在其中包含要传递给Bar()的所有定制
--snip--
my_config = pygal.Config() # 定制外观
my_config.x_label_rotation = 45
my_config.show_legend = False
my_config.title_font_size = 24
my_config.label_font_size = 14
my_config.major_label_font_size = 18
my_config.truncate_label = 15
my_config.show_y_guides = False
my_config.width = 1000
chart = pygal.Bar(my_config, style=my_style) # 创建bar实例
--snip--
在Pygal中,将鼠标指向条形将显示它表示的信息,这通常称为工具提示创建一个自定义工具提示,以同时显示项目的描述
import pygal
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS
my_style = LS('#333366', base_style=LCS)
chart = pygal.Bar(style=my_style, x_label_rotation=45, show_legend=False)
chart.title = 'Python Projects'
chart.x_labels = ['httpie', 'django', 'flask'] # x标签
plot_dicts = [
{'value': 16101, 'label': 'Description of httpie.'},
{'value': 15028, 'label': 'Description of django.'},
{'value': 14798, 'label': 'Description of flask.'},
]
chart.add('', plot_dicts) # 方法 add() 接受一个字符串和一个列表
chart.render_to_file('bar_descriptions.svg')
--snip--
names, plot_dicts = [], []
for repo_dict in repo_dicts:
names.append(repo_dict['name'])
plot_dict = {
'value': repo_dict['stargazers_count'],
'label': repo_dict['description'],
}
plot_dicts.append(plot_dict)
chart.add('', plot_dicts)
--snip--
--snip--
plot_dict = {
'value': repo_dict['stargazers_count'],
'label': repo_dict['description'],
'xlink': repo_dict['html_url'],
}
plot_dicts.append(plot_dict)
--snip--
为探索如何使用其他网站的API调用,我们来看看Hacker News ( http://news.ycombinator. com/)在Hacker News网站,用户分享编程和技术方面的文章,并就这些文章展开积极的讨论HackerNews的API让你能够访问有关该网站所有文章和评论的信息,且不要求你通过注册获得密钥。
https://hacker-news.firebaseio.com/v0/item/9884165.json
import requests
from operator import itemgetter
# 执行API调用并存储响应
url = 'https://hacker-news.firebaseio.com/v0/topstories.json'
r = requests.get(url)
print("Status code:", r.status_code) # 打印响应状态
# 处理有关每篇文章的信息
submission_ids = r.json() # 转为Python列表
submission_dicts = [] # 空列表,储存前面的字典
for submission_id in submission_ids[:30]: # 遍历30篇文章的ID
# 对于每篇文章,都执行一个API调用
url = ('https://hacker-news.firebaseio.com/v0/item/' +
str(submission_id) + '.json')
submission_r = requests.get(url)
print(submission_r.status_code) # 打印每次请求状态
response_dict = submission_r.json() # Python列表
submission_dict = {
'title': response_dict['title'],
'link': 'http://news.ycombinator.com/item?id=' + str(submission_id),
'comments': response_dict.get('descendants', 0)
}
submission_dicts.append(submission_dict)
submission_dicts = sorted(submission_dicts, key=itemgetter('comments'),
reverse=True)
for submission_dict in submission_dicts:
print("\nTitle:", submission_dict['title'])
print("Discussion link:", submission_dict['link'])
print("Comments:", submission_dict['comments'])