문제 정의
사용자들의 학습성과를 높이려면?
기능 구현을 하면서 사용자들의 학습 성과를 높이려면 어떤 요소가 중요한지 고민하며 분석을 진행했다.
데이터는 Kaggle의 ONLINE EDUCATION SYSTEM REVIEW 데이터를 사용했으며
실제 프로젝트 데이터는 양이 적어 편향된 결과를 가져올 수 있어 제외했다.
데이터 전처리
1
2
3
4
import pandas as pd
data = pd.read_csv('../kaggle/ONLINE EDUCATION SYSTEM REVIEW.csv')
data.head()
data.shape # 1033개의 행, 23개의 열
결측치 확인
1
print(data.isnull().sum())
결측치 없음
범주형 개수 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data.info()
data.nunique()
data['Gender'].nunique() # 2
data['Home Location'].nunique() # 2
data['Level of Education'].nunique() # 3
data['Device type used to attend classes'].nunique() # 3
data['Economic status'].nunique() # 3
data['Are you involved in any sports?'].nunique() # 2
data['Do elderly people monitor you?'].nunique() # 2
data['Interested in Gaming?'].nunique() # 2
data['Have separate room for studying?'].nunique() # 2
data['Engaged in group studies?'].nunique() # 2
data['Average marks scored before pandemic in traditional classroom'].nunique() # 10
data['Interested in?'].nunique() # 3
data['Your level of satisfaction in Online Education'].nunique() # 3
이상치 확인
1
data.describe()
계산 가능한 데이터들 가운데 평균과 중앙값(50%)의 차이가 크지 않은것으로 보아
특이점이나 이상치가 크지 않은 것으로 보인다.
상관 분석
1
2
3
import plotly.express as px
corr_matrix = data._get_numeric_data().corr().round(2)
px.imshow(corr_matrix, text_auto=True, width=930, height=930)
Your interaction in online mode와 Performance in online의 상관계수 : 0.56
Your interaction in online mode와 Clearing doubts with faculties in online mode의 상관계수 : 0.72
상호작용이 많을수록 학습 성과가 높을 가능성이 있다.
가설 설정
상호작용이 많을수록 학습성과가 높을 것이다.
데이터 확인
1
2
data['Performance in online'].describe()
data['Your interaction in online mode'].describe()
데이터 시각화 및 분석
산점도
1
2
3
import plotly.express as px
fig = px.scatter(x='Your interaction in online mode', y='Performance in online', data_frame=data, trendline="ols") # 추세선 추가
fig.show()
산점도에서 선형성이 보이지 않고 격자 형태로 띄워져 boxplot으로 분석을 진행했다.
Boxplot
1
2
3
import plotly.express as px
fig = px.box(data, x="Your interaction in online mode", y="Performance in online", points="outliers")
fig.show()
3과 4에서 이상치가 발견되었다.
x=1에서 중앙값이 50%에서 살짝 위에 형성되었다.
x=4, 5에서 중앙값이 Q3(상위 75%)와 같은 수준으로 높게 나타났다.
→ 상호작용이 많을수록 성과 상승 가능성 증가
그래프에서 이상치가 존재하여 IQR을 통해 Performance in online의 이상치 개수가 37개로 나왔으나
describe()
를 통해 mean과 50% 데이터의 차이가 크지 않으며
min과 max 모두 정상범주에 있어 이상치를 제거하지 않고 실행했다.
카이제곱
qcut()
을 활용했으며 몇개의 범주로 나눠야 하는지 체크했다.
히스토그램
1
2
3
4
5
sns.histplot(data["Performance in online"])
plt.show()
sns.histplot(data["Your interaction in online mode"], discrete=True)
plt.show()
- 2~3 : 낮은 분포
- 6~8: 높은 분포 (집중됨)
- 4 ~ 5, 9 ~ 10: 중간 정도의 분포
- 3에 데이터가 가장 많고 나머지는 비슷한 수준
구간 개수 설정하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency, kruskal
import matplotlib
matplotlib.rcParams['font.family'] = 'Malgun Gothic' # Windows
matplotlib.rcParams['axes.unicode_minus'] = False
for q in [2, 3, 4, 5]:
print(f"\n=== 개수: {q} ===")
x_cut = pd.qcut(data["Your interaction in online mode"], q=q, duplicates='drop')
y_cut = pd.qcut(data["Performance in online"], q=q, duplicates='drop')
print(f"x 개수 : {len(x_cut.cat.categories)}")
print(f"y 개수 : {len(y_cut.cat.categories)}")
# qcut
data["X_cut"] = pd.qcut(data["Your interaction in online mode"], q=len(x_cut.cat.categories), duplicates='drop')
data["Y_cut"] = pd.qcut(data["Performance in online"], q=len(y_cut.cat.categories), duplicates='drop')
# 카이제곱 검정
observed = pd.crosstab(data["X_cut"], data["Y_cut"])
chi2, p, dof, expected = chi2_contingency(observed)
percentage = ((expected < 5).sum() / expected.size) * 100
if percentage >= 20:
print("카이제곱 검정 사용 불가능")
else:
print("카이제곱 검정 사용 가능")
if stats.chi2.ppf(0.95, dof).round(2) < chi2 and p < 0.05:
print("대립가설 채택 (유의한 관계 있음)")
print(f"카이제곱 통계량: {chi2.round(2)}, p-value: {p.round(2)}")
residuals = (observed - expected) / np.sqrt(expected)
print(residuals)
else:
print("귀무가설 채택 (유의한 관계 없음)")
# 시각화
plt.figure(figsize=(8, 6))
sns.heatmap(residuals, annot=True, cmap="coolwarm", fmt=".2f") # 값 표시
plt.title("잔차 (Residuals)")
plt.xlabel("Y_cut")
plt.ylabel("X_cut")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
=== 개수: 3 ===
x 개수 : 2
y 개수 : 3
카이제곱 검정 사용 가능
대립가설 채택 (유의한 관계 있음)
카이제곱 통계량: 219.33, p-value: 0.0
Y_cut (1.999, 6.0] (6.0, 8.0] (8.0, 10.0]
X_cut
(0.999, 3.0] 4.654189 -1.237490 -6.056555
(3.0, 5.0] -7.595205 2.019469 9.883734
=== 개수: 4 ===
x 개수 : 4
y 개수 : 4
카이제곱 검정 사용 가능
대립가설 채택 (유의한 관계 있음)
카이제곱 통계량: 385.62, p-value: 0.0
Y_cut (1.999, 6.0] (6.0, 7.0] (7.0, 8.0] (8.0, 10.0]
X_cut
(0.999, 2.0] 5.884570 -2.507761 -2.943029 -4.025926
(2.0, 3.0] 1.086484 3.114782 -0.562652 -4.526174
(3.0, 4.0] -5.339457 0.327358 5.401652 2.536907
(4.0, 5.0] -5.614034 -2.550390 -1.044280 13.594376
q=3과 q=4 모두 유의한 관계가 나타났으며
x=2, y=4로 나눠도 큰 차이 없이 같은 경향이 나타나
해석이 쉬운 q=3을 사용하여 “낮음, 보통, 높음”으로 구분했다.
X(상호작용) → 2개, Y(성과) → 3개로 구분
1
2
Categories (2, interval[float64, right]): [(0.999, 3.0] < (3.0, 5.0]]
Categories (3, interval[float64, right]): [(1.999, 6.0] < (6.0, 8.0] < (8.0, 10.0]]
*duplicates=’drop’
1
2
ValueError: Bin edges must be unique: Index([1.0, 3.0, 3.0, 5.0], dtype='float64', name='Your interaction in online mode').
You can drop duplicate edges by setting the 'duplicates' kwarg
와 같이 에러가 떠서 설정
*만약 x값도 3개로 범주를 설정했다면
Bin labels must be one fewer than the number of bin edges
라는 에러가 뜬다.
범주화
1
2
data["X_cut"] = pd.qcut(data["Your interaction in online mode"], q=2, labels=['낮음', '높음'], duplicates='drop')
data["Y_cut"] = pd.qcut(data["Performance in online"], q=3, labels=['낮음', '보통', '높음'], duplicates='drop')
카이제곱 실행
귀무가설: 두 변수는 연관성이 없다. (독립이다)
대립가설: 두 변수는 연관성이 있다. (독립이 아니다)
1
2
3
4
5
6
7
8
9
10
11
12
from scipy.stats import chi2_contingency
import pandas as pd
# scipy를 이용한 카이제곱 검정(카이제곱, p, 자유도, 기대치)
observed = pd.crosstab(data['X_cut'], data['Y_cut'])
chi2, p, dof, expected = chi2_contingency(observed)
print(f"카이제곱 통계량: {chi2}, p-value: {p}")
print(f"자유도: {dof}")
print(f"기대 빈도표: \n{expected}")
import scipy.stats as stats
print(stats.chi2.ppf(0.95,2).round(2)) # 5.99
1
2
3
4
5
카이제곱 통계량: 219.32830534852573, p-value: 2.3629995067156613e-48
자유도: 2
기대 빈도표:
[[344.60212972 290.07647628 116.321394 ]
[129.39787028 108.92352372 43.678606 ]]
5미만 셀 개수가 0이므로 카이제곱 통계량을 활용할 수 있다.
자유도가 2이고 유의 수준 0.05인 임계값 5.99보다 카이제곱 통계량이 219.3으로 크게 나왔으며
유의수준인 0.05보다 p값이 더 작으므로 귀무가설을 기각하고 대립가설을 채택한다.
→ 상호작용과 학습성과 간의 유의미한 관계 확인
기여도 확인
1
2
3
4
residuals = (observed - expected) / np.sqrt(expected)
# 잔차 = (교차표(관측된 빈도수)) - 기대빈도수 / 제곱근(기대빈도 수)
print(residuals)
#print(pow(residuals,2)) # 기여도 강조
1
2
3
4
Y_cut 낮음 보통 높음
X_cut
낮음 4.654189 -1.237490 -6.056555
높음 -7.595205 2.019469 9.883734
상호작용이 높고 학습성과가 높은 데이터가 가장 큰 기여를 했고
상호작용이 높고 학습성과가 낮은 데이터가 2번째로 높은 기여도를 보였다.
ANOVA
정규성 검정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data["X_Group"] = pd.qcut(data["Your interaction in online mode"], q=2, labels=["Low", "High"])
low = data[data["X_Group"] == "Low"]["Performance in online"]
high = data[data["X_Group"] == "High"]["Performance in online"]
_, p_low = stats.shapiro(low)
_, p_high = stats.shapiro(high)
print(f"p_low 정규성 검정 p-value: {p_low}")
print(f"p_high 정규성 검정 p-value: {p_high}")
if p > 0.05 :
print("정규성 만족")
else :
print("정규성 만족 X")
정규성 검정 결과 P값이 0.0으로 나와 귀무가설이 기각되어 정규성을 만족하지 않아
비모수 검정인 Kruskal-Wallis(크루스칼 왈리스) 검정을 진행했다.
Kruskal-Wallis 검정
귀무가설 : 모든 그룹의 중앙값이 같다
대립가설 : 모든 그룹의 중앙값이 모두 같은 것은 아니다.
1
2
3
4
H, p = stats.kruskal(low, high)
print(f"Kruskal-Wallis 결과: H-statistic={H.round(2)}, p-value={p.round(2)}")
if p < 0.05 :
print("중앙값 차이 존재")
p-value=0.0으로 나와 0.05보다 작으므로 중앙값 차이가 존재한다고 볼 수 있다.
사후 검정
Kruskal-Wallis 검정 결과에서 유의미한 차이가 있었으므로
어떤 그룹 간 차이가 존재하는지 확인을 해봤다.
귀무 가설(H0) : 그룹 간에 차이가 없다. (그룹간의 평균값은 같다.)
대립 가설(H1) : 그룹 간에 차이가 있다. (적어도 하나의 평균값은 다르다.)
1
2
3
4
5
import scikit_posthocs as sp # scikit-posthocs로 설치
# Dunn's test (Bonferroni 보정 적용)
dunn_result = sp.posthoc_dunn([low, high], p_adjust='bonferroni')
print(dunn_result.round(2))
1
2
3
1 2
1 1.0 0.0
2 0.0 1.0
으로 나와 p-value < 0.05이므로 그룹 간 차이가 유의미하다.
결론
상호작용이 많을수록 학습성과가 높아지는 것을 확인했다.
특히 상호작용이 가장 높은 그룹(4, 5)의 경우 대부분 성과가 높게 나타났다.
따라서 사용자의 상호작용을 높이는 다양한 기능을 제공하는 것이
학습 성과를 이끄는 데 도움을 주는 요소가 될 것이다.