File Coverage

blib/lib/ISS/AH/Predictor.pm
Criterion Covered Total %
statement 50 52 96.1
branch 25 28 89.2
condition 15 18 83.3
subroutine 9 9 100.0
pod 2 2 100.0
total 101 109 92.6


line stmt bran cond sub pod time code
1              
2             use v5.10;
3 2     2   107625 use strict;
  2         15  
4 2     2   8 use warnings;
  2         3  
  2         33  
5 2     2   7  
  2         3  
  2         45  
6             use Scalar::Util qw(looks_like_number);
7 2     2   17  
  2         5  
  2         292  
8             =head1 NAME
9              
10             ISS::AH::Predictor - Provides prediction models for adult height (AH) in patients with idiopathic short stature (ISS).
11              
12             =cut
13              
14             our $VERSION = '0.1.0';
15              
16              
17             =head1 SYNOPSIS
18              
19             use ISS::AH::Predictor;
20              
21             =head1 ATTRIBUTES
22              
23             =head2 MODELS
24              
25             This attribute contains the default models and parameters, estimated by Blum et al.
26              
27             my $models = ISS::AH::Predictor::MODELS;
28              
29             # each element of the $models arrayref is an arrayref with estimated parameters of one model
30             $models->[0]->[0]; # first parameter (intercept) of the first model
31              
32             =cut
33              
34             use constant MODELS => [
35 2         226 [ 63.3339, -2.9595, 0.7256, 0.3173, undef, undef, -13.0399, 1.2695, -6.2213 ],
36             [ 62.1795, -2.9892, 0.7328, 0.3442, undef, undef, -12.6821, undef, -6.3021 ],
37             [ 50.3654, -2.6372, 0.6408, 0.3986, undef, undef, undef, undef, -5.9171 ],
38             [ 80.3645, -3.4309, 0.8241, undef, 0.2242, undef, -15.1678, 1.2688, -10.2474 ],
39             [ 83.4866, -3.4717, 0.8374, undef, 0.2234, undef, -14.7156, undef, -10.6257 ],
40             [ 75.8792, -3.1944, 0.7581, undef, 0.2449, undef, undef, undef, -10.9519 ],
41             [ 93.2475, -3.4020, 0.8239, undef, undef, 0.1203, -14.2739, 1.6156, -10.0340 ],
42             [ 94.2381, -3.5784, 0.8759, undef, undef, 0.1348, -14.2476, undef, -10.5319 ],
43             [ 84.3600, -3.2207, 0.7623, undef, undef, 0.1775, undef, undef, -10.8759 ],
44             [ 110.8863, -4.1250, 0.9661, undef, undef, undef, -16.0541, undef, -10.4331 ],
45             ];
46 2     2   13  
  2         3  
47             =head2 PARAMETER_NAMES
48              
49             Hashref that contains the names of the model parameters for each variable index.
50              
51             =cut
52              
53             use constant PARAMETER_NAMES => {
54 2         879 0 => 'intercept',
55             1 => 'age',
56             2 => 'body_height',
57             3 => 'target_height',
58             4 => 'mother_height',
59             5 => 'father_height',
60             6 => 'bone_age_per_age',
61             7 => 'birth_weight',
62             8 => 'sex',
63             };
64 2     2   12  
  2         3  
65             =head1 SUBROUTINES/METHODS
66              
67             =head2 predict
68              
69             Provide a hash with properties of a subject to predict adult height (AH).
70             You can also provide custom model parameters to overwrite the default once from Blum et al.
71              
72             my $ah = ISS::AH::Predictor::predict(
73             age => ..., # years
74             body_height => ..., # cm
75             target_height => ..., # cm
76             mother_height => ..., # cm
77             father_height => ..., # cm
78             bone_age => ..., # years
79             birth_weight => ..., # kg
80             sex => ..., # 'male' or 'female'
81             models => [ ... ],
82             );
83              
84             =cut
85              
86             my %params = _modify_params(@_);
87             my $model = get_model(%params) or return undef;
88 2     2 1 2780 my $result = $model->[0]; # intercept
89 2 100       6  
90 1         2 for my $index (keys %{ PARAMETER_NAMES() }) {
91             my $parameter_name = PARAMETER_NAMES->{$index};
92 1         3 next if ($parameter_name eq 'intercept');
  1         3  
93 9         11 next unless (defined $model->[$index] and defined $params{$parameter_name});
94 9 100       12 $result += $model->[$index] * $params{$parameter_name};
95 8 100 66     22 }
96 5         11  
97             return $result;
98             }
99 1         3  
100             =head2 get_model
101              
102             Returns an appropriate model with a set of parameters for the given subject properties.
103             The returned model is determined by the amount of available parameters.
104             You can also provide custom model parameters to overwrite the default once from Blum et al.
105              
106             If no model is appropriate, the result is undef.
107             If there are multiple matching models (with identical parameter count), the first one will be returned.
108              
109             my $model = ISS::AH::Predictor::get_model(
110             age => ..., # years
111             body_height => ..., # cm
112             target_height => ..., # cm
113             mother_height => ..., # cm
114             father_height => ..., # cm
115             bone_age => ..., # years
116             birth_weight => ..., # kg
117             sex => ..., # 'male' or 'female'
118             models => [ ... ],
119             );
120              
121             =cut
122              
123             my %params = _modify_params(@_);
124             my $models = $params{models} || MODELS;
125             my $results = [];
126 6     6 1 3709  
127 6   100     19 for my $model (@$models) {
128 6         9 my $match = 1;
129             for my $index (keys %{ PARAMETER_NAMES() }) {
130 6         10 my $parameter_name = PARAMETER_NAMES->{$index};
131 41         42 $match = 0 if (defined $model->[$index] and !looks_like_number($model->[$index]));
132 41         42 next if ($parameter_name eq 'intercept');
  41         74  
133 110         124 $match = 0 unless (defined $params{$parameter_name} xor !defined $model->[$index]);
134 110 50 66     260 last unless ($match);
135 110 100       141 }
136 107 100 100     247 push @$results, $model if ($match);
137 107 100       158 }
138              
139 41 100       77 return (scalar @$results) ? $results->[0] : undef;
140             }
141              
142 6 100       26 my %params = @_;
143              
144             $params{bone_age_per_age} = $params{bone_age} / $params{age}
145             if (defined $params{bone_age} and $params{age});
146 9     9   1508  
147             if (defined $params{sex}) {
148             my $sex = lc $params{sex};
149 9 100 100     55 $params{sex} = ($sex eq 'male') ? 1 : ($sex eq 'female') ? 2 : $sex;
150             }
151 9 100       18  
152 5         7 for my $parameter_name (values %{ PARAMETER_NAMES() }) {
153 5 50       16 if (defined $params{$parameter_name} and !looks_like_number($params{$parameter_name})) {
    100          
154             warn "input parameter $parameter_name removed, because value $params{$parameter_name} is not numeric";
155             delete $params{$parameter_name};
156 9         12 }
  9         21  
157 81 50 66     214 }
158 0         0  
159 0         0 return %params;
160             }
161              
162             =head1 AUTHOR
163 9         37  
164             Christoph Beger, C<< <christoph.beger at medizin.uni-leipzig.de> >>
165              
166             =cut
167              
168             1;