python中unique_财险数据交互式可视化——运用Python的Bokeh包

c6520a80765fe4e8de76b99364b58905.png
本文由珂珂撰写。

导引

继 Alonso 上篇

Li2O:用Python分析财险数据——菜鸟向​zhuanlan.zhihu.com
86f7bb54770ff4194b210aa489a85f84.png

,我对同样的数据用 Bokeh server 进行了可视化。

Bokeh简单介绍:Bokeh 是 Python 的一个制作交互式可视化工具的包,R 中也有相应的包叫做 shiny (https://shiny.rstudio.com/)。Boken 目前对中文的支持不太友好,但本文我们将用 JS 将网页语言改变为中文。Boken 有两种用法:

  • 第一种是不利用 Bokeh server,这种情况下能做出好看的交互图,实现拖曳,放大,鼠标悬浮标签等功能。最后能够生成静态的HTML文件。
  • 第二种是利用 Bokeh server,做一个 Web application。这种情况下能实现数据筛选调用等更多功能。一般使用 Flask + Bokeh,把 Bokeh 放置于 Flask application 里面。我们的这个例子中没有使用 FLask,而用了一个默认的 HTML 模板,叫做 Jinja,很多可以修改的功能被限制了。

本文介绍的是第二种,Bokeh server 的 Web application 应用示例,代码基于 Bokeh Gallery 里面的两个 sample。一个是 movie,一个是 crossfilter,链接见文末的参考文献。

先来示范一下效果:

  • 筛选数据功能:

726ebc656a972ddc81f7ae8b556b7f81.png
  • 拖曳,选择,数据标签功能:

fd8b97325ca88aa7b3057a710d226c2c.gif
  • 通过拖曳点的方式修改数据的功能:

74409d7cd3a5de2195a8d2bcfee14d97.png

该交互式图表目前 host 于http://49.234.103.189:5006/test 这个网页中。

步骤

安装 Bokeh

pure python 用户打开命令行:

pip install bokeh

conda 用户:

conda install bokeh

文件树

我们需要的文件树大概是这样的结构:

0a1115694ef876ea3208b2896b493734.png

app 文件夹下有三个文件:一个是 main.py,是我们的 python 主文件;另一个是 templates 文件夹,里面放 index.html,是我们对于基本 html 框架的补充;还有一个是 lidata.csv,是我们的数据源文件。

分析数据

我们要根据公司,险种,险别来进行数据筛选,因此,我们首先要得到这几列有哪些情况。

# lidata就是Alonso的数据集
df_all = pd.read_csv(r'./app/data/lidata.CSV', header = 0)
# 计算ULR
df_all['ULR'] = df_all['UL'] / df_all['EP']
# 加入all是为了能够选择所有情况
unique_company = ["All"] + df_all['公司'].unique().tolist()
unique_business = ["All"] + df_all['险种'].unique().tolist() 
unique_product = ["All"] + df_all['险别'].unique().tolist() 

需要对不同险别展示不同颜色,代码如下

color = pl.mpl['Plasma'][len(unique_product)]
#这里Plasma是一个Bokeh自带的调色盘,帮助我们找到好看的配色
df_all["color"] = [color[unique_product.index(pro)] for pro in df_all["险别"].values]

我们还要筛选展示的事故年,因此,我们需要读取最小的事故年和最大的事故年。

year_start = df_all['事故年'].min()
year_end =  df_all['事故年'].max()

最后一个要准备的是要展示的数据y列是什么。这里需要做一个字典用来对应选项和数据列名的关系。

axis_map = {
    "ULR": "ULR",
    "ULAE": "EP",
    "DAC":'DAC'
}

接下来就是作图啦。图分为左右两边。左边的部分叫做 control,右边的部分叫做 plot。

制作control

# year_range: 展示的事故年范围
year_range = RangeSlider(start=year_start, end=year_end, value=(year_start,year_end), step=1,
                       title="展示年")
Slider(title="开始展示年", start=year_start, end=year_end, value=year_start, step=1)
max_year = Slider(title="结束展示年", start=year_start, end=year_end, value=year_end, step=1)
# 选择的公司,险别,险种
company = Select(title="公司选择", value="All",
               options=unique_company)
business = Select(title="险别选择", value="All",
               options=unique_business)
product = Select(title="险种选择", value="All",
               options=unique_product)
y_axis = Select(title="展示值", options=sorted(axis_map.keys()), value="ULR")

controls = [company, business, product, year_range,  y_axis]

制作plot

# Tooltips用来制作鼠标悬浮于数据时的数据标签
TOOLTIPS=[
    ("公司为", "@com"),
    ("年:", "@year"),
    ("险别为", "@business"),
    ("险种为", "@pro")
]
# TOOLS规定了哪些工具要显示出来,比如拖曳等
TOOLS="pan,wheel_zoom,box_select,lasso_select,reset"
p = figure(tools=TOOLS,plot_height=100, plot_width=200, title="", toolbar_location="above", tooltips=TOOLTIPS, sizing_mode="scale_both")
r = p.circle(x="x",y="y" ,source=source, size=10, color = 'color', alpha=0.6, hover_color='white', hover_alpha=0.5)
# PointDrawTool这个工具需要单独放入其中
draw_tool = PointDrawTool(renderers=[r], empty_value='black')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool

更新数据

def select_products():
    # strip可以去除数据前面或者后面的空格
    company_val = company.value.strip()
    business_val = business.value.strip()
    product_val = product.value.strip()
    # 选择事故年
    selected = df_all[
        (df_all.事故年 >= year_range.value[0]) &
        (df_all.事故年 <= year_range.value[1]) 
    ]
    # 选择公司,险种,险别等
    if (company_val != "All"):
        selected = selected[selected.公司.str.contains(company_val)==True]
    if (business_val != "All"):
        selected = selected[selected.险种.str.contains(business_val)==True]
    if (product_val != "All"):
        selected = selected[selected.险别.str.contains(product_val)==True]
    return selected

# 这个函数用来更新数据源
def update():
    df = select_products()
    x_name = "事故年"
    y_name = axis_map[y_axis.value]
    p.title.text = "%d points selected" % len(df)
    source.data = dict(
        x=df[x_name],
        y=df[y_name],
        com=df["公司"].values,
        year=df["事故年"].values,
        business=df["险别"].values,
        pro=df["险种"].values,
        color = df["color"]
    )
# control中的每一个元素改变后,都需要运行update()
for control in controls:
    control.on_change('value', lambda attr, old, new: update())

生成图

# input就是图左边的control
inputs = column(*controls, width=320, height=1000)
inputs.sizing_mode = "fixed"

l = layout([
    [inputs, p],
], sizing_mode="scale_both")

update()
curdoc().add_root(l, p)

templates文件夹:利用Bokeh自带Jinja模板对网页更改基本样式

这个时候就要用到templates文件夹啦!它里面的index.html是对于jinja模板的补充。

Jinja模板如下,这个我们没有办法改,想要改的话只能用JS在后面改。

<!DOCTYPE html>
<html lang="en">
{% block head %}
<head>
    {% block inner_head %}
    <meta charset="utf-8">
    <title>{% block title %}{{ title | e if title else "Bokeh Plot" }}{% endblock %}</title>
    {% block preamble %}{% endblock %}
    {% block resources %}
        {% block css_resources %}
        {{ bokeh_css | indent(8) if bokeh_css }}
        {% endblock %}
        {% block js_resources %}
        {{ bokeh_js | indent(8) if bokeh_js }}
        {% endblock %}
    {% endblock %}
    {% block postamble %}{% endblock %}
    {% endblock %}
</head>
{% endblock %}
{% block body %}
<body>
    {% block inner_body %}
    {% block contents %}
        {% for doc in docs %}
        {{ embed(doc) if doc.elementid }}
        {% for root in doc.roots %}
            {{ embed(root) | indent(10) }}
        {% endfor %}
        {% endfor %}
    {% endblock %}
    {{ plot_script | indent(8) }}
    {% endblock %}
</body>
{% endblock %}
</html>

index.html 的基本格式如下:

{% extends base %}

<!-- goes in head -->
{% block preamble %}
<link href="app/static/css/custom.min.css" rel="stylesheet">
{% endblock %}

<!-- goes in body -->
{% block contents %}
<div> {{ embed(roots.scatter) }} </div>
<div> {{ embed(roots.line) }} </div>
{% endblock %}

我在标准模板中加了一些代码,来保证 html 的语言选项是 zh,也就是中文,加以对CSS文件的修改,就大功告成啦!

window.onload = function() {
  document.querySelector("html").lang = "zh";
};

运行

在命令行中先 cd 到 app 所在文件夹,并输入:

bokeh serve app

或者

bokeh serve --show app

或在 Debug 模式运行

bokeh serve --log-level=debug app

当 python 由于版本不同可能有冲突时,可以使用:

python3 -m bokeh serve app

完整代码可以点击阅读原文在珂珂的github下载: https://github.com/Mengkee/bokeh_example

参考文献

Bokeh Gallery

Bokeh Sample: movies

Bokeh Sample: crossfilter

http://weixin.qq.com/r/CTtkfKvEi-b0re8f924b (二维码自动识别)

  • 微信公众号&B 站&知乎:精算后花园
  • 电子邮箱:Jackie@actuarygarden.cn
  • 精算博客:actuarygarden.cn
  • 精算论坛:actuarygarden.com
  • QQ 群:544736656

专辑|英国精算师考试导引篇

专辑|CM1:利息理论&寿险精算

专辑|北美精算师考试导引篇

专辑|寓教于乐

专辑|调侃精算

专辑|精算考研申研和竞赛

专辑|数据科学

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值