见【Python Onramp】 0. 卷首语
上一篇:【Python Onramp】6.一篇文章了解web开发要点:用Python开发简易的网页端成绩查询系统
下一篇:【Python Onramp】8.Python爬虫(1)基于requests和BeautifulSoup的全国区划数据爬虫
一个思想:查找资料!将关键词百度一下,比如你想得到Echarts的模板,就可以直接百度。由于编程学习内容非常多样,查找不仅是方法,更是技能
几个要点:
详见:https://github.com/Honour-Van/CS50/tree/master/Visualization2
这个项目中你将通过北京地铁数据的展示进行
对地铁数据进行分析之后的可视化分析。
数据见 data,数据是北京地铁刷卡数据。第一行是数据标题,其含义如下:
压缩包内的 PDF 文档是地铁线路编号和车站编号,供参考。
为便于程序调试,数据中有 Subway_20190301_top100000.txt(实际上是 Subway_20180301_top100000.txt,误为 2019),是 Subway_20180301.txt 的前 100000 行数据,可以用于初步调试程序,待调试完毕后再用完整数据跑,节省时间
计算乘车耗时时段内的人数。每条记录是一次乘车行为,下车时间减去上车时间,即为耗时,题目要求统计耗时 10 分钟有多少人,耗时 15 分钟有多少人。以分钟为单位,计算出每个时间段内的人数。如 1 分钟:10 人,2 分钟 20 人,……,10 分钟 500 人,30 分钟 5000 人。计算耗时,用 round()函数四舍五入到分中。对下车时间早于或等于上车时间的数据予以清除,对超过 120 分钟的数据予以清除,对于不是 20180301 的数据清除。输出时,按耗时升序排序输出到 PeopleInSubwayTime.txt,并在 Excel 中打开该文件,形成条形图。
所有地铁内人数时间分布。以 10 分钟为间隔,统计地铁内人数多少。进站相当于人数增加,出站相当于人数减少。以凌晨 00:00 开始计数,此刻人数为 0。统计 00:00-00:09,00:10-00:19,以此类推,要求标记为:00:00-00:00,00:10-00:19(以下同)。清除数据的规则与 5 相同。按时间自然升序输出到 PeopleInSubwayCount.txt,形成折线图;
利用两个数据文件,通过 Python+Flask 模式,将数据输出到网页,利用 eCharts 显示出形如 Excel 的统计图。每个统计图都支持柱状图和折线图切换;
利用两个数据文件,通过包含 JS 数据文件方式,显示出形如 Excel 的统计图。与 task3 页面要求一致,仅数据源不同而已。
主要的内容包括Flask和JavaScript我们在之前都已经学过了,如果你已经掌握了,那么这个项目将会是一个非常愉悦的项目。
Echarts是一个基于 JavaScript 的开源可视化图表库,版式精美简约,具有可交互性。
之前我们已经使用过其Python版Pyecharts,但实际上,PyEcharts只是Echarts的一个封装,自动生成对应的JavaScript代码。我们现在直接动手编辑HTML和JavaScript。
在网页端,可以直接利用图形界面生成所需的options对象,在实际操作时,只需选取指定对象进行options设置即可。
<script type="text/javascript">
var EChart1 = echarts.init(document.getElementById("stat1"));
var EChart2 = echarts.init(document.getElementById("stat2"));
var option = {
title: { text: "" },
tooltip: {},
legend: { data: ["折线图", "柱形图"] },
toolbox: {
feature: {
magicType: { type: ["line", "bar"] },
saveAsImage: {},
},
},
xAxis: { data: [] },
yAxis: {},
series: [
{ name: "折线图", type: "line", data: [] },
{ name: "柱形图", type: "bar", data: [] },
],
}; /*设置统计图形参数*/
$.get("/getData1", function (data) {
option.title.text = "地铁乘车用时统计";
option.xAxis.data = data[0];
option.series[0].data = eval("[" + data[1] + "]");
option.series[1].data = eval("[" + data[1] + "]");
EChart1.setOption(option);
});
$.get("/getData2", function (data) {
option.title.text = "地铁站内人员统计";
option.xAxis.data = data[0];
option.series[0].data = eval("[" + data[1] + "]");
option.series[1].data = eval("[" + data[1] + "]");
EChart2.setOption(option);
});
script>
datetime 的使用方法:
strptime()
函数:将datetime对象转换为指定时间格式的字符串timedelta
类型:用于求时间差我们每十分钟一分组,还需要使用如下两个函数,分别为时间编组和对应组别时间的格式化输出。
def time_group(etime: datetime.time) -> int:
return etime.hour * 6 + (etime.minute // 10)
def group2time(group_num: int) -> str:
hour = group_num // 6
dec_min = group_num % 6
return str(hour).rjust(2, '0') + ":" + str(dec_min * 10).rjust(2, '0') + '-' + str(hour).rjust(2, '0') + ":" + str(dec_min * 10 + 9).rjust(2, '0')
导出行列数以表示进度:https://blog.csdn.net/lwgkzl/article/details/80988126
dataframe 更名:https://blog.csdn.net/weixin_43745169/article/details/89306686
dataframe 定位:https://blog.csdn.net/W_weiying/article/details/81411257
另外,不会把 python 列表直接变成 js 中的变量,从而使用了复制粘贴。
https://www.runoob.com/json/json-tutorial.html
JSON是JavaScript的内置数据结构,也通用于各种编程语言中,在flask端的数据传递过程中,我们可以利用jsonify函数将dataframe编码为JSON,随后传到网页端进行显示。这样的程序,数据不在网页端显示,保密等级上升一层。
详细可以自行搜索用法。
不正则数据:
返回上车和下车时间即可,上述的工作可以交由具体实现时。
第二次决定加入时间差作为第三个返回值
from datetime import datetime, time
import pandas as pd
from mytool import progress_bar, time_group, group2time
# filename = "test/test.txt"
# filename = "data/Subway_20190301_Top100000.txt"
filename = "data/Subway_20180301.txt"
df = pd.read_csv(filename)
res = {}
pb = progress_bar(df.shape[0], 100)
for _, line in df.iterrows():
starttime = datetime.strptime(str(line["UpTime"]), "%Y%m%d%H%M%S")
endtime = datetime.strptime(str(line["DownTime"]), "%Y%m%d%H%M%S")
if starttime.day != 1 or endtime.day != 1:
continue
delta = endtime - starttime
if delta.days < 0:
continue
if delta.seconds > 7200:
continue
tg = time_group(starttime)
res[tg] = res.get(tg, 0) + 1
tg = time_group(endtime)
res[tg] = res.get(tg, 0) - 1
pb.progress(_)
for i in range(143):
res[i+1] = res.get(i+1, 0) + res.get(i, 0)
res = sorted(res.items(), key=lambda x: x[0])
res = [(group2time(x[0]),x[1]) for x in res]
pd.DataFrame(res, columns=['时间组', '人数']).to_csv(
"./out/PeopleInSubwayCount.csv",index=False)
其中,mytool是自己实现的几个组件,包括时间处理以及进度条组件:
from datetime import datetime
class progress_bar():
def __init__(self, workload, length) -> None:
self._workload = workload
self.stage_d = int(workload/length)
self.stage_c = 0
self.stage_n = 0
self.length = length
def progress(self, cur):
if cur > self.stage_c:
self.stage_c += self.stage_d
self.stage_n += 1
print('\r' + "[" + (self.stage_n *
'o').ljust(self.length) + "]" + "loading...", end='')
def time_group(etime: datetime.time) -> int:
return etime.hour * 6 + (etime.minute // 10)
def group2time(group_num: int) -> str:
hour = group_num // 6
dec_min = group_num % 6
return str(hour).rjust(2, '0') + ":" + str(dec_min * 10).rjust(2, '0') + '-' + str(hour).rjust(2, '0') + ":" + str(dec_min * 10 + 9).rjust(2, '0')
如果记录的进入离开时间在特定范围内,记增减。注意,每次要做部分和,保证当前是之前所有进站出站数据的综合结果。
from datetime import datetime
import pandas as pd
from mytool import progress_bar
# filename = "data/Subway_20190301_top100000.txt"
filename = "data/Subway_20180301.txt"
res = {}
df = pd.read_csv(filename)
pb = progress_bar(df.shape[0], 100)
for _, line in df.iterrows():
starttime = datetime.strptime(str(line["UpTime"]), "%Y%m%d%H%M%S")
endtime = datetime.strptime(str(line["DownTime"]), "%Y%m%d%H%M%S")
if starttime.day != 1 or endtime.day != 1:
continue
delta = endtime - starttime
if delta.days < 0:
continue
if delta.seconds > 7200:
continue
minutes = round(delta.seconds / 60)
res[minutes] = res.get(minutes, 0) + 1
pb.progress(_)
res = sorted(res.items(), key=lambda x: x[0])
pd.DataFrame(res, columns=['耗时(分钟)', '人数']).to_csv(
"./out/PeopleInSubwayTime.csv", index=False)
from flask import Flask, jsonify # 新增代码。装入Flask
import pandas as pd
app = Flask(__name__) # 新增代码
@app.route("/") # 新增代码,对应执行root()函数
def root():
return app.send_static_file("visual.html")
@app.route("/getData1")
def getData1():
df = pd.read_csv("./out/PeopleInSubwayTime.csv")
data = [df.iloc[:, 0].tolist(), df.iloc[:, 1].tolist()]
print(data)
return jsonify(data)
@app.route("/getData2")
def getData2():
df = pd.read_csv("./out/PeopleInSubwayCount.csv")
data = [df.iloc[:, 0].tolist(), df.iloc[:, 1].tolist()]
print(data)
return jsonify(data)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)
# eof
<html>
<head>
<meta charset="utf-8" />
<title>EChartstitle>
<script src="/static/echarts.min.js">script>
<script src="/static/jquery-3.6.0.min.js">script>
head>
<body>
<div id="stat1" style="width: 600px; height: 400px">div>
<div id="stat2" style="width: 600px; height: 400px">div>
<script type="text/javascript">
var EChart1 = echarts.init(document.getElementById("stat1"));
var EChart2 = echarts.init(document.getElementById("stat2"));
var option = {
title: { text: "" },
tooltip: {},
legend: { data: ["折线图", "柱形图"] },
toolbox: {
feature: {
magicType: { type: ["line", "bar"] },
saveAsImage: {},
},
},
xAxis: { data: [] },
yAxis: {},
series: [
{ name: "折线图", type: "line", data: [] },
{ name: "柱形图", type: "bar", data: [] },
],
}; /*设置统计图形参数*/
$.get("/getData1", function (data) {
option.title.text = "地铁乘车用时统计";
option.xAxis.data = data[0];
option.series[0].data = eval("[" + data[1] + "]");
option.series[1].data = eval("[" + data[1] + "]");
EChart1.setOption(option);
});
$.get("/getData2", function (data) {
option.title.text = "地铁站内人员统计";
option.xAxis.data = data[0];
option.series[0].data = eval("[" + data[1] + "]");
option.series[1].data = eval("[" + data[1] + "]");
EChart2.setOption(option);
});
script>
body>
html>
这样的话,flask端需要做的工作就很少了
from flask import Flask, jsonify # 新增代码。装入Flask
import pandas as pd
app = Flask(__name__) # 新增代码
@app.route("/") # 新增代码,对应执行root()函数
def root():
return app.send_static_file("visjs.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)
# eof
js端需要稍微多加一点数据重组的操作:
<html>
<head>
<meta charset="utf-8" />
<title>EChartstitle>
<script src="./static/echarts.min.js">script>
<script src="./static/myData.js">script>
head>
<body>
<div id="stat1" style="width: 600px; height: 400px">div>
<div id="stat2" style="width: 600px; height: 400px">div>
<script type="text/javascript">
var EChart1 = echarts.init(document.getElementById("stat1"));
var EChart2 = echarts.init(document.getElementById("stat2"));
var option = {
title: { text: "" },
tooltip: {},
legend: { data: ["折线图", "柱形图"] },
toolbox: {
feature: {
magicType: { type: ["line", "bar"] },
saveAsImage: {},
},
},
xAxis: { data: [] },
yAxis: {},
series: [
{ name: "折线图", type: "line", data: [] },
{ name: "柱形图", type: "bar", data: [] },
],
}; /*设置统计图形参数*/
option.title.text = "地铁乘车用时统计";
option.xAxis.data = userData1[0];
option.series[0].data = userData1[1];
option.series[1].data = userData1[1];
EChart1.setOption(option);
option.title.text = "地铁站内人员统计";
option.xAxis.data = userData2[0];
option.series[0].data = userData2[1];
option.series[1].data = userData2[1];
EChart2.setOption(option);
script>
body>
html>