在高中階段源祈,學(xué)生考試成績的分析是一個(gè)重要的而頻繁的應(yīng)用場景,快速墓塌、有效瘟忱、精準(zhǔn)的生成學(xué)生成績的分析報(bào)告,是學(xué)情監(jiān)控和開展個(gè)性化教學(xué)的前提苫幢。這個(gè)問題是基礎(chǔ)性問題访诱,實(shí)現(xiàn)的方法非常多,主要是基于Excel韩肝。在這里用python的pandas做一遍触菜,體會一下辦公自動化的樂趣。這篇文章以高中學(xué)生的成績分析為背景哀峻,使用pandas(是一個(gè)基于numpy的python的數(shù)據(jù)分析包)對學(xué)生成績進(jìn)行分析涡相。本文分為如下部分:
- 全校成績表的生成(虛構(gòu))哲泊;
- 年級成績分析;
- 班級成績分析催蝗;
- 學(xué)科成績分析切威;
- 總結(jié)與思考。
學(xué)生成績分析是本文的場景丙号,寫這篇文章的目的是總結(jié)我參加華為云大數(shù)據(jù)挑戰(zhàn)賽時(shí)對于pandas的學(xué)習(xí)體會先朦,供大家參考,本文的源碼地址:https://github.com/Fire2341/Learning_Summary槽袄。
在開始之前烙无,導(dǎo)入numpy和pandas,按照習(xí)慣寫成如下形式遍尺。如果沒有這個(gè)模塊截酷,還是老規(guī)矩,使用pip install numpy和pip install pandas安裝一下乾戏。
import numpy as np
import pandas as pd
一迂苛、全校成績表的生成(虛構(gòu))
在開始之前,先生成我們的分析對象鼓择,學(xué)生成績表三幻,假設(shè)本次考試為理科班的摸底考試。學(xué)生成績表包括:
- 基本信息呐能,包括:學(xué)生姓名念搬、學(xué)生年級、學(xué)生班級摆出;
- 學(xué)生成績朗徊,假設(shè)學(xué)生的成績服從正態(tài)分布,生成的成績包括如下科目:語文(150分)偎漫、數(shù)學(xué)(150分)爷恳、英語(150)、物理(100分)象踊、化學(xué)(100分)温亲、生物(100分),并計(jì)算總分杯矩。
生成的表格流程如下:先確定每個(gè)年級的班級數(shù)目栈虚,并隨機(jī)生成各班人數(shù)(55-68人之間),由此計(jì)算得到全校人數(shù)菊碟。根據(jù)全校人數(shù)隨機(jī)生成學(xué)生姓名节芥,并在確定各科平均值和標(biāo)準(zhǔn)差后,根據(jù)正態(tài)分布規(guī)律隨機(jī)生成各個(gè)學(xué)生的各科成績逆害,并計(jì)算每位學(xué)生的總分头镊,以此獲得一份總的成績匯總表,主要代碼如下魄幕。由于這部分代碼較為冗長且不是主要部分相艇,感興趣的朋友可以點(diǎn)擊源碼查看。
class_name, student_num = generate_class() # 生成班級信息
all_num = students_sum(student_num) # 生成全校學(xué)生總數(shù)
student_name_group = generate_student_name(all_num) # 生成全校學(xué)生名字
student_info = init_table(class_name, student_num, student_name_group) # 將年級纯陨、班級坛芽、學(xué)生信息初始化到表格中
student_list = get_list(all_num, student_info) #生成成績匯總表
student_list.to_excel('學(xué)生成績表2.xlsx') # 保存成績表
取數(shù)據(jù)表的前5個(gè)來看,還真像那么回事(所有名字和成績數(shù)據(jù)均為python隨機(jī)生成翼抠,如有雷同咙轩,純屬巧合)。在生成的成績表中阴颖,一共有3個(gè)年級活喊,其中高一26個(gè)班,高二27個(gè)班量愧,高三個(gè)23班钾菊,各班學(xué)生人數(shù)介于55-68人之間,全校一共4619名學(xué)生偎肃。
為了展現(xiàn)pandas的相較于excel的優(yōu)越性煞烫,在下面的分析中,各部分使用的代碼盡量不超過5行累颂。
二滞详、年級分析
2.1 各年級的最低分、最高分紊馏、平均分和中位數(shù)
為了直觀的反映各年級的整體教學(xué)情況料饥,在這里計(jì)算各年級的各科最高分、最低分瘦棋、平均分和中位數(shù)稀火。在這一部分用到的函數(shù)主要是.groupby和.agg。groupby可以按年級分組赌朋,.agg能夠?qū)Ω髂昙壐鞣纸M應(yīng)用各個(gè)函數(shù)(求最大值凰狞、最小值、平均值沛慢、中位數(shù))進(jìn)行計(jì)算赡若。
subject_name = ['語文','數(shù)學(xué)','英語','物理','化學(xué)','生物','總分']
grade_analysis = student_list.groupby('年級')[subject_name].agg(['max','min','mean','median']).reset_index()
grade_analysis.head()
從結(jié)果來看,這份隨機(jī)生成的成績表团甲,各個(gè)科目的都有人考滿分逾冬,不符合實(shí)際情況,但是符合我的正態(tài)分布規(guī)律了……
2.2 獲取各年級的成績前5的學(xué)生
不管哪個(gè)層級的學(xué)校,拔尖學(xué)生都在學(xué)校人才培養(yǎng)工作占有重要地位身腻,而學(xué)習(xí)成績可以在一個(gè)側(cè)面反映拔尖學(xué)生的范圍产还。在這里篩選各年級成績排名前5的學(xué)生。這一部分用到的函數(shù)主要是.groupby和.sort_values嘀趟。使用.groupby的.rank()獲取年級排名脐区,使用.sort_values按照年級和總分進(jìn)行分組,使用.groupby的.get_group('高三')獲得高三學(xué)生的年級排名她按,在這里展示高三前五名的情況牛隅。
student_list['年級排名'] = student_list['總分'].groupby(student_list['年級']).rank(ascending=False).astype(int)
student_list.sort_values(['年級','總分'], ascending=False, inplace=True)
student_list.groupby('年級').get_group('高三').head()
第1名真是天選之子,隨機(jī)生成的成績都能有3科拿滿分酌泰。
獲取年級倒數(shù)前5的代碼一樣媒佣,把.sort_values()的ascending參數(shù)(是否升序)改為True就可以。
student_list.sort_values(['年級','總分'], ascending=True, inplace=True)
student_list.groupby('年級').get_group('高三').head()
2.3 數(shù)據(jù)保存
數(shù)據(jù)保存是一個(gè)重要的環(huán)節(jié)陵刹,畢竟學(xué)校的成績分析報(bào)告是要打印出來發(fā)給各個(gè)年級主任默伍、班主任的。使用.to_excel()導(dǎo)出到excel中授霸,在這里導(dǎo)出整體情況巡验,以及各個(gè)年級的前五和倒數(shù)前五的情況。
grade_excel = pd.ExcelWriter(r'年級分析.xlsx')
grade_analysis.to_excel(grade_excel, sheet_name='整體情況')
grade_name = ['高一','高二','高三']
student_list.sort_values(['年級','總分'], ascending=False, inplace=True)
for name in grade_name:
student_list.groupby('年級').get_group(name).head().to_excel(grade_excel, sheet_name=name+'-前五')
student_list.sort_values(['年級','總分'], ascending=True, inplace=True)
for name in grade_name:
student_list.sort_values(['年級','總分'], ascending=False, inplace=True)
student_list.groupby('年級').get_group(name).head().to_excel(grade_excel, sheet_name=name+'-倒數(shù)前五')
grade_excel.save()
導(dǎo)出的效果還是不錯(cuò)的碘耳。
3 班級分析
3.1 班級整體情況分析
分析各班第1在年級的位置显设,能夠幫助學(xué)校在整體層面把握各班的教學(xué)質(zhì)量。在班級排名的獲取方法與獲取年級排名的方法一致辛辨,對數(shù)據(jù)表“總分”這一列用“班級”這一列取groupby捕捂,然后對每個(gè)groupby進(jìn)行rank()計(jì)算。
student_list['班級排名'] = student_list['總分'].groupby(student_list['班級']).rank(ascending=False).astype(int)
student_list[student_list['班級排名'] == 1].groupby('年級').get_group('高三')
從下表可以看出斗搞,有個(gè)班級有兩位同學(xué)拿了年級前10指攒,真是天選之班。
使用如下代碼僻焚,進(jìn)一步分析是哪個(gè)班允悦。
student_list.sort_values(['年級排名'],ascending=True,inplace=True)
student_list.groupby('年級').get_group('高三').head(10).groupby(['班級'])['年級排名'].count()
原來天選之班不僅僅只有一個(gè)……
3.2 各班一本率及學(xué)科成績分析
一本率可以說是高中班級中的重要數(shù)據(jù)了,以廣西2019年高考理科一本線(509分)為標(biāo)準(zhǔn)虑啤,計(jì)算各班一本率隙弛。在對各班進(jìn)行g(shù)roupby的基礎(chǔ)上,使用一個(gè)apply函數(shù)計(jì)算各班過一本線的人數(shù)狞山,進(jìn)而計(jì)算一本率全闷。與年級整體數(shù)據(jù)的分析方法類似,按班級分析各科成績的最高分萍启、最低分总珠、平均分屏鳍、中位數(shù),使用.merge()函數(shù)(相當(dāng)于Excel中的vlookup函數(shù))將各班各科的數(shù)據(jù)與前面的一本率數(shù)據(jù)匹配局服,最后按照一本線降序呈現(xiàn)數(shù)據(jù)钓瞭。
class_list = pd.DataFrame()
class_list['一本人數(shù)'] = student_list.groupby('班級')['總分'].apply(lambda x: np.sum((x >=510).astype(int)))
class_list['班級人數(shù)'] = student_num
class_list['一本率'] = class_list['一本人數(shù)']/class_list['班級人數(shù)']
subject_name = ['語文','數(shù)學(xué)','英語','物理','化學(xué)','生物','總分','年級排名']
class_list = class_list.merge(student_list.groupby('班級')[subject_name].agg(['max','min','mean','median']).reset_index(), on='班級', how='left')
class_list.head()
從圖中可以看出,全校一本率最高的班級是1704班腌逢,但是一本率僅有36.2%降淮,看來這個(gè)虛構(gòu)的學(xué)校成績并不好……
3.3 數(shù)據(jù)保存
按照類似與年級數(shù)據(jù)的保存方法超埋,保存班級分析的數(shù)據(jù)搏讶。
class_excel = pd.ExcelWriter(r'班級分析.xlsx')
class_list.to_excel(class_excel, sheet_name='整體情況')
for name in grade_name:
student_list[student_list['班級排名'] == 1].groupby('年級').get_group(name).to_excel(class_excel, sheet_name=name)
class_excel.save()
四、學(xué)科分析
4.1 分?jǐn)?shù)段分析
對于各個(gè)學(xué)科霍殴,比較重要的數(shù)據(jù)是各個(gè)年級媒惕、各個(gè)分?jǐn)?shù)段的人數(shù)情況。使用的方法pd.cut()函數(shù)来庭,對各個(gè)學(xué)科的各個(gè)分?jǐn)?shù)段進(jìn)行切割妒蔚,并且保存為DataFrame(本文所有數(shù)據(jù)均為此格式)。由于在這個(gè)分析中有按照年級的循環(huán)求解月弛,所以直接在開頭就定義寫入excel的subject_excel對象肴盏,在循環(huán)結(jié)尾直接.to_excel()保存。
subject_excel = pd.ExcelWriter(r'學(xué)科分析.xlsx')
bins = [0,40,60,80,100,120,140,150]
group_name = ['高一','高二','高三']
subject_name = ['語文','數(shù)學(xué)','英語','物理','化學(xué)','生物']
for name in group_name:
grade = student_list.groupby('年級').get_group(name)
df = pd.DataFrame()
for s_name in subject_name:
cuts = pd.cut(grade[s_name],bins=bins) #可選label添加自定義標(biāo)簽
subject_cut = grade.groupby(cuts)[s_name].count()
df[s_name] = subject_cut
df.index.name = name
df.to_excel(subject_excel, sheet_name=name+'成績分布')
df
從成績區(qū)間可以看出帽衙,成績主要分布在平均數(shù)附近菜皂,是很符合正態(tài)分布了。想起在前面分析年級倒數(shù)的時(shí)候有個(gè)學(xué)生生物81分厉萝,但是年級排名倒數(shù)第3恍飘,是很偏科了。
4.2 偏科學(xué)生分析
偏科學(xué)生在某些學(xué)科是潛力股谴垫,發(fā)現(xiàn)偏科學(xué)生在某種意義上是發(fā)現(xiàn)“好苗子”的過程章母。在本次成績表中,假定數(shù)學(xué)成績大于130且語文和英語成績小于90分的學(xué)生為偏科學(xué)生翩剪。先構(gòu)建一個(gè)'數(shù)學(xué)偏科'的flag乳怎,然后進(jìn)行篩選。在這一部分的最后前弯,把學(xué)科分析部分的數(shù)據(jù)保存到excel中蚪缀。
student_list['數(shù)學(xué)偏科'] = ((student_list['數(shù)學(xué)']>=130) & (student_list['語文']<90) & (student_list['英語']<90)).astype(int)
partial = student_list[student_list['數(shù)學(xué)偏科'] == 1]
partial.drop(['數(shù)學(xué)偏科'],axis=1,inplace=True)
partial.to_excel(subject_excel, sheet_name='偏科學(xué)生')
subject_excel.save()
partial.head()
圖中的晉啟同學(xué)數(shù)學(xué)考了149,其他學(xué)科都不超過80博杖,是很偏科了椿胯。
使用如下代碼,看看各個(gè)年級有多少人是數(shù)學(xué)偏科的剃根×ぃ可以看到,高二年級的偏科人數(shù)最多。
partial.groupby('年級')['姓名'].count()
五廉油、思考
代碼和文章寫到這里已經(jīng)寫了 6個(gè)多小時(shí)了惠险,需要寫一個(gè)結(jié)尾總結(jié)一下收獲,減輕一下不務(wù)正業(yè)抒线,沒有讀文獻(xiàn)的負(fù)罪感班巩。有人說這些事情用Excel完全可以辦到,為什么要在這里寫代碼呢嘶炭?全文中使用到的方法抱慌,函數(shù)均為我在做一個(gè)大數(shù)據(jù)比賽時(shí)學(xué)到的函數(shù),換一個(gè)場景使用證明我學(xué)會了眨猎;其次抑进,那個(gè)比賽需要處理的數(shù)據(jù)量是1.47億條,用Excel打不開(T_T)睡陪,所以寫代碼也是適用于大規(guī)模的數(shù)據(jù)處理寺渗;另外,代碼具有復(fù)用性兰迫,只要使用場景不變信殊,數(shù)據(jù)格式不變,可以說是一勞永逸的汁果,適合于重復(fù)機(jī)械的工作場景涡拘。比如高中學(xué)生考試,有周測须鼎、月考鲸伴、段考、期末考晋控、摸底考……汞窗,而跑一次代碼就可以生成成績報(bào)告,為啥還要每次重復(fù)操作Excel呢赡译?
附錄
這篇文章主要用到如下方法:
- .groupby()系列方法仲吏,包括.groupby().agg(), .groupby().apply(), .groupby().get_group()等,用以實(shí)現(xiàn)分年級蝌焚、分班級計(jì)算裹唆;
- pd.read_excel(), pd.to_excel(),Excel的讀取與存儲只洒;
- pd.merge()许帐,數(shù)據(jù)篩選,相當(dāng)于Excel中的vlookup毕谴;
- df.sort_values()成畦,對數(shù)據(jù)進(jìn)行排序距芬;
- df.drop(),按條件去除某行或某列循帐;
- df.cut()框仔,給定區(qū)間,分析數(shù)據(jù)所屬區(qū)間拄养;
- df.count()离斩,分析某列數(shù)據(jù)各個(gè)元素的值,結(jié)合.groupby可以實(shí)現(xiàn)Excel的數(shù)據(jù)透視表效果瘪匿。
其他方法(未在此場景中應(yīng)用):
- np.unique(), 去除numpy的重復(fù)值跛梗,df.drop_duplicates(),按條件去除DataFrame的重復(fù)值柿顶;
- pd.to_datetime()茄袖,將其他類型的時(shí)間數(shù)據(jù)轉(zhuǎn)換為時(shí)間戳,df.dt.total_seconds()嘁锯,把時(shí)間數(shù)據(jù)轉(zhuǎn)化為秒;
- df.diff(1)聂薪,計(jì)算某兩列的差值(一般是計(jì)算dt時(shí)間內(nèi)變量的變化量)
- df.shift()家乘,向上或向下移動某列數(shù)據(jù);
- df.fillna()藏澳,缺失值填充仁锯;
- .loc[],按當(dāng)前索引提取某行翔悠;.iloc[]业崖,按數(shù)字提取某行;
- .isin()蓄愁,分析某列元素是否在另一數(shù)組中双炕。