[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3

文章目录

  • 前言
  • 一、导入库
  • 二、数据处理
    • 1、用户特征数据标签化
    • 2、用户特征数据与作业表合并
    • 3、计算附加其他信息
    • 4、用户行为数据与作业表合并
    • 5、用户特征行为数据、用户行为数据与作业/考试明细数据合并
  • 三、学生成绩数据分析
    • 1、变量之间相关性分析
    • 2、数据建模
      • 2.1 knn
      • 2.2 Logistic回归
      • 2.3 决策树
      • 2.4 随机森林


前言

该文章为连载的第三篇:学生成绩分析

某家在线教育机构拥有自己开发的教育产品VLE,该教育机构提供了他们四个学期里,开展的七门课的数据,接下来我会根据这些数据,为该教育机构做一系列的数据分析,包括用户的RFM模型、用户分群特征、用户成绩分析等等。

该教育机构部分数据库结构如下

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第1张图片


如下这三篇文章为:用户成绩分析

做用户成绩分析,主要是把学生特征和学生使用产品习惯,与成绩结果做关联,所以会用到如下几个表:

  1. studentInfo 学生信息表 (总数据量三万多,记为stu)
  2. studentVle 学生产品交互行为表 (总数据量一千多万,记为vle)
  3. studentAssessment 学生成绩表 (总数据量十七余万,记为stu_ass)
  4. assessments 作业/考试表(总数据量两百多,记为ass)

注意assessment意为作业,但涵盖Exam的考试信息,因为在国外,作业和考试都有一个权重分值,都会占最终成绩的一部分,并不是考试占最终成绩的100%。

用户数据:学生编号,性别,学生居住区,最高学历,多重剥夺指数范围(衡量学生所在区的城市贫富等情况),年龄段,是否残疾,学生尝试该课程次数,当前课程的学分,最终成绩

行为数据:学习时长,点击次数

课程数据:课程编号,开课时间,使用产品的ip编码,作业/考试编号、日期等


一、导入库

import pandas as pd
import numpy as np
import datetime
import time
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus']=False
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn
from sklearn.preprocessing import LabelEncoder
np.set_printoptions(suppress=True)
pd.set_option('display.float_format', lambda x: '%.4f' % x)


二、数据处理

1、用户特征数据标签化

用df.isna().sum()的方法,查看三个表的空缺值情况

学生信息表里:
只有多重剥夺指数范围有1111条空值(总数据量32593,空缺占比3.4%)

学生成绩表里:
只有分数有173条空缺值,总数据量173912,空缺占比极低,会用该学期该门课的平均值填补。

考试或作业表:
只有考试日期有11条空缺值,是因为部分考试/作业无日期限制。

先看一下学生信息表大概长什么样子
[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第2张图片
把object类型的重要字段标签化

#性别
stu["gender"]=stu["gender"].map({"M":1,"F":2})

#最高教育程度
highest_education_dict={"No Formal quals":0,"Lower Than A Level":1,"A Level or Equivalent":2,"HE Qualification":3,"Post Graduate Qualification":4}
stu["highest_edu_label"]=stu['highest_education'].map(highest_education_dict)


#多重剥夺指数范围

#先去中间值,把范围转成数值
imd_band_dict={'0-10%':5,
'10-20':15,
'20-30%':25,
'30-40%':35,
'40-50%':45,
'50-60%':55,
'60-70%':65,
'70-80%':75,
'80-90%':85,
'90-100%':95}
stu["imd_band_label"]=stu['imd_band'].map(imd_band_dict)

#平均数填补空值
stu["imd_band_label"].fillna(stu["imd_band_label"].mean(),inplace=True)

#年龄:
age_encoder = LabelEncoder()
age_encoder.fit(stu['age_band'].value_counts().sort_index().index.tolist())
stu["age_band_label"]=age_encoder.transform(stu['age_band'])


#是否残疾
stu["disability"]=stu["disability"].map({"Y":1,"N":0})

#最终成绩
stu["final_result_label"]=stu["final_result"].map({'Withdrawn':0, 'Fail':1,'Pass':2, 'Distinction':3})

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第3张图片

筛选所需要的数据:

stu=stu[["code_module","code_presentation","id_student","gender","num_of_prev_attempts","studied_credits","disability","highest_edu_label","imd_band_label","age_band_label","final_result_label"]]

2、用户特征数据与作业表合并

合并考试作业表个学生成绩表

stu_ass=pd.merge(stu_ass,ass,on="id_assessment")

plt.figure(figsize=(8, 6))
#sns.set_style('white')
sns.boxplot(x="code_presentation", y="score", hue="code_module", data=stu_ass.sort_values("code_presentation"))
plt.legend(loc=0,bbox_to_anchor=(1, 0.8),title="课程",frameon=False)#图示靠右显示
plt.title("各学期各课程考试/作业分数分布")
plt.xlabel("学期")
plt.tight_layout()
plt.show

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第4张图片
每学期每门课程均有分数低的异常值,并且较多
BBB和GGG课程的分数,在三个学期中都是大多数同学集中高分位置
CCC和DDD课程的分数集中在较低分数

看一下整体分数分布

sns.distplot(stu_ass["score"],kde=False)
plt.title("整体学生作业/考试分数分布")
plt.show()

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第5张图片

看整体的话异常值并不多,大多数分数都分布在60-100之间。0分、40分、60分、80分、100分都有相对于附近的分数值分布突然变集中。

stu_ass["score"].value_counts().sort_index().head(30)

但确实低分每个分数都有并且很集中

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第6张图片

用盖帽法处理异常,考虑每学期每门课的难度或许不同,分数分布情况也会不同。这里每学期每门课的的低分异常值定义为:中位数-2倍标准差,

只有


stu_ass["score_chuli"]=stu_ass["score"]

#定义一个方法
def yichang(code_pre,code_module):
    score_list=stu_ass[(stu_ass["code_presentation"]==code_pre)&(stu_ass["code_module"]==code_module)]["score"]
    s_mean = score_list.mean()
    s_min = score_list.mean() - 2*score_list.std() 
    stu_ass.loc[(stu_ass["code_presentation"]==code_pre)&(stu_ass["code_module"]==code_module)&(stu_ass["score"] < s_min), 'score_chuli'] = s_min
    stu_ass.loc[(stu_ass["code_presentation"]==code_pre)&(stu_ass["code_module"]==code_module)&(stu_ass["score"].isna()), 'score_chuli'] = s_mean

#每学期每门课
for m in stu_ass["code_presentation"].unique().tolist():
    for n in stu_ass["code_module"].unique().tolist():
        yichang(m,n)

修改后的各学期各门课程的分数分布:

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第7张图片

3、计算附加其他信息

加上交付时间与应交时间的间隔、和是否准时提交:

stu_ass["day_gap"]=stu_ass["date_submitted"]-stu_ass["date"]

stu_ass["submit_on_time"]=stu_ass["day_gap"].apply(lambda x:1 if x<=0 else 0)

4、用户行为数据与作业表合并

每个用户在每学期每门课使用产品用了多少天,总的点击次数是多少

vle_df=vle.groupby(["code_presentation","code_module","id_student"]).agg({"date":pd.Series.nunique,"sum_click":"sum"}).reset_index().rename(columns={"date":"cnt_study_days"})

vle_df["avg_click"]=vle_df["sum_click"]/vle_df["cnt_study_days"]

stu_vle=pd.merge(stu,vle_df,on=["code_module","code_presentation","id_student"],how="left")

stu_vle.isna().sum()

聚合后发现有些用户并没有使用产品,导致该用户的cnt_study_days、sum_click和avg_click为nan,用fillna(0)填补

5、用户特征行为数据、用户行为数据与作业/考试明细数据合并

stu_ass_vle=pd.merge(stu_ass,stu_vle,on=["id_student","code_module","code_presentation"],how="left")

得到一个173912行, 24列的数据集,每一行代表一个学生在一个学期里的一门课的作业/考试情况,以及对应的该学生的特征和使用产品情况。

三、学生成绩数据分析

1、变量之间相关性分析

这里想分析学生的平时分数(作业/考试分数)和最终总分与其他特征会不会有什么相关性。这里用热力图表示。

#筛选出带标签的变量,建模需要使用
train_df=stu_ass_vle[["weight","day_gap","gender","num_of_prev_attempts","studied_credits","cnt_study_days","avg_click","highest_edu_label","imd_band_label","age_band_label","score_chuli","final_result_label"]]

corr_data=train_df.drop("id_student",axis=1).corr().round(4)

plt.figure(figsize=(12, 12))

# np.zeros_like() 返回一个零数组,其形状和类型与给定的数组相同;
mask = np.zeros_like(corr_data)

#np.triu_indices_from(mask) 返回数组上三角形的索引。
mask[np.triu_indices_from(mask)] = True
sns.heatmap(corr_data, cmap='viridis',annot=True,fmt='g',mask=mask)
#fmt='g'保持数据小数一致

[Python]学生成绩分析, 可视化以及建模--在线教育行业分析案例连载3_第8张图片

这里看到很多特征两两之间都没有太多很强的相关性,最后一行的最终成绩可以看到:
每天使用产品越多的学生最终成绩可能越高。
最高学历越高的学生最终成绩可能越高。
平时成绩越好的学生最终成绩可能越高。

其余比较明显相关性的是:
交付作业/考试的间隔时间越大,这个作业/考试的权重可能越高。
重修课程的用户学分会更高。
使用产品天数也多的用户,相对日均点击产品去学习也会越高。
产品日均使用次数越高的,交作业会越早。
女性日均使用产品比男性高

2、数据建模

我们选择使用以下几种模型进行建置,并比较模型的分类效能。

首先在将训练集划分为训练集和验证集,其中训练集用于训练模型,验证集用于验证模型效果。首先导入建模库:


from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier


# 预处理
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 模型评估
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import confusion_matrix, classification_report, accuracy_score, f1_score, roc_auc_score

凭经验、以及从上面跟最终成绩跟其他变量的相关性挑出一些重要的指标,并划分特征和标签,并采用分层抽样的方式划分训练集和验证集。

train_df=train_df[["id_student","gender","num_of_prev_attempts","cnt_study_days","avg_click","highest_edu_label","imd_band_label","age_band_label","final_result_label"]]

# 划分特征和标签
X = train_df.drop(['id_student', 'final_result_label'], axis=1)
y = train_df['final_result_label'] 


# 划分训练集和验证集(分层抽样) 
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0) 
print(X_train.shape, X_val.shape, y_train.shape, y_val.shape) 
#(139129, 7) (34783, 7) (139129,) (34783,)

2.1 knn

# 建立knn
knn = KNeighborsClassifier(n_neighbors=4, n_jobs=-1)
#ovo_knn=OneVsOneClassifier(knn)
knn.fit(X_train, y_train)

y_pred = knn.predict(X_val)

#print('Simple KNeighborsClassifier accuracy:%.3f' % (ovo_knn.score(y_val, y_pred)))

print('Simple KNeighborsClassifier accuracy:%.3f' % (accuracy_score(y_val, y_pred)))
print('Simple KNeighborsClassifier f1_score: %.3f' % (f1_score(y_val, y_pred,average='macro')))  

Simple KNeighborsClassifier accuracy:0.953
Simple KNeighborsClassifier f1_score: 0.914

2.2 Logistic回归

lr = LogisticRegression(multi_class='multinomial')
lr.fit(X_train, y_train)

y_pred = lr.predict(X_val)

print('Simple LogisticRegression accuracy:%.3f' % (accuracy_score(y_val, y_pred)))
print('Simple LogisticRegression f1_score: %.3f' % (f1_score(y_val, y_pred,average='macro')))  
Simple LogisticRegression accuracy:0.613
Simple LogisticRegression f1_score: 0.203

2.3 决策树

dtc = DecisionTreeClassifier(max_depth=10, random_state=0) 
dtc.fit(X_train, y_train)

y_pred = dtc.predict(X_val) 

print('Simple DecisionTreeClassifier accuracy:%.3f' % (accuracy_score(y_val, y_pred)))

print('Simple DecisionTreeClassifier f1_score: %.3f' % (f1_score(y_val, y_pred,average='macro')))  

Simple DecisionTreeClassifier accuracy:0.657
Simple DecisionTreeClassifier f1_score: 0.394

2.4 随机森林

rfc = RandomForestClassifier(n_estimators=100, max_depth=10, n_jobs=-1)  
rfc.fit(X_train, y_train)

y_pred = rfc.predict(X_val) 

print('Simple RandomForestClassifier accuracy:%.3f' % (accuracy_score(y_val, y_pred)))
print('Simple RandomForestClassifier f1_score: %.3f' % (f1_score(y_val, y_pred,average='macro'))) 
Simple RandomForestClassifier accuracy:0.658
Simple RandomForestClassifier f1_score: 0.350

综上,以f1-score作为评价标准的情况下,KNN算法有较好的分类效能,同时可以通过参数调整的方式来优化其他模型,通过调整预测的门槛值来增加预测效能等其他方式。


你可能感兴趣的:(数据分析,python,学习,数据分析)