美文网首页
动态排名柱状图的两种做法

动态排名柱状图的两种做法

作者: 弓长巳寸 | 来源:发表于2020-01-29 16:47 被阅读0次

    受B站拜年祭发射最多的弹幕是什么?视频启发,对日常维护工作中的故障硬件也做了一次盘点。这里介绍两种方法,第一种是Python + Matplotlib;第二种是利用GitHub上现成的“轮子”。如果需要快速出图,后者更简单(也更美观)。

    方法一:Python

    MATPLOTLIB的动画类

    MATPLOTLIB包含了一个动画类——Animation,用这个类就可以实现图标的动画效果。先看下官方文档给出的例子:

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    
    fig, ax = plt.subplots()xdata, ydata = [], []ln, = plt.plot([], [], 'ro')
    
    def init():
        ax.set_xlim(0, 2*np.pi)
        ax.set_ylim(-1, 1)
        return ln,
    
    def update(frame):
        xdata.append(frame)
        ydata.append(np.sin(frame))
        ln.set_data(xdata, ydata)
        return ln,
    
    ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                        init_func=init, blit=True)plt.show()
    

    上例中,init初始化图表,update函数则负责更新图表中的数据。最主要的是FuncAnimation,通过不断调用update,并将frames参数中的值依次传递给update,更新图表。这个例子的运行结果如下:


    示例gif

    数据整理

    先将故障硬件的名称、故障数量和发生日期整理好。好在这些数据一直以来都有不定期梳理。梳理后的格式如下:


    整理后的数据

    保存成csv后由pandas读取到DataFrame中,同时解析日期:

    with open('plugin_faults.csv', 'r') as f:
        data = pd.read_csv(f, parse_dates=['date'])
    

    因为柱状图中每根柱的长度是个累计值,而表中的是当天故障的数量,因此要按故障硬件名称累积求和:

    data = data.sort_values('date')
    group = data.groupby('name')
    data['value_cumsum'] = group.cumsum()
    data.sort_values(['date','value_cumsum','name'], inplace=True)
    data.drop_duplicates(['date','name'], keep='last', inplace=True)
    

    填充所有日期,所有硬件类型

    plugins = ['PCU', '电源板', 'AS7-D', 'ESB24-D', 'CPU', 'HWAT-B', 'SW256B', 'SWPRO-C',
               'ETP', 'ET16', '时钟板', 'DRAM', 'TR3T', '硬盘', 'HDSAM-A', 'DCAR1-A', '光模块', '熔丝', '网管交换机'] # 所有硬件名称
    start = datetime(2017,1,1) # 开始日期
    end = datetime(2019,12,31) # 结束日期
    dates = [start + timedelta(days=i) for i in range((end - start).days + 1)]
    index = pd.MultiIndex.from_product([dates, plugins], names = ["date", "name"]) # 3年内所有日期与硬件名称的笛卡尔积作为多重索引
    df = pd.DataFrame(index=index)
    df['value'] = data.set_index(['date', 'name'])['value_cumsum'] # 向DataFrame填入data中的数据
    df = df.unstack(level=1).fillna(method='ffill') # 将硬件名称索引unstack,前向填充累计故障数(比如累计到昨天一共坏n件,而今天没有该硬件故障,则累计数量还是n件)
    df = df.stack(dropna=False).reset_index().fillna(0) # 转换成无索引的DataFrame,并填充缺失值为0(从开始时间直至第一个同类型硬件发生故障的值均为缺失值)
    

    至此数据整理部分结束,得到了2017至2019年共计3年内每天每种硬件故障的累计数的DataFrame。

    作图

    通过水平柱状图将故障硬件的数量进行排名可视化,每个柱一个颜色,由多到少自上往下排列。
    水平柱状图用barh生成,动画需更新每日的排序,但同一种硬件的颜色不能改变,因此初始化了plugin_color字典。
    update函数中的df_i为当前帧所使用的数据,以日期作为筛选标准。

    fig, ax = plt.subplots()
    
    # 分配颜色的字典
    colormap = [i / len(plugins) * 0.7 + 0.15 for i, _ in enumerate(plugins)]
    colormap = plt.get_cmap('Paired')(colormap)
    plugin_color = {plugins[i]: colormap[i] for i in range(len(plugins))}
    
    def update(i):
    
        date = datetime(2017, 1, 1) + timedelta(days=i)
        # 筛选当日的数据,按数量排序
        df_i = df[df['date'] == date]
        df_i = df_i.sort_values('value', ascending=True)
        names = df_i['name'] # 硬件名称作为Y轴标签
        colors = [plugin_color[name] for name in names]
        value = df_i['value'] # X轴的值
    
        ax.clear()  # 清理ax,否则排序不会重做
        x = max(value) * 0.85 # 图中日期标签的水平相对位置
        if i == 0:
            ax.set_xlim(0, 1.05)
            x = 0.85
        # 水平柱状图
        rects = ax.barh(y=names,
                        width=value,
                        align='center',
                        height=0.5,
                        color=colors)
    
        # 每个柱的数字标签
        for i, v in enumerate(value):
            ax.text(v + 0.1, i, int(v), color=colors[i], ha='left', va='center')
    
        # 日期标签
        ax.text(x, 0.5, date.strftime('%Y-%m-%d'),
                fontweight='bold', fontsize='25', color='red')
    
        return rects
    

    生成动画对象,回调函数为update,frames传入3年的日期数,interval定义每一帧的时延(每秒8帧)。
    blit我试了下没感觉有啥区别,按照官方文档的说法,是一种比较老的绘图技术,这里设成False。
    repeat控制是否循环播放。

    ani = animation.FuncAnimation(fig=fig,
                                  func=update,
                                  frames=int(len(df) / len(plugins)),
                                  interval=1000/8,  # 每帧之间的时延,单位:ms
                                  blit=False,
                                  repeat=False)
    

    保存为视频或GIF:

    ani.save('test.gif', dpi=10, writer='pillow')
    ani.save('test.mp4')
    

    如果要在jupyter notebook中直接显示(中文显示问题参见“参考3”):

    %matplotlib inline
    from IPython.display import HTML
    HTML(ani.to_html5_video())
    

    生成的动态排名柱状图GIF(完整的GIF太大,只截部分):


    生成gif

    方法二:现成的“轮子”

    从ID来看,应该就是B站视频的原作者在GitHub上的一个库(见“参考2”),使用的是d3.js的前端作图。只要将数据整理成规定的格式,直接从网页导入即可。也可以修改一些简单的配置,非常好用,在此推荐一下。
    最后一帧截图:


    最后一帧

    参考

    1. Embedding Matplotlib Animations in Jupyter as Interactive JavaScript Widgets
    2. 手把手教你制作一个动态炫酷的可视化图表(历年中国大学学术排行榜)
    3. 用Matplotlib的animation.FuncAnimation画动画
    4. matplotlib图例中文乱码?

    相关文章

      网友评论

          本文标题:动态排名柱状图的两种做法

          本文链接:https://www.haomeiwen.com/subject/fmzvthtx.html