前言:由于全国受到新型冠状病毒的影响,我们学校在已有的学工系统开发了 “每日一报”和“i签到” 两个功能来记录学生的身体状况和位置信息,确保并监督学生无误填写,每天辅导员都要多次从系统中导出今日打卡记录,并让班长提醒未打卡同学打卡或者有信息异常的同学重新确认信息。
文章目录
操作平台: win10,python37,jupyter
图片名字等信息均打码处理
1、初步打卡情况简介
- 刚开始的时候,辅导员每天导出打卡的名单,然后发到通知群里,每个班干再用Excel筛选出自己班的信息,进行相关的信息通知同学,全部打卡完成后,辅导员还需要查看每一个同学的信息是否填写有误。辅导员一共管理6班班级,共340名学生,每次都会在上面花费大量的时间,不小心还会筛选统计错误。
● 通知情况: 导出表格给班长查看打卡情况!
● 筛选出未信息通知学生: 需要一直统计催打卡,过程艰辛复杂!
● 领导反馈: 学生出现乱填乱写的情况很严重,需要老师来做好把关工作!
- 我每次筛选我们班的信息也感觉一点麻烦,先是在Excel中筛选出我们班的信息,然后在对“打卡情况进行”排序,再截屏发到班群里通知同学打卡,有时候还要一个一个的检查同学们是否打卡有误,这个估计也是每一个辅导员和班长都要共同面临的问题
《每日一报》打卡表格信息:
《i签到》打卡表格信息:
- 接下来到python出场了,用它完成全部过程的自动化统计,然后复制粘贴到消息通知群就可以了!
2、pandas导入数据
2.1、导入数据并查看
import pandas as pd
data1 = pd.read_excel("./data/测试/每日一报.xlsx")#导入每日一报数据
data1.columns#查看表头
Index(['学号', '姓名', '性别代码', '性别', '联系方式', '学院', '专业代码', '专业', '班级', '年级',
'是否完成填报', '辅导员填报', '本人体温', '本人是否是疑似病例或确诊病例', '本人是否接触过疑似或确诊病例',
'本人是否是湖北、武汉经历的人', '本人是否是确诊病例的密切接触者共同居住人员',
'本人居住地是否是湖北省以外疫点人员(指生活的小区、单元楼发生确诊病例)', '本人昨天是否外出', '外出地点',
'本人是否被社区列为重点排查对象', '被确定为重点排查对象时间', '是否解除隔离', '解除隔离时间', '具体解除时间',
'家庭成员感染新冠状病毒情况', '居住地是否发生变化', '是否有发热、咳嗽等症状'],
dtype='object')
2.2、查看数据形状
data.shape
(340, 28)
结果分析: 从学工系统中导出的《每日一报》表格一共有340行,也就是340人;一共有28列数据信息,其中大部分是需要填写的,其他信息可以更改。
3、每日一报未打卡人数
3.1、查看打卡情况
data['是否完成填报'].value_counts()
已完成 282
未完成 58
Name: 是否完成填报, dtype: int64
3.2、提取出未打卡的同学
data['是否完成填报'] == '未完成'
返回的结果为True
和False
,当它相等时返回True,提取返回True
的所有数据,就是没有打卡的数据。
noDo = data[data['是否完成填报'] == '未完成'] #提取出未完成的学生
noDo.head()#显示前五行
3.3、提取出对应的学生
noDo = data[data['是否完成填报'] == '未完成'] #提取出未完成的学生
noDo_num = noDo.shape[0] #获取未打卡人数,如果全部完成,就不需要查找对应的同学
if noDo_num == 0:
print ("▼ 每日一报已全部打卡完毕!")
else:
print ("▼ 每日一报未打卡人数: %s(人)"%noDo_num)#记录未打卡人数
for bj in range(len(noDo['班级'].unique())): #noDo['班级'].unique()取出所有未打卡的班级,并去重,计算班级数
class_xinxi = noDo['班级'].unique()[bj] #依次取出每个
index = noDo[noDo['班级'] == class_xinxi] #在该班级中取出对应的同学
name_list = [] #每次循环到这里都会把它置空
for name in index['姓名']:
name_list.append(name)
names = "、".join(name_list)#将数组变为字符串
print (class_xinxi + ": "+ names +'\n')
结果如下:
备注: 找出《i签到》未打卡的同学,方式也是一样的,这里就不重述了。
4、查找打卡信息异常同学
- 由于在填写信息时,不小心很容易把自己的信息填写错,本来没有生病的也填写成生病,没有被隔离也填写为被隔离,所以必须要把这类信息找出来,让填写的同学确认一下,是否填写有误。
- 因为大部分同学的信息在选择填写时是一致的,所以我们可以选择众数比对的方式来找出不符合众数的值
4.1、求众数
(1)查看众数
# 取众数
mode = data['是否完成填报'].mode()[0] #它输出的值为数组,加上[0]提取第一个值为字符串,在这里几乎不会出现两个众数
mode
'已完成'
- 这样就找出了大部分同学填写的值,如果谁没有填这个值,那么就判定可能是异常值,并提取出该同学的信息。
(2)提取出异常的数据
do_data = data[data['是否完成填报'] == '已完成'] #只统计完成的同学,为打卡的为空值,以免被空值干扰众数
mode = do_data['本人是否是疑似病例或确诊病例'].mode()[0] #众数
dif_do = do_data[do_data['本人是否是疑似病例或确诊病例'] != mode] #提取出完成打卡中的异常值,不等于众数的就是异常值
dif_do
4.2、标明我要审核的表头
(1)提取出表头
columns =['本人是否是疑似病例或确诊病例', '本人是否接触过疑似或确诊病例', '本人是否是湖北、武汉经历的人', '本人是否是确诊病例的密切接触者共同居住人员', '本人居住地是否是湖北省以外疫点人员(指生活的小区、单元楼发生确诊病例)', '家庭成员感染新冠状病毒情况', '是否有发热、咳嗽等症状']
for col in columns:
print (col)
本人是否是疑似病例或确诊病例
本人是否接触过疑似或确诊病例
本人是否是湖北、武汉经历的人
本人是否是确诊病例的密切接触者共同居住人员
本人居住地是否是湖北省以外疫点人员(指生活的小区、单元楼发生确诊病例)
家庭成员感染新冠状病毒情况
是否有发热、咳嗽等症状
(2)用法
data['本人是否是疑似病例或确诊病例']
0 否,身体健康
1 否,身体健康
2 否,身体健康
3 否,身体健康
4 否,身体健康
...
335 否,身体健康
336 否,身体健康
337 NaN
338 否,身体健康
339 否,身体健康
Name: 本人是否是疑似病例或确诊病例, Length: 340, dtype: object
NaN
表示空值,没有数据,也就是没有打卡
(3)总结
我提取出我需要额外审核的列,把它放进 data[ ]
中,就可以获取到同学们打卡的所有信息了
4.3、提取出该同学
# 移除不必要的列
columns =['本人是否是疑似病例或确诊病例', '本人是否接触过疑似或确诊病例', '本人是否是湖北、武汉经历的人', '本人是否是确诊病例的密切接触者共同居住人员', '本人居住地是否是湖北省以外疫点人员(指生活的小区、单元楼发生确诊病例)', '家庭成员感染新冠状病毒情况', '是否有发热、咳嗽等症状']
do_data = data[data['是否完成填报'] == '已完成'] #只统计完成的同学,为打卡的为空值,以免被空值干扰众数
for col in columns:
mode = do_data[col].mode()[0] #众数
dif_do = do_data[do_data[col] != mode] #提取出完成打卡中的异常值,不等于众数的就是异常值
dif_do_num = dif_do.shape[0] #统计异常值数量,如果为0,就结束这个循环
if dif_do_num == 0:
pass
else:
print ("●",col + ":",dif_do_num, "人")
for bj in range(len(dif_do['班级'].unique())):
class_xinxi = dif_do['班级'].unique()[bj]
index = dif_do[dif_do['班级'] == class_xinxi]
name_list = []
for name in index['姓名']:
name_list.append(name)
names = "、".join(name_list)
print (class_xinxi + ": "+ names)
print ("")
结果:
5、查找体温异常的同学
- 医学上把人的正常体温定为:35.5~37.2℃之间,我就以它作为判断的标准。
- 由于打卡系统的温度信息是全手动填写的,所以容易出现各种各样的格式,如:
- 36, 36.3, 体温:36.4, 36.5度, 体温正常, 36.6℃, 36度7 等等
- 学校要求填写具体体温,所以必须要找出填写“体温正常”之类的学生,要求填写具体温度。
5.1、体温预处理
- 这个主要是把学生的体温标准化处理,让它可以正常进行大小判断。
- 原因:有些同学的体温是标准的数值,有些带了汉字,有些忘记小数点,有些带了特殊符号
- 列如:
import re
#学生可能出现的体温填写情况
text_list = ['36','36.3', '体温:36.4', '36.5度', '体温正常', '36.6℃','36度7', '38度8','3690', '368', '370度', '体温:38度1']
for txt in text_list:
text = re.sub("[^0-9\u4e00.]", "", txt) #只保留数字“0~9”和“.”
if text == '':
print("text没有数值:", txt)
else:
if float(text) < 35.5 or float(text) > 37.2:
#如果体温中有“度”字,如:36度8,用“度”字进行分隔,分别去掉干扰因子,下一步拼接完整,末尾接“0”防止小数点在末尾
if "度" in txt:
temperature = re.sub("[^0-9\u4e00.]","", txt.split('度')[0]) + "." + re.sub("[^0-9\u4e00.]","", txt.split('度')[1]) + "0"
if float(temperature) < 35.5 or float(temperature) > 37.2:
print ("超过范围:",txt)
else:
print ("体温异常:", text)
text没有数值: 体温正常
超过范围: 38度8
体温异常: 3690
体温异常: 368
超过范围: 370度
超过范围: 体温:38度1
结果分析: 这样就可以找出没有填写具体体温的同学了。
5.2、没有填写具体体温
temperature_tab = do_data['本人体温'] #体温列
for i in do_data.index:
temperature = re.sub("[^0-9\u4e00.]","", str(temperature_tab[i])) #体温清洗,只保留"数值"和“.”
if temperature == '':
print ("没有填写具体体温: ", do_data['班级'][i], do_data['姓名'][i], do_data['本人体温'][i])
运行结果:
5.3、获取所有的异常体温
- 所用上面的方法,先对一些带有汉字的体温进行预处理,再进行大小判断!
print ("\n◙以下同学的体温不在35.5~37.2度之间")
for i in do_data.index:#从数据索引中循环出索引
temperature = re.sub("[^0-9\u4e00.]","", str(temperature_tab[i]))
if temperature == '':
continue #运行到这里后就结束程序当前运行,过滤掉没有数值的数据
single_tem = do_data['本人体温'][i] #遍历个人体温
# 为了预防学生填写的类型超过我的判断,设置一个异常捕捉
try:
if float(temperature) < 35.5 or float(temperature) > 37.2: #体温不在[3.5, 37.2]之间,进行下一步,初步判断异常
#如果体温中有“度”字,如:36度8,用“度”字进行分隔,分别去掉干扰因子,下一步拼接完整
if "度" in single_tem:
temperature = re.sub("[^0-9\u4e00.]","", str(single_tem.split('度')[0])) + "." + re.sub("[^0-9\u4e00.]","", str(single_tem.split('度')[1])) + "0"
print ("b"*50)
if float(temperature) < 35.5 or float(temperature) > 37.2:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)
else:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)
except:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)
运行结果:
6、查询所有信息代码汇总
- 在发通知时,最好的方法就是把填写有误的同学也提出了,方便让他改正。直接发输出的结果发到群里是很直观的方法,所有需要把我们需要的功能汇总在一起,一起输出结果。
import pandas as pd
import numpy as np
from pandas import DataFrame,Series
import re
#导入数据
data1 = pd.read_excel("./data/每日一报.xlsx")
data2 = pd.read_excel("./data/i签到.xlsx")
time = input("数据导出时间:") #输入时间,目的是方便直接复制到群里
"""查找出没有完成每日一报签到的同学"""
noDo = data1[data1['是否完成填报'] == '未完成'] #提取出未打卡的同学
noDo_num = noDo.shape[0]
if noDo_num == 0:
print ("▼ 每日一报已全部打卡完毕!")
else:
print ("▼ 每日一报未打卡人数: %s(人)"%noDo_num) #打印出人数
for bj in range(len(noDo['班级'].unique())):
class_xinxi = noDo['班级'].unique()[bj]
index = noDo[noDo['班级'] == class_xinxi]
name_list = []
for name in index['姓名']:
name_list.append(name)
names = "、".join(name_list)
print (class_xinxi + ": "+ names +'\n')
print ("")
"""查找出没有完成i签到打卡的同学"""
noDo = data2[data2['签到状态'] == '未签到'] #提取出未签到的同学
noDo_num = noDo.shape[0]
if noDo_num == 0:
print ("◆ i签到已全部打卡完毕!")
else:
print ("◆ i签到未打卡人数: %s(人)"%noDo_num)
for bj in range(len(noDo['班级'].unique())):
class_xinxi = noDo['班级'].unique()[bj]
index = noDo[noDo['班级'] == class_xinxi]
name_list = []
for name in index['姓名']:
name_list.append(name)
names = "、".join(name_list)
print (class_xinxi + ": "+ names +'\n')
print ("\n☢以下同学“每日一报”打卡的信息可能有误☟☟☟")
"""查找出表格中的异常值"""
# 移除不必要的列
columns =['本人是否是疑似病例或确诊病例', '本人是否接触过疑似或确诊病例', '本人是否是湖北、武汉经历的人', '本人是否是确诊病例的密切接触者共同居住人员', '本人居住地是否是湖北省以外疫点人员(指生活的小区、单元楼发生确诊病例)', '家庭成员感染新冠状病毒情况', '是否有发热、咳嗽等症状']
do_data = data1[data1['是否完成填报'] == '已完成']
for col in columns:
mode = do_data[col].mode()[0] #众数
dif_do = do_data[do_data[col] != mode] #提取与众数不一样的值,也就是异常值
dif_do_num = dif_do.shape[0]
if dif_do_num == 0:
pass
else:
print ("●",col + ":",dif_do_num, "人")
for bj in range(len(dif_do['班级'].unique())): #班级去重dif_do['班级'].unique()
class_xinxi = dif_do['班级'].unique()[bj] #提取出班级
index = dif_do[dif_do['班级'] == class_xinxi]
name_list = []
for name in index['姓名']:
name_list.append(name)
names = "、".join(name_list)
print (class_xinxi + ": "+ names)
print ("")
"""找出没有填写具体体温的同学"""
temperature_tab = do_data['本人体温'] #体温列
for i in do_data.index:
temperature = re.sub("[^0-9\u4e00.]","", str(temperature_tab[i])) #体温清洗,只保留"数值"和“.”
if temperature == '':
print ("没有填写具体体温: ", do_data['班级'][i], do_data['姓名'][i], do_data['本人体温'][i])
"""找出体温不在35.5~37.2度之间的同学"""
print ("\n◙以下同学的体温不在35.5~37.2度之间")
for i in do_data.index:
temperature = re.sub("[^0-9\u4e00.]","", str(temperature_tab[i]))
if temperature == '':
continue #运行到这里后就结束程序当前运行,过滤掉没有数值的数据
single_tem = do_data['本人体温'][i] #遍历个人体温
# 为了预防学生填写的类型超过我的判断,设置一个异常捕捉
try:
if float(temperature) < 35.5 or float(temperature) > 37.2: #体温不在[3.5, 37.2]之间,进行下一步,初步判断异常
#如果体温中有“度”字,如:36度8,用“度”字进行分隔,分别去掉干扰因子,下一步拼接完整
if "度" in single_tem:
temperature = re.sub("[^0-9\u4e00.]","", str(single_tem.split('度')[0])) + "." + re.sub("[^0-9\u4e00.]","", str(single_tem.split('度')[1])) + "0"
print ("b"*50)
if float(temperature) < 35.5 or float(temperature) > 37.2:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)#输出班级,姓名,体温
else:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)
except:
print (do_data['班级'][i], do_data['姓名'][i], single_tem)
运行结果: 直接把它复制粘贴到通知群里就完事了☟☟☟
7、绘制打卡分布图
7.1、认识cpca
-
cpca官网: https://pypi.org/project/cpca/
-
cpca : chinese_province_city_area_mapper:一个用于识别简体中文字符串中省,市和区并能够进行映射,检验和简单绘图的python模块。
-
安装:目前只支持python3
pip install cpca
7.1.1、全文模式
- 默认情况下transform方法的cut参数为True,即采用分词匹配的方式,这种方式速度比较快,但是准确率可能会比较低,如果追求准确率而不追求速度的话,建议将cut设为False(全文模式)
- jieba分词并不能百分之百保证分词的正确性,所以我们引入了全文模式,不进行分词,直接全文匹配,使用方法如下:
location_str = ["贵州省黔西南布依族苗族自治州贞丰县210省道", "湖南省岳阳市岳阳楼区对门山路", "贵州省遵义市余庆县方竹街", "贵州省黔南布依族苗族自治州都匀市75国道"]
import cpca
df = cpca.transform(location_str, cut=False)
df
省 | 市 | 区 | 地址 | |
---|---|---|---|---|
0 | 贵州省 | 黔西南布依族苗族自治州 | 贞丰县 | 黔西南布依族苗族自治州贞丰县210省道 |
1 | 湖南省 | 岳阳市 | 岳阳楼区 | 对门山路 |
2 | 贵州省 | 遵义市 | 余庆县 | 方竹街 |
3 | 贵州省 | 黔南布依族苗族自治州 | 都匀市 | 黔南布依族苗族自治州都匀市75国道 |
7.1.2、查看同省重名的地点
location_str = ["江苏省鼓楼区软件大道89号"]
import cpca
df = cpca.transform(location_str)
df
WARNING:root:鼓楼区 无法映射, 建议添加进umap中
省 | 市 | 区 | 地址 | |
---|---|---|---|---|
0 | 江苏省 | 鼓楼区 | 软件大道89号 |
在结果中,它没有把市映射出来,因为还有其他的地名和鼓楼区同名,江苏省徐州市也有一个鼓楼区:
import cpca
cpca.province_area_map.get_relational_addrs(('江苏省', '鼓楼区'))
[('江苏省', '南京市', '鼓楼区'), ('江苏省', '徐州市', '鼓楼区')]
7.1.3、加入自定义地点
- 当程序发现重名区并且不知道将其映射到哪一个市时,会将其加入警告信息。
- 如果你想要让“鼓楼区”只映射到南京市的话,在transform方法中加入umap参数指定
映射即可:
location_str = ["江苏省鼓楼区软件大道89号"]
import cpca
df = cpca.transform(location_str, umap={"鼓楼区":"南京市"})
df
省 | 市 | 区 | 地址 | |
---|---|---|---|---|
0 | 江苏省 | 南京市 | 鼓楼区 | 软件大道89号 |
7.2、cpca绘图
- 模块中还自带一些简单绘图工具,可以在地图上将上面输出的数据以热力图的形式画出来。
- 这个工具依赖folium,为了减小本模块的体积,所以并不会预装这个依赖,在使用之前请使用
pip install folium
- 代码运行结束后会在运行代码的当前目录下生成一个df.html文件,用浏览器打开即可看到
绘制好的地图。
如我绘制《i签到》中 17级信息管理与信息系统班
班同学定位打卡的分布图:
(1)查看信息
xinguan = data2[data2['班级'] == '17信息管理与信息系统班'] #提取出17信息管理与信息系统班信息
print (xinguan.shape)
print (xinguan.columns)
(57, 9)
Index(['序号', '姓名', '学号', '学院', '班级', '签到状态', '签到时间', '地址', '备注'], dtype='object')
(2)绘图
import cpca #用于划分中国的省份
from cpca import drawer #用于画图
import folium #导入地图
from folium.plugins import HeatMap
loc = cpca.transform(xinguan['地址'], cut=False)#转化地点
drawer.draw_locations(loc, "./std_loc.html")#画出具体地点
图中显示:有两名同学打卡位置没有在贵州
贵州板块放大后:
7.4、绘制分布密度
- 这里需要安装几个画图的库来辅助
pip install pyecharts
pip install echarts-countries-pypkg
pip install pyecharts-snapshot
- 通过额外传入一个样本的分类信息,能够在地图上以不同的颜色画出属于不同分类的样本散点图。
- 当鼠标移到点上时,它可以显示具体的位置。
绘制6个班的打卡位置:
import cpca #用于划分中国的省份库
from cpca import drawer#画中国地图库
processed = cpca.transform(data2['地址'], cut=False)#转化信管班地点
drawer.echarts_cate_draw(processed, processed["区"], "echarts_cates.html")#显示地理位置,区,并画图爆粗