Thư viện Python chuyên nghiệp phân tích chất lượng đề thi trắc nghiệm theo CTT và mô hình IRT 1PL (JMLE), 2PL/3PL (MML-EM). Đã kiểm chứng vs R psych + ltm.
Project description
rasch-irt
Thư viện đo lường khảo thí cổ điển & hiện đại (CTT & IRT) hiệu năng cao cho Python.
High-performance Classical Test Theory (CTT) & Item Response Theory (IRT) library for Python.
🇻🇳 TIẾNG VIỆT
rasch-irt là một thư viện Python chuyên nghiệp, tối ưu hóa thuật toán phục vụ việc phân tích đánh giá chất lượng câu hỏi thi trắc nghiệm và năng lực thí sinh. Thư viện tích hợp đầy đủ hai cột trụ chính của khoa học đo lường giáo dục: Lý thuyết Khảo thí Cổ điển (Classical Test Theory - CTT) và Lý thuyết Ứng đáp Câu hỏi (Item Response Theory - IRT) với các mô hình 1PL (Rasch) sử dụng JMLE (Joint Maximum Likelihood Estimation) và mô hình 2PL/3PL sử dụng thuật toán MML-EM (Marginal Maximum Likelihood via Expectation-Maximization) chuẩn ngành.
🌟 Tính năng nổi bật
- ⚡ Tốc độ & Hiệu năng: Thiết kế tính toán ma trận vector hóa hoàn toàn trên NumPy và SciPy.
- 📐 Mô hình IRT toàn diện: Hỗ trợ 1PL (Rasch), 2PL và 3PL với hằng số chuẩn hóa $D=1.702$ nhất quán.
- 🛡️ Thuật toán ổn định: Ước lượng MAP với Beta Prior cho tham số đoán mò $c$ nhằm chống phân kỳ toán học.
- 📊 Đầy đủ chỉ số kiểm định: Tính toán Infit/Outfit MNSQ, Sai số chuẩn Fisher (SE), và kiểm định các tiên đề (Đơn hướng PCA, Độc lập cục bộ Q3).
- 🔍 Phân tích CTT nâng cao: Độ khó $p$, độ phân biệt $D$ (nhóm 27%), tương quan Point-Biserial hiệu chỉnh loại trừ trùng lặp (Henrysson 1963), và độ tin cậy KR-20 / Cronbach's Alpha.
- 🤖 Lựa chọn mô hình tự động (Auto-Fit): Tự động so sánh và chọn mô hình tối ưu dựa trên AIC/BIC.
🗺️ Luồng xử lý dữ liệu
rasch-irt tổ chức luồng dữ liệu khép kín từ lúc nhận phản hồi thô cho tới khi xuất báo cáo khảo thí hoàn chỉnh:
flowchart TD
subgraph G1["GIAI ĐOẠN 1: CHẤM THI & SÀNG LỌC"]
Raw["Phản hồi thô (raw_responses) <br> [N câu × M học sinh]"]
Key["Đáp án đúng (answer_key) <br> [N câu]"]
Score["Hàm score_responses() <br> Chuẩn hóa & Đối chiếu"]
Binary["Ma trận nhị phân U [0/1]"]
Sanity["Sanity Check <br> Lọc câu hỏi p=0%, p=100%, Var=0"]
CleanU["Ma trận U sạch"]
Raw & Key --> Score --> Binary --> Sanity --> CleanU
end
subgraph G2["GIAI ĐOẠN 2: PHÂN TÍCH CỔ ĐIỂN (CTT)"]
ItemCTT["Phân tích Câu hỏi <br> - Độ khó p <br> - Độ phân biệt D <br> - Point-Biserial hiệu chỉnh <br> - Biserial correlation"]
Distractor["Phân tích Phương án nhiễu <br> Tần suất, tỷ lệ chọn <br> Tương quan nhóm cao/thấp"]
TestCTT["Phân tích Đề thi <br> - Hệ số KR-20 <br> - Cronbach Alpha <br> - Sai số chuẩn SEM"]
FilterBad["Lọc câu hỏi lỗi nặng <br> (D < 0 hoặc r_pb < 0)"]
IrtU["Ma trận U hội đủ tiêu chuẩn IRT"]
CleanU --> ItemCTT & Distractor & TestCTT
ItemCTT --> FilterBad --> IrtU
end
subgraph G3["GIAI ĐOẠN 3: PHÂN TÍCH HIỆN ĐẠI (IRT)"]
Init["Khởi tạo tham số ban đầu <br> - b: Logit của p <br> - a = 1.0 <br> - c = 1 / num_options"]
subgraph Engine["Động cơ ước lượng (Estimation Engine)"]
ModelCheck{"Loại mô hình?"}
JMLE["Vòng lặp JMLE (1PL/Rasch) <br> Ước lượng đồng thời theta & b <br> chuẩn hóa Mean=0"]
MMLEM["Vòng lặp MML-EM (2PL/3PL) <br> E-Step: expected stats via GH quadrature <br> M-Step: Newton-Raphson cho item parameters <br> Post-hoc: EAP scoring cho theta"]
ModelCheck -- "1PL" --> JMLE
ModelCheck -- "2PL/3PL" --> MMLEM
end
Post["HẬU XỬ LÝ (Post-Processing) <br> - Tính Infit / Outfit MNSQ <br> - Tính sai số chuẩn SE <br> - Quy đổi điểm thực True Score (thang 10)"]
Output["KẾT QUẢ CUỐI CÙNG <br> (theta, a, b, c, SE, MNSQ, True Score)"]
IrtU --> Init --> ModelCheck
JMLE & MMLEM --> Post --> Output
end
📦 1. Cài đặt (Installation)
Thư viện yêu cầu Python >= 3.9 cùng với numpy và scipy.
Cài đặt trực tiếp từ GitHub (Khuyên dùng):
pip install git+https://github.com/buithanhninh/rasch-irt.git
Cài đặt từ mã nguồn cục bộ (Dành cho nhà phát triển):
git clone https://github.com/buithanhninh/rasch-irt.git
cd rasch-irt
pip install .
📝 2. Quy trình Sử dụng E2E (Chấm thi → CTT → IRT)
Bước A: Chấm điểm tự động (score_responses)
Chuyển đổi ma trận phản hồi trắc nghiệm gốc dạng chữ cái ('A', 'B', ...) hoặc số sang ma trận nhị phân 0/1:
import numpy as np
from rasch_irt import score_responses
# Phản hồi thô của 5 thí sinh làm bài thi 4 câu hỏi (Ký tự trống '' là bỏ qua)
raw_responses = np.array([
['A', 'B', 'A', 'D', ''],
['B', 'B', 'C', 'D', 'A'],
['C', 'A', 'D', 'C', 'C'],
['A', 'B', 'D', 'D', 'D']
])
# Đáp án chuẩn tương ứng
answer_key = np.array(['A', 'B', 'D', 'D'])
# Chấm thi tự động
U = score_responses(raw_responses, answer_key)
print("Ma trận nhị phân chấm thi U:\n", U)
Bước B: Phân tích Lý thuyết Cổ điển (run_ctt)
Đánh giá độ tin cậy của bài thi và lọc các câu hỏi lỗi trước khi phân tích mô hình IRT phức tạp:
from rasch_irt import run_ctt
ctt_result = run_ctt(U, raw_responses=raw_responses, answer_key=answer_key)
print(f"Độ tin cậy bài thi KR-20: {ctt_result.kr20:.4f}")
print(f"Hệ số Cronbach's Alpha: {ctt_result.cronbach_alpha:.4f}")
print(f"Điểm trung bình thô: {ctt_result.mean_score:.2f}")
print("Các câu hỏi chất lượng kém đề xuất loại bỏ:", ctt_result.bad_items)
Bước C: Ước lượng mô hình IRT 1PL / 2PL / 3PL (run_irt)
Tự động định tuyến thuật toán tối ưu: Mô hình 1PL (Rasch) sử dụng động cơ JMLE Newton-Raphson, mô hình 2PL/3PL sử dụng động cơ MML-EM tích hợp Gauss-Hermite quadrature và EAP scoring:
from rasch_irt import run_irt, JMLEConfig
# Cấu hình ước lượng mô hình 3PL cho đề thi 4 phương án
config = JMLEConfig(
model_type=3, # 1 = 1PL (Rasch) [JMLE], 2 = 2PL [MML], 3 = 3PL [MML]
num_options=4, # Số phương án để tính Beta Prior cho tham số đoán mò c
max_iter=100, # Số vòng lặp tối đa
tol=0.001 # Ngưỡng hội tụ
)
# Loại trừ các câu hỏi thô bị lỗi tính toán (D < 0 hoặc point-biserial tương quan âm)
excluded_indices = [idx - 1 for idx in ctt_result.bad_items]
U_clean = np.delete(U, excluded_indices, axis=0)
irt_result = run_irt(U_clean, config)
# Đọc kết quả câu hỏi (Item Parameters)
for item in irt_result.items:
print(f"Câu {item.item_number} -> Phân biệt (a): {item.param_a:.2f}, Độ khó (b): {item.param_b:.2f}, Đoán mò (c): {item.param_c:.2f}")
print(f" SE_b: {item.se_b:.3f}, Infit MNSQ: {item.infit_mnsq:.2f} ({item.fit_flag})")
# Đọc năng lực thí sinh (Person Abilities)
for person in irt_result.persons:
print(f"Thí sinh {person.student_code} -> Năng lực (theta): {person.theta:.2f}, Quy đổi thang 10: {person.true_score_10:.2f}")
🧮 3. Đặc tả Thuật toán & Toán học
A. Phương trình Mô hình 3PL (3PL Model Probability)
Xác suất thí sinh $j$ có năng lực $\theta_j$ trả lời đúng câu hỏi $i$ có các tham số ($a_i, b_i, c_i$) được tính bằng: $$P_i(\theta_j) = c_i + \frac{1 - c_i}{1 + e^{-D \cdot a_i(\theta_j - b_i)}}$$ Trong đó:
- $b_i$: Tham số độ khó câu hỏi (Difficulty).
- $a_i$: Tham số độ phân biệt câu hỏi (Discrimination). Cố định bằng $1.0$ trong mô hình 1PL.
- $c_i$: Tham số đoán mò ngẫu nhiên (Guessing). Cố định bằng $0.0$ trong mô hình 1PL/2PL.
- $D = 1.702$: Hằng số scaling đưa mô hình Logistic tiệm cận mô hình tích phân chuẩn Normal Ogive.
B. Beta Prior cho Tham số Đoán mò $c$ (MAP Estimation)
Nhằm tránh việc ước lượng tham số đoán mò $c$ bị phân kỳ khi cỡ mẫu nhỏ, thư viện áp dụng phân phối Beta Prior làm MAP estimator:
$$\text{Beta}(\alpha, \beta)$$
Các tham số Prior tự động thiết lập dựa trên số lượng đáp án lựa chọn (num_options) của bài thi:
- 4 phương án: $E[c] = 0.25 \rightarrow \alpha = 5.0, \beta = 15.0$
- 5 phương án: $E[c] = 0.20 \rightarrow \alpha = 4.0, \beta = 16.0$
- 3 phương án: $E[c] = 0.33 \rightarrow \alpha = 6.6, \beta = 13.4$
C. Chỉ số Trùng khớp (Fit Statistics MNSQ)
Đánh giá mức độ khớp giữa dữ liệu thực nghiệm và mô hình toán học: $$\text{OUTFIT MNSQ} = \frac{1}{M}\sum_{j=1}^M \frac{(u_{ij} - P_{ij})^2}{W_{ij}}$$ $$\text{INFIT MNSQ} = \frac{\sum_{j=1}^M (u_{ij} - P_{ij})^2}{\sum_{j=1}^M W_{ij}}$$ Trong đó $W_{ij} = P_{ij}(1 - P_{ij})$ là phương sai kỳ vọng.
- MNSQ $\in [0.7, 1.3]$: Câu hỏi chất lượng Tốt (Lý tưởng).
- MNSQ $> 1.3$: Underfit (Dữ liệu bị nhiễu động cao hoặc có hiện tượng đoán mò).
- MNSQ $< 0.7$: Overfit (Câu hỏi quá dễ đoán hoặc trùng lặp thông tin).
🇬🇧 ENGLISH
rasch-irt is a professional Python library designed for psychometric test analysis and person ability evaluation. The library integrates both foundational pillars of educational measurement: Classical Test Theory (CTT) and Item Response Theory (IRT) with 1PL (Rasch) estimated via an optimized JMLE (Joint Maximum Likelihood Estimation) engine and 2PL/3PL models estimated via industry-standard MML-EM (Marginal Maximum Likelihood via Expectation-Maximization).
🌟 Key Features
- ⚡ High Performance: Vectorized matrix calculations powered entirely by NumPy and SciPy.
- 📐 Comprehensive IRT Support: Standard 1PL (Rasch), 2PL, and 3PL models using consistent $D=1.702$ scaling.
- 🛡️ Stable Convergence: MAP estimation utilizing a Beta Prior for guessing parameter $c$ to prevent mathematical divergence.
- 📊 Deep Verification: Infit/Outfit MNSQ calculations, Fisher Standard Errors (SE), and test assumption checks (Unidimensionality via PCA, Local Independence via Yen's Q3).
- 🔍 Advanced CTT Engine: Difficulty index $p$, discrimination index $D$ (27% upper/lower method), corrected point-biserial correlation (Henrysson 1963), biserial correlation, distractor analysis, and test reliability (KR-20 / Cronbach's Alpha).
- 🤖 Automated Model Selection (Auto-Fit): Compares 1PL, 2PL, and 3PL models automatically using AIC/BIC.
🗺️ Data Flow Diagram
rasch-irt provides a complete closed-loop workflow from raw item responses to final psychometric reports:
flowchart TD
subgraph G1["STAGE 1: SCORING & PRE-PROCESSING"]
Raw["Raw Responses (raw_responses) <br> [N items × M students]"]
Key["Answer Key (answer_key) <br> [N items]"]
Score["score_responses() <br> Standardize & Compare"]
Binary["Binary Score Matrix U [0/1]"]
Sanity["Sanity Check <br> Filter p=0%, p=100%, Var=0"]
CleanU["Cleaned Matrix U"]
Raw & Key --> Score --> Binary --> Sanity --> CleanU
end
subgraph G2["STAGE 2: CLASSICAL TEST THEORY (CTT)"]
ItemCTT["Item Analysis <br> - Difficulty p <br> - Discrimination D <br> - Corrected Point-Biserial <br> - Biserial Correlation"]
Distractor["Distractor Analysis <br> Frequency, Proportion <br> Upper/Lower Discrimination"]
TestCTT["Test Reliability <br> - KR-20 Coefficient <br> - Cronbach Alpha <br> - Standard Error SEM"]
FilterBad["Filter Defective Items <br> (D < 0 or r_pb < 0)"]
IrtU["Qualified Matrix U for IRT"]
CleanU --> ItemCTT & Distractor & TestCTT
ItemCTT --> FilterBad --> IrtU
end
subgraph G3["STAGE 3: ITEM RESPONSE THEORY (IRT)"]
Init["Parameter Initialization <br> - b: Logit of difficulty p <br> - a = 1.0 <br> - c = 1 / num_options"]
subgraph Engine["Estimation Engine"]
ModelCheck{"Model Type?"}
JMLE["JMLE Loop (1PL/Rasch) <br> Jointly estimate theta & b <br> + Mean standardization"]
MMLEM["MML-EM Loop (2PL/3PL) <br> E-Step: Expected stats via GH quadrature <br> M-Step: Newton-Raphson for item params <br> Post-hoc: EAP scoring for theta"]
ModelCheck -- "1PL" --> JMLE
ModelCheck -- "2PL/3PL" --> MMLEM
end
Post["Post-Processing <br> - Infit / Outfit MNSQ <br> - Standard Error SE <br> - True Score Conversion (Scale 0-10)"]
Output["FINAL CALIBRATION <br> (theta, a, b, c, SE, MNSQ, True Score)"]
IrtU --> Init --> ModelCheck
JMLE & MMLEM --> Post --> Output
end
📦 1. Installation
The library requires Python >= 3.9 along with numpy and scipy.
Install directly from GitHub (Recommended):
pip install git+https://github.com/buithanhninh/rasch-irt.git
Install from local source (For developers):
git clone https://github.com/buithanhninh/rasch-irt.git
cd rasch-irt
pip install .
📝 2. E2E Usage Workflow (Scoring → CTT → IRT)
Step A: Automated Scoring (score_responses)
Converts raw responses (alphabetic 'A', 'B', ... or numeric) into a binary score matrix $U$ (1 for correct, 0 for incorrect or blank):
import numpy as np
from rasch_irt import score_responses
# Raw responses of 5 students on 4 items (Empty string '' represents skipped questions)
raw_responses = np.array([
['A', 'B', 'A', 'D', ''],
['B', 'B', 'C', 'D', 'A'],
['C', 'A', 'D', 'C', 'C'],
['A', 'B', 'D', 'D', 'D']
])
# Answer key
answer_key = np.array(['A', 'B', 'D', 'D'])
# Automated scoring
U = score_responses(raw_responses, answer_key)
print("Binary Score Matrix U:\n", U)
Step B: Classical Test Theory (run_ctt)
Calculates baseline test statistics, reliability, and flags defective items:
from rasch_irt import run_ctt
ctt_result = run_ctt(U, raw_responses=raw_responses, answer_key=answer_key)
print(f"KR-20 Reliability: {ctt_result.kr20:.4f}")
print(f"Cronbach's Alpha: {ctt_result.cronbach_alpha:.4f}")
print(f"Mean Raw Score: {ctt_result.mean_score:.2f}")
print("Flagged items suggested for exclusion:", ctt_result.bad_items)
Step C: Item Response Theory (run_irt)
Routes to the optimal estimation algorithm: 1PL (Rasch) via JMLE Newton-Raphson, and 2PL/3PL models via MML-EM with Gauss-Hermite quadrature and EAP scoring:
from rasch_irt import run_irt, JMLEConfig
# Set up 3PL model configuration for 4-option items
config = JMLEConfig(
model_type=3, # 1 = 1PL (Rasch) [JMLE], 2 = 2PL [MML], 3 = 3PL [MML]
num_options=4, # Used for calculating Beta Prior for guessing parameter c
max_iter=100, # Maximum number of iterations
tol=0.001 # Convergence threshold
)
# Exclude flagged items (D < 0 or negative point-biserial correlation)
excluded_indices = [idx - 1 for idx in ctt_result.bad_items]
U_clean = np.delete(U, excluded_indices, axis=0)
irt_result = run_irt(U_clean, config)
# Print estimated item parameters
for item in irt_result.items:
print(f"Item {item.item_number} -> a: {item.param_a:.2f}, b: {item.param_b:.2f}, c: {item.param_c:.2f}")
print(f" SE_b: {item.se_b:.3f}, Infit MNSQ: {item.infit_mnsq:.2f} ({item.fit_flag})")
# Print estimated person parameters
for person in irt_result.persons:
print(f"Student {person.student_code} -> Theta: {person.theta:.2f}, Scaled Score (0-10): {person.true_score_10:.2f}")
🧮 3. Mathematical Specifications
A. 3PL Model Probability Formula
The probability of student $j$ with ability $\theta_j$ answering item $i$ correctly is: $$P_i(\theta_j) = c_i + \frac{1 - c_i}{1 + e^{-D \cdot a_i(\theta_j - b_i)}}$$ Where:
- $b_i$: Item difficulty parameter.
- $a_i$: Item discrimination parameter. Fixed to $1.0$ in the 1PL model.
- $c_i$: Guessing parameter. Fixed to $0.0$ in 1PL/2PL models.
- $D = 1.702$: Scaling constant used to match the logistic model to the normal ogive metric.
B. Beta Prior for Guessing Parameter $c$ (MAP Estimation)
To stabilize estimation and prevent the guessing parameter $c$ from diverging on small samples, a Beta Prior is applied:
$$\text{Beta}(\alpha, \beta)$$
The prior hyper-parameters are automatically derived from the number of item choices (num_options):
- 4 options: $E[c] = 0.25 \rightarrow \alpha = 5.0, \beta = 15.0$
- 5 options: $E[c] = 0.20 \rightarrow \alpha = 4.0, \beta = 16.0$
- 3 options: $E[c] = 0.33 \rightarrow \alpha = 6.6, \beta = 13.4$
C. Fit Statistics (MNSQ)
Measures the agreement between observed data and expected probabilities: $$\text{OUTFIT MNSQ} = \frac{1}{M}\sum_{j=1}^M \frac{(u_{ij} - P_{ij})^2}{W_{ij}}$$ $$\text{INFIT MNSQ} = \frac{\sum_{j=1}^M (u_{ij} - P_{ij})^2}{\sum_{j=1}^M W_{ij}}$$ Where $W_{ij} = P_{ij}(1 - P_{ij})$ is the variance.
- MNSQ $\in [0.7, 1.3]$: Indicates a Good item fit (Ideal).
- MNSQ $> 1.3$: Underfit (Higher noise or guessing than expected).
- MNSQ $< 0.7$: Overfit (Item is overly predictable or redundant).
📜 LICENSE
Distributed under the MIT License. Copyright (c) 2026 Bùi Thành Ninh.
All source code packaged within rasch-irt is ready for commercial or open-source integration.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rasch_irt-1.1.0.tar.gz.
File metadata
- Download URL: rasch_irt-1.1.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92a1e15a785b26f81e5d702141024d64416bc13f733263236e7857972adef573
|
|
| MD5 |
17093719bf82cd8e87d650c17f023492
|
|
| BLAKE2b-256 |
58f0cad6ced67d6b4639677c9f921a45447067ca44763f4c0d37892eb72a5a54
|
File details
Details for the file rasch_irt-1.1.0-py3-none-any.whl.
File metadata
- Download URL: rasch_irt-1.1.0-py3-none-any.whl
- Upload date:
- Size: 30.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
797b5b1ab58aec80de51cd3605d2638823a2d42d6f8be4f733f4b92c135fc317
|
|
| MD5 |
b47def7057dcd59eda2d6039fbe456fe
|
|
| BLAKE2b-256 |
761165589b72a08da6b256a446cccaf7f57d32175970ec67b3164ef806e88782
|