Assessment Test
Assessment Test
[4]: # Load the CSV files for the years 2021 and 2022
data_2021 = pd.read_csv('C:/Users/nabee/Downloads/student_responses_2021.csv')
data_2022 = pd.read_csv('C:/Users/nabee/Downloads/student_responses_2022.csv')
[5]: # Display the first few rows and basic info of each dataset to understand their␣
↪structure and check for any anomalies
1
print("\n")
print(" ########## Student Data 2021 list First Five Row ########## ")
print(data_2022.head())
print("\n")
print(" ########## Student Data 2021 list First Five Row ########## ")
print(data_2022.head())
2
3 difficulty 49000 non-null float64
4 answered_correctly 50000 non-null bool
dtypes: bool(1), float64(2), int64(2)
memory usage: 1.6 MB
None
1.1.1 Cecking the treands of ability and difficulty without handling missing value
[7]: # Combine the two datasets and add a 'year' column for easier year-based␣
↪analysis
data_2021['year'] = 2021
data_2022['year'] = 2022
combined_data = pd.concat([data_2021, data_2022], ignore_index=True)
print(" ########## 2021 and 2022 Student's Ability Trend with missing value␣
↪########## ")
print(ability_trend)
print("\n")
print(" ########## 2021 and 2022 Student's Difficulty Trend with missing value␣
↪########## ")
print(difficulty_trend)
print("\n")
########## 2021 and 2022 Student's Ability Trend with missing value ##########
year
2021 -0.041824
2022 0.330433
Name: ability, dtype: float64
########## 2021 and 2022 Student's Difficulty Trend with missing value
##########
year
3
2021 -0.057131
2022 0.116709
Name: difficulty, dtype: float64
[8]: # Plot average ability and difficulty by year using bar charts
fig1 = go.Figure()
fig1.add_trace(go.Bar(
x=ability_trend.index,
y=ability_trend.values,
name='Average Ability',
marker_color='cornflowerblue'
))
fig1.add_trace(go.Bar(
x=difficulty_trend.index,
y=difficulty_trend.values,
name='Average Difficulty',
marker_color='salmon'
))
fig1.update_layout(
title="Average Student Ability and Question Difficulty by Year",
xaxis_title="Year",
yaxis_title="Score",
barmode='group',
template='plotly_dark'
)
fig2.add_trace(go.Histogram(
x=data_2021['ability'],
name='2021',
opacity=0.5,
marker_color='cornflowerblue',
nbinsx=20
))
fig2.add_trace(go.Histogram(
x=data_2022['ability'],
name='2022',
opacity=0.5,
marker_color='salmon',
4
nbinsx=20
))
fig2.update_layout(
title="Distribution of Ability Scores in 2021 and 2022",
xaxis_title="Ability",
yaxis_title="Density",
barmode='overlay',
template='plotly_dark'
)
fig3.add_trace(go.Histogram(
x=data_2021['difficulty'],
name='2021',
opacity=0.5,
marker_color='cornflowerblue',
nbinsx=20
))
fig3.add_trace(go.Histogram(
x=data_2022['difficulty'],
name='2022',
opacity=0.5,
marker_color='salmon',
nbinsx=20
))
fig3.update_layout(
title="Distribution of Difficulty Scores in 2021 and 2022",
xaxis_title="Difficulty",
yaxis_title="Density",
barmode='overlay',
template='plotly_dark'
)
5
1.1.2 Cecking the treands of ability and difficulty after handling missing value
[10]: # Fill missing values with the mean using updated syntax to avoid warnings
data_2021_final = data_2021.assign(
ability=data_2021['ability'].fillna(data_2021['ability'].mean()),
difficulty=data_2021['difficulty'].fillna(data_2021['difficulty'].mean())
)
data_2022_final = data_2022.assign(
ability=data_2022['ability'].fillna(data_2022['ability'].mean()),
difficulty=data_2022['difficulty'].fillna(data_2022['difficulty'].mean())
)
print(" ########## 2021 and 2022 Student's Ability Trend ########## ")
print(average_ability_final)
print("\n")
print(" ########## 2021 and 2022 Student's Difficulty Trend ########## ")
print(average_difficulty_final)
print("\n")
6
[11]: # Plot average ability and difficulty by year using bar charts
fig1 = go.Figure()
fig1.add_trace(go.Bar(
x=average_ability_final.index,
y=average_ability_final.values,
name='Average Ability',
marker_color='cornflowerblue'
))
fig1.add_trace(go.Bar(
x=average_difficulty_final.index,
y=average_difficulty_final.values,
name='Average Difficulty',
marker_color='salmon'
))
fig1.update_layout(
title="Average Student Ability and Question Difficulty by Year",
xaxis_title="Year",
yaxis_title="Score",
barmode='group',
template='plotly_dark'
)
fig2.add_trace(go.Histogram(
x=data_2021_final['ability'],
name='2021',
opacity=0.5,
marker_color='cornflowerblue',
nbinsx=20
))
fig2.add_trace(go.Histogram(
x=data_2022_final['ability'],
name='2022',
opacity=0.5,
marker_color='salmon',
nbinsx=20
))
fig2.update_layout(
title="Distribution of Ability Scores in 2021 and 2022",
xaxis_title="Ability",
7
yaxis_title="Density",
barmode='overlay',
template='plotly_dark'
)
fig3.add_trace(go.Histogram(
x=data_2021_final['difficulty'],
name='2021',
opacity=0.5,
marker_color='cornflowerblue',
nbinsx=20
))
fig3.add_trace(go.Histogram(
x=data_2022_final['difficulty'],
name='2022',
opacity=0.5,
marker_color='salmon',
nbinsx=20
))
fig3.update_layout(
title="Distribution of Difficulty Scores in 2021 and 2022",
xaxis_title="Difficulty",
yaxis_title="Density",
barmode='overlay',
template='plotly_dark'
)
student_performance_2021 = student_performance_2021.
↪rename(columns={'answered_correctly': 'avg_correct_2021'})
8
bottom_10_students = student_performance_2021.nsmallest(10,␣
↪'avg_correct_2021')['student_id']
top_10_ability_2022 = data_2022[data_2022['student_id'].isin(top_10_students)].
↪groupby('student_id')['ability'].mean()
bottom_10_ability_2021 = data_2021[data_2021['student_id'].
↪isin(bottom_10_students)].groupby('student_id')['ability'].mean()
bottom_10_ability_2022 = data_2022[data_2022['student_id'].
↪isin(bottom_10_students)].groupby('student_id')['ability'].mean()
bottom_10_comparison = pd.DataFrame({
'2021_ability': bottom_10_ability_2021,
'2022_ability': bottom_10_ability_2022
}).reset_index()
# Display results
print("Top 10 Students Ability Comparison:")
print(top_10_comparison)
9
0 9 -3.104761 NaN
1 10 -3.958711 NaN
2 14 -2.726788 NaN
3 19 -2.163419 NaN
4 25 -2.602048 NaN
5 37 -5.302896 NaN
6 41 -2.389137 NaN
7 42 -2.415692 NaN
8 43 -4.696739 NaN
9 46 -2.334270 NaN
common_abilities_2022 = data_2022[data_2022['student_id'].
↪isin(common_students_sample)].groupby('student_id')['ability'].mean()
fig_top.add_trace(go.Bar(
x=top_10_comparison['student_id'],
y=top_10_comparison['2021_ability'],
name='2021 Ability',
marker_color='blue'
10
))
fig_top.add_trace(go.Bar(
x=top_10_comparison['student_id'],
y=top_10_comparison['2022_ability'],
name='2022 Ability',
marker_color='green'
))
fig_top.update_layout(
title="Top 10 Students: Ability Comparison (2021 vs 2022)",
xaxis_title="Student ID",
yaxis_title="Ability",
barmode='group',
template="plotly_white",
legend_title="Year"
)
fig_top.show()
fig_bottom.add_trace(go.Bar(
x=bottom_10_comparison['student_id'],
y=bottom_10_comparison['2021_ability'],
name='2021 Ability',
marker_color='red'
))
fig_bottom.add_trace(go.Bar(
x=bottom_10_comparison['student_id'],
y=bottom_10_comparison['2022_ability'],
name='2022 Ability',
marker_color='orange'
))
fig_bottom.update_layout(
title="Bottom 10 Students: Ability Comparison (2021 vs 2022)",
xaxis_title="Student ID",
yaxis_title="Ability",
barmode='group',
template="plotly_white",
legend_title="Year"
)
fig_bottom.show()
11
[15]: # Perform t-tests for ability and difficulty
ability_ttest = ttest_ind(
data_2021_final['ability'],
data_2022_final['ability'],
equal_var=False
)
difficulty_ttest = ttest_ind(
data_2021_final['difficulty'],
data_2022_final['difficulty'],
equal_var=False
)
12
Difficulty: t-statistic = -24.3243, p-value = 0.0000
scipy.stats.shapiro: For N > 5000, computed p-value may not be accurate. Current
N is 45000.
13
[21]: stat, p = f_oneway(data_2021_final['ability'], data_2022_final['ability'])
print(f"One-Way ANOVA: Statistic={stat:.4f}, p-value={p:.4f}")
scipy.stats.shapiro: For N > 5000, computed p-value may not be accurate. Current
N is 45000.
C:\Users\nabee\anaconda3\Lib\site-packages\scipy\stats\_axis_nan_policy.py:531:
UserWarning:
scipy.stats.shapiro: For N > 5000, computed p-value may not be accurate. Current
N is 50000.
14
[24]: # Levene Test for Variance
levene_ability = levene(data_2021_final['ability'], data_2022_final['ability'])
levene_difficulty = levene(data_2021_final['difficulty'],␣
↪data_2022_final['difficulty'])
print(f"Difficulty: Statistic={levene_difficulty.statistic:.4f},␣
↪p-value={levene_difficulty.pvalue:.4f}")
print("\nCorrelation Analysis:")
print(f"2021 Pearson Correlation: {pearson_corr_2021:.4f},␣
↪p-value={p_pearson_2021:.4f}")
Correlation Analysis:
2021 Pearson Correlation: -0.0007, p-value=0.8798
2022 Pearson Correlation: -0.0004, p-value=0.9234
2021 Spearman Correlation: -0.0011, p-value=0.8158
2022 Spearman Correlation: -0.0000, p-value=0.9944
15
[26]: # Paired students in both years
paired_abilities = pd.merge(data_2021_final[['student_id', 'ability']],
data_2022_final[['student_id', 'ability']],
on='student_id', suffixes=('_2021', '_2022'))
# Wilcoxon Test
wilcoxon_stat, wilcoxon_p = wilcoxon(paired_abilities['ability_2021'],␣
↪paired_abilities['ability_2022'])
# Mann-Whitney U Test
mannwhitney_stat, mannwhitney_p = mannwhitneyu(high_performers, low_performers)
print("\nMann-Whitney U Test:")
print(f"Statistic={mannwhitney_stat:.4f}, p-value={mannwhitney_p:.4f}")
Mann-Whitney U Test:
Statistic=471964431.0000, p-value=0.0000
• This, therefore, means that the application of non-parametric tests best suits these variables.
16
• The correlation coefficients are near zero indicating that there is no significant linear or
monotonic relationship between the two variables.
• It indicates that the spread in ability and difficulty has changed significantly over the two
years.
• This implies that the student performance levels, between high and low, have a sharp and
valid discrimination by their ability.
• This indicates that student abilities and question difficulties have significantly changed during
these years.
• This may indicate some interactions or relationships worth exploring more with the data.
17
• Students were better prepared and better able in 2022 compared with 2021. This
has been observed from the increase in average ability from -0.0418 to 0.3304 in
2022..
• This might be due to better teaching, wide resource usage, or getting familiar
with the question format..
2.0.11 Implications
1. Assessment Design:
• This interaction between increasing question difficulty and improving student
ability suggests a shift in assessment design. If question difficulty continues to
rise, it will be very important to continue monitoring student ability trends
so as to not allow assessments to become too difficult or unbalanced.
2. Instructional Strategy:
• Recognizing these patterns can help educators adjust curriculum difficulty,
focus on areas for improvement, and design assessments that challenge stu-
dents while remaining achievable as skills evolve..
3 Key Insights
3.0.1 Student Ability and Question Difficulty Over Time:
• Student ability and question difficulty both differ substantially between 2021 and 2022.
• These metrics are more inconsistent than in previous years, suggesting that tests and student
readiness may have shifted.
18
3.0.2 No Correlation Between Ability and Difficulty:
• A lack of correlation implies that greater difficulty does not indicate lower ability and vice
versa.
• Results suggest a need for specific intervention programs to improve low-achieving students.
# Feature importance
importance = pd.DataFrame({
'Feature': ['student_id','ability', 'difficulty','question_id'],
'Importance': rf_model.feature_importances_
}).sort_values(by='Importance', ascending=False)
19
3.0.6 Here we iniatialize four type of ML models
1. Logistic Regression
2. Random Forest
3. Gradient Boosting
4. K-Nearest Neighbors
[35]: # Initialize Logistic Regression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
# Predictions
y_pred_log_reg = log_reg.predict(X_test)
# Performance metrics
log_reg_performance = {
"Model": "Logistic Regression",
"Accuracy": accuracy_score(y_test, y_pred_log_reg),
"Precision": precision_score(y_test, y_pred_log_reg),
"Recall": recall_score(y_test, y_pred_log_reg),
"F1 Score": f1_score(y_test, y_pred_log_reg)
}
# Predictions
y_pred_rf = rf.predict(X_test)
# Performance metrics
rf_performance = {
"Model": "Random Forest",
"Accuracy": accuracy_score(y_test, y_pred_rf),
"Precision": precision_score(y_test, y_pred_rf),
"Recall": recall_score(y_test, y_pred_rf),
"F1 Score": f1_score(y_test, y_pred_rf)
}
# Predictions
y_pred_gb = gb.predict(X_test)
# Performance metrics
gb_performance = {
20
"Model": "Gradient Boosting",
"Accuracy": accuracy_score(y_test, y_pred_gb),
"Precision": precision_score(y_test, y_pred_gb),
"Recall": recall_score(y_test, y_pred_gb),
"F1 Score": f1_score(y_test, y_pred_gb)
}
# Predictions
y_pred_knn = knn.predict(X_test)
# Performance metrics
knn_performance = {
"Model": "K-Nearest Neighbors",
"Accuracy": accuracy_score(y_test, y_pred_knn),
"Precision": precision_score(y_test, y_pred_knn),
"Recall": recall_score(y_test, y_pred_knn),
"F1 Score": f1_score(y_test, y_pred_knn)
}
21
template="plotly_dark",
legend_title_text="Metrics",
width=800,
height=500
)
fig.show()
3.1 Conclusion
Logistic Regression performs the best overall, making it the preferred model based on
both accuracy and F1 Score. This model likely performs well because the relationship between
ability, difficulty, and the likelihood of answering correctly might be nearly linear, making
logistic regression an effective choice.
In a practical application, Logistic Regression would be favored here, but since the other models
perform very closely, Random Forest or KNN could also be considered, especially if the
underlying relationships prove to be more complex in future data.
[74]: !choco install pandoc
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[1], line 1
----> 1 pandoc --version
[ ]:
22