可视化算是数分人员的必备能力了,实际工作中,我们主要在BI系统上实现可视化,BI系统可能是技术团队自己开发的也可能是采购第三方的。但除BI系统之外,数分人员最好也能熟练使用Excel或Python实现可视化,以备不时之需。我们已经讲过Excel报表可视化的实现,本文就继续总结下 Python可视化大屏 的实现。


1. 工具准备

Python可视化工具包有很多,我们选用pyecharts. pyecharts是百度出品的,官方文档非常完善且是中文的,网络资源也非常多,使用过程中遇到问题时,很容易找到解决方法。

确定使用哪种工具包后,接下来就是安装工具包,本文不做演示,请自行安装pyecharts.


2. 代码开发

# 导入要用的包
import numpy as np
import pandas as pd
from pyecharts.charts import Line, Pie, Bar, Timeline, Map, Sankey, Page
from pyecharts import options as opts
from pyecharts.globals import ThemeType
from pyecharts.components import Table


# 取数
# 实际工作中是从数据库中取数,这里我们简化一下,直接伪造一些示例数据
data = pd.DataFrame()
data['date'] = pd.date_range('2022-01-01', periods=90)
data['gender'] = np.random.randint(0, 2, size=90)
data['product'] = np.random.randint(0, 5, size=90)
data['area'] = np.random.randint(0, 26, size=90)
data['amount'] = np.random.randint(1000, 2000, size=90)
data['cnt'] = np.random.randint(100, 200, size=90)

data['gender'] = data['gender'].apply(lambda x: '男性' if x == 0 else '女性')
data['product'] = data['product'].map({0: '产品A', 1: '产品B', 2: '产品C', 3: '产品D', 4: '产品E'})
area = ['北京市',
        '天津市',
        '上海市',
        '重庆市',
        '河北省',
        '山西省',
        '辽宁省',
        '吉林省',
        '黑龙江省',
        '江苏省',
        '浙江省',
        '安徽省',
        '福建省',
        '江西省',
        '山东省',
        '河南省',
        '湖北省',
        '湖南省',
        '广东省',
        '海南省',
        '四川省',
        '贵州省',
        '云南省',
        '陕西省',
        '甘肃省',
        '青海省']
data['area'] = data['area'].apply(lambda x: area[x])
data['date_month'] = data['date'].dt.strftime('%Y-%m')


# 查看数据
data.sample(10)

我们伪造的数据如下:

取数完成后,接下来就是按需逐一画图,并将代码封装为函数以便后续调用:

# 图表1-每日销售额-折线图
def chart1() -> Line:
    table1 = data.groupby(by='date')['amount'].sum()
    table1_x = table1.index.date.tolist()
    table1_y = table1.values.tolist()

    chart1 = (
        Line(
            init_opts=opts.InitOpts(
                width='700px',
                height='420px',
                bg_color='#EDF3F8',
                chart_id='chart1',
            )
        )
        .add_xaxis(table1_x)
        .add_yaxis(
            series_name='',
            y_axis=table1_y,
            markline_opts=opts.MarkLineOpts(data=[opts.MarkLineItem(type_="average")]),
        )
        .set_series_opts(label_opts=opts.LabelOpts(is_show=False))
        .set_global_opts(
            title_opts=opts.TitleOpts('每日销售额'),
            datazoom_opts=opts.DataZoomOpts(type_='slider')
        )
    )

    return chart1
# 图表2-销售额分性别-饼图
def chart2() -> Pie:
    table2 = data.groupby(by='gender')['amount'].sum()
    table2_x = table2.index.tolist()
    table2_y = table2.values.tolist()

    chart2 = (
        Pie(
            init_opts=opts.InitOpts(
                width='700px',
                height='420px',
                bg_color='#EDF3F8',
                chart_id='chart2',
            )
        )
        .add(
            series_name='性别',
            data_pair=[list(z) for z in zip(table2_x, table2_y)],
            radius=["50%", "70%"],
            label_opts=opts.LabelOpts(is_show=True)
        )
        .set_series_opts(
            tooltip_opts=opts.TooltipOpts(
                trigger="item", formatter="{a} <br/>{b}: {c} ({d}%)"
            ),
            label_opts=opts.LabelOpts(formatter="{b}: {d}%")
        )
        .set_global_opts(title_opts=opts.TitleOpts('不同性别的销售额占比'))
    )

    return chart2
# 图表3-每月销售额分产品-堆积柱形图
def chart3() -> Bar:
    table3 = data.pivot_table(values='amount', index='date_month', columns='product', aggfunc='sum')
    table3_x = table3.index.tolist()
    table3_y1 = table3['产品A'].tolist()
    table3_y2 = table3['产品B'].tolist()
    table3_y3 = table3['产品C'].tolist()
    table3_y4 = table3['产品D'].tolist()
    table3_y5 = table3['产品E'].tolist()

    chart3 = (
        Bar(
            init_opts=opts.InitOpts(
                width='700px',
                height='420px',
                bg_color='#EDF3F8',
                chart_id='chart3',
            )
        )
        .add_xaxis(table3_x)
        .add_yaxis('产品A', table3_y1, stack='stack0', bar_width=40)
        .add_yaxis('产品B', table3_y2, stack='stack0', bar_width=40)
        .add_yaxis('产品C', table3_y3, stack='stack0', bar_width=40)
        .add_yaxis('产品D', table3_y4, stack='stack0', bar_width=40)
        .add_yaxis('产品E', table3_y5, stack='stack0', bar_width=40)
        .set_series_opts(label_opts=opts.LabelOpts(is_show=True, position='inside'))
        .set_global_opts(
            title_opts=opts.TitleOpts(title="每月不同产品销售额"),
            legend_opts=opts.LegendOpts(pos_left='right', orient='vertical')
        )
    )

    return chart3
# 图表4-每月销售额分地区-地图
def chart4() -> Timeline:
    data['new_area'] = data['area'].str.replace('省|市', '', regex=True)  # pyecharts中的地图地区名称不带后缀

    chart4 = Timeline(
        init_opts=opts.InitOpts(
            width='700px',
            height='420px',
            bg_color='#EDF3F8',
            chart_id='chart4',
        )
    )
    for date_month in list(data['date_month'].unique()):
        table4 = data.loc[data['date_month']==date_month, :].groupby('new_area')['amount'].sum()
        table4_x = table4.index.tolist()
        table4_y = table4.values.tolist()

        map_chart = (
            Map(
                init_opts=opts.InitOpts(
                    width='700px',
                    height='420px',
                    bg_color='#EDF3F8',
                )
            )
            .add(
                series_name='销售额',
                data_pair=[list(z) for z in zip(table4_x, table4_y)],
                maptype='china',
                zoom=1,
            )
            .set_global_opts(
                title_opts=opts.TitleOpts('每月各省份销售额'), 
                visualmap_opts=opts.VisualMapOpts(
                    is_calculable=True,
                    dimension=0,
                    pos_left="30",
                    pos_top="center",
                    range_text=["High", "Low"],
                    range_color=["lightskyblue", "yellow", "orangered"],
                    textstyle_opts=opts.TextStyleOpts(color="#000000"),
                    min_=min(table4_y),
                    max_=max(table4_y),
                ),
                legend_opts=opts.LegendOpts(is_show=False),
            )
        )

        chart4.add(map_chart, date_month)

    return chart4
# 图表5-每月销售额及平均单价-柱线组合图
def chart5() -> Bar:
    table5 = data.groupby(by='date_month')[['amount', 'cnt']].sum()
    table5['average_price'] = (table5['amount']/table5['cnt']).round(2)
    table5_x = table5.index.tolist()
    table5_y1 = table5['amount'].tolist()
    table5_y2 = table5['average_price'].tolist()

    chart5 = (
        Bar(
            init_opts=opts.InitOpts(
                width='700px',
                height='420px',
                bg_color='#EDF3F8',
                chart_id='chart5',
            ),
        )
        .add_xaxis(table5_x)
        .add_yaxis(
            series_name='销售额',
            y_axis=table5_y1,
            yaxis_index=0,
            color='#91A6DD',
            bar_width=40,
            label_opts=opts.LabelOpts(is_show=True, position='inside'),
        )
        .extend_axis(yaxis=opts.AxisOpts())
        .set_global_opts(title_opts=opts.TitleOpts('每月销售额及平均单价'))
    )
    chart501 = (
        Line()
        .add_xaxis(table5_x)
        .add_yaxis(
            series_name='平均单价',
            y_axis=table5_y2,
            yaxis_index=1,
            color='#AD82A4',
            label_opts=opts.LabelOpts(is_show=True),
        )
    )
    chart5.overlap(chart501)

    return chart5
# 图表6-每月客群迁移-桑基图
def chart6() -> Sankey:
    # 先参考招商银行的会员等级伪造样例数据
    table6 = pd.DataFrame({
        '202201': np.random.randint(0, 10, size=81),
        '202202': np.random.randint(0, 10, size=81),
        'cnt': np.random.randint(1000, 10000, size=81)
    })
    table6 = pd.DataFrame(table6.groupby(by=['202201', '202202'])['cnt'].sum().reset_index())
    table6.sort_values(by=['202201', '202202'], inplace=True)
    table6['202201'] = '202201_M' + table6['202201'].astype('str')  #父节点和子节点的值不能相同,因此加前缀
    table6['202202'] = '202202_M' + table6['202202'].astype('str')  #父节点和子节点的值不能相同,因此加前缀

    nodes_content = list(set(table6['202201'].tolist() + table6['202202'].tolist()))
    nodes_content = sorted(nodes_content)
    nodes = [{"name": x} for x in nodes_content]

    links = [{"source": x, "target": y, "value": z} for x, y, z in zip(table6['202201'], table6['202202'], table6['cnt'])]

    chart6 = (
        Sankey(
            init_opts=opts.InitOpts(
                width='700px',
                height='420px',
                theme=ThemeType.WHITE,
                bg_color='#EDF3F8',
                chart_id='chart6',
            )
        )
        .add(
            series_name='',
            nodes=nodes,
            links=links,
            linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"),
            label_opts=opts.LabelOpts(position="right"),
            node_width=30,
            node_gap=10,
        )
        .set_global_opts(title_opts=opts.TitleOpts(title='会员等级迁移图'))
    )

    return chart6
# 创建可视化大屏的标题
# pyechats没有单独的绘制标题模块的东西,我们借助Table表格实现
def page_title() -> Table:
    page_title = (
        Table()
        .add(
            headers=['Python可视化大屏'],
            rows=[],
            attributes={
                'align': 'center',
                'border': True,
                'padding': '1px',
                'style': "background: #3578AE; color:#FFFFFF; width:1400px; height:50px; font-size:30px"
            }
        )
    )
    page_title.chart_id = 'page_title'

    return page_title
# 将图表组合成可视化大屏
page = (
    Page(
        page_title='Python可视化大屏',
        layout=Page.DraggablePageLayout,  # 拖拽方式
        interval=1,
    )
    .add(
        page_title(),  # 标题
        chart1(),  # 图表1
        chart2(),  # 图表2
        chart3(),  # 图表3
        chart4(),  # 图表4
        chart5(),  # 图表5
        chart6(),  # 图表6
    )
)
page.render('Python可视化大屏_Demo.html')

至此我们生成了可视化大屏的初版-Python可视化大屏_Demo.html,但还需进一步调整图表布局:

  1. 在浏览器中打开 Python可视化大屏_Demo.html
  2. 手动拖拽布局并保存配置得到配置文件 chart_config.json
  3. 打开配置文件 chart_config.json 并按需手动调整参数

# 基于新的配置文件重新生成可视化大屏
page_new = page.save_resize_html(
    'Python可视化大屏_Demo.html',
    cfg_file='chart_config.json',
    dest='Python可视化大屏.html',  # 该文件即是最终的可视化大屏,使用浏览器打开即可
)

至此我们生成了可视化大屏的终版-Python可视化大屏.html,将该文件的内网地址分享给同事,他们就能查看了。再结合Python定时任务实现每天定时更新数据,这个可视化大屏就可以用来监控业务指标了。


3. 小结

以上即是 Python可视化大屏 的实现过程,pyecharts的图表种类齐全、使用文档详细、具有一定交互性,用其制作可视化大屏很合适。不过值得强调的是,作为数分人员,不要舍本逐末,要明白可视化的核心是业务思维,其次才是可视化的实现,在可视化之前我们要基于对业务的熟悉设计出结构化模块化的业务指标体系,在有产品原型的基础上,我们可以选用BI系统或Excel或Python等趁手的工具来实现可视化。


原创文章,转载请务必注明出处并留下原文链接。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注