报告自动化生成

5.2 图表绘制 : 作图模块化及模板:以柱状图为例

2018-07-26  本文已影响20人  数据成长之路
# -*- coding:utf-8 -*-
import numpy as np
from IPython.display import Image
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.font_manager as mfm
import matplotlib.gridspec as gridspec
import matplotlib.ticker as plticker

目标图表形式

Image('./source/plot_bar.png')
data = np.array([['$0-$5', 8],
       ['$5-$10', 28],
       ['$10-$15', 22],
       ['$15-$20', 15],
       ['$20-$25', 9],
       ['$25-$30', 13],
       ['$30-$35', 12],
       ['$35-$40', 2],
       ['$40-$45', 3],
       ['$50以上', 11]], dtype=object)
data[:,1]
array([8, 28, 22, 15, 9, 13, 12, 2, 3, 11], dtype=object)

前期准备

准备需要的字体handle

要想在图表中显示中文,需要使用中文字体,而matplotlib中提供了直接使用自定义字体的handle,我们只需要预设好想要使用的字体。

# 把字体文件放入font文件夹中,也可以在此设置字体文件的路径
font_path = {'hei':'./font/MSYHMONO.ttf',
    'english' : './font/Calibri.ttf'}
# 实例化可以直接使用的字体handle
prop = {font_name : mfm.FontProperties(fname=font_path[font_name]) 
        for font_name in list(font_path)}
# 设置可选配色
default_colors = {}
default_colors['blue'] = '#6CADDD'
default_colors['yellow'] = '#F3903F'
default_colors['red'] = '#CB615A'
default_colors['orange'] = '#F3903F'
default_colors['gray'] = '#B4B4B4'
default_colors['lightyellow'] = '#FCC900'
default_colors['royalblue'] = '#5488CF'

做出初始的图布

length = len(data)
x = np.arange(length)
y = data[:,1]
# 首先设置作图的大小
IMAGE_WIDTH = 5.708
IMAGE_HIGH = 2.756
# 实例化Figure
fig = plt.figure(figsize=(IMAGE_WIDTH, IMAGE_HIGH))
# 添加gs,方便后续的设置
gs = gridspec.GridSpec(1, 1)
# 在gs上添加作图用的Axes
ax = fig.add_subplot(gs[0])
fig

设置横坐标标签

# 字体为黑体9号
xticks_font = prop['hei'].copy()
xticks_font.set_size(9)
# 文字的倾斜角度为水平
xticks_rotation = 'horizontal'
labels = data[:,0]
plt.xticks(x, labels, fontproperties=xticks_font, rotation=xticks_rotation) 
fig
labels
array(['$0-$5', '$5-$10', '$10-$15', '$15-$20', '$20-$25', '$25-$30',
       '$30-$35', '$35-$40', '$40-$45', '$50以上'], dtype=object)

注意

labels中的结果使用'$0-$5'的形式无法正常显示$,需要在$前加入\

labels = list(map(lambda x:x.replace('$', '\$'), data[:,0]))
plt.xticks(x, labels, fontproperties=xticks_font, rotation=xticks_rotation) 
fig

画出基本图形

rects = plt.bar(x, y, 0.4, zorder=3, color=default_colors['blue'])
fig

添加标注

for rect in rects:
    height = rect.get_height()
    ax.text(rect.get_x() + rect.get_width()/2., 1.05*height, 
                '%d' % int(height), 
                ha='center', va='bottom')
fig

调整边框Spines

ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
fig

去除多余的Tick

ax.tick_params(axis='both', which='both', bottom=False, top=False, 
        labelbottom=True, left=False, right=False, labelleft=True)
fig

添加标题

# 设置标题为黑体,14号
title_font = prop['hei'].copy()
title_font.set_size(14)
# 设置标题内容
title = "榜单商品价格分布"
# 设置标题相对图片的位置
title_y = 1.2
ax.set_title(title, fontproperties=title_font, y=title_y)
fig

添加图例

# 设置图例格式
legend_font = prop['hei'].copy()
legend_font.set_size(9)
# 图例内容
legend_name=['商品数量']
ax.legend(legend_name, loc='upper right', bbox_to_anchor=(1, 1.2), prop=legend_font, frameon=True)
fig

紧凑排布

现在虽然内容已经添加设置完毕,但是排布太过稀松,需要使用较为紧密的排布

gs.tight_layout(fig)
fig

添加Grid

使用快速grid可以添加方格状的grid,但是我们需要的是对y轴进行5-6次分割的grid,所以我们可以手动设置分割间隔。

ax.grid()
fig

使用快速grid可以添加方格状的grid,但是我们需要的是对y轴进行5-6次分割的grid,所以我们可以手动设置分割间隔。

intervals = 5
loc = plticker.MultipleLocator(base=intervals)
ax.yaxis.set_major_locator(loc)
ax.grid(axis='y', zorder=0)
fig

不过每一次都手动设定y轴间隔太过麻烦了,所以我们可以手动写个自动划分的函数,确定间隔

# 获取y轴取值范围
get_ax_space = lambda x: x.get_ylim()[1] - x.get_ylim()[0]
ax_space = get_ax_space(ax)
print("y轴取值范围 : {}".format(ax_space))
def get_interval(ax_space, space_number=5):
    digit_number = len(str((ax_space)))
    intervals = int((ax_space)/(space_number*(10**digit_number)))
    while intervals == 0:
        digit_number -= 1
        intervals = int((ax_space)/(space_number*(10**digit_number)))
    linshi = round((ax_space)/(space_number*(10**digit_number)))
    intervals = linshi*(10**digit_number)
    return intervals

intervals = get_interval(ax_space)
print("y轴grid间隔为 :{}".format(intervals))

loc = plticker.MultipleLocator(base=intervals)
ax.yaxis.set_major_locator(loc)
ax.grid(axis='y', zorder=0)
fig
y轴取值范围 : 29.4
y轴grid间隔为 :6.0

模块化: 整理为可复用的模板(作业)

模块化结果

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.font_manager as mfm
import matplotlib.gridspec as gridspec
import matplotlib.ticker as plticker

class Image(object):
    font_path = {}
    prop = {}
    font_path['hei'] = './font/MSYHMONO.ttf'
    font_path['english'] = './font/Calibri.ttf'
    for font_name in list(font_path):
        prop[font_name] = mfm.FontProperties(fname=font_path[font_name])

    title_font = prop['hei'].copy()
    title_font.set_size(14)
    xticks_font = prop['hei'].copy()
    xticks_font.set_size(9)
    ylable_font = prop['hei'].copy()
    ylable_font.set_size(10)
    legend_font = prop['hei'].copy()
    legend_font.set_size(9)
    default_colors = {}
    default_colors['blue'] = '#6CADDD'
    default_colors['yellow'] = '#F3903F'
    default_colors['red'] = '#CB615A'
    default_colors['orange'] = '#F3903F'
    default_colors['gray'] = '#B4B4B4'
    default_colors['lightyellow'] = '#FCC900'
    default_colors['royalblue'] = '#5488CF'

    IMAGE_WIDTH = 5.708
    IMAGE_HIGH = 2.756

    def __init__(self, title=None, labels=None, data=None, 
        image_path=None, title_y=1.1, xticks_rotation='vertical', legend_name=[]):
        self.length = len(data)
        self.x = np.arange(self.length)
        self.y = data
        self.data = data
        self.title_y = title_y
        self.title = title
        self.labels = labels
        self.legend_name = legend_name
        self.xticks_rotation = xticks_rotation

    def init(self):
        self.fig = plt.figure(figsize=(self.IMAGE_WIDTH, self.IMAGE_HIGH))
        self.gs = gridspec.GridSpec(1, 1)
        self.ax = self.fig.add_subplot(self.gs[0])
        self.set_xticks()
        self.add_title()
        self.plot()
        self.set_spines()
        self.set_tick_marks()
        self.add_legend()
        # 补充设定
        self.config_add()
        self.tight_layout()
        self.set_grid()
        plt.close()

    def add_legend(self):
        if not (self.legend_name is None):
            self.ax.legend(self.legend_name, loc='upper right', bbox_to_anchor=(1, 1.2), prop=self.legend_font, frameon=True)

    def tight_layout(self, **karg):
        self.gs.tight_layout(self.fig, **karg)

    def plot(self):
        rects = plt.bar(self.x, self.y, 0.4, zorder=3, color=self.default_colors['blue'])
        for rect in rects:
            height = rect.get_height()
            self.ax.text(rect.get_x() + rect.get_width()/2., 1.05*height, 
                        '%d' % int(height), 
                        ha='center', va='bottom')
    def add_title(self):
        if self.title:
            self.ax.set_title(self.title, fontproperties=self.title_font, y=self.title_y)

    def set_grid(self):
        get_ax_space = lambda x: x.get_ylim()[1] - x.get_ylim()[0]
        self.ax_space = get_ax_space(self.ax)
        def get_interval(ax_space, space_number=5):
            digit_number = len(str((ax_space)))
            intervals = int((ax_space)/(space_number*(10**digit_number)))
            while intervals == 0:
                digit_number -= 1
                intervals = int((ax_space)/(space_number*(10**digit_number)))
            linshi = round((ax_space)/(space_number*(10**digit_number)))
            intervals = linshi*(10**digit_number)
            return intervals

        if not 'intervals' in self.__dict__.keys():
            self.intervals = get_interval(self.ax_space)
        loc = plticker.MultipleLocator(base=self.intervals)
        self.ax.yaxis.set_major_locator(loc)
        self.ax.grid(axis='y', zorder=0)
        if 'ax2' in self.__dict__.keys():
            print("有双轴需要设置副轴grid")
            self.ax_space2 = get_ax_space(self.ax2)
            self.intervals2 = get_interval(self.ax_space2, space_number=5)
            loc2 = plticker.MultipleLocator(base=self.intervals2)
            self.ax2.yaxis.set_major_locator(loc2)

    def set_spines(self):
        self.ax.spines['right'].set_visible(False)
        self.ax.spines['top'].set_visible(False)
        self.ax.spines['left'].set_visible(False)

    def set_tick_marks(self):
        self.ax.tick_params(axis='both', which='both', bottom=False, top=False, 
                labelbottom=True, left=False, right=False, labelleft=True)
        
    def set_xticks(self):
        plt.xticks(self.x, self.labels, fontproperties=self.xticks_font, rotation=self.xticks_rotation) # 设置横坐标标签

    def show(self):
        plt.show()

    def save(self):
        if image_path:
            self.fig.savefig(image_path)
        else:
            logging.warning("Please sure image path firse")
            
    def config_add(self):
        """
        保留用于扩展功能或者设定
        """
        pass
image = Image(data=data[:,1],
             labels=list(map(lambda x:x.replace('$', '\$'), data[:,0])),
             title='榜单商品价格分布',
             xticks_rotation=0,
             legend_name=['商品数量']
             )
image.init()
image.fig
上一篇 下一篇

猜你喜欢

热点阅读