anthro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthro
4
+ VERSION = "0.1.0"
5
+ end
data/lib/anthro.rb ADDED
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "distribution"
4
+ require_relative "anthro/version"
5
+ require_relative "anthro/data"
6
+
7
+ module Anthro
8
+ class Calculator
9
+ VALID_MEASUREMENTS = DATA.keys.freeze
10
+ VALID_SEXES = %w[male female m f].freeze
11
+ AGE_RANGE_MONTHS = (0..240)
12
+ DAYS_PER_MONTH = 30.4375
13
+
14
+ def self.load_reference_data
15
+ @reference_data ||= begin
16
+ data = {}
17
+
18
+ Anthro::DATA.keys.each do |measurement|
19
+ data[measurement] = { m: {}, f: {} }
20
+ Anthro::DATA[measurement].keys.each do |source|
21
+ Anthro::DATA[measurement][source].each do |sex, month, l, m, s|
22
+ sex = sex == 1 ? :m : :f
23
+ data[measurement][sex][month.to_f] = {
24
+ l: l,
25
+ m: m,
26
+ s: s
27
+ }
28
+ end
29
+ end
30
+ end
31
+
32
+ data
33
+ end
34
+ end
35
+
36
+ attr_reader :z_score, :percentile
37
+
38
+ def initialize(measurement_type:, sex:, value:, age_months: nil, age_days: nil)
39
+ @measurement_type = measurement_type.to_sym
40
+ @sex = sex[0].downcase
41
+ @value = value.to_f
42
+ @reference_data = self.class.load_reference_data
43
+
44
+ raise ArgumentError, "Specify either age_months or age_days, not both" if age_months && age_days
45
+ raise ArgumentError, "Either age_months or age_days must be provided" unless age_months || age_days
46
+
47
+ @key_value = if age_months
48
+ age_months.to_f
49
+ else
50
+ age_days.to_f / DAYS_PER_MONTH
51
+ end
52
+
53
+ validate_inputs
54
+ @z_score = calculate_z_score
55
+ @percentile = calculate_percentile
56
+ end
57
+
58
+ private
59
+
60
+ def validate_inputs
61
+ unless VALID_MEASUREMENTS.include?(@measurement_type)
62
+ raise ArgumentError, "Invalid measurement type. Must be one of: #{VALID_MEASUREMENTS.join(", ")}"
63
+ end
64
+ raise ArgumentError, "Invalid sex. Must be 'male', 'female', 'm', or 'f'" unless VALID_SEXES.include?(@sex)
65
+
66
+ unless AGE_RANGE_MONTHS.cover?(@key_value)
67
+ raise ArgumentError, "Age must be between 0 and 240 months (0 to 7305 days)"
68
+ end
69
+ if @measurement_type == :bmi_for_age && @key_value < 24
70
+ raise ArgumentError, "BMI is only valid for ages 24 months (730.5 days) and up"
71
+ end
72
+ if @measurement_type == :head_circumference_for_age && @key_value > 24
73
+ raise ArgumentError, "Head circumference is only valid for ages 0 to 24 months"
74
+ end
75
+
76
+ return unless @value <= 0
77
+
78
+ raise ArgumentError, "Value must be positive"
79
+ end
80
+
81
+ def calculate_z_score
82
+ lms = get_lms_params
83
+ raise "No LMS data available for #{@measurement_type}, #{@sex}, #{@key_value}" unless lms
84
+
85
+ l = lms[:l]
86
+ m = lms[:m]
87
+ s = lms[:s]
88
+ if l.zero?
89
+ Math.log(@value / m) / s
90
+ else
91
+ (((@value / m)**l) - 1) / (l * s)
92
+ end
93
+ end
94
+
95
+ def calculate_percentile
96
+ z = @z_score
97
+ return nil unless z
98
+
99
+ Distribution::Normal.cdf(z) * 100
100
+ end
101
+
102
+ def get_lms_params
103
+ data = @reference_data[@measurement_type]
104
+ return nil unless data && data[@sex.to_sym]
105
+
106
+ data_set = data[@sex.to_sym]
107
+ return data_set[@key_value] if data_set[@key_value]
108
+
109
+ keys = data_set.keys.sort
110
+ lower_key = keys.select { |k| k <= @key_value }.last
111
+ upper_key = keys.select { |k| k > @key_value }.first
112
+
113
+ raise "No LMS data available for #{@measurement_type}, #{@sex}, #{@key_value}" unless lower_key && upper_key
114
+
115
+ interpolate_lms(data_set[lower_key], data_set[upper_key], lower_key, upper_key)
116
+ end
117
+
118
+ def interpolate_lms(lower, upper, lower_key, upper_key)
119
+ fraction = (@key_value - lower_key) / (upper_key - lower_key)
120
+ {
121
+ l: lower[:l] + (upper[:l] - lower[:l]) * fraction,
122
+ m: lower[:m] + (upper[:m] - lower[:m]) * fraction,
123
+ s: lower[:s] + (upper[:s] - lower[:s]) * fraction
124
+ }
125
+ end
126
+ end
127
+
128
+ def self.weight_for_age(sex:, value:, age_months: nil, age_days: nil)
129
+ Calculator.new(measurement_type: :weight_for_age, sex: sex, age_months: age_months, age_days: age_days,
130
+ value: value)
131
+ end
132
+
133
+ def self.height_for_age(sex:, value:, age_months: nil, age_days: nil)
134
+ Calculator.new(measurement_type: :height_for_age, sex: sex, age_months: age_months, age_days: age_days,
135
+ value: value)
136
+ end
137
+
138
+ def self.bmi_for_age(sex:, value:, age_months: nil, age_days: nil)
139
+ Calculator.new(measurement_type: :bmi_for_age, sex: sex, age_months: age_months, age_days: age_days, value: value)
140
+ end
141
+
142
+ def self.head_circumference_for_age(sex:, value:, age_months: nil, age_days: nil)
143
+ Calculator.new(measurement_type: :head_circumference_for_age, sex: sex, age_months: age_months, age_days: age_days,
144
+ value: value)
145
+ end
146
+ end
data/sig/anthro.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Anthro
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,183 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: anthro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joon Lee
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-03-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bigdecimal
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: distribution
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.8'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: prime
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.3
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.3
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.6'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.6'
68
+ - !ruby/object:Gem::Dependency
69
+ name: irb
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.15'
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.15.1
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '1.15'
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 1.15.1
88
+ - !ruby/object:Gem::Dependency
89
+ name: minitest
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '5.25'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '5.25'
102
+ - !ruby/object:Gem::Dependency
103
+ name: rake
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '13.2'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '13.2'
116
+ - !ruby/object:Gem::Dependency
117
+ name: rubocop
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.74'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.74'
130
+ description: A Ruby gem for calculating anthropometric z-scores and percentiles based
131
+ on the WHO and CDC 2000 growth chart data.
132
+ email:
133
+ - seouri@gmail.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".devcontainer/devcontainer.json"
139
+ - ".rubocop.yml"
140
+ - CHANGELOG.md
141
+ - LICENSE.md
142
+ - README.md
143
+ - Rakefile
144
+ - data/.DS_Store
145
+ - data/WHO-Boys-Head-Circumference-for-age-Percentiles.csv
146
+ - data/WHO-Boys-Length-for-age-Percentiles.csv
147
+ - data/WHO-Boys-Weight-for-age-Percentiles.csv
148
+ - data/WHO-Girls-Head-Circumference-for-age-Percentiles.csv
149
+ - data/WHO-Girls-Length-for-age-Percentiles.csv
150
+ - data/WHO-Girls-Weight-for-age%20Percentiles.csv
151
+ - data/bmiagerev.csv
152
+ - data/statage.csv
153
+ - data/wtage.csv
154
+ - lib/anthro.rb
155
+ - lib/anthro/data.rb
156
+ - lib/anthro/version.rb
157
+ - sig/anthro.rbs
158
+ homepage: https://github.com/hms-dbmi/anthro
159
+ licenses:
160
+ - MIT
161
+ metadata:
162
+ homepage_uri: https://github.com/hms-dbmi/anthro
163
+ source_code_uri: https://github.com/hms-dbmi/anthro
164
+ changelog_uri: https://github.com/hms-dbmi/anthro/CHANGELOG.md
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: 3.1.0
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ requirements: []
179
+ rubygems_version: 3.6.2
180
+ specification_version: 4
181
+ summary: Calculate anthropometric z-scores and percentiles using WHO and CDC 2000
182
+ growth charts.
183
+ test_files: []