]> git.donarmstrong.com Git - mothur.git/blob - sensspeccommand.cpp
1.23.0
[mothur.git] / sensspeccommand.cpp
1 /*
2  *  sensspeccommand.cpp
3  *  Mothur
4  *
5  *  Created by Pat Schloss on 7/6/10.
6  *  Copyright 2010 Schloss Lab. All rights reserved.
7  *
8  */
9
10 #include "sensspeccommand.h"
11
12 //**********************************************************************************************************************
13 vector<string> SensSpecCommand::setParameters(){        
14         try {
15                 CommandParameter plist("list", "InputTypes", "", "", "none", "none", "none",false,true); parameters.push_back(plist);
16                 CommandParameter pphylip("phylip", "InputTypes", "", "", "PhylipColumn", "PhylipColumn", "none",false,false); parameters.push_back(pphylip);
17                 //CommandParameter pname("name", "InputTypes", "", "", "none", "none", "ColumnName",false,false); parameters.push_back(pname);
18                 CommandParameter pcolumn("column", "InputTypes", "", "", "PhylipColumn", "PhylipColumn", "none",false,false); parameters.push_back(pcolumn);
19                 CommandParameter plabel("label", "String", "", "", "", "", "",false,false); parameters.push_back(plabel);
20                 CommandParameter pcutoff("cutoff", "Number", "", "-1.00", "", "", "",false,false); parameters.push_back(pcutoff);
21                 CommandParameter pprecision("precision", "Number", "", "100", "", "", "",false,false); parameters.push_back(pprecision);
22                 CommandParameter phard("hard", "Boolean", "", "F", "", "", "",false,false); parameters.push_back(phard);
23                 CommandParameter pinputdir("inputdir", "String", "", "", "", "", "",false,false); parameters.push_back(pinputdir);
24                 CommandParameter poutputdir("outputdir", "String", "", "", "", "", "",false,false); parameters.push_back(poutputdir);
25                 
26                 vector<string> myArray;
27                 for (int i = 0; i < parameters.size(); i++) {   myArray.push_back(parameters[i].name);          }
28                 return myArray;
29         }
30         catch(exception& e) {
31                 m->errorOut(e, "SensSpecCommand", "setParameters");
32                 exit(1);
33         }
34 }
35 //**********************************************************************************************************************
36 string SensSpecCommand::getHelpString(){        
37         try {
38                 string helpString = "";
39                 helpString += "The sens.spec command....\n";
40                 return helpString;
41         }
42         catch(exception& e) {
43                 m->errorOut(e, "SensSpecCommand", "getHelpString");
44                 exit(1);
45         }
46 }
47 //**********************************************************************************************************************
48 SensSpecCommand::SensSpecCommand(){     
49         try {
50                 abort = true; calledHelp = true; 
51                 setParameters();
52                 vector<string> tempOutNames;
53                 outputTypes["sensspec"] = tempOutNames;
54         }
55         catch(exception& e) {
56                 m->errorOut(e, "SensSpecCommand", "SensSpecCommand");
57                 exit(1);
58         }
59 }
60 //***************************************************************************************************************
61
62 SensSpecCommand::SensSpecCommand(string option)  {
63         try {
64                 
65                 abort = false; calledHelp = false;  
66                 allLines = 1;
67                 
68                 //allow user to run help
69                 if(option == "help") { help(); abort = true; calledHelp = true; }
70                 else if(option == "citation") { citation(); abort = true; calledHelp = true;}
71                 
72                 else {
73                         string temp;
74                         
75                         vector<string> myArray = setParameters();
76                         
77                         OptionParser parser(option);
78                         map<string,string> parameters = parser.getParameters();
79                         
80                         ValidParameters validParameter;
81                         map<string,string>::iterator it;
82                         
83                         //check to make sure all parameters are valid for command
84                         for (it = parameters.begin(); it != parameters.end(); it++) { 
85                                 if (validParameter.isValidParameter(it->first, myArray, it->second) != true) {  abort = true;  }
86                         }
87                         
88                         //initialize outputTypes
89                         vector<string> tempOutNames;
90                         outputTypes["sensspec"] = tempOutNames;
91                         
92                         //if the user changes the input directory command factory will send this info to us in the output parameter 
93                         string inputDir = validParameter.validFile(parameters, "inputdir", false);              
94                         if (inputDir == "not found"){   inputDir = "";          }
95                         else {
96                                 string path;
97                                 it = parameters.find("list");
98                                 //user has given a template file
99                                 if(it != parameters.end()){ 
100                                         path = m->hasPath(it->second);
101                                         //if the user has not given a path then, add inputdir. else leave path alone.
102                                         if (path == "") {       parameters["list"] = inputDir + it->second;             }
103                                 }
104                                 
105                                 it = parameters.find("phylip");
106                                 //user has given a template file
107                                 if(it != parameters.end()){ 
108                                         path = m->hasPath(it->second);
109                                         //if the user has not given a path then, add inputdir. else leave path alone.
110                                         if (path == "") {       parameters["phylip"] = inputDir + it->second;           }
111                                 }
112                                 
113                                 it = parameters.find("column");
114                                 //user has given a template file
115                                 if(it != parameters.end()){ 
116                                         path = m->hasPath(it->second);
117                                         //if the user has not given a path then, add inputdir. else leave path alone.
118                                         if (path == "") {       parameters["column"] = inputDir + it->second;           }
119                                 }
120                                 
121                                 //it = parameters.find("name");
122                                 //user has given a template file
123                                 //if(it != parameters.end()){ 
124                                         //path = m->hasPath(it->second);
125                                         //if the user has not given a path then, add inputdir. else leave path alone.
126                                         //if (path == "") {     parameters["name"] = inputDir + it->second;             }
127                                 //}
128                                 
129                         }
130                         //check for required parameters
131                         listFile = validParameter.validFile(parameters, "list", true);
132                         if (listFile == "not found") {          
133                                 listFile = m->getListFile(); 
134                                 if (listFile != "") { m->mothurOut("Using " + listFile + " as input file for the list parameter."); m->mothurOutEndLine(); }
135                                 else {  m->mothurOut("You have no current list file and the list parameter is required."); m->mothurOutEndLine(); abort = true; }
136                         }
137                         else if (listFile == "not open") { abort = true; }      
138                         else { m->setListFile(listFile); }
139                         
140                         phylipfile = validParameter.validFile(parameters, "phylip", true);
141                         if (phylipfile == "not found") { phylipfile = "";  }
142                         else if (phylipfile == "not open") { abort = true; }    
143                         else { distFile = phylipfile; format = "phylip"; m->setPhylipFile(phylipfile);  }
144                         
145                         columnfile = validParameter.validFile(parameters, "column", true);
146                         if (columnfile == "not found") { columnfile = ""; }
147                         else if (columnfile == "not open") { abort = true; }    
148                         else { distFile = columnfile; format = "column";   m->setColumnFile(columnfile); }
149                         
150                         if ((phylipfile == "") && (columnfile == "")) { //is there are current file available for either of these?
151                                 //give priority to column, then phylip
152                                 columnfile = m->getColumnFile(); 
153                                 if (columnfile != "") {  distFile = columnfile; format = "column";  m->mothurOut("Using " + columnfile + " as input file for the column parameter."); m->mothurOutEndLine(); }
154                                 else { 
155                                         phylipfile = m->getPhylipFile(); 
156                                         if (phylipfile != "") {  distFile = phylipfile; format = "phylip"; m->mothurOut("Using " + phylipfile + " as input file for the phylip parameter."); m->mothurOutEndLine(); }
157                                         else { 
158                                                 m->mothurOut("No valid current files. You must provide a phylip or column file."); m->mothurOutEndLine(); 
159                                                 abort = true;
160                                         }
161                                 }
162                         }else if ((phylipfile != "") && (columnfile != "")) { m->mothurOut("When executing a sens.spec command you must enter ONLY ONE of the following: phylip or column."); m->mothurOutEndLine(); abort = true; }
163                         
164                         
165                         //if the user changes the output directory command factory will send this info to us in the output parameter 
166                         outputDir = validParameter.validFile(parameters, "outputdir", false);
167                         if (outputDir == "not found"){  
168                                 outputDir = ""; 
169                                 outputDir += m->hasPath(listFile); //if user entered a file with a path then preserve it        
170                         }
171
172                         //check for optional parameter and set defaults
173                         // ...at some point should added some additional type checking...
174                         temp = validParameter.validFile(parameters, "hard", false);
175                         if (temp == "not found"){       hard = 0;       }
176                         else if(!m->isTrue(temp))       {       hard = 0;       }
177                         else if(m->isTrue(temp))        {       hard = 1;       }
178                         
179 //                      temp = validParameter.validFile(parameters, "name", true);
180 //                      if (temp == "not found")        {       nameFile = "";          }
181 //                      else if(temp == "not open")     {       abort = true;           }
182 //                      else                                            {       nameFile = temp;        }
183 //                      cout << "name:\t" << nameFile << endl;
184
185                         temp = validParameter.validFile(parameters, "cutoff", false);           if (temp == "not found") { temp = "-1.00"; }
186                         m->mothurConvert(temp, cutoff);  
187 //                      cout << cutoff << endl;
188                         
189                         temp = validParameter.validFile(parameters, "precision", false);        if (temp == "not found") { temp = "100"; }
190                         m->mothurConvert(temp, precision);  
191 //                      cout << precision << endl;
192                         
193                         string label = validParameter.validFile(parameters, "label", false);                    
194                         if (label == "not found") { label = ""; }
195                         else { 
196                                 if(label != "all") {  m->splitAtDash(label, labels);  allLines = 0;  }
197                                 else { allLines = 1;  }
198                         }
199                         
200                         sensSpecFileName = outputDir + m->getRootName(m->getSimpleName(listFile)) + "sensspec";
201                 }
202         }
203         catch(exception& e) {
204                 m->errorOut(e, "SensSpecCommand", "SensSpecCommand");
205                 exit(1);
206         }
207 }
208 //***************************************************************************************************************
209
210 int SensSpecCommand::execute(){
211         try{
212                 if (abort == true) { if (calledHelp) { return 0; }  return 2;   }
213
214                 setUpOutput();
215                 outputNames.push_back(sensSpecFileName); outputTypes["sensspec"].push_back(sensSpecFileName);
216                 if(format == "phylip")          {       processPhylip();        }
217                 else if(format == "column")     {       processColumn();        }
218                 
219                 if (m->control_pressed) { m->mothurRemove(sensSpecFileName); return 0; }
220                 
221                 m->mothurOutEndLine();
222                 m->mothurOut("Output File Name: "); m->mothurOutEndLine();
223                 m->mothurOut(sensSpecFileName); m->mothurOutEndLine();  
224                 m->mothurOutEndLine();
225                 
226                 
227                 return 0;       
228         }
229         catch(exception& e) {
230                 m->errorOut(e, "SensSpecCommand", "execute");
231                 exit(1);
232         }
233 }
234
235 //***************************************************************************************************************
236
237 int SensSpecCommand::processPhylip(){
238         try{
239                 //probably need some checking to confirm that the names in the distance matrix are the same as those in the list file
240                 string origCutoff = "";
241                 bool getCutoff = 0;
242                 if(cutoff == -1.00)     {       getCutoff = 1;                                                                                                                  }
243                 else                            {       origCutoff = toString(cutoff);  cutoff += (0.49 / double(precision));   }               
244                 
245                 map<string, int> seqMap;
246                 string seqList;
247                 
248                 InputData input(listFile, "list");
249                 ListVector* list = input.getListVector();
250                 string lastLabel = list->getLabel();
251                 
252                 //if the users enters label "0.06" and there is no "0.06" in their file use the next lowest label.
253                 set<string> processedLabels;
254                 set<string> userLabels = labels;
255                 
256                 
257                 while((list != NULL) && ((allLines == 1) || (userLabels.size() != 0))) {
258                         
259                         if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) {        m->mothurRemove(outputNames[i]);  }  delete list;  return 0;  }
260                         
261                         if(allLines == 1 || labels.count(list->getLabel()) == 1){                       
262                                 
263                                 processedLabels.insert(list->getLabel());
264                                 userLabels.erase(list->getLabel());
265                                 
266                                 //process
267                                 fillSeqMap(seqMap, list);
268                                 process(seqMap, list->getLabel(), getCutoff, origCutoff);
269                         }
270                         
271                         if ((m->anyLabelsToProcess(list->getLabel(), userLabels, "") == true) && (processedLabels.count(lastLabel) != 1)) {
272                                 string saveLabel = list->getLabel();
273                                 
274                                 delete list;
275                                 list = input.getListVector(lastLabel);
276                                 
277                                 processedLabels.insert(list->getLabel());
278                                 userLabels.erase(list->getLabel());
279                                 
280                                 //process
281                                 fillSeqMap(seqMap, list);
282                                 process(seqMap, list->getLabel(), getCutoff, origCutoff);
283                                 
284                                 //restore real lastlabel to save below
285                                 list->setLabel(saveLabel);
286                         }               
287                         
288                         lastLabel = list->getLabel();                   
289                         
290                         delete list;
291                         list = input.getListVector();
292                 }
293                 
294                 
295                 //output error messages about any remaining user labels
296                 set<string>::iterator it;
297                 bool needToRun = false;
298                 for (it = userLabels.begin(); it != userLabels.end(); it++) {  
299                         m->mothurOut("Your file does not include the label " + *it); 
300                         if (processedLabels.count(lastLabel) != 1) {
301                                 m->mothurOut(". I will use " + lastLabel + "."); m->mothurOutEndLine();
302                                 needToRun = true;
303                         }else {
304                                 m->mothurOut(". Please refer to " + lastLabel + "."); m->mothurOutEndLine();
305                         }
306                 }
307                 
308                 //run last label if you need to
309                 if (needToRun == true)  {
310                         if (list != NULL) {     delete list;    }
311                         list = input.getListVector(lastLabel);
312                         
313                         //process
314                         fillSeqMap(seqMap, list);
315                         process(seqMap, list->getLabel(), getCutoff, origCutoff);
316                         
317                         delete list;
318                 }
319                 
320                 return 0;
321         }
322         catch(exception& e) {
323                 m->errorOut(e, "SensSpecCommand", "processPhylip");
324                 exit(1);
325         }
326 }
327 //***************************************************************************************************************
328 int SensSpecCommand::fillSeqMap(map<string, int>& seqMap, ListVector*& list){
329         try {
330                 //for each otu
331                 for(int i=0;i<list->getNumBins();i++){
332                         
333                         if (m->control_pressed) { return 0; }
334                         
335                         string seqList = list->get(i);
336                         int seqListLength = seqList.length();
337                         string seqName = "";
338                         
339                         //parse bin by name, mapping each name to its otu number
340                         for(int j=0;j<seqListLength;j++){
341                                 
342                                 if(seqList[j] == ','){
343                                         seqMap[seqName] = i;
344                                         seqName = "";
345                                 }
346                                 else{
347                                         seqName += seqList[j];
348                                 }
349                                 
350                         }
351                         seqMap[seqName] = i;
352                 }
353                 
354                 return 0;
355         }
356         catch(exception& e) {
357                 m->errorOut(e, "SensSpecCommand", "fillSeqMap");
358                 exit(1);
359         }
360 }
361 //***************************************************************************************************************
362 int SensSpecCommand::fillSeqPairSet(set<string>& seqPairSet, ListVector*& list){
363         try {
364                 int numSeqs = 0;
365                 
366                 //for each otu
367                 for(int i=0;i<list->getNumBins();i++){
368                         
369                         if (m->control_pressed) { return 0; }
370                         
371                         vector<string> seqNameVector;
372                         string bin = list->get(i);
373                         m->splitAtComma(bin, seqNameVector);
374                         
375                         numSeqs += seqNameVector.size();
376                         
377                         for(int j=0;j<seqNameVector.size();j++){
378                                 string seqPairString = "";                              
379                                 for(int k=0;k<j;k++){
380                                         if(seqNameVector[j] < seqNameVector[k]) {       seqPairString = seqNameVector[j] + '\t' + seqNameVector[k];     }
381                                         else                                                                    {       seqPairString = seqNameVector[k] + '\t' + seqNameVector[j];     }
382                                         seqPairSet.insert(seqPairString);
383                                 }
384                         }
385                 }
386                 
387                 return numSeqs;
388         }
389         catch(exception& e) {
390                 m->errorOut(e, "SensSpecCommand", "fillSeqMap");
391                 exit(1);
392         }
393 }
394 //***************************************************************************************************************
395 int SensSpecCommand::process(map<string, int>& seqMap, string label, bool& getCutoff, string& origCutoff){
396         try {
397                                                 
398                 int lNumSeqs = seqMap.size();
399                 int pNumSeqs = 0;
400                 
401                 ifstream phylipFile;
402                 m->openInputFile(distFile, phylipFile);
403                 phylipFile >> pNumSeqs;
404                 if(pNumSeqs != lNumSeqs){       m->mothurOut("numSeq mismatch!\n"); m->control_pressed = true; }
405                 
406                 string seqName;
407                 double distance;
408                 vector<int> otuIndices(lNumSeqs, -1);
409                 
410                 truePositives = 0;
411                 falsePositives = 0;
412                 trueNegatives = 0;
413                 falseNegatives = 0;
414                 
415                 if(getCutoff == 1){
416                         if(label != "unique"){
417                                 origCutoff = label;
418                                 convert(label, cutoff);
419                                 if(hard == 0){  cutoff += (0.49 / double(precision));   }               
420                         }
421                         else{
422                                 origCutoff = "unique";
423                                 cutoff = 0.0000;
424                         }
425                 }
426                 
427                 m->mothurOut(label); m->mothurOutEndLine();
428                 
429                 for(int i=0;i<lNumSeqs;i++){
430                         
431                         if (m->control_pressed) { return 0; }
432                         
433                         phylipFile >> seqName;
434                         otuIndices[i] = seqMap[seqName];
435                         
436                         for(int j=0;j<i;j++){
437                                 phylipFile >> distance;
438                                 
439                                 if(distance <= cutoff){
440                                         if(otuIndices[i] == otuIndices[j])      {       truePositives++;        }
441                                         else                                                            {       falseNegatives++;       }
442                                 }
443                                 else{
444                                         if(otuIndices[i] == otuIndices[j])      {       falsePositives++;       }
445                                         else                                                            {       trueNegatives++;        }
446                                 }
447                         }
448                 }
449                 phylipFile.close();
450                 
451                 outputStatistics(label, origCutoff);
452                 
453                 return 0;
454         }
455         catch(exception& e) {
456                 m->errorOut(e, "SensSpecCommand", "process");
457                 exit(1);
458         }
459 }
460 //***************************************************************************************************************
461 int SensSpecCommand::process(set<string>& seqPairSet, string label, bool& getCutoff, string& origCutoff, int numSeqs){
462         try {
463                 int numDists = (numSeqs * (numSeqs-1) / 2);
464                 
465                 ifstream columnFile;
466                 m->openInputFile(distFile, columnFile);
467                 string seqNameA, seqNameB, seqPairString;
468                 double distance;
469                 
470                 truePositives = 0;
471                 falsePositives = 0;
472                 trueNegatives = numDists;
473                 falseNegatives = 0;
474                 
475                 if(getCutoff == 1){
476                         if(label != "unique"){
477                                 origCutoff = label;
478                                 convert(label, cutoff);
479                                 if(hard == 0){  cutoff += (0.49 / double(precision));   }               
480                         }
481                         else{
482                                 origCutoff = "unique";
483                                 cutoff = 0.0000;
484                         }
485                 }
486                 
487                 m->mothurOut(label); m->mothurOutEndLine();
488                 
489                 while(columnFile){
490                         columnFile >> seqNameA >> seqNameB >> distance;
491                         if(seqNameA < seqNameB) {       seqPairString = seqNameA + '\t' + seqNameB;     }
492                         else                                    {       seqPairString = seqNameB + '\t' + seqNameA;     }
493                         
494                         set<string>::iterator it = seqPairSet.find(seqPairString);
495                         
496                         if(distance <= cutoff){
497                                 if(it != seqPairSet.end()){
498                                         truePositives++;
499                                         seqPairSet.erase(it);   
500                                 }
501                                 else{
502                                         falseNegatives++;
503                                 }
504                                 trueNegatives--;
505                         }
506                         else if(it != seqPairSet.end()){        
507                                 falsePositives++;
508                                 trueNegatives--;
509                                 seqPairSet.erase(it);   
510                         }
511                         
512                         m->gobble(columnFile);
513                 }
514                 falsePositives += seqPairSet.size();
515                 
516                 outputStatistics(label, origCutoff);
517                 
518                                 
519                 return 0;
520         }
521         catch(exception& e) {
522                 m->errorOut(e, "SensSpecCommand", "process");
523                 exit(1);
524         }
525 }
526 //***************************************************************************************************************
527
528 int SensSpecCommand::processColumn(){
529         try{            
530                 string origCutoff = "";
531                 bool getCutoff = 0;
532                 if(cutoff == -1.00)     {       getCutoff = 1;                                                                                                                  }
533                 else                            {       origCutoff = toString(cutoff);  cutoff += (0.49 / double(precision));   }
534                 
535                 set<string> seqPairSet;
536                 int numSeqs = 0;
537                 
538                 InputData input(listFile, "list");
539                 ListVector* list = input.getListVector();
540                 string lastLabel = list->getLabel();
541                 
542                 //if the users enters label "0.06" and there is no "0.06" in their file use the next lowest label.
543                 set<string> processedLabels;
544                 set<string> userLabels = labels;
545                 
546                 
547                 while((list != NULL) && ((allLines == 1) || (userLabels.size() != 0))) {
548                         
549                         if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) {        m->mothurRemove(outputNames[i]);  }  delete list;  return 0;  }
550                         
551                         if(allLines == 1 || labels.count(list->getLabel()) == 1){                       
552                                 
553                                 processedLabels.insert(list->getLabel());
554                                 userLabels.erase(list->getLabel());
555                                 
556                                 //process
557                                 numSeqs = fillSeqPairSet(seqPairSet, list);
558                                 process(seqPairSet, list->getLabel(), getCutoff, origCutoff, numSeqs);
559                         }
560                         
561                         if ((m->anyLabelsToProcess(list->getLabel(), userLabels, "") == true) && (processedLabels.count(lastLabel) != 1)) {
562                                 string saveLabel = list->getLabel();
563                                 
564                                 delete list;
565                                 list = input.getListVector(lastLabel);
566                                 
567                                 processedLabels.insert(list->getLabel());
568                                 userLabels.erase(list->getLabel());
569                                 
570                                 //process
571                                 numSeqs = fillSeqPairSet(seqPairSet, list);
572                                 process(seqPairSet, list->getLabel(), getCutoff, origCutoff, numSeqs);
573                                 
574                                 //restore real lastlabel to save below
575                                 list->setLabel(saveLabel);
576                         }               
577                         
578                         lastLabel = list->getLabel();                   
579                         
580                         delete list;
581                         list = input.getListVector();
582                 }
583                 
584                 
585                 //output error messages about any remaining user labels
586                 set<string>::iterator it;
587                 bool needToRun = false;
588                 for (it = userLabels.begin(); it != userLabels.end(); it++) {  
589                         m->mothurOut("Your file does not include the label " + *it); 
590                         if (processedLabels.count(lastLabel) != 1) {
591                                 m->mothurOut(". I will use " + lastLabel + "."); m->mothurOutEndLine();
592                                 needToRun = true;
593                         }else {
594                                 m->mothurOut(". Please refer to " + lastLabel + "."); m->mothurOutEndLine();
595                         }
596                 }
597                 
598                 //run last label if you need to
599                 if (needToRun == true)  {
600                         if (list != NULL) {     delete list;    }
601                         list = input.getListVector(lastLabel);
602                         
603                         //process
604                         numSeqs = fillSeqPairSet(seqPairSet, list);
605                         delete list;
606                         process(seqPairSet, list->getLabel(), getCutoff, origCutoff, numSeqs);
607                 }
608                 
609                 return 0;
610         }
611         catch(exception& e) {
612                 m->errorOut(e, "SensSpecCommand", "processColumn");
613                 exit(1);
614         }
615 }
616
617 //***************************************************************************************************************
618
619 void SensSpecCommand::setUpOutput(){
620         try{            
621                 ofstream sensSpecFile;
622                 m->openOutputFile(sensSpecFileName, sensSpecFile);
623                 
624                 sensSpecFile << "label\tcutoff\ttp\ttn\tfp\tfn\tsensitivity\tspecificity\tppv\tnpv\tfdr\taccuracy\tmcc\tf1score\n";
625
626                 sensSpecFile.close();
627         }
628         catch(exception& e) {
629                 m->errorOut(e, "SensSpecCommand", "setUpOutput");
630                 exit(1);
631         }
632 }
633
634 //***************************************************************************************************************
635
636 void SensSpecCommand::outputStatistics(string label, string cutoff){
637         try{            
638                 double tp = (double) truePositives;
639                 double fp = (double) falsePositives;
640                 double tn = (double) trueNegatives;
641                 double fn = (double) falseNegatives;
642                 
643                 double p = tp + fn;
644                 double n = fp + tn;
645                 double pPrime = tp + fp;
646                 double nPrime = tn + fn;
647                 
648                 double sensitivity = tp / p;    
649                 double specificity = tn / n;
650                 double positivePredictiveValue = tp / pPrime;
651                 double negativePredictiveValue = tn / nPrime;
652                 double falseDiscoveryRate = fp / pPrime;
653                 
654                 double accuracy = (tp + tn) / (p + n);
655                 double matthewsCorrCoef = (tp * tn - fp * fn) / sqrt(p * n * pPrime * nPrime);  if(p == 0 || n == 0){   matthewsCorrCoef = 0;   }
656                 double f1Score = 2.0 * tp / (p + pPrime);
657                 
658                 
659                 if(p == 0)                      {       sensitivity = 0;        matthewsCorrCoef = 0;   }
660                 if(n == 0)                      {       specificity = 0;        matthewsCorrCoef = 0;   }
661                 if(p + n == 0)          {       accuracy = 0;                                                           }
662                 if(p + pPrime == 0)     {       f1Score = 0;                                                            }
663                 if(pPrime == 0)         {       positivePredictiveValue = 0;    falseDiscoveryRate = 0; matthewsCorrCoef = 0;   }
664                 if(nPrime == 0)         {       negativePredictiveValue = 0;    matthewsCorrCoef = 0;                                                   }
665                 
666                 ofstream sensSpecFile;
667                 m->openOutputFileAppend(sensSpecFileName, sensSpecFile);
668                 
669                 sensSpecFile << label << '\t' << cutoff << '\t';
670                 sensSpecFile << truePositives << '\t' << trueNegatives << '\t' << falsePositives << '\t' << falseNegatives << '\t';
671                 sensSpecFile << setprecision(4);
672                 sensSpecFile << sensitivity << '\t' << specificity << '\t' << positivePredictiveValue << '\t' << negativePredictiveValue << '\t';
673                 sensSpecFile << falseDiscoveryRate << '\t' << accuracy << '\t' << matthewsCorrCoef << '\t' << f1Score << endl;
674                 
675                 sensSpecFile.close();
676         }
677         catch(exception& e) {
678                 m->errorOut(e, "SensSpecCommand", "outputStatistics");
679                 exit(1);
680         }
681 }
682
683 //***************************************************************************************************************
684
685
686