Item Response Theory Library
A comprehensive Ruby gem for Item Response Theory (IRT) analysis. Implements Rasch, 2PL (Two-Parameter Logistic), and 3PL (Three-Parameter Logistic) models with support for missing data, adaptive optimization, and detailed psychometric reporting.
Add irt_ruby to your Gemfile:
gem 'irt_ruby'
Or install directly:
gem install irt_ruby
Rasch (1PL), 2PL, and 3PL models with full parameter estimation.
Robust handling of incomplete response patterns and missing values.
Advanced optimization algorithms for accurate parameter estimation.
Detailed psychometric statistics, item fit indices, and ability estimates.
Here's a simple example using the Rasch (1PL) model:
require 'irt_ruby'
# Response data: rows = examinees, columns = items
# 1 = correct, 0 = incorrect
responses = [
[1, 1, 1, 0, 0], # Examinee 1
[1, 1, 0, 0, 0], # Examinee 2
[1, 1, 1, 1, 0], # Examinee 3
[0, 0, 1, 0, 0], # Examinee 4
[1, 1, 1, 1, 1] # Examinee 5
]
# Fit Rasch model
rasch = IrtRuby::Rasch.new(responses)
rasch.fit!
# Get item difficulties
puts "Item Difficulties:"
rasch.item_parameters.each_with_index do |difficulty, i|
puts "Item #{i + 1}: #{difficulty.round(3)}"
end
# Get ability estimates
puts "\nAbility Estimates:"
rasch.ability_estimates.each_with_index do |theta, i|
puts "Examinee #{i + 1}: #{theta.round(3)}"
end
# Calculate item fit statistics
fit_stats = rasch.item_fit_statistics
puts "\nItem Fit (Infit MNSQ):"
fit_stats[:infit].each_with_index do |infit, i|
puts "Item #{i + 1}: #{infit.round(3)}"
end
The 2PL model estimates both difficulty and discrimination parameters:
require 'irt_ruby'
# Larger dataset for 2PL model
responses = [
[1, 1, 1, 0, 0, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 0, 1, 0, 0, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
# Fit 2PL model
two_pl = IrtRuby::TwoParameterLogistic.new(responses)
two_pl.fit!(max_iterations: 100, tolerance: 0.001)
# Get item parameters
puts "Item Parameters (Difficulty, Discrimination):"
two_pl.item_parameters.each_with_index do |params, i|
difficulty = params[:difficulty]
discrimination = params[:discrimination]
puts "Item #{i + 1}: b = #{difficulty.round(3)}, a = #{discrimination.round(3)}"
end
# Calculate item information
puts "\nItem Information at θ = 0:"
two_pl.item_information(theta: 0.0).each_with_index do |info, i|
puts "Item #{i + 1}: #{info.round(3)}"
end
# Test information function
theta_range = (-3.0..3.0).step(0.5).to_a
puts "\nTest Information Function:"
theta_range.each do |theta|
info = two_pl.test_information(theta)
se = 1.0 / Math.sqrt(info)
puts "θ = #{theta.round(1)}: Info = #{info.round(2)}, SE = #{se.round(3)}"
end
The 3PL model adds a guessing parameter for multiple-choice items:
require 'irt_ruby'
# Multiple-choice test data
responses = [
[1, 1, 1, 0, 0, 1, 1, 0],
[1, 1, 1, 1, 0, 1, 1, 1],
[0, 1, 1, 0, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 1, 1, 0]
]
# Fit 3PL model
three_pl = IrtRuby::ThreeParameterLogistic.new(responses)
three_pl.fit!(max_iterations: 150)
# Get all item parameters
puts "Item Parameters (Difficulty, Discrimination, Guessing):"
three_pl.item_parameters.each_with_index do |params, i|
b = params[:difficulty]
a = params[:discrimination]
c = params[:guessing]
puts "Item #{i + 1}: b = #{b.round(3)}, a = #{a.round(3)}, c = #{c.round(3)}"
end
# Probability of correct response
theta = 0.5 # Ability level
puts "\nProbability of correct response at θ = #{theta}:"
three_pl.item_parameters.each_with_index do |params, i|
prob = three_pl.probability(theta, params)
puts "Item #{i + 1}: #{(prob * 100).round(1)}%"
end
# Model fit comparison
puts "\nModel Information:"
puts "Log-Likelihood: #{three_pl.log_likelihood.round(2)}"
puts "AIC: #{three_pl.aic.round(2)}"
puts "BIC: #{three_pl.bic.round(2)}"
irt_ruby gracefully handles incomplete response patterns:
require 'irt_ruby'
# Response data with missing values (nil or :missing)
responses = [
[1, 1, nil, 0, 0], # Examinee 1: missing item 3
[1, nil, 0, 0, nil], # Examinee 2: missing items 2 and 5
[1, 1, 1, nil, 0], # Examinee 3: missing item 4
[nil, 0, 1, 0, 0], # Examinee 4: missing item 1
[1, 1, 1, 1, 1] # Examinee 5: complete data
]
# Fit model with missing data
rasch = IrtRuby::Rasch.new(responses)
rasch.fit!(handle_missing: :ignore) # or :impute
# Get estimates (missing data handled automatically)
puts "Ability Estimates (with missing data):"
rasch.ability_estimates.each_with_index do |theta, i|
completed = responses[i].compact.size
total = responses[i].size
puts "Examinee #{i + 1}: θ = #{theta.round(3)} (#{completed}/#{total} items)"
end
# Item statistics with missing data counts
puts "\nItem Statistics:"
rasch.item_statistics.each_with_index do |stats, i|
puts "Item #{i + 1}:"
puts " Responses: #{stats[:n_responses]}"
puts " Difficulty: #{stats[:difficulty].round(3)}"
puts " P-value: #{stats[:p_value].round(3)}"
end
Analyze standardized tests, classroom assessments, and student ability levels.
Validate psychological assessments, personality tests, and cognitive measures.
Analyze survey responses, questionnaires, and attitude scales with precision.
Build computer-adaptive tests (CAT) with real-time ability estimation.
Calibrate and maintain large item banks for test development and equating.
Choose the right IRT model for your data:
| Model | Parameters | Best For |
|---|---|---|
| Rasch (1PL) | Difficulty | Achievement tests with similar discrimination |
| 2PL | Difficulty + Discrimination | Most educational and psychological tests |
| 3PL | Difficulty + Discrimination + Guessing | Multiple-choice tests with guessing |