课程教学评估是对教学质量和课程内容是否受欢迎的重要反馈。学校管理机构可以根据学生的反馈, 对课程和任课教师作出及时的调整, 以改进教学水平。土耳其加奇大学 (Gazi university) 在学生中进行了课程教学问卷调查. 在这项问卷调查中, 学生需要回答28个有关课程以及任课教师的问题. 学生从 {1, 2, 3, 4, 5} 中选择一个数字来回答,数字大小代表了对问题描述的赞同程度。以下显示了其中的几个问题:
Q1: 本课程在课程开始阶段明确了学期课程内容、教学方法和评价体系
Q9: 我非常喜欢这门课,且热切希望主动参与这门课的教学
Q17: 每次课授课教师都能准时到达教室
整理后的数据集一共有 5820 个样本, 每个样本包含 33 个特征, 其中28个特征为调查问题的反馈。剩余的 5 个特征为课程以及任课老师的信息, 包括教师工号、课程 ID、上课次数、出勤率和课程难度。
案例使用特征选择方法,试图找出影响出勤率和课程受欢迎程度的关键特征。具体来说,针对出勤率,选择过滤式的特征选择方法,如卡方检验;针对课程的受欢迎程度,选择互信息MIC的特征选择方法。
本案例的数据来源于Gazi University于2013年在学生中做的课程教学问卷,可以参考其官网,在这次调查中,每位学生需要回答28个与课程相关的选择题,每题的5个选项{1,2,3,4,5}代表答题人对该题的赞成程度。除此之外,还有5个特征为课程及其任课老师的信息,包括教师工号、课程ID、上课次数、出勤率和课程难度。
下面是33个特征的含义介绍:
% config InlineBackend.figure_format='retina'
# 载入一些基本的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 读取介绍特征信息的文本文件
df_intro=pd.read_table("./input/feature_introduction.txt",names=["intro"],sep="\n")
# 逐行输出方便观察
for i in range(len(df_intro)):
print(df_intro["intro"][i])
本案例的目的是找到影响出勤率和课程受欢迎程度的关键特征,所以我们需要关注attendance
(出勤率)和Q9
(课程喜欢程度),并分别建立以之为目标变量的特征选择。
读取数据:
# 为显示全部的列,添加如下的命令。
pd.set_option('display.max_columns',None)
data=pd.read_csv("./input/turkiye-student-evaluation_generic.csv")
data.head()
观察数据集的基本信息:
data.info()
观察发现:
1)数据集维度是(5820, 33);
2)所有特征都很完整,不需要做缺失值处理;
3)所有数据都是int64类型,不需要做数值化转换;
4)所有特征都是离散型变量,不适合某些针对连续数据的特征选择方法(比如Pearson
相关系数)。
data.describe()
观察各特征的常用统计量发现,数据间没有量级差异,不需要做标准化处理。
观察特征间的相关性:
# 为显示直观,我们将5个代表课程信息的特征命名为I1-I5
df_cor=data.copy()
df_cor.columns=pd.Index(["I1","I2","I3","I4","I5"]+list(df_cor.columns[5:34]))
df_cor.head()
# 计算相关矩阵
corr = df_cor.corr()
# 设置图形类型为下三角矩阵
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
# 做相关矩阵图
f, ax = plt.subplots(figsize=(10, 10))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=1.0,
square=True, xticklabels=2, yticklabels=2,
linewidths=.3, cbar_kws={"shrink": .5}, ax=ax)
plt.show()
特征I4也就是attendance
与I5
(difficulty
)相关性较高,28个问题之间都存在较强的相关性,关于课程基本信息的特征I1
-I3
与其它特征没有明显的相关性。
特征选择一般有两个目的:
1)选择最优特征以提升模型性能;
2)理解数据,比如特征之间的关系、数据结构特点等;
对于第一种情况,适合采用封装式、嵌入式的方法,因为过滤式的特征选择对有同样作用的特征没有筛选功能,比如多重共线性等问题,容易造成冗余;对于第二种情况,则适合采用过滤式方法或者稳定的嵌入式方法,过滤式方法简单有效,能阐释特征之间的关系,一般的封装式和嵌入式方法都是以模型为主的,容易出现重要特征得分低的情况(存在关联特征),因此稳定性不强,稳定性较强的嵌入式方法有:随机lasso、岭回归、随机logistic等。
本案例只要求找到影响出勤率和课程受欢迎程度的关键特征,而不需要进一步建立模型预测,所以我们可以使用简单的过滤式特征选择:基于出勤率的特征选择采用卡方检验方法,基于课程受欢迎程度的特征选择采用互信息MIC为指标。
特征选择前的准备工作:
# 定义选取的关键特征个数
N=6
# 准备好原始特征
df_feature=data.copy()
del df_feature["attendance"]
# 准备好标签
label=data["attendance"]
"""
函数find_name(new_feature, df_feature)用来输出关键特征的名称;
输入:原始特征、选择的关键特征;
输出:关键特征的名称
"""
def find_name(new_feature, df_feature):
# 定义列表存储关键特征名称
feature_name=[]
col=df_feature.columns
# 寻找关键特征的名称信息
for i in range(N):
for j in range(df_feature.shape[1]):
# 判别标准为new_feature中的特征向量与df_feature中的特征向量一致
if np.mean(abs(new_feature[i]-df_feature[col[j]]))==0:
feature_name.append(col[j])
print i+1,col[j]
break
return feature_name
下面利用卡方检验方法选择影响attendance
的前N个关键特征:
# 用卡方检验选择前N个关键特征
from sklearn.feature_selection import SelectKBest,chi2
new_feature=SelectKBest(chi2,k=N).fit_transform(df_feature,label).T
FS_list=find_name(new_feature, df_feature)
通过卡方检验方法,我们选出了前6个影响attendance
的关键特征,分别是difficulty
, Q17
, Q21
, Q22
,Q25
,Q28
,下面我们进一步观察这些关键特征对出勤率分布的影响如何,以期找到他们之间的关系。
# 画出不同特征值下出勤率分布情况的箱型图
f, axes = plt.subplots(2, 3, figsize=(20,10))
for i,item in enumerate(FS_list):
sns.boxplot(x=item,y='attendance',data=data,ax=axes[i/3,i%3])
plt.show()
观察上述箱型图可以发现:
1)当课程难度为3-5时,出勤率分布并没有明显的区别;当课程难度为2时,出勤率的中位数降为1;当课程难度为1时,绝大部分出勤率都为0,这说明当课程难度很小时,学生们几乎不出勤;同时课程难度与出勤率在一定区间内是存在正相关关系的。
2)我们容易注意到,Q17
, Q21
, Q22
,Q25
,Q28
这五个特征对出勤率的影响方式是相似的,因为它们的箱型图分布几乎完全一样。观察特征的含义:
Q17
:老师按时上课
Q21
:老师以积极的态度授课
Q22
:老师拥有开放的心态,并尊重学生的观点
Q25
:老师会在课上课下回答关于课程的问题
Q28
:老师会客观有礼地对待所有学生
这几个特征都是在描述老师的授课方式及礼仪态度问题,这说明这几个因素是学生们比较关注的。进一步观察单个箱型图,当特征程度为1时,出勤率的中位数是0;当特征程度为2-3时,出勤率的中位数是1;当特征程度为4-5时,出勤率的中位数是2;以上分析说明这几个特征与出勤率也存在一定程度上的正相关关系。
总体来说,选择的6个特征与出勤率都存在一定程度上的正相关关系,以课程难度难度为例,进一步观察这种关系的强度如何。需要注意,特征都是离散变量,下面做简单的回归是用均值代替x_estimator
的。
# 进一步观察正相关强度如何
sns.jointplot(x='difficulty',y='attendance',data=data,kind='reg',x_estimator= np.mean,order=2)
plt.show()
课程难度与出勤率确实存在正相关关系,但这种关系在课程难度较低时更加明显,所以综合全局来看,抛物线更符合这种关系。
如果还想观察具体的分布情况,可以用如下程序实现:
# 输出课程难度为1时的出勤率统计
diff_1=data["attendance"][data["difficulty"]==1]
index=diff_1.index
count=np.zeros(5)
for i in range(len(diff_1)):
for j in range(5):
if diff_1[index[i]]==j:
count[j] += 1
print(count)
利用互信息MIC作指标来选择影响Q9
(课程喜欢程度)的关键特征,首先,做一些准备工作:
# 定义选取的关键特征个数
N=6
# 准备好原始特征
df_feature=data.copy()
del df_feature["Q9"]
# 准备好标签
label=data["Q9"]
下面利用互信息MIC来选择影响Q9
(课程喜欢程度)的前N个关键特征:
# 引入MINE模块
from minepy import MINE
"""
函数FS_MIC(target, feature, N)用来根据MIC系数选择特征
输入:目标变量、特征、需要选出的特征个数
输出:根据MIC系数求出来的前N个特征
"""
def FS_MIC(target, feature, N):
# 记录各特征与target的互信息系数
corr_list=[]
col=feature.columns
for i in range(len(col)):
m=MINE()
m.compute_score(target, feature[col[i]])
corr_list.append(m.mic())
# 字典存储
dict_order=dict(zip(col, corr_list))
# 按MIC系数大小排序
new_feature=sorted(dict_order.iteritems(),key=lambda item:item[1], reverse=True)
# 输出前N个特征
for i in range(N):
print(new_feature[i])
# 画出前N个特征与标签的MIC系数柱状图
feature_top, score=zip(*new_feature)
df=pd.DataFrame(np.array(score[:N]),columns=["MIC"],index=feature_top[:N])
fig=plt.figure(figsize=(6,4))
ax1=fig.add_subplot(1,1,1)
df.plot(kind="bar",ax=ax1)
plt.ylim([min(score[:N])*0.75,max(score[:N])*1.25])
plt.show()
return list(feature_top[:N])
FS_list=FS_MIC(label, df_feature, N)
通过互信息MIC方法,我们选出了6个影响Q9
的关键特征,分别是Q10
, Q8
, Q11
, Q7
,Q5
,Q12
,下面我们进一步观察这些关键特征对课程受欢迎程度分布的影响如何,以期找到他们之间的关系。
# 画出不同特征值下课程受欢迎程度分布情况的箱型图
f, axes = plt.subplots(2, 3, figsize=(20,10))
for i,item in enumerate(FS_list):
sns.boxplot(x=item,y='Q9',data=data,ax=axes[i/3,i%3])
plt.show()
首先观察特征的含义:
Q10
:课程达到了我初始的期望
Q8
:测验、作业及考试对学习有作用
Q11
:课程对我的职业发展有利
Q7
:课程组织形式丰富
Q5
:对课堂讨论、作业布置很满意
Q12
:课程教会我以一个新的角度看待世界
这几个特征都是在描述课程的收获及感受问题,这说明这几个因素是学生们比较关注的。进一步观察单个箱型图,发现这几个特征与课程受欢迎程度都存在一定程度上的正相关关系,以Q10
为例,进一步观察这种关系的强度如何。需要注意,特征都是离散变量,下面做简单的回归是用均值代替x_estimator
的。
# 进一步观察正相关强度如何
sns.jointplot(x='Q10',y='Q9',data=data,kind='reg',x_estimator= np.mean)
plt.show()
特征Q10
和Q9
存在明显的线性正相关关系,说明在一般情况下,当课程内容达到了学生期望时,学生就会喜欢这门课。