Skip to content

Commit 22b0d94

Browse files
authored
Add Three-Parameter IRT Model and Corresponding Tests (#4)
- Implement Three-Parameter Model (3PL) for Item Response Theory - Add tests for Three-Parameter Model - Update main library file to include the new model - Adjust README for the new model usage
1 parent 212ec5b commit 22b0d94

5 files changed

Lines changed: 117 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# IrtRuby
33

4-
IrtRuby is a Ruby gem that provides implementations of the Rasch model and the Two-Parameter model for Item Response Theory (IRT). It allows you to estimate the abilities of individuals and the difficulties of items based on their responses to a set of items.
4+
IrtRuby is a Ruby gem that provides implementations of the Rasch model, the Two-Parameter model, and the Three-Parameter model for Item Response Theory (IRT). It allows you to estimate the abilities of individuals and the difficulties of items based on their responses to a set of items.
55

66
## Installation
77

lib/irt_ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "irt_ruby/version"
44
require "irt_ruby/rasch_model"
55
require "irt_ruby/two_parameter_model"
6+
require "irt_ruby/three_parameter_model"
67

78
module IrtRuby
89
class Error < StandardError; end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
require "matrix"
4+
5+
module IrtRuby
6+
# A class representing the Three-Parameter model for Item Response Theory.
7+
class ThreeParameterModel
8+
def initialize(data)
9+
@data = data
10+
@abilities = Array.new(data.row_count) { rand }
11+
@difficulties = Array.new(data.column_count) { rand }
12+
@discriminations = Array.new(data.column_count) { rand }
13+
@guessings = Array.new(data.column_count) { rand * 0.3 }
14+
@max_iter = 1000
15+
@tolerance = 1e-6
16+
end
17+
18+
def sigmoid(x)
19+
1.0 / (1.0 + Math.exp(-x))
20+
end
21+
22+
def probability(theta, a, b, c)
23+
c + (1 - c) * sigmoid(a * (theta - b))
24+
end
25+
26+
def likelihood
27+
likelihood = 0
28+
@data.row_vectors.each_with_index do |row, i|
29+
row.to_a.each_with_index do |response, j|
30+
prob = probability(@abilities[i], @discriminations[j], @difficulties[j], @guessings[j])
31+
if response == 1
32+
likelihood += Math.log(prob)
33+
elsif response.zero?
34+
likelihood += Math.log(1 - prob)
35+
end
36+
end
37+
end
38+
likelihood
39+
end
40+
41+
def update_parameters
42+
last_likelihood = likelihood
43+
@max_iter.times do |_iter|
44+
@data.row_vectors.each_with_index do |row, i|
45+
row.to_a.each_with_index do |response, j|
46+
prob = probability(@abilities[i], @discriminations[j], @difficulties[j], @guessings[j])
47+
error = response - prob
48+
@abilities[i] += 0.01 * error * @discriminations[j]
49+
@difficulties[j] -= 0.01 * error * @discriminations[j]
50+
@discriminations[j] += 0.01 * error * (@abilities[i] - @difficulties[j])
51+
@guessings[j] += 0.01 * error * (1 - prob)
52+
end
53+
end
54+
current_likelihood = likelihood
55+
break if (last_likelihood - current_likelihood).abs < @tolerance
56+
57+
last_likelihood = current_likelihood
58+
end
59+
end
60+
61+
def fit
62+
update_parameters
63+
{
64+
abilities: @abilities,
65+
difficulties: @difficulties,
66+
discriminations: @discriminations,
67+
guessings: @guessings
68+
}
69+
end
70+
end
71+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
RSpec.describe IrtRuby::ThreeParameterModel do
6+
let(:data) { Matrix[[1, 0, 1], [0, 1, 0], [1, 1, 1]] }
7+
let(:model) { IrtRuby::ThreeParameterModel.new(data) }
8+
9+
describe "#initialize" do
10+
it "initializes with data" do
11+
expect(model.instance_variable_get(:@data)).to eq(data)
12+
end
13+
end
14+
15+
describe "#sigmoid" do
16+
it "calculates the sigmoid function" do
17+
expect(model.sigmoid(0)).to eq(0.5)
18+
end
19+
end
20+
21+
describe "#probability" do
22+
it "calculates the probability with guessing parameter" do
23+
expect(model.probability(0, 1, 0, 0.2)).to be_within(0.01).of(0.6)
24+
end
25+
end
26+
27+
describe "#likelihood" do
28+
it "calculates the likelihood of the data" do
29+
expect(model.likelihood).to be_a(Float)
30+
end
31+
end
32+
33+
describe "#fit" do
34+
it "fits the model and returns abilities, difficulties, discriminations, and guessings" do
35+
result = model.fit
36+
expect(result[:abilities].size).to eq(data.row_count)
37+
expect(result[:difficulties].size).to eq(data.column_count)
38+
expect(result[:discriminations].size).to eq(data.column_count)
39+
expect(result[:guessings].size).to eq(data.column_count)
40+
end
41+
end
42+
end

spec/irt_ruby/two_parameter_model_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
require "spec_helper"
24

35
RSpec.describe IrtRuby::TwoParameterModel do

0 commit comments

Comments
 (0)