暑假这段时间参加了一个学校的小培训,培训布置的项目“用户消费行为分析应用案例”。从开始的数据预处理到数据分析再到最后的数据可视化。原计划一个月完成的项目,奈何自己太菜,一直拖到现在才算勉强完成(因为完成的不算完美)。博客也是从这个培训开始写,从最开始用pandas进行数据预处理开始,再到后边的hive数据分析,再到这篇ECharts可视化,从中间学到了不少东西,也花费了不少心力,看起来很简单的东西实际操作起来错误频出,感觉自己好菜啊,希望自己越来越强OwO
前几篇文章地址:
使用pandas进行数据预处理
使用Hive进行数据分析以及将分析后的数据导入MySQL数据库
注意:
用Python进行数据可视化分析
展示业务量:
大致流程:
因为前边数据分析篇的分析后建表的考虑不周,可视化用到了两个新的表,就把新表的生成过程写在这里吧,用到了hive,MySQL,sqoop:
1.双十二当天top10的用户信息(包括:useid,购买数量,省份)
MySQL建表:
MySQL>create table fx_5 (user_id int(10),b_count int(12),province varchar(12));
hive建表:
hive>create table fx_5 (user_id int,b_count int,province string) row format delimited fields terminated by ',';
分析存储:
hive>insert into table fx_5
>select user_id,count(behavior_type) count,province
>from small_user_out
>where time='2014-12-12' and behavior_type=4
>group by user_id,behavior_type,province
>order by count desc
>limit 10;
hive导入MySQL:
sqoop export --connect jdbc:mysql://localhost:3306/hive --username hive -password 123123 --table fx_5 --export-dir hdfs://master-slave:8020/user/hive/warehouse/hive.db/fx_5 --input-fields-terminated-by ','
2.每天的购买率
MySQL建表:
mysql>create table fx_6 (time data,month int(8),day int(8),v_count int,b_count int,b_u double);
hive建表:
create table fx_6 (time date,month int,day int,v_count int,b_count int,b_all double) row format delimited fields terminated by ',';
分析存储:
hive>insert into table fx_6
>select time,month(time),
>day(time),
>sum(case behavior_type when 1 then 1 else 0 end),
>sum(case behavior_type when 4 then 1 else 0 end),
>sum(case behavior_type when 4 then 1 else 0 end)/sum(case when behavior_type=1 then 1 when behavior_type=2 then 1 when behavior_type=3 then 1 when behavior_type=4 then 1 else 0 end)
>from small_user_out
>group by time;
hive导入MySQL:
sqoop export --connect jdbc:mysql://localhost:3306/hive --username hive -password 123123 --table fx_6 --export-dir hdfs://master-slave:8020/user/hive/warehouse/hive.db/fx_6 --input-fields-terminated-by ','
使用PyCharm创建一个Flask项目,如果是专业版的PyCharm自带的有Flask模板,直接创建即可。直接使用Flask模板创建的Flask项目里边会有已经自动生成的static目录,templates模板目录以及一个app.py文件,文件里也有一些简单的代码,运行可生成一个简单的web页面。如果不是专业版的,这些内容都可以手动创建。
创建工具python文件utils,引入pymysql库,编写连接数据库的函数,获取查询到的数据的函数以及关闭连接的函数,方便后边使用的时候可以直接调用utils里封装的函数。
import pymysql
// 创建连接
def get_conn():
conn = pymysql.connect(
host="192.168.172.140", // 连接数据库的IP地址
port=3306,
user="root", //连接数据库的用户名
password="123123", //密码
db="hive", //连接的数据库
charset="utf8"
)
cursor = conn.cursor()
return conn,cursor
// 关闭连接
def close_conn(conn, cursor):
cursor.close()
conn.close()
// 获取查询结果
def query(sql):
conn, cursor = get_conn()
cursor.execute(sql)
res = cursor.fetchall()
close_conn(conn, cursor)
return res
数据库连接成功后,接下来我们要做的是获取每个问题所需要的数据,针对每个问题定义不同的函数和SQL语句(同样写在utils里):
// 获取问题一的数据
def get_q1_data():
sql = "select day,b_count,v_count " \
"from fx_6 " \
"where month=11 " \
"order by day;"
res = query(sql)
print("q1 Query the database successfully")
return res
// 获取问题二的数据
def get_q2_data():
sql = "select day,b_count,v_count " \
"from fx_6 " \
"where month=12 " \
"order by day;"
res = query(sql)
print("q2 Query the database successfully")
return res
// 获取问题三的数据
def get_q3_data():
sql = "select province,buy_count " \
"from fx_3;"
res = query(sql)
print("q3 Query the database successfully")
return res
//获取问题四的数据
def get_q4_data():
sql = "select * " \
"from fx_5 " \
"order by b_count desc;"
res = query(sql)
print("q1 Query the database successfully")
return res
//获取问题五的数据
def get_q5_data():
sql = "select date_format(time,'%m-%d') as time,b_u " \
"from fx_6; " \
res = query(sql)
print("q5 Query the database successfully")
return res
至此,连接数据库以及获取数据的函数已经定义完成,后边需要只需调用即可。
我将问题中的,每天的浏览量和购买量拆分成了十一月的浏览量和购买量和十二月的浏览量和购买量,所以四个问题变成了五个问题即:
我的计划是Q1,Q2柱状图,Q3在地图上显示,Q4列表,Q5折线图
我的页面的布局:
先在templates目录下创建我们的模板页面:
Python不是专业的HTML编写工具,建议使用别的工具操作方便,我用的VScode,对页面进行布局:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div id="title">用户消费行为分析div>
<div id="q1">十一月的浏览量和购买量div>
<div id="q2">十二月的浏览量和购买量div>
<div id="q3">双十二当天的各个省的发货量div>
<div id="q4">双十二当天top10的用户信息(包括:useid,购买数量,省份)div>
<div id="q5">每天的购买率div>
body>
html>
为了使代码看起来更简单舒服,在static目录下新创建一个CSS目录,并在该目录下创建一个css文件用来存放main模板的样式。使用absolute绝对定位划分板块,并连接到模板文件上。
#title {
position: absolute;
width: 100%;
height: 14%;
top: 0;
left: 0;
font-size: 45px;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
}
#q1 {
position: absolute;
width: 30%;
height: 43%;
top: 14%;
left: 0;
background: rgba(36, 94, 86, 0.692);
}
#q2 {
position: absolute;
width: 30%;
height: 43%;
top: 57%;
left: 0;
background: rgba(11, 74, 116, 0.603);
}
#q3 {
position: absolute;
width: 40%;
height: 86%;
top: 14%;
left: 30%;
background: rgba(15, 17, 128, 0.479);
}
#q4 {
position: absolute;
width: 30%;
height: 43%;
top: 14%;
left: 70%;
background: rgba(165, 31, 192, 0.411);
}
#q5 {
position: absolute;
width: 30%;
height: 43%;
top: 57%;
left: 70%;
background: rgba(107, 9, 78, 0.493);
}
在head之间添加代码来引入css文件。
<link rel="stylesheet" href="../static/css/main.css">
ECharts和jQuery都可去官网下载,同时因为我计划的是使用中国地图实现问题三的可视化,所以还需要一个中国地图的ECharts文件:
下载echarts.min.js即可:ECharts下载地址
选择一个版本下载即可:jQuery下载地址
针对问题三我们需要引入一个中国地图的js文件:china.js下载地址
在static目录下建立js目录存放js文件包括echarts,jQuery等所有js文件
我们给每个问题单独创建一个JS文件,可以更加清晰的找到代码使用,后边调试也更加方便。
前两个问题都是柱状图,在Echarts官网找个模板:
官方也有5分钟上手ECharts等教程,没用过的可以体验一下,不算太难。下边我使用的模板是根据官方实例进行修改后的代码,自己使用的时候可以根据个人喜好进行调整等。
链接:ECharts官网实例页面
// 问题1 echarts代码
// 基于准备好的dom,初始化echarts实例
var ec_left_1 = echarts.init(document.getElementById('q1'),'white');
var ec_left_1_option = {
title: { // 图标标题设置
text: '2014年11月日浏览量和购买量',
x: 'center',
bottom:10
},
legend: { // 图例设置
data: ['购买数量','浏览数'],
top:10
},
toolbox: { // 工具栏设置
top:10,
feature: {
magicType: {
type: ['stack', 'tiled']
},
dataView: {},
saveAsImage: {
pixelRatio: 2
}
}
},
tooltip: {}, // 提示框设置
xAxis: [{ // 横坐标,这里加了个[]方便Ajax传入数据
data: [],
}],
yAxis: [{
}],
series: [{ // 系列列表
name: '购买数量',
type: 'bar',
data: [],
barMaxWidth:"50%"
},{
name:'浏览数',
type:'bar',
data: [],
barMaxWidth:"50%"
}]
};
ec_left_1.setOption(ec_left_1_option) // 使用刚指定的配置项和数据显示图表
因为问题一个问题二只是月份不一样,所以代码基本可以使用相同的
// 问题2 echarts代码
var ec_left_2 = echarts.init(document.getElementById('q2'),'white');
var ec_left_2_option = {
title: {
text: '2014年12月日浏览量和购买量',
x: 'center',
bottom:10
},
legend: {
data: ['购买数量','浏览数'],
top:10
},
toolbox: {
top:10,
feature: {
magicType: {
type: ['stack', 'tiled']
},
dataView: {},
saveAsImage: {
pixelRatio: 2
}
}
},
tooltip: {},
xAxis: [{
data: [],
}],
yAxis: [{
}],
series: [
{
name: '购买数量',
type: 'bar',
data: [],
barMaxWidth:"50%"
},
{
name:'浏览数',
type:'bar',
data: [],
barMaxWidth:"50%"
}
]
};
ec_left_2.setOption(ec_left_2_option)
问题三注意数据的格式,我们可以先把mydata的数据传入,打开页面查看是否可以正常显示
// 问题3 echarts代码
var ec_center = echarts.init(document.getElementById('q3'),'white');
var mydata = [{'name':'河南','value':123},{'name':'湖南','value':321}]
var ec_center_option = {
title:{
text:'2014-12-12 全国各地销量一览',
textStyle:{
fontSize:25
},
subtext:'',
left:'center',
y:'',
top:50,
},
tooltip:{
trigger:'item'
},
visualMap: { // 小导航图标
min:0,
max:25,
left:"40%",
bottom:60,
text:['high','low'],
calculable:true,
orient: 'horizontal',
inRange:{
color:['yellow', 'orangered','red']
}
},
series: [{ //配置属性
name: '购买人数',
type: 'map',
mapType: 'china',
roam: false, //拖动和缩放
itemStyle: {
normal: {
borderWidth: .5, //区域边框宽度
borderColor: '#009fe8', //区域边框颜色
areaColor: "#ffefd5", //区域颜色
},
emphasis: { //鼠标滑过地图高亮的相关设置
borderWidth: .5,
borderColor: '#4b0082',
areaColor: "#fff",
}
},
label: {
normal: {
show: true, //省份名称
fontSize: 10,
},
emphasis: {
show: true,
fontSize: 12,
}
},
data:[] //mydata //数据
}]
};
ec_center.setOption(ec_center_option)
问题四是表格,而不是使用ECharts。因为JS的内容我不怎懂,这里的表格生成代码不是我写的,而是我的一个很好的朋友最近在学JS发给我用的,然而我用的并不好。当我好不容易勉强看懂怎么使用以及一点原理后,在我使用时发现Ajax从后端获取到的数据到前端顺序不一样了,这个顺序是我无法控制的,网上找方法也没有头绪。无奈,只好从数据库查询数据后,先把数据写死了,这也就是我前边说做的不完美的原因。有缘人看到解决了可以告诉我,或者有别的什么办法也可以评论一下。我也打算去深入学一些JS的内容来尝试自己做一下会不会更可控一点。
// 问题4生成表格
(function ($) {
// 给 $ 的原型添加方法
$.fn.table = function (arrTableHead, arrTableBody) {
// this 是调用table方法的 jQ 对象
// arrTableHead, arrTableBody 分别生成表头和主体
var list = [];
list.push('')
// 生成表头
list.push('');
list.push('');
for (var i = 0; i < arrTableHead.length; i++) {
list.push('');
list.push(arrTableHead[i]);
list.push(' ');
}
list.push(' ');
list.push('');
// 生成主体
list.push('');
for (var i = 0; i < arrTableBody.length; i++) {
list.push('');
for (var key in arrTableBody[i]) {
list.push('');
list.push(arrTableBody[i][key]);
list.push(' ');
}
list.push(' ');
}
list.push('');
list.push('
');
console.log(list.join(""));
// 把list里面的内容拼接成字符串
this.html(list.join(""));
}
}(jQuery));
var a_h = ['top','user_id','购买数量','城市']
var a_b = [{'top': 1, 'id': 103794013, 'count': 3, 'city': '浙江'}, {'top': 2, 'id': 103794013, 'count': 3, 'city': '江西'}, {'top': 3, 'id': 101490976, 'count': 3, 'city': '广东'}, {'top': 4, 'id': 102650143, 'count': 3, 'city': '云南'}, {'top': 5, 'id': 10095384, 'count': 3, 'city': '甘肃'}, {'top': 6, 'id': 101686429, 'count': 2, 'city': '安徽'}, {'top': 7, 'id': 101490976, 'count': 2, 'city': '西藏'}, {'top': 8, 'id': 103995979, 'count': 2, 'city': '内蒙古'}, {'top': 9, 'id': 101847145, 'count': 2, 'city': '福建'}, {'top': 10, 'id': 103995979, 'count': 2, 'city': '湖北'}]
function handle(){
console.log(typeof a_h)
console.log(a_b)
$('#c').table(a_h,a_b);
}
$('#c').table(a_h,a_b);
问题五是一个折线图,直接使用的最简单的例子
// 问题5 echarts代码
var ec_right_2 = echarts.init(document.getElementById('q5'),'white');
var ec_right_2_option = {
title:{
text:'每日购买率',
left:'center',
bottom:10
},
tooltip:{},
xAxis: [{
type: 'category',
data: []
}],
yAxis: {
type: 'value'
},
series: [{
name:"购买率",
data: [],
type: 'line'
}]
};
ec_right_2.setOption(ec_right_2_option)
建立连接并对模板文件和CSS文件进行修改将页面调整成自己想要的样子
HTML:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>用户消费行为分析title>
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/jquery-3.5.1.js">script>
<script src="../static/js/echarts.min.js">script>
<script src="../static/js/china.js">script>
head>
<body>
<div id="gyroContain">
<div id="title">用户消费行为分析div>
<div id="q1">div>
<div id="q2">div>
<div id="q3">div>
<div id="q4">
<div id="table">
<table border="1" id="c">
table>
div>
div>
<div id="q5">div>
div>
body>
html>
<script src="../static/js/ec_center.js">script>
<script src="../static/js/ec_left_1.js">script>
<script src="../static/js/ec_left_2.js">script>
<script src="../static/js/ec_right_1.js">script>
<script src="../static/js/ec_rigth_2.js">script>
CSS:
html, body {height:100%;overflow:auto;margin: 0;} /* 防止页面抖动 */
html{overflow-y:scroll;}
#title {
position: absolute;
width: 100%;
height: 14%;
top: 0;
left: 0;
font-size: 45px;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
}
#q1 {
position: absolute;
width: 30%;
height: 43%;
top: 14%;
left: 0;
background: white;
}
#q2 {
position: absolute;
width: 30%;
height: 43%;
top: 57%;
left: 0;
background: white;
}
#q3 {
position: absolute;
width: 40%;
height: 86%;
top: 14%;
left: 30%;
background: white;
}
#q4 {
position: absolute;
width: 30%;
height: 43%;
top: 14%;
left: 70%;
background: white;
}
#q5 {
position: absolute;
width: 30%;
height: 43%;
top: 57%;
left: 70%;
background: white;
}
table{
width: 80%;
border: 1px solid #3F3F3F;
padding: 5px;
margin: 14px auto;
border-collapse: collapse;
}
th{
border: 1px solid #3F3F3F;
background-color: #3F3F3F;
color: #fff;
padding: 3px 10px;
}
td{
border: 1px solid #3F3F3F;
padding: 3px 10px;
font-size: 16px;
text-align: center;
}
此时打开HTML页面视图应该是:
这个时候,我们可以在所有问题所对应的JS文件里自定义一些数据,这样看是否可以成功显示,是否达到预期效果,并记住需要的数据格式应该是什么。如果没有再做调整,如果达到了便可开始下一步。
首先我们需要使用render_template,将我们做好的模板引入:
from flask import render_template # 导入Flask引入模板的模块
import utils # 引入自己编写的工具模块,方便调用获取数据
from flask import jsonify # 导入Flask的jsonify方便返回json数据
@app.route('/')
def template():
return render_template("main.html")
其次我们需要知道每个数据所需要的格式
Q1、Q2:柱状图,我们需要的数据是Day、浏览量和购买量。
Day对应的是xAxis数据,浏览量和购买量对应的是两个data数据,数据库查询返回的数据格式是:
((day,购买量,浏览量),(day2,购买量2,浏览量2))
我们需要的数据是在列表里的因此我们需要把数据的格式转换成需要的格式:
# 定义问题一的路由以及数据格式处理
@app.route('/q1')
def get_q1_data():
data = utils.get_q1_data()
day,b_count,v_count = [],[],[]
for a,b,c in data[:]:
day.append(int(a))
b_count.append(b)
v_count.append(c)
print(day)
print(b_count)
print(v_count)
return jsonify({"day_11": day, "b_count_11": b_count, "v_count_11": v_count})
# 定义问题二的路由以及数据格式处理
@app.route('/q2')
def get_q2_data():
data = utils.get_q2_data()
day,b_count,v_count = [],[],[]
for a,b,c in data[:]:
day.append(int(a))
b_count.append(b)
v_count.append(c)
print(day)
print(b_count)
print(v_count)
return jsonify({"day_12": day, "b_count_12": b_count, "v_count_12": v_count})
问题三我们是使用中国地图展示的,从前边创建的JS文件我们可以得知问题三需要的数据格式为:
[{‘name’:‘河南’,‘value’:123},{‘name’:‘湖南’,‘value’:321}]
而我们直接从数据库获取的数据格式为:
我们需要的是一种类似与字典的格式:
# 定义问题三的路由以及数据格式处理
@app.route('/q3')
def get_q3_data():
lis = []
for tup in utils.get_q3_data():
lis.append({"name":tup[0],"value":int(tup[1])})
return jsonify({"data":lis})
问题四前边已经说了问题,这里就先把有问题的代码写上吧,不做阐述:
# 定义问题四的路由以及数据格式处理
@app.route('/q4')
def get_q4_data():
lis = []
i = 0
for tup in utils.get_q4_data():
i += 1
lis.append({"top":i,"id":tup[0],"count":int(tup[1]),"city":tup[2]})
print(lis)
return jsonify(lis)
Q5是折线图,类似与柱状图,需要的数据格式也基本和柱状图一样,直接从数据库获取的数据格式为:
路由创建及格式整理:
# 定义问题五的路由以及数据格式处理
@app.route('/q5')
def get_q5_data():
data = utils.get_q5_data()
time,b_u = [],[]
for a,b in data[:]:
time.append(a)
b_u.append(b)
print(time)
print(b_u)
return jsonify({"time": time, "b_u": b_u})
使用Flask创建路由,并把数据处理成需要的格式已经做完,接下来我们只要从前端获取数据并上传上去,我们的这个可视化大屏基本就算完成了。
我们单独写一个命名为control的JS文件(命名随意,自己决定无特殊要求),在这个文件下我们把每个问题获取数据的Ajax代码分别封装成五个函数,再进行调用,这样就算后期要做动态可视化大屏,我们直接添加一个定时刷新新就可以了。
// Ajax获取每个问题的数据
function get_q1_data(){
$.ajax({
url:"/q1",
success:function(data){
ec_left_1_option.xAxis[0].data=data.day_11
ec_left_1_option.series[0].data=data.b_count_11
ec_left_1_option.series[1].data=data.v_count_11
ec_left_1.setOption(ec_left_1_option)
},
error:function(xhr,type,errorThrown){
alert("图表1请求数据失败!");
}
})
}
function get_q2_data(){
$.ajax({
url:"/q2",
success:function(data){
ec_left_2_option.xAxis[0].data=data.day_12
ec_left_2_option.series[0].data=data.b_count_12
ec_left_2_option.series[1].data=data.v_count_12
ec_left_2.setOption(ec_left_2_option)
},
error:function(xhr,type,errorThrown){
alert("图表2请求数据失败!");
}
})
}
function get_q3_data(){
$.ajax({
url:"/q3",
success:function(data){
ec_center_option.series[0].data=data.data
ec_center.setOption(ec_center_option)
},
error:function(xhr,type,errorThrown){
alert("图表3请求数据失败!");
}
})
}
// function get_q4_data(){
// $.ajax({
// url:"/q4",
// success:function(data){
// a_b=data
// console.log(typeof a_b)
// console.log(a_b)
// alert(a_b)
// },error:function(xhr,type,errorThrown){
// alert("表格获取数据失败")
// }
// })
// }
function get_q5_data(){
$.ajax({
url:"/q5",
success:function(data){
ec_right_2_option.xAxis[0].data=data.time
ec_right_2_option.series[0].data=data.b_u
ec_right_2.setOption(ec_right_2_option)
},
error:function(xhr,type,errorThrown){
alert("图表5请求数据失败!");
}
})
}
get_q1_data()
get_q2_data()
get_q3_data()
// get_q4_data()
get_q5_data()
这个项目到这里就结束了,我们直接在PyCharm运行app.py文件,查看是否可以正常运行,如果不可以可能是某个地方出错了。具体的排查方法因出现问题的不同而异,你可以在后端的PyCharm单独建立一个test文件用来测试后端的数据获取等步骤是否可以正常执行;同时你也可以在前端直接在网页上按F12查看里边的Console查看前端代码是否有问题。还可以评论,私聊我们一起探讨学习。
Flask、Ajax、JS、ECharts这些东西算是第一次接触,搞了好久才把这个可视化做完。在使用这些工具的时候大多磕磕绊绊且需要摸索好久,中间也是错误频出,最后虽然结束却不完美,仍需继续努力学习。这次培训真的学了不少东西吧,重要的是发现自己真的菜,啥都不会,学的还慢。至此历经一个月多一点终于把这个案例完整做完,暑假也接近尾声,开学之后要加倍努力了,希望可以越来越好且学到的东西越来越精。
另外我把这个项目的源码也传到了GitHub上,还没用过不太会,上传应该没出错,大家有需要可以自行去下载,里边包括这个项目的源数据以及我处理过的数据:
GitHub源码链接