文章目录
- 前言
- 目标读者
- 正文
前言
本文是一个完整的Web数据可视化案例。文章分为前台和后台两个部分。第一部分实现了前台功能,前台页面使用Echarts这个简便极易上手的数据可视化库,将示例的各个城市,以及城市下工厂人员数据组织成Echarts旭日图的数据结构,将数据按照2个层级展现出来;第二部分也就是本篇文章的内容:使用python进行后台数据处理,即使用pandas库将前台需要的数组结构组织好,然后通过Bottle这个极其轻便、能够快速上手的Web框架将处理后的数据传递给前台。
第一部分内容,包括了Echarts库简要介绍、Echarts配置手册查找配置属性的方法、通过学习丰富的Echarts示例程序,将示例程序修改从我们自己的可视化图表、以及本案例的具体前台实现代码。
第一部分请参阅上一篇文章。
目标读者
如果你想学习数据处理和分析,那么本文是为你准备的。本文将使用既容易理解,数据处理又相对简单的案例,带领你了解使用pandas进行数据处理的过程,以及如何选择数据展现模型进行数据可视化。
如果你想了解和学习Web前台开发的相关技能,并且想熟练使用Echart,本文将带你学习Echart使用和配置方法,并进一步掌握更加复杂的数据可视化方法。对于如何配置Echarts中对象属性,本文也会耐心介绍如何查找和配置ECharts对象属性。
如果你想学习python,并且想掌握Web后台技能,并搭建一个Web开发框架。那么本文也非常适合你。本文将搭建一个轻便极易上手的,以python为开发语言的Web框架。
简而言之,本文适合想入门大数据处理和数据可视化,想使用python进行快速开发Web应用的读者。
正文
我们将从零开始构建后台项目,讲解后台开发过程。我们假设你已理解python语法,具有python开发经验,能够熟练使用vscode开发python应用程序。
开发python工程项目的时候,我们经常遇到不同的项目使用不同的依赖库,或者依赖库相同而版本不同。为了避免不同项目之间的依赖库互相影响和冲突,我们经常为一个新的python项目创建一个干净独立的python开发环境,这个环境就是虚拟环境。本文使用conda创建虚拟环境,当然你也可以使用其他方式创建虚拟环境。
创建虚拟环境的命令
conda create --name echartssample
在这个虚拟环境中,我们需要安装以下依赖库,方便我们的后台开发工作。
Bottle(版本号0.12.25)
Bottle是一个超轻量级的python开源库。其超轻量级体现在bottle的实现代码仅仅4k多,无需安装其他第三方的依赖即可运行。使用bottle库,仅仅数行就可以实现一个后台服务。
安装命令
pip install bottle
安装完成之后,我们一起看下bottle帮助文档中Quickstart的示例,如何快速创建一个后台服务。
from bottle import route, run
@route('/hello')
def hello():
return "Hello World!"
run(host='localhost', port=8080, debug=True)
在vscode中输入上面这段代码,然后运行。打开浏览器,输入http://localhost:8080/hello并回车,你会看到页面上显示出"Hello World!"简单的几行代码,并且无需任何配置,bottle后台服务就可以运行了,这就是bottle吸引我的原因。
Bottle的开源地址是https://github.com/bottlepy,其中还包括帮助文档以及简易教程。如果想深入使用bottle,向源码以及文档寻求帮助是必不可少的方法。
Pandas(版本号2.0.3)
pandas 是基于NumPy 的数据处理和统计工具,它可以帮助用户解决数据分析以及对数据进行处理的任务。Pandas 其中包括了很多其他库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。Pandas还提供了非常多能使我们快速便捷地处理数据的函数和方法。
安装命令
pip install pandas
学习pandas的最好方法就是学习官方推荐的内容。阅读十分钟入门 Pandas和pandas的cookbook
sqlalchemy(版本号2.0.28)
SQLAlchemy是Python下操作数据库的一款开源软件。它提供了SQL工具包及对象关系映射(ORM)工具,配置简单使用简便,提供了通用的数据操作接口。
安装命令
pip install sqlalchemy
只需要安装这三个依赖库,本案例的后台项目就可以进行开发了。
我们在第一部分已经完成了前台的静态页面,当时使用了旭日图进行展示数据,其中旭日图使用的数据是hardcode在页面中。
前台已经实现的主要代码和静态页面效果如下,我们将代码保存在名称为showsunrise.html文件中。
<script type="text/javascript">
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var srData=[{
"value": 8266,
"name": "城市01",
"path": "城市01",
"children": [{
"path": "城市01",
"name": "工厂01",
"value": 356
}, …]
}];
const formatUtil = echarts.format;
function getLevelOption() {
return [
{
itemStyle: {
borderWidth: 0,
gapWidth: 5
}
},
{
itemStyle: {
gapWidth: 1
}
},
{
colorSaturation: [0.35, 0.5],
itemStyle: {
gapWidth: 1,
borderColorSaturation: 0.6
}
}
];
}
myChart.setOption(
(option = {
title: {
text: ' People of the factory in the city',
left: 'center'
},
animation: false,
tooltip: {
formatter: function (info) {
var value = info.value;
var treePathInfo = info.treePathInfo;
var treePath = [];
for (var i = 1; i < treePathInfo.length; i++) {
treePath.push(treePathInfo[i].name);
}
return [
'<div class="tooltip-title">' +
formatUtil.encodeHTML(treePath.join('/')) +
'</div>',
' Head Count: ' + formatUtil.addCommas(value)
].join('');
}
},
series: [
{
name: ' People of the factory in the city',
type: 'sunburst',
radius: ['40%', '80%'],
nodeClick: 'link',
visibleMin: 300,
label: {
show: true,
minAngle: 3,
formatter: '{b}'
},
itemStyle: {
borderColor: '#fff'
},
levels: getLevelOption(),
data: srData
}
]
})
);
option && myChart.setOption(option);
</script>
下面我们将一步步将其改造成从后台动态获取数据。
通过阅读bottle的帮助文档以及教程,Bottle支持使用tpl模板作为动态页面。我们发现只需要将我们写好的静态页面文件的后缀从html修改成tpl即可。另外,其中旭日图的json数据变量只需要修改成tpl模板中定义的模板变量格式即可。
var srData ={{!json_data}};
json_data是从后台传过来的json对象,之所以在变量前增加“!”,是告诉bottle,这个模板变量是安全的数据变量,使其不再被转义,否则从后台传过来的json数据会被转义,最后导致页面错误无法显示图表。
在vscode输入如下代码,并将文件命名为echartssample.py,我们Web后台代码就写完了。这么快的开发效率太令人惊讶了。运行代码后,直接输入http://127.0.0.1:8099/showsunrise,就可以看到跟之前一模一样的静态页面了!
from bottle import route, run, template
@route('/showsunrise')
def showsunrise():
res = [{
"value": 8266,
"name": "城市01",
"path": "城市01",
"children": [{
"path": "城市01",
"name": "工厂01",
"value": 356
}, …]
}]
return template('./view/showsunrise', json_data=res)
run(host='127.0.0.1', port=8099,reloader=True)
这段代码中最重要,起决定性作用的代码,只有三行。
@route('/showsunrise')
这条语句用来设置页面的url,确保浏览器中输入http://127.0.0.1:8099/showsunrise即可访问页面。
res = [{…}]
res对象就是我们之前写在静态页面中的hardcode的json数据。我们只是把它写在后台了。
return template('./view/showsunrise', json_data=res)
这条语句将res数据赋值给模板变量json_data,然后将整个tpl模板发送给前台。json_data前面已经介绍,就是将json对象填充到页面上。
直接将json数据hardcode到后台,也不是我们的目的。我们希望从数据库中查询数据,然后按照json格式要求再组织数据。我们接下来就解决这个问题。
我们创建一个新的python文件,取名叫model.py,专门处理数据。
在文件中输入如下代码。
from sqlalchemy import text, create_engine
import pandas as pd
import json
dbfile = r'sqlite:///./bottle/sample.db'
def getsrdata():
engine = create_engine(dbfile)
sql_cmd = "SELECT city, factory, headcount FROM city"
df = pd.read_sql(sql=sql_cmd, con=engine)
idf = df.groupby(by=['city'])['headcount'].sum()
obj_lst = []
for it in range(idf.size):
obj = {}
obj['value'] = int(idf.iloc[it])
obj['name'] = idf.index[it]
obj['path'] = idf.index[it]
citydf = df[(df['city']==idf.index[it])]
citydf.rename(columns={'city':'path','factory':'name','headcount':'value'},inplace=True)
obj['children'] = citydf.to_json(orient='records',force_ascii=False)
obj_lst.append(obj)
json4fe = json.dumps(obj_lst,ensure_ascii=False).replace("\\","")
json4fe = json4fe.replace("\"[","[")
json4fe = json4fe.replace("]\"","]")
return json4fe
这部分代码是整个项目中最核心,最重要的代码。
代码头部引入sqlalchemy 、pandas和json,前两个依赖库就是我们前面介绍的库,第三个json时python的标准库。然后我们指定sqlite数据库文件的位置,这个sample.db只有一个数据表city,每条记录有三个字段city,factory,headcount,一共635记录。
Model.py中只有一个函数,该函数的功能介绍如下。
函数一开始先创建数据库连接,使用sql语句将查询出来的数据保存到pandas的Dataframe对象中。
因为查询出来的数据需要按照旭日图要求的json结构组织,我们需要计算每个城市所有工厂的总人数,函数中我们直接使用Dataframe提供的分组求和的方法计算。
idf = df.groupby(by=['city'])['headcount'].sum()
这行代码看着简单实际做了不少事情。它首先按照城市分组,然后对每组中所有工厂人数求和。如果将这行代码改成使用sql的分组语句按照城市分组求和也是可以的,这样做的好处容易是让阅读者容易理解,但是缺点也很明显,就是需要多一次的sql查询。当前使用的方法直接使用数据帧对数据进行操作,操作效率比使用sql查询效率更高。
接下来,我们使用循环遍历每个城市的数据,将其城市名称,工厂总人数,以及层级路径保存到包含name、value、path和children的对象中。Children属性对应的对象,就是该城市下所有工厂的名称、人口数量,以及层级路径信息。Children对象中的数据使用循环迭代器获得,每个循环迭代器就是一个数据帧,其中有城市下所有工厂的人数信息。随后,我们直接使用pandas中Dataframe提供的将数据帧转换成json对象的to_json方法,将所有工厂信息打包转成children对象下的列表。
循环结束之后,我们已经将所有信息都保存到list对象中。前台使用的是json对象,所以我们还需要将list对象转换成json对象,数据才按照需求组织完成。
值得注意的两个问题,一是中文字符编码的问题,一个是json数据格式的问题。
因为我们使用的城市和工厂名称包含中文字符,将数据帧转换成json对象时,to_json方法默认使用ascii编码。json对象中的中文字符使用ascii编码方式,前台界面不能正确显示中文字符,所以我们需要关闭强制使用ascii编码的开关。
to_json方法将数据帧转换成json对象后,children属性对应的对象数组会被增加引号。这样会导致前台无法正确将children识别成数组对象,所以最后我们需要将这些引号手动替换掉。
Model.py的getsrdata()方法被echartssample.py调用,改动后的echartssample.py代码如下。
from bottle import route, run, template,HTTPError,static_file, debug
from model import getsrdata
@route('/:filename')
def getfile(filename):
return static_file(filename, root='./')
@route('/showsunrise')
def showsunrise():
res = getsrdata()
# return res
return template('./view/showsunrise', json_data=res)
debug(True)
run(host='127.0.0.1', port=8099,reloader=True)
到此为止,我们使用Echarts的旭日图展示多层级数据的项目就完成了,项目文件结构。
整个工程只有三个代码文件和一个数据库文件就实现一个数据可视化项目,加上前台页面受到Echarts加持得到的炫酷图形显示效果,还挺让人惊讶的。
旭日图在使用过程中,我们还是发现不少缺点的。比如,如果全部显示工厂的名称,图像上所有工厂名称会重叠起来,导致混乱的视觉效果,要是不显示工厂名称就无法直接查看工厂名称;日常中我们更关注top10的数据信息,因此也没有必要将所有城市下面的所有工厂信息都显示出来;每个工厂的人员数量不够直观,需要将指针指向扇形才能显示数量。
对于这些不足之处,由于篇幅的限制,我们不在这篇文章中优化。敬请期待后续文章。