File Coverage

blib/lib/UMLS/Association/StatFinder.pm
Criterion Covered Total %
statement 18 255 7.0
branch 0 118 0.0
condition 0 54 0.0
subroutine 6 17 35.2
pod 0 1 0.0
total 24 445 5.3


line stmt bran cond sub pod time code
1             #UMLS::Association
2             #
3             # Perl module for scoring the semantic association of terms in the Unified
4             # Medical Language System (UMLS).
5             #
6             # Copyright (c) 2015
7             #
8             # Bridget T. McInnes, Virginia Commonwealth University
9             # btmcinnes at vcu.edu
10             #
11             # Keith Herbert, Virginia Commonwealth University
12             # herbertkb at vcu.edu
13             #
14             # Alexander D. McQuilkin, Virginia Commonwealth University
15             # alexmcq99 at yahoo.com
16             #
17             # This program is free software; you can redistribute it and/or
18             # modify it under the terms of the GNU General Public License
19             # as published by the Free Software Foundation; either version 2
20             # of the License, or (at your option) any later version.
21             #
22             # This program is distributed in the hope that it will be useful,
23             # but WITHOUT ANY WARRANTY; without even the implied warranty of
24             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25             # GNU General Public License for more details.
26             #
27             # You should have received a copy of the GNU General Public License
28             # along with this program; if not, write to
29             #
30             # The Free Software Foundation, Inc.,
31             # 59 Temple Place - Suite 330,
32             # Boston, MA 02111-1307, USA.
33              
34             package UMLS::Association::StatFinder;
35              
36 1     1   3 use Fcntl;
  1         1  
  1         147  
37 1     1   4 use strict;
  1         1  
  1         13  
38 1     1   3 use warnings;
  1         1  
  1         15  
39 1     1   3 use DBI;
  1         1  
  1         21  
40 1     1   3 use bytes;
  1         1  
  1         3  
41 1     1   13 use File::Spec;
  1         0  
  1         1611  
42              
43             # error handling variables
44             my $errorhandler = "";
45             my $cuifinder = "";
46              
47             my $pkg = "UMLS::Association::StatFinder";
48              
49             # debug variables
50             local(*DEBUG_FILE);
51              
52             # global variables
53             my $debug = 0;
54             my $NPP = 0;
55             my $umls = undef;
56             my $precision = 4;
57             my $getdescendants = 0;
58              
59             ######################################################################
60             # functions to initialize the package
61             ######################################################################
62              
63             # method to create a new UMLS::Association::StatFinder object
64             # input : $params <- reference to hash of database parameters
65             # $handler <- reference to cuifinder object
66             # output: $self
67             sub new {
68 0     0 0   my $self = {};
69 0           my $className = shift;
70 0           my $params = shift;
71 0           my $handler = shift;
72              
73             # bless the object.
74 0           bless($self, $className);
75              
76             # initialize error handler
77 0           $errorhandler = UMLS::Association::ErrorHandler->new();
78 0 0         if(! defined $errorhandler) {
79 0           print STDERR "The error handler did not get passed properly.\n";
80 0           exit;
81             }
82              
83             # initialize the cuifinder
84 0           $cuifinder = $handler;
85              
86             # initialize global variables
87 0           $debug = 0;
88              
89             # initialize the object.
90 0           $self->_initialize($params);
91 0           return $self;
92             }
93              
94             # method to initialize the UMLS::Association::StatFinder object.
95             # input : $parameters <- reference to a hash of database parameters
96             # output:
97             sub _initialize {
98              
99 0     0     my $self = shift;
100 0           my $params = shift;
101 0           my %params = %{$params};
  0            
102              
103             #set global variables using option hash
104 0           $umls = $params{'umls'};
105 0           $getdescendants = $params{'getdescendants'};
106              
107 0           my $function = "_initialize";
108 0           &_debug($function);
109              
110             # check self
111 0 0 0       if(!defined $self || !ref $self) {
112 0           $errorhandler->_error($pkg, $function, "", 2);
113             }
114              
115 0 0         $params = {} if(!defined $params);
116 0 0         if(defined $params{'precision'})
117             {
118 0           $precision = $params{'precision'};
119             }
120             }
121            
122             sub _debug {
123 0     0     my $function = shift;
124 0 0         if($debug) { print STDERR "In UMLS::Association::StatFinder::$function\n"; }
  0            
125              
126             }
127              
128             ######################################################################
129             # functions to get statistical information about the cuis
130             ######################################################################
131             # Method to return the frequency of a concept pair
132             # input : $concept1 <- string containing a cui 1
133             # $concept2 <- string containing a cui 2
134             # output: $frequency <- frequency of cui pair
135             sub _getFrequency {
136              
137 0     0     my $self = shift;
138 0           my $concept1 = shift;
139 0           my $concept2 = shift;
140              
141 0           my $function = "_getFrequency";
142              
143             # check self
144 0 0 0       if(!defined $self || !ref $self) {
145 0           $errorhandler->_error($pkg, $function, "", 2);
146             }
147              
148             # check parameter exists
149 0 0         if(!defined $concept1) {
150 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept1.", 4);
151             }
152 0 0         if(!defined $concept2) {
153 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept2.", 4);
154             }
155              
156             # check if valid concept
157 0 0         if(! ($errorhandler->_validCui($concept1)) ) {
158 0           $errorhandler->_error($pkg, $function, "Concept ($concept1) is not valid.", 6);
159             }
160 0 0         if(! ($errorhandler->_validCui($concept2)) ) {
161 0           $errorhandler->_error($pkg, $function, "Concept ($concept2) is not valid.", 6);
162             }
163            
164             # check if concept exists
165 0 0         if(! ($cuifinder->_exists($concept1)) ) {
166 0           return -1; #$errorhandler->_error($pkg, $function, "Concept ($concept1) does not exist.", 6);
167             }
168             # check if concept exists
169 0 0         if(! ($cuifinder->_exists($concept2)) ) {
170 0           return -1; $errorhandler->_error($pkg, $function, "Concept ($concept2) does not exist.", 6);
  0            
171             }
172             # set up database
173 0           my $db = $cuifinder->_getDB();
174            
175 0           my $freqRef = $db->selectcol_arrayref("select n_11 from N_11 where cui_1='$concept1' and cui_2='$concept2'");
176            
177 0           my $freq = shift @{$freqRef};
  0            
178            
179 0 0         if(defined $freq) { return $freq; } else { return 0; }
  0            
  0            
180             }
181            
182             # Method to return the np1 of a concept
183             # input : $concept <- string containing a cui 1
184             # output: $np1 <- number of times concept occurs in second bigram position
185             sub _getNp1 {
186              
187 0     0     my $self = shift;
188 0           my $concept = shift;
189              
190 0           my $function = "_getNp1";
191              
192             # check self
193 0 0 0       if(!defined $self || !ref $self) {
194 0           $errorhandler->_error($pkg, $function, "", 2);
195             }
196              
197             # check parameter exists
198 0 0         if(!defined $concept) {
199 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept.", 4);
200             }
201              
202             # check if valid concept
203 0 0         if(! ($errorhandler->_validCui($concept)) ) {
204 0           $errorhandler->_error($pkg, $function, "Concept ($concept) is not valid.", 6);
205             }
206              
207             # check if concept exists
208 0 0         if(! ($cuifinder->_exists($concept)) ) {
209 0           return -1; #$errorhandler->_error($pkg, $function, "Concept ($concept) does not exist.", 6);
210             }
211            
212             # set up database
213 0           my $db = $cuifinder->_getDB();
214            
215 0           my $np1Ref = $db->selectcol_arrayref("select n_p1 from N_P1 where cui_2='$concept'");
216            
217 0           my $np1 = shift @{$np1Ref};
  0            
218            
219 0 0         if(defined $np1) { return $np1; } else { return 0; }
  0            
  0            
220             }
221              
222             # Method to return the n1p of a concept
223             # input : $concept <- string containing a cui 1
224             # output: $n1p <- number of times concept occurs in second bigram position
225             sub _getN1p {
226              
227 0     0     my $self = shift;
228 0           my $concept = shift;
229              
230 0           my $function = "_getN1p";
231              
232             # check self
233 0 0 0       if(!defined $self || !ref $self) {
234 0           $errorhandler->_error($pkg, $function, "", 2);
235             }
236              
237             # check parameter exists
238 0 0         if(!defined $concept) {
239 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept.", 4);
240             }
241              
242             # check if valid concept
243 0 0         if(! ($errorhandler->_validCui($concept)) ) {
244 0           $errorhandler->_error($pkg, $function, "Concept ($concept) is not valid.", 6);
245             }
246            
247             # check if concept exists
248 0 0         if(! ($cuifinder->_exists($concept)) ) {
249 0           return -1; #$errorhandler->_error($pkg, $function, "Concept ($concept) does not exist.", 6);
250             }
251            
252             # set up database
253 0           my $db = $cuifinder->_getDB();
254            
255 0           my $n1pRef = $db->selectcol_arrayref("select n_1p from N_1P where cui_1='$concept'");
256            
257 0           my $n1p = shift @{$n1pRef};
  0            
258            
259 0 0         if(defined $n1p) { return $n1p; } else { return 0; }
  0            
  0            
260             }
261              
262             # Method to return the n1p of a concept
263             # input : none
264             # output: $npp <- number of total concept pairs
265             sub _getNpp {
266              
267 0     0     my $self = shift;
268            
269 0           my $function = "_getNpp";
270              
271 0 0         if($NPP > 0) { return $NPP; }
  0            
272            
273             # check self
274 0 0 0       if(!defined $self || !ref $self) {
275 0           $errorhandler->_error($pkg, $function, "", 2);
276             }
277             # set up database
278 0           my $db = $cuifinder->_getDB();
279            
280 0           my $nppRef = $db->selectcol_arrayref("select n_pp from N_PP");
281            
282 0           $NPP = shift @{$nppRef};
  0            
283              
284 0 0         if($NPP <= 0) { errorhandler->_error($pkg, $function, "", 5); }
  0            
285            
286 0           return $NPP;
287             }
288              
289             # Method to optimized data retrieval
290             # input : $concept1 <- string containing a cui 1
291             # $concept2 <- string containing a cui 2
292             # output: reference to @data = (n_11, n_1p, n_p1)
293              
294             sub _getData{
295            
296 0     0     my $self = shift;
297            
298 0           my $concept1 = shift;
299 0           my $concept2 = shift;
300            
301 0           my $function = "_getData";
302            
303             # check self
304 0 0 0       if(!defined $self || !ref $self) {
305 0           $errorhandler->_error($pkg, $function, "", 2);
306             }
307            
308             # check parameter exists
309 0 0         if(!defined $concept1) {
310 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept1.", 4);
311             }
312 0 0         if(!defined $concept2) {
313 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept2.", 4);
314             }
315            
316             # check if concept exists
317 0 0         if(! ($cuifinder->_exists($concept1)) ) {
318 0           return -1; #$errorhandler->_error($pkg, $function, "Concept ($concept1) does not exist.", 6);
319             }
320             # check if concept exists
321 0 0         if(! ($cuifinder->_exists($concept2)) ) {
322 0           return -1; $errorhandler->_error($pkg, $function, "Concept ($concept2) does not exist.", 6);
  0            
323             }
324             # set up database
325 0           my $dbh = $cuifinder->_getDB();
326            
327 0           my $queryString =
328             "SELECT N_11.n_11, N_1P.n_1p, N_P1.n_p1 FROM N_11
329             JOIN N_1P ON (N_11.cui_1 = N_1P.cui_1)
330             JOIN N_P1 ON (N_11.cui_2 = N_P1.cui_2)
331             WHERE N_11.cui_1 = '$concept1' AND N_11.cui_2 = '$concept2'
332             LIMIT 1;";
333            
334 0           my $sth = $dbh->prepare($queryString);
335 0 0         $sth->execute() or die $DBI::errstr;
336 0           my @data = ($sth->fetchrow_array());
337 0           return \@data;
338            
339              
340             }
341              
342             # Method to optimized data retrieval (using the descendants of each cui)
343             # input : $concept1 <- string containing a cui 1
344             # $concept2 <- string containing a cui 2
345             # output: reference to @data = (n_11, n_1p, n_p1)
346             sub _getDescendantData{
347            
348 0     0     my $self = shift;
349            
350 0           my $concept1 = shift;
351 0           my $concept2 = shift;
352            
353             # get descendants of each cui
354 0           my @descendants1 =@{_findDescendants($concept1)};
  0            
355 0           my @descendants2 = @{_findDescendants($concept2)};
  0            
356              
357 0           my $function = "_getData";
358            
359             # check self
360 0 0 0       if(!defined $self || !ref $self) {
361 0           $errorhandler->_error($pkg, $function, "", 2);
362             }
363            
364             # check parameter exists
365 0 0         if(!defined $concept1) {
366 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept1.", 4);
367             }
368 0 0         if(!defined $concept2) {
369 0           $errorhandler->_error($pkg, $function, "Error with input variable \$concept2.", 4);
370             }
371            
372             # check if concept exists
373 0 0         if(! ($cuifinder->_exists($concept1)) ) {
374 0           return -1; #$errorhandler->_error($pkg, $function, "Concept ($concept1) does not exist.", 6);
375             }
376             # check if concept exists
377 0 0         if(! ($cuifinder->_exists($concept2)) ) {
378 0           return -1; $errorhandler->_error($pkg, $function, "Concept ($concept2) does not exist.", 6);
  0            
379             }
380             # set up database
381 0           my $dbh = $cuifinder->_getDB();
382              
383             #build query string
384 0           my $queryString =
385             "SELECT table1.A, table2.B, table3.C FROM
386             ((SELECT SUM(n_11) as 'A' FROM N_11 WHERE (N_11.cui_1 = '$concept1' ";
387            
388 0           foreach my $desc (@descendants1)
389             {
390 0           $queryString .= "OR N_11.cui_1 = '$desc' ";
391             }
392            
393 0           $queryString .= ") AND (N_11.cui_2 = '$concept2' ";
394              
395 0           foreach my $desc (@descendants2)
396             {
397 0           $queryString .= "OR N_11.cui_2 = '$desc' ";
398             }
399              
400             $queryString .=
401 0           ")) table1,
402             (SELECT SUM(n_1p) as 'B' FROM N_1P WHERE (N_1P.cui_1 = '$concept1' ";
403            
404 0           foreach my $desc (@descendants1)
405             {
406 0           $queryString .= "OR N_1P.cui_1 = '$desc' ";
407             }
408              
409             $queryString .=
410 0           ")) table2,
411             (SELECT SUM(n_p1) as 'C' FROM N_P1 WHERE (N_P1.cui_2 = '$concept2' ";
412              
413 0           foreach my $desc (@descendants2)
414             {
415 0           $queryString .= "OR N_P1.cui_2 = '$desc' ";
416             }
417              
418             $queryString .=
419 0           ")) table3);";
420              
421 0           my $sth = $dbh->prepare($queryString);
422 0 0         $sth->execute() or die $DBI::errstr;
423 0           my @data = ($sth->fetchrow_array());
424 0           $sth->finish();
425              
426 0           return \@data;
427             }
428              
429             # Method to retrieve descendants of a cui
430             # input : $cui <- string containing a cui
431             # output: reference to @descendants, the descendants of the given cui
432             sub _findDescendants
433             {
434 0     0     my $cui = shift;
435              
436 0           my $hashref = $umls->findDescendants($cui);
437 0           my @descendants = (sort keys %{$hashref});
  0            
438 0           return \@descendants;
439             }
440            
441             sub _calculateStatistic {
442 0     0     my $self = shift;
443 0           my $concept1 = shift;
444 0           my $concept2 = shift;
445 0           my $statistic = shift;
446            
447 0           my $function = "_calculateStatistic";
448              
449 0 0 0       if(!defined $self || !ref $self) {
450 0           $errorhandler->_error($pkg, $function, "", 2);
451             }
452            
453             # get frequency and marginal totals optimized
454 0           my $valid = -1;
455              
456 0 0         if($getdescendants)
457             {
458 0           $valid = $self->_getDescendantData($concept1, $concept2);
459             }
460             else
461             {
462 0           $valid = $self->_getData($concept1, $concept2);
463             }
464              
465 0 0         if($valid == -1){
466 0           return -1;
467             }
468 0           my @data = @{$valid};
  0            
469            
470             # get frequency and marginal totals
471             # my $n11 = $self->_getFrequency($concept1, $concept2);
472             # my $n1p = $self->_getN1p($concept1);
473             # my $np1 = $self->_getNp1($concept2);
474 0           my $n11 = $data[0];
475 0           my $n1p = $data[1];
476 0           my $np1 = $data[2];
477              
478 0 0 0       if(!defined $n11 || !defined $n1p || !defined $np1){
      0        
479 0           return -1;
480             }
481            
482            
483 0           my $npp = $self->_getNpp();
484            
485             # set frequency and marginal totals
486 0           my %values = (n11=>$n11,
487             n1p=>$n1p,
488             np1=>$np1,
489             npp=>$npp);
490            
491 0 0 0       if($n11 < 0 || $n1p < 0 || $np1 < 0) {
      0        
492 0           return -1.000;
493             }
494            
495 0 0         if($n11 == 0) {
496 0           return 0.000;
497             }
498            
499             # set default statistic
500 0 0         if(! defined $statistic) { $statistic = "tscore"; }
  0            
501            
502             # set statistic module
503 0           my $includename = ""; my $usename = ""; my $ngram = 2;
  0            
  0            
504 0 0 0       if($statistic eq "ll") {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
505 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::MI::'.$statistic;
506 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','MI',$statistic.'.pm');
507             }
508             elsif($statistic eq "pmi" || $statistic eq "tmi" || $statistic eq "ps") {
509 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::MI::'.$statistic;
510 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','MI',$statistic.'.pm');
511             }
512             elsif($statistic eq "x2"||$statistic eq "phi") {
513 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::CHI::'.$statistic;
514 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','CHI',$statistic.'.pm');
515             }
516             elsif($statistic eq "leftFisher"||$statistic eq "rightFisher"||$statistic eq "twotailed") {
517 0 0         if($statistic eq "leftFisher") { $statistic = "left"; }
  0 0          
518 0           elsif($statistic eq "rightFisher") { $statistic = "right"; }
519 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::Fisher::'.$statistic;
520 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','Fisher',$statistic.'.pm');
521             }
522             elsif($statistic eq "dice" || $statistic eq "jaccard") {
523 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::Dice::'.$statistic;
524 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','Dice',$statistic.'.pm');
525             }
526             elsif($statistic eq "odds") {
527 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::'.$statistic;
528 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D',$statistic.'.pm');
529             }
530             elsif($statistic eq "tscore") {
531 0           $usename = 'Text::NSP::Measures::'.$ngram.'D::CHI::'.$statistic;
532 0           $includename = File::Spec->catfile('Text','NSP','Measures',$ngram.'D','CHI',$statistic.'.pm');
533             }
534            
535             # import module
536 0           require $includename;
537 0           import $usename;
538            
539             # get statistics
540 0           my $statisticValue = calculateStatistic(%values);
541            
542             # check for errors/warnings from statistics.pm
543 0           my $errorMessage="";
544 0           my $errorCode = getErrorCode();
545 0 0         if (defined $errorCode) {
546 0 0         if($errorCode =~ /^1/) {
547 0           printf(STDERR "Error from statistic library!\n Error code: %d\n", $errorCode);
548 0           $errorMessage = getErrorMessage();
549 0 0         print STDERR " Error message: $errorMessage\n" if( $errorMessage ne "");
550 0           exit; # exit on error
551             }
552 0 0         if ($errorCode =~ /^2/) {
553 0           printf(STDERR "Warning from statistic library!\n Warning code: %d\n", $errorCode);
554 0           $errorMessage = getErrorMessage();
555 0 0         print STDERR " Warning message: $errorMessage\n" if( $errorMessage ne "");
556 0           print STDERR "Skipping ngram $concept1<>$concept2\n";
557 0           next; # if warning, dont save the statistic value just computed
558             }
559             }
560              
561             #return statistic to given precision. if no precision given, default is 4
562 0           my $floatFormat = join '', '%', '.', $precision, 'f';
563            
564 0           my $statScore = sprintf $floatFormat, $statisticValue;
565 0           return $statScore;
566            
567              
568             }
569              
570             1;
571              
572             __END__