Oh-Seung-Rok

안녕하세요. 정량적인 분석을 통해 제품-서비스를 개선하고 소비자들의 욕구를 먼저 파악하는 분석가가 되고 싶은 오승록입니다. 포트폴리오 [https://seungrok0317.com]

Kaggle - Student's Academic Performance

19 Mar 2021 » Kaggle

Studnet’s Academic Performance

  • 데이터 셋 : https://www.kaggle.com/aljarah/xAPI-Edu-Data

  • 학생들의 인적사항과 평가데이터를 통해 학생들의 성적을 예측하고자 하는 데이터셋이다.

라이브러리 설정 및 데이터 불러오기

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

df = pd.read_csv('C:/Users/dissi/Kaggle Practice/xAPI-Edu-Data.csv')
df.sample(10)
genderNationalITyPlaceofBirthStageIDGradeIDSectionIDTopicSemesterRelationraisedhandsVisITedResourcesAnnouncementsViewDiscussionParentAnsweringSurveyParentschoolSatisfactionStudentAbsenceDaysClass
453FJordanJordanMiddleSchoolG-08AGeologySFather29784012YesGoodAbove-7M
412MPalestineJordanMiddleSchoolG-07BBiologyFFather78806651YesGoodUnder-7M
339FPalestineJordanlowerlevelG-02BFrenchSFather79891114NoGoodUnder-7M
55MKWKuwaITMiddleSchoolG-07AMathFFather1614620YesGoodAbove-7L
471MPalestineJordanMiddleSchoolG-08AHistorySFather78827853YesGoodUnder-7M
140MTunisTunisMiddleSchoolG-07AQuranFFather1060520YesBadAbove-7L
327MJordanJordanlowerlevelG-02AFrenchSFather3010205NoBadAbove-7L
228MKWKuwaITHighSchoolG-11BMathSMum73847781YesGoodAbove-7H
418MPalestineJordanMiddleSchoolG-07BBiologyFFather88907681YesGoodUnder-7H
378MJordanJordanlowerlevelG-02BArabicFFather10305091YesBadAbove-7L
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 480 entries, 0 to 479
Data columns (total 17 columns):
 #   Column                    Non-Null Count  Dtype 
---  ------                    --------------  ----- 
 0   gender                    480 non-null    object
 1   NationalITy               480 non-null    object
 2   PlaceofBirth              480 non-null    object
 3   StageID                   480 non-null    object
 4   GradeID                   480 non-null    object
 5   SectionID                 480 non-null    object
 6   Topic                     480 non-null    object
 7   Semester                  480 non-null    object
 8   Relation                  480 non-null    object
 9   raisedhands               480 non-null    int64 
 10  VisITedResources          480 non-null    int64 
 11  AnnouncementsView         480 non-null    int64 
 12  Discussion                480 non-null    int64 
 13  ParentAnsweringSurvey     480 non-null    object
 14  ParentschoolSatisfaction  480 non-null    object
 15  StudentAbsenceDays        480 non-null    object
 16  Class                     480 non-null    object
dtypes: int64(4), object(13)
memory usage: 63.9+ KB
  • gender : 학생 성별
  • NationalITy : 학생 국적
  • PlaceofBirth : 학생이 태어난 국가
  • StageID : 학생이 다니는 학교 (초, 중, 고)
  • GradeID : 학생이 속한 성적 등급
  • SectionID : 학생이 속한 반 이름
  • Topic : 수강한 과목
  • Semester : 수강한 학기 (1학기/2학기)
  • Relatioin : 주 보호자와 학생의 관계
  • raisedhands : 학생이 수업 중 손을 든 횟수
  • VisITedReseources : 학생이 과목 교과과정을 확인한 횟수
  • Discussion : 학생이 토론 그룹에 참여한 횟수
  • AnnouncementsView : 학생이 공지를 확인한 횟수
  • ParentAnsweringSurvey : 부모가 학교 설문에 참여했는지 여부
  • ParentschoolSatisfaction : 부모가 학교에 만족했는지 여부
  • StudentAbsenceDays : 학생 결석 횟수 (7회 이상/ 미만)
  • Class : 학생 성적 등급 (L 낮음, M 보통, H 높음)
df['NationalITy'].value_counts()
KW             179
Jordan         172
Palestine       28
Iraq            22
lebanon         17
Tunis           12
SaudiArabia     11
Egypt            9
Syria            7
Lybia            6
USA              6
Iran             6
Morocco          4
venzuela         1
Name: NationalITy, dtype: int64
df['PlaceofBirth'].unique()
array(['KuwaIT', 'lebanon', 'Egypt', 'SaudiArabia', 'USA', 'Jordan',
       'venzuela', 'Iran', 'Tunis', 'Morocco', 'Syria', 'Iraq',
       'Palestine', 'Lybia'], dtype=object)

EDA 및 기초 통계 분석

수치형 데이터

  • raisedhands, visitedresources, announcementsview, discussion
df.describe()
raisedhandsVisITedResourcesAnnouncementsViewDiscussion
count480.000000480.000000480.000000480.000000
mean46.77500054.79791737.91875043.283333
std30.77922333.08000726.61124427.637735
min0.0000000.0000000.0000001.000000
25%15.75000020.00000014.00000020.000000
50%50.00000065.00000033.00000039.000000
75%75.00000084.00000058.00000070.000000
max100.00000099.00000098.00000099.000000
  • 요약을 통해 보았을때 전체적으로 균형이 맞는 데이터라 볼 수 있다.
sns.histplot(data=df, x='raisedhands',hue='Class', hue_order=['L', 'M', 'H'], kde=True)
<matplotlib.axes._subplots.AxesSubplot at 0x1661d411708>

output_15_1

  • 손을 드는 횟수는 적거나 많거나 양쪽으로 나뉘어 나타나는데 Class 구분을 잘 반영하고 있다. 다만 수업시간에 손을 적게들어도, 많이들어도 중위권에 들어간 학생이 있는것으로 보아 완벽히 구분해내지는 못함을 보여준다.
sns.histplot(data=df, x='VisITedResources',hue='Class', hue_order=['L', 'M', 'H'], kde=True)
<matplotlib.axes._subplots.AxesSubplot at 0x1661dc92108>

output_17_1

  • raisedhands와 비슷한 양상으로 역시 Class 구분을 잘 반영하고 있다.
sns.histplot(data=df, x='AnnouncementsView',hue='Class', hue_order=['L', 'M', 'H'], kde=True)
<matplotlib.axes._subplots.AxesSubplot at 0x1661dd81948>

output_19_1

  • 성적이 낮은 학생들의 구분은 쉽지만 중위권과 상위권 학생들의 구분이 모호함.
sns.histplot(data=df, x='Discussion',hue='Class', hue_order=['L', 'M', 'H'], kde=True)
<matplotlib.axes._subplots.AxesSubplot at 0x1661de54548>

output_21_1

  • 다른 지표 대비 특별한 경향성이 보이지 않음.(하위권 학생들도 참여율이 나름 있고, 상위권 학생들도 두 양상으로 나타난다.)
# raisedhands와 visitedresources의 경우 잘 나누어주고 있기 때문에 jointplot으로 함께 확인해본다.
sns.jointplot(data=df, x='VisITedResources', y='raisedhands', hue='Class', hue_order=['L','M',"H"])
<seaborn.axisgrid.JointGrid at 0x1661df00488>

output_23_1

  • 중위권과 상위권 구분은 여전히 어렵지만 하위권과 중위권은 jointplot, 2차원으로 확인시 더 분류할 수 있다..
sns.pairplot(df, hue='Class', hue_order=['L','M','H'])
<seaborn.axisgrid.PairGrid at 0x1661e05be08>

output_25_1

범주형 데이터

sns.countplot(data=df, x='Class', order=['L', 'M', 'H']) 
<matplotlib.axes._subplots.AxesSubplot at 0x1661e960dc8>

output_27_1

sns.countplot(data=df, x='gender', hue='Class', hue_order=['L', 'M', 'H'])

# 남녀 카테고리에 따른 성적 비교
<matplotlib.axes._subplots.AxesSubplot at 0x1661fbad688>

output_28_1

sns.countplot(data=df, x='NationalITy', hue='Class', hue_order=['L', 'M', 'H'])
plt.xticks(rotation=90)
plt.show()

# 국적에 따른 성적 비교

output_29_0

sns.countplot(data=df, x='ParentAnsweringSurvey', hue='Class', hue_order=['L', 'M', 'H'])

# 부모 응답에 따른 성적 비교 
# 학교만족도의 경우 성적과 연관 가능성이 높으므로 빼는게 좋다고 판단 됨.
<matplotlib.axes._subplots.AxesSubplot at 0x1661eac8788>

output_30_1

sns.countplot(data=df, x='Topic', hue='Class', hue_order=['L', 'M', 'H'])
plt.xticks(rotation=90)
plt.show()

# 과목에 따른 성적 비교 // 어떤 과목이 어려운지 

output_31_0

범주형 대상 Class을 수치로 바꾸어 표현

  • 비율 파악을 위해, Low를 -1로, Middle을 0으로, High를 1로
df['Class_value'] = df['Class'].map(dict(L=-1, M=0, H=1))
gb_gender = df.groupby('gender').mean()['Class_value']
gb_gender
gender
F    0.291429
M   -0.118033
Name: Class_value, dtype: float64
plt.bar(gb_gender.index, gb_gender)
<BarContainer object of 2 artists>

output_35_1

gb_Topic = df.groupby('Topic').mean()['Class_value'].sort_values()
plt.barh(gb_Topic.index, gb_Topic)
<BarContainer object of 12 artists>

output_36_1

데이터 전처리

범주형 데이터를 one-hot vector로 변환

# 컴퓨터는 0,1 밖에 인식할 수 없기 때문.

df.columns
Index(['gender', 'NationalITy', 'PlaceofBirth', 'StageID', 'GradeID',
       'SectionID', 'Topic', 'Semester', 'Relation', 'raisedhands',
       'VisITedResources', 'AnnouncementsView', 'Discussion',
       'ParentAnsweringSurvey', 'ParentschoolSatisfaction',
       'StudentAbsenceDays', 'Class', 'Class_value'],
      dtype='object')
# 다중공선성을 줄이기위해 drop_first를 True로
# drop을 써주지 않으면 해당칼럼을 수치형으로 인식하여 그대로 가져오게 됨. 빼고 싶으면 미표기가 아닌 drop를 써야 함.

X = pd.get_dummies(df.drop(['ParentschoolSatisfaction', 'Class', 'Class_value'], axis=1),
                   columns=['gender', 'NationalITy', 'PlaceofBirth', 'StageID', 'GradeID',
                            'SectionID', 'Topic', 'Semester', 'Relation','ParentAnsweringSurvey',
                            'StudentAbsenceDays'], drop_first=True)

y = df['Class']
X.tail()
raisedhandsVisITedResourcesAnnouncementsViewDiscussiongender_MNationalITy_IranNationalITy_IraqNationalITy_JordanNationalITy_KWNationalITy_Lybia...Topic_HistoryTopic_ITTopic_MathTopic_QuranTopic_ScienceTopic_SpanishSemester_SRelation_MumParentAnsweringSurvey_YesStudentAbsenceDays_Under-7
4755458000100...0000001000
47650771428000100...0000000001
47755742529000100...0000001001
47830171457000100...1000000000
47935142362000100...1000001000

5 rows × 59 columns

학습데이터 테스트 데이터 분리

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

모델 학습 및 평가

Logistic Regression 모델 학습

from sklearn.linear_model import LogisticRegression

model_lr = LogisticRegression(max_iter=1000)
model_lr.fit(X_train, y_train)
C:\Users\dissi\anaconda31\lib\site-packages\sklearn\linear_model\_logistic.py:940: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG)





LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=1000,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
# 평가

from sklearn.metrics import classification_report
pred = model_lr.predict(X_test)
print(classification_report(y_test, pred))
              precision    recall  f1-score   support

           H       0.77      0.67      0.72        55
           L       0.78      0.76      0.77        33
           M       0.59      0.68      0.63        56

    accuracy                           0.69       144
   macro avg       0.72      0.70      0.71       144
weighted avg       0.70      0.69      0.70       144

XGBoost 모델 학습

from xgboost import XGBClassifier
model_xgb = XGBClassifier()
model_xgb.fit(X_train, y_train)
C:\Users\dissi\anaconda31\lib\site-packages\xgboost\sklearn.py:888: UserWarning: The use of label encoder in XGBClassifier is deprecated and will be removed in a future release. To remove this warning, do the following: 1) Pass option use_label_encoder=False when constructing XGBClassifier object; and 2) Encode your labels (y) as integers starting with 0, i.e. 0, 1, 2, ..., [num_class - 1].
  warnings.warn(label_encoder_deprecation_msg, UserWarning)


[21:42:35] WARNING: C:/Users/Administrator/workspace/xgboost-win64_release_1.3.0/src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'multi:softprob' was changed from 'merror' to 'mlogloss'. Explicitly set eval_metric if you'd like to restore the old behavior.





XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=4, num_parallel_tree=1,
              objective='multi:softprob', random_state=0, reg_alpha=0,
              reg_lambda=1, scale_pos_weight=None, subsample=1,
              tree_method='exact', use_label_encoder=True,
              validate_parameters=1, verbosity=None)
# 평가

pred = model_xgb.predict(X_test)
print(classification_report(y_test, pred))
              precision    recall  f1-score   support

           H       0.79      0.69      0.74        55
           L       0.85      0.85      0.85        33
           M       0.65      0.73      0.69        56

    accuracy                           0.74       144
   macro avg       0.76      0.76      0.76       144
weighted avg       0.75      0.74      0.74       144

결과 분석

model_lr.classes_
array(['H', 'L', 'M'], dtype=object)
# 회귀분석 결과

plt.figure(figsize=(15, 10))
plt.bar(X.columns, model_lr.coef_[0, :]) # H Class에 관여하는 요소들의 영향 정도
plt.xticks(rotation=90)
plt.show()

output_53_0

  • 결석일수 7일 미만, 부모응답이 높고, 보호자가 어머니이며, 사우디아라비아 국적,출생이며 수학을 선택하면 성적이 높게 나온다.
# 회귀분석 결과(성적을 낮게 하는 요소)
plt.figure(figsize=(15, 10))
plt.bar(X.columns, model_lr.coef_[1, :]) # L Class
plt.xticks(rotation=90)
plt.show()

output_55_0

# xgboost분석 결과

plt.figure(figsize=(15, 10))
plt.bar(X.columns, model_xgb.feature_importances_)
plt.xticks(rotation=90)
plt.show()

output_56_0

  • 결석일수 7일 미만, 보호자가 어머니이며, 손을 많이 들고 공지를 많이 확인 할 경우 성적이 높음.