数据可视化--上(2018 2.19 14:02)

生成数据

安装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
%H24小时制的小时数(00~23)
%I12小时制的小时数(00~12)
%pam或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 值系列,两个y值系列,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--

输出:
这里写图片描述

差别不大,就是从亮光到了哑光。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值