前言:由於全國受到新型冠狀病毒的影響,我們學校在已有的學工系統開發了 “每日一報”和“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")#顯示地理位置,區,並畫圖爆粗