疫情期间,我用python辅助统计班级打卡信息

前言:由于全国受到新型冠状病毒的影响,我们学校在已有的学工系统开发了 “每日一报”和“i签到” 两个功能来记录学生的身体状况和位置信息,确保并监督学生无误填写,每天辅导员都要多次从系统中导出今日打卡记录,并让班长提醒未打卡同学打卡或者有信息异常的同学重新确认信息。

操作平台: win10,python37,jupyter
图片名字等信息均打码处理



1、初步打卡情况简介

  1. 刚开始的时候,辅导员每天导出打卡的名单,然后发到通知群里,每个班干再用Excel筛选出自己班的信息,进行相关的信息通知同学,全部打卡完成后,辅导员还需要查看每一个同学的信息是否填写有误。辅导员一共管理6班班级,共340名学生,每次都会在上面花费大量的时间,不小心还会筛选统计错误。

● 通知情况: 导出表格给班长查看打卡情况!
在这里插入图片描述


● 筛选出未信息通知学生: 需要一直统计催打卡,过程艰辛复杂!
在这里插入图片描述


● 领导反馈: 学生出现乱填乱写的情况很严重,需要老师来做好把关工作!
在这里插入图片描述
在这里插入图片描述


  1. 我每次筛选我们班的信息也感觉一点麻烦,先是在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['是否完成填报'] == '未完成' 返回的结果为 TrueFalse ,当它相等时返回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没有数值: 体温正常
超过范围: 388
体温异常: 3690
体温异常: 368
超过范围: 370度
超过范围: 体温:381

结果分析: 这样就可以找出没有填写具体体温的同学了。

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")#显示地理位置,区,并画图爆粗

在这里插入图片描述

发布了84 篇原创文章 · 获赞 64 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ayouleyang/article/details/104646681