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.
- checksums.yaml +7 -0
- data/.devcontainer/devcontainer.json +24 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.md +20 -0
- data/README.md +89 -0
- data/Rakefile +12 -0
- data/data/.DS_Store +0 -0
- data/data/WHO-Boys-Head-Circumference-for-age-Percentiles.csv +26 -0
- data/data/WHO-Boys-Length-for-age-Percentiles.csv +26 -0
- data/data/WHO-Boys-Weight-for-age-Percentiles.csv +26 -0
- data/data/WHO-Girls-Head-Circumference-for-age-Percentiles.csv +26 -0
- data/data/WHO-Girls-Length-for-age-Percentiles.csv +26 -0
- data/data/WHO-Girls-Weight-for-age%20Percentiles.csv +26 -0
- data/data/bmiagerev.csv +440 -0
- data/data/statage.csv +437 -0
- data/data/wtage.csv +437 -0
- data/lib/anthro/data.rb +1501 -0
- data/lib/anthro/version.rb +5 -0
- data/lib/anthro.rb +146 -0
- data/sig/anthro.rbs +4 -0
- metadata +183 -0
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
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: []
|