一、热力图介绍
- 热力图,有时也称之为交叉填充表。该图形最典型的用法就是实现列联表的可视化,即通过图形的方式展现两个离散变量之间的组合关系
- 类型:关系型数据的可视化
- 特点:热力图体现了两个离散变量之间的组合关系
- Echarts官方实例:
返回顶部
二、案例
① 分析提取需求
- 数据集
数据主要字段:日期、省份、城市、新增确诊、新增出院、新增死亡、消息来源、来源1,来源2,来源3。
- 1.通过对数据集的观察,
城市字段缺失较难恢复,本想通过来源提取城市信息,但是在来源字段中内容分布不规则,无法有效提取
。所以只能对这部分空值记录删除。 - 2.通过热力图的定义,以及官方实例得出最关键的是:热力图热力图体现了两个离散变量之间的组合关系。也就是包含有两个变量,以及最后的组合结果,可以反映出哪些信息内容。在这里,我们不妨设想:展现出某一省份与其城市之间组合可以得到什么。比如:某一省份的不同城市新增确诊人数/新增出院人数…
- 3.问题又来了,只用某一省份与其城市,绘制出来热力图就是一张横条,未免显得太尴尬。可以继续扩充。同时加上日期,但是日期按照天数太多(若只统计某一个月还可以),这里就暂定月份。
- 4.综上所述,需求基本如下:绘制
某省
的各市
在已知的每个月
中新增确诊数热力图
。 - 5.基本模板
② 代码实现
▶读取数据
import pandas as pd
from flask import Flask,render_template
# 日期、省份、城市、新增确诊、新增出院、新增死亡、消息来源、来源1,来源2,来源3
columns = ['日期','省份','城市','新增确诊','新增出院','新增死亡','消息来源','来源1','来源2','来源3']
data = pd.read_csv("G:\Projects\pycharmeProject-C\Flask\dataset\yiqing.csv",names=columns,index_col=False)
数据读取的时候通过names参数添加列名
;index_col参数可以强制读取时不使用第一列作为索引
,否则会出现下图二错位的现象。
▶简单清洗
# 简单清洗数据
# 去重
print(data.shape)
print(data.loc[data.duplicated()])
print(data.drop_duplicates().shape)
# 去空 --- 提取城市不为空的记录
print(data.isnull().sum())
data = data.loc[~data['城市'].isnull()]
# 查看数据类型
print(data.dtypes)
▶提取需求信息
# 提取武汉信息
hb_data = data.loc[data['省份']=='湖北',['城市','日期','新增确诊']]
# 提取月份信息
hb_data['日期'] = [str(x)[0:2] for x in hb_data['日期']]
# 分组聚合统计
result = hb_data.groupby(['城市','日期']).agg(count=('新增确诊','count')).reset_index()
print(result.shape[0]) # 58
citys = result['城市'].unique().tolist() # x轴标签
dates = result['日期'].unique().tolist() # y轴标签
count = result['count'].values.tolist()
▶Flask部分
# 创建Flask对象
app = Flask(__name__)
# 视图
@app.route("/")
def index():
city_list = city
date_list = date
# 关联html页面,传输数据
return render_template("yiqing_heatmap.html",city = city_list,date = date_list)
if __name__ == '__main__':
# 运行
app.run(debug=True)
▶可视化部分
<!DOCTYPE html>
<html lang="en" style="height:100%">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body style="height:100%">
<div id="container" style="height:100%"></div>
<script type="text/javascript" src="../static/echarts.min.js"></script>
<script type="text/javascript">
var dom = document.getElementById("container");
var myecharts = echarts.init(dom);
// 获取后端传输数据
var city = [{% for item in city %}'{{ item }}',{% endfor %}];
var date = [{% for item in date %}'{{ item }}',{% endfor %}];
var data = [
[0,0,8],[0,1,36],[0,2,21],[0,3,1],
[1,0,8],[1,1,38],[1,2,23],[1,3,0],
[2,0,6],[2,1,38],[2,2,18],[2,3,0],
[3,0,0],[3,1,0],[3,2,0],[3,3,1],
[4,0,7],[4,1,41],[4,2,18],[4,3,0],
[5,0,8],[5,1,46],[5,2,26],[5,3,0],
[6,0,4],[6,1,41],[6,2,25],[6,3,0],
[7,0,7],[7,1,38],[7,2,17],[7,3,0],
[8,0,24],[8,1,62],[8,2,38],[8,3,18],
[9,0,5],[9,1,34],[9,2,19],[9,3,0],
[10,0,4],[10,1,8],[10,2,5],[10,3,0],
[11,0,10],[11,1,45],[11,2,24],[11,3,0],
[12,0,10],[12,1,39],[12,2,25],[12,3,1],
[13,0,6],[13,1,41],[13,2,21],[13,3,1],
[14,0,6],[14,1,43],[14,2,30],[14,3,0],
[15,0,7],[15,1,43],[15,2,24],[15,3,0],
[16,0,10],[16,1,46],[16,2,20],[16,3,0],
[17,0,6],[17,1,54],[17,2,26],[17,3,0],
]
// 对data进行形态转变
// 处理 0 值样式
data = data.map(function (item) {
return [item[0], item[1], item[2] || '-'];
});
var option = null;
option = {
tooltip:{
position:'top'
},
grid:{
height:'50%',
top:'10%'
},
xAxis:{
type:'category',
data:city,
// 划分区域是否显示,类似于绘制的网格
splitArea:{
show:true
},
boundaryGap:true,
axisTick:{
alignWithLabel:true
},
axisLabel: {
interval:0,
rotate:0
}
},
yAxis:{
type:'category',
data:date,
splitArea:{
show:true
}
},
visualMap:{
min:0,
max:70,
calculable:true,
orient:'horizontal', //热力分布标签布局
left:'center',
bottom:'15%'
},
series:[
{
name:'新增确诊',
type:'heatmap',
data:data,
label:{
show:true // 每个item上的标签,对应的聚合值
},
emphasis:{ // 高亮显示,设置阴影
itemStyle:{
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
if(option && typeof option=='object'){
myecharts.setOption(option);
}
</script>
</body>
</html>
③ 效果展示
④ 代码优化
var data = [
[0,0,8],[0,1,36],[0,2,21],[0,3,1],
[1,0,8],[1,1,38],[1,2,23],[1,3,0],
[2,0,6],[2,1,38],[2,2,18],[2,3,0],
[3,0,0],[3,1,0],[3,2,0],[3,3,1],
[4,0,7],[4,1,41],[4,2,18],[4,3,0],
[5,0,8],[5,1,46],[5,2,26],[5,3,0],
[6,0,4],[6,1,41],[6,2,25],[6,3,0],
[7,0,7],[7,1,38],[7,2,17],[7,3,0],
[8,0,24],[8,1,62],[8,2,38],[8,3,18],
[9,0,5],[9,1,34],[9,2,19],[9,3,0],
[10,0,4],[10,1,8],[10,2,5],[10,3,0],
[11,0,10],[11,1,45],[11,2,24],[11,3,0],
[12,0,10],[12,1,39],[12,2,25],[12,3,1],
[13,0,6],[13,1,41],[13,2,21],[13,3,1],
[14,0,6],[14,1,43],[14,2,30],[14,3,0],
[15,0,7],[15,1,43],[15,2,24],[15,3,0],
[16,0,10],[16,1,46],[16,2,20],[16,3,0],
[17,0,6],[17,1,54],[17,2,26],[17,3,0],
]
在上面的案例中,最后的data数据是本人纯手打,采用的是读取静态数据的方式,并且热力图数据以坐标的形式定位展现,实际上并没有通过前后端真实实现动态获取数据。
所以,进行了代码优化,能够实现在后端将数据处理成对应的形式。
▶ 如何优化?
通过上图的对比可以看出,通过静态数据(手动录入)时,对于原始数据进行了填补。在聚合统计后会发现每个城市并不是每个月都有统计结果的。以仙桃市、十堰市、咸宁市为例,仙桃市统计结果为4个月的,而两个市,存在4月数据缺失(默认为0),所以最后的时候本人进行了手动填充。
所以,优化的方向就明确了:在统计聚合之后,对结果进行填充优化
-
这里有两种方案:
方案一:
对月进行分组
,提取每月的城市信息,与全部城市信息比对,若有缺失,对该月聚合结果进行城市信息填充方案二:
对城市进行分组
,提取城市的不同月份信息,查看月份数量,若有缺失,对该城市聚合结果进行月份信息填充
由于这里月份信息数量少,最后合并较为简便,所以采用方案一
▶ 具体优化
# 优化
def add_all(citynames,month):
# 按照日期分组
date_grouped = result.loc[result['日期']== month].values.tolist()
# 遍历提取每个月的城市信息
date_city = []
for item in date_grouped:
date_city.append(item[0])
# 对比全部城市信息,补全当前月份每个城市的信息
# 排序返回
for i in citynames:
if i not in date_city:
date_grouped.append([i,month,0])
return sorted(date_grouped,key=lambda x:x[0])
data_1 = add_all(citys,'1月')
data_2 = add_all(citys,'2月')
data_3 = add_all(citys,'3月')
data_4 = add_all(citys,'4月')
可以看到最后的填补效果,主要是4月的信息缺失比较多~
整个数据集填充完成之后,就是要将其转换成坐标的形式传输到前端.
['仙桃市', '1月', 8] ----> [0,0,8]
['十堰市', '1月', 8] ----> [1,0,8]
# 坐标转化
def trans_data(data,y):
for i in range(len(data)): // i代表x轴坐标值
data[i][0] = i
data[i][1] = y
return data
ts_data_1 = trans_data(data_1,0)
ts_data_2 = trans_data(data_2,1)
ts_data_3 = trans_data(data_3,2)
ts_data_4 = trans_data(data_4,3)
# 合并结果集
data_1.extend(data_2)
data_3.extend(data_4)
data_1.extend(data_3)
list = data_1
app = Flask(__name__)
@app.route("/")
def index():
city_list = citys
date_list = dates
# 添加list数据
list_all = list
return render_template("yiqing_heatmap.html",city = city_list,date = date_list,list = list_all)
if __name__ == '__main__':
app.run(debug=True)
<!DOCTYPE html>
<html lang="en" style="height:100%">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body style="height:100%">
<div id="container" style="height:100%"></div>
<script type="text/javascript" src="../static/echarts.min.js"></script>
<script type="text/javascript">
var dom = document.getElementById("container");
var myecharts = echarts.init(dom);
// 获取后端数据
var city = [{% for item in city %}'{{ item }}',{% endfor %}];
var date = [{% for item in date %}'{{ item }}',{% endfor %}];
var list = [{% for item in list %}{{ item }},{% endfor %}];
// 转变list,主要处理 0 值
data = list.map(function (item) {
return [item[0], item[1], item[2] || '-'];
});
var option = null;
option = {
tooltip:{
position:'top'
},
grid:{
height:'50%',
top:'10%'
},
xAxis:{
type:'category',
data:city,
splitArea:{
show:true
},
boundaryGap:true,
axisTick:{
alignWithLabel:true
},
axisLabel: {
interval:0,
rotate:0
}
},
yAxis:{
type:'category',
data:date,
splitArea:{
show:true
}
},
visualMap:{
min:0,
max:70,
calculable:true,
orient:'horizontal',
left:'center',
bottom:'15%'
},
series:[
{
name:'新增确诊',
type:'heatmap',
data:data,
label:{
show:true
},
emphasis:{
itemStyle:{
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
if(option && typeof option=='object'){
myecharts.setOption(option);
}
</script>
</body>
</html>