Skip to content

Commit 1bd558b

Browse files
authored
fix: validate response data inputs (#15)
1 parent 3047d22 commit 1bd558b

11 files changed

Lines changed: 127 additions & 3 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes to this project are documented in this file.
44

5+
## [Unreleased]
6+
7+
### Changed
8+
- Clarified that response data must be a `Matrix` or array of arrays containing only integer `0`, integer `1`, or `nil`; floats, strings, booleans, and other values are rejected.
9+
10+
---
11+
512
## [0.3.0] - 2025-01-14
613

714
### Changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ result = model.fit
4747
puts "Abilities: #{result[:abilities]}"
4848
puts "Difficulties: #{result[:difficulties]}"
4949
```
50+
51+
Response data passed to model constructors must be either a `Matrix` or an
52+
array of arrays. Each response value must be the integer `0`, the integer `1`,
53+
or `nil` for missing data; floats such as `0.0`/`1.0`, strings, booleans, and
54+
other values are rejected.
55+
5056
### Using 2PL and 3PL Models
5157
```ruby
5258
two_pl_model = IrtRuby::TwoParameterModel.new(data)

lib/irt_ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "irt_ruby/version"
44
require "matrix"
5+
require "irt_ruby/response_data_validator"
56
require "irt_ruby/rasch_model"
67
require "irt_ruby/two_parameter_model"
78
require "irt_ruby/three_parameter_model"

lib/irt_ruby/rasch_model.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "irt_ruby/response_data_validator"
4+
35
module IrtRuby
46
# A class representing the Rasch model for Item Response Theory (ability - difficulty).
57
# Incorporates:
@@ -20,7 +22,7 @@ def initialize(data,
2022
# missing_strategy: :ignore (skip), :treat_as_incorrect, :treat_as_correct
2123

2224
@data = data
23-
@data_array = data.to_a
25+
@data_array = ResponseDataValidator.validate!(data)
2426
num_rows = @data_array.size
2527
num_cols = @data_array.first.size
2628

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
module IrtRuby
4+
# Validates response data accepted by IRT model constructors.
5+
# @api private
6+
module ResponseDataValidator
7+
module_function
8+
9+
def validate!(data)
10+
raise ArgumentError, "response data must be a Matrix or array of arrays" unless valid_data_container?(data)
11+
12+
data_array = data.to_a
13+
14+
raise ArgumentError, "response data must have at least one row" unless data_array.any?
15+
16+
validate_rows!(data_array)
17+
validate_values!(data_array)
18+
19+
data_array
20+
end
21+
22+
def validate_rows!(data_array)
23+
first_row = data_array.first
24+
25+
raise ArgumentError, "response data must be a Matrix or array of arrays" unless first_row.is_a?(Array)
26+
27+
expected_columns = first_row.size
28+
raise ArgumentError, "response data must have at least one column" if expected_columns.zero?
29+
30+
data_array.each_with_index do |row, index|
31+
row_number = index + 1
32+
raise ArgumentError, "response data row #{row_number} must be an Array" unless row.is_a?(Array)
33+
34+
next if row.size == expected_columns
35+
36+
raise ArgumentError, "response data must be rectangular; row #{row_number} has #{row.size} columns, expected #{expected_columns}"
37+
end
38+
end
39+
40+
def validate_values!(data_array)
41+
data_array.each_with_index do |row, row_index|
42+
row.each_with_index do |value, column_index|
43+
next if valid_response?(value)
44+
45+
raise ArgumentError,
46+
"response data contains invalid value #{value.inspect} at row #{row_index + 1}, column #{column_index + 1}; allowed values are 0, 1, and nil"
47+
end
48+
end
49+
end
50+
51+
def valid_response?(value)
52+
value.nil? || value.eql?(0) || value.eql?(1)
53+
end
54+
55+
def valid_data_container?(data)
56+
data.is_a?(Array) || (defined?(::Matrix) && data.is_a?(::Matrix))
57+
end
58+
end
59+
end

lib/irt_ruby/three_parameter_model.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "irt_ruby/response_data_validator"
4+
35
module IrtRuby
46
# A class representing the Three-Parameter model (3PL) for Item Response Theory.
57
# Incorporates:
@@ -19,7 +21,7 @@ def initialize(data,
1921
decay_factor: 0.5,
2022
missing_strategy: :ignore)
2123
@data = data
22-
@data_array = data.to_a
24+
@data_array = ResponseDataValidator.validate!(data)
2325
num_rows = @data_array.size
2426
num_cols = @data_array.first.size
2527

lib/irt_ruby/two_parameter_model.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "irt_ruby/response_data_validator"
4+
35
module IrtRuby
46
# A class representing the Two-Parameter model (2PL) for IRT.
57
# Incorporates:
@@ -15,7 +17,7 @@ def initialize(data, max_iter: 1000, tolerance: 1e-6, param_tolerance: 1e-6,
1517
learning_rate: 0.01, decay_factor: 0.5,
1618
missing_strategy: :ignore)
1719
@data = data
18-
@data_array = data.to_a
20+
@data_array = ResponseDataValidator.validate!(data)
1921
num_rows = @data_array.size
2022
num_cols = @data_array.first.size
2123

spec/irt_ruby/rasch_model_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
require "spec_helper"
44

55
RSpec.describe IrtRuby::RaschModel do
6+
it_behaves_like "response data validation"
7+
68
let(:data_array) do
79
[
810
[1, 1, 0],

spec/irt_ruby/three_parameter_model_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
require "spec_helper"
44

55
RSpec.describe IrtRuby::ThreeParameterModel do
6+
it_behaves_like "response data validation"
7+
68
let(:data_array) do
79
[
810
[1, 1, 0],

spec/irt_ruby/two_parameter_model_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
require "spec_helper"
44

55
RSpec.describe IrtRuby::TwoParameterModel do
6+
it_behaves_like "response data validation"
7+
68
let(:data_array) do
79
[
810
[1, 1, 0],

0 commit comments

Comments
 (0)