diff --git a/libraries/GST/GST.cpp b/libraries/GST/GST.cpp new file mode 100644 index 00000000..d2dacb5b --- /dev/null +++ b/libraries/GST/GST.cpp @@ -0,0 +1,226 @@ +// +// FILE: GST.cpp +// VERSION: 0.1.1 +// PURPOSE: Arduino library for Gold Standard Test metrics +// URL: https://github.com/RobTillaart/GST +// https://en.wikipedia.org/wiki/Sensitivity_and_specificity +// https://en.wikipedia.org/wiki/Confusion_matrix +// +// formula's based upon Wikipedia. + + +#include "GST.h" + + +GST::GST() +{ + clearAll(); +}; + + +/////////////////////////////////////////////////////// +// +// INPUT FUNCTIONS +// +void GST::setTruePositive(float v) +{ + TP = v; + AP = TP + FN; +}; + + +void GST::setTrueNegative(float v) +{ + TN = v; + AN = TN + FP; +}; + + +void GST::setFalsePositive(float v) +{ + FP = v; + AN = TN + FP; +}; + + +void GST::setFalseNegative(float v) +{ + FN = v; + AP = TP + FN; +}; + + +void GST::clearAll() +{ + AP = 0; + AN = 0; + TP = 0; + TN = 0; + FP = 0; + FN = 0; +} + + +// These are used for updating the test matrix +float GST::addTruePositive(float v) +{ + TP += v; + AP = TP + FN; + return TP; +}; + + +float GST::addTrueNegative(float v) +{ + TN += v; + AN = TN + FP; + return TN; +}; + + +float GST::addFalsePositive(float v) +{ + FP += v; + AN = TN + FP; + return FP; +}; + + +float GST::addFalseNegative(float v) +{ + FN += v; + AP = TP + FN; + return FN; +}; + + +/////////////////////////////////////////////////////// +// +// OUTPUT FUNCTIONS I +// +float GST::getTruePositive() { return TP; }; +float GST::getTrueNegative() { return TN; }; +float GST::getFalsePositive() { return FP; }; +float GST::getFalseNegative() { return FN; }; + +float GST::getTotal() { return AP + AN; }; +float GST::getActualPositive() { return AP; }; +float GST::getActualNegative() { return AN; }; +float GST::getTestedPositive() { return TP + FP; }; +float GST::getTestedNegative() { return TN + FN; }; + +float GST::sensitivity() { return TPR(); }; +float GST::specificity() { return TNR(); }; + + +float GST::truePositiveRate() { return TPR(); }; +float GST::TPR() { return TP / AP; }; +float GST::trueNegativeRate() { return TNR(); }; +float GST::TNR() { return TN / AN; }; +float GST::falseNegativeRate() { return FNR(); }; +float GST::FNR() { return FN / AP; }; +float GST::falsePositiveRate() { return FPR(); }; +float GST::FPR() { return FP / AN; }; + + +/////////////////////////////////////////////////////// +// +// OUTPUT FUNCTIONS II +// +float GST::positivePredictiveValue() { return PPV(); }; +float GST::PPV() { return TP / (TP + FP); }; +float GST::negativePredictiveValue() { return NPV(); }; +float GST::NPV() { return TN / (TN + FN); }; +float GST::falseDiscoveryRate() { return FDR(); }; +float GST::FDR() { return FP / (TP + FP); }; +float GST::falseOmissionRate() { return FOR(); }; +float GST::FOR() { return FN / (TN + FN); }; + + +float GST::positiveLikelihoodRatio() { return LRplus(); }; +float GST::LRplus() { return TPR() / FPR(); }; +float GST::negativeLikelihoodRatio() { return LRminus(); }; +float GST::LRminus() { return FNR() / TNR(); }; + + +float GST::prevalenceThreshold() +{ + return sqrt(FPR()) / (sqrt(TPR()) + sqrt(FPR())); +}; + + +float GST::threatScore() +{ + return TP / (TP + FN + FP); +}; + + +float GST::criticalSuccessIndex() +{ + return threatScore(); +}; + + +float GST::prevalence() +{ + return AP / (AP + AN); +}; + + +float GST::accuracy() +{ + return (TP + TN) / (AP + AN); +}; + + +float GST::balancedAccuracy() +{ + return (TPR() + TNR()) * 0.5; +}; + + +float GST::F1Score() +{ + return (2 * TP) / (2 * TP + FP + FN); +}; + + +float GST::MatthewsCorrelationCoefficient() { return MCC(); }; +float GST::phi() { return MCC(); }; +float GST::MCC() +{ + return (TP*TN - FP*FN)/sqrt((TP+FP) * (TP+FN) * (TN+FP) * (TN+FN)); +}; + + +float GST::FowlkesMallowsIndex() { return FM(); }; +float GST::FM() +{ + return sqrt(PPV()*TPR()); +}; + + +float GST::BookmakerInformedness() { return BM(); }; +float GST::BM() +{ + return TPR() + TNR() - 1; +}; + + +float GST::markedness() { return MK(); }; +float GST::deltaP() { return MK(); }; +float GST::MK() +{ + return PPV() + NPV() - 1; +}; + + +float GST::diagnosticOddsRatio() { return DOR(); }; +float GST::DOR() +{ + return LRplus() / LRminus(); +}; + + +// -- END OF FILE -- + diff --git a/libraries/GST/GST.h b/libraries/GST/GST.h index 443c7026..5103073e 100644 --- a/libraries/GST/GST.h +++ b/libraries/GST/GST.h @@ -1,110 +1,116 @@ #pragma once // // FILE: GST.h -// VERSION: 0.1.0 +// VERSION: 0.1.1 // PURPOSE: Arduino library for Gold Standard Test metrics // URL: https://github.com/RobTillaart/GST // https://en.wikipedia.org/wiki/Sensitivity_and_specificity // https://en.wikipedia.org/wiki/Confusion_matrix // -// formula's based upon wikipedia. +// formula's based upon Wikipedia. + +#define GST_LIB_VERSION (F("0.1.1")) -#define GST_LIB_VERSION (F("0.1.0")) +#include "Arduino.h" class GST { public: - GST() {}; + GST(); - // These 4 need to be filled in. - void setTruePositive(float v) { TP = v; P = TP + FN; }; - void setTrueNegative(float v) { TN = v; N = TN + FP; }; - void setFalsePositive(float v) { FP = v; N = TN + FP; }; - void setFalseNegative(float v) { FN = v; P = TP + FN; }; + // These four values of the matrix need to be set to get started. + void setTruePositive(float v = 0); + void setTrueNegative(float v = 0); + void setFalsePositive(float v = 0); + void setFalseNegative(float v = 0); + void clearAll(); - float getTruePositive() { return TP; }; - float getTrueNegative() { return TN; }; - float getFalsePositive() { return FP; }; - float getFalseNegative() { return FN; }; - - float getTotal() { return P + N; }; - float getActualPositive() { return P; }; - float getActualNegative() { return N; }; - float getTestedPositive() { return TP + FP; }; - float getTestedNegative() { return TN + FN; }; - - float sensitivity() { return TPR(); }; - float specificity() { return TNR(); }; + // These are used for updating the test matrix + float addTruePositive(float v); + float addTrueNegative(float v); + float addFalsePositive(float v); + float addFalseNegative(float v); + // Output functions I + float getTruePositive(); + float getTrueNegative(); + float getFalsePositive(); + float getFalseNegative(); - // true positive rate - float TPR() { return TP / P; }; - // true negative rate - float TNR() { return TN / N; }; + float getTotal(); + float getActualPositive(); + float getActualNegative(); + float getTestedPositive(); + float getTestedNegative(); - // false negative rate - float FNR() { return FN / (FN + TP); }; - // false positive rate - float FPR() { return FP / (FP + TN); }; + float sensitivity(); + float specificity(); - - // positive predictive value - float PPV() { return TP / (TP + FP); }; - // negative predictive value - float NPV() { return TN / (TN + FN); }; - - // false discovery rate - float FDR() { return FP / (FP + TP); }; - // false omission rate - float FOR() { return FN / (FN + TN); }; + float truePositiveRate(); + float TPR(); + float trueNegativeRate(); + float TNR(); + float falseNegativeRate(); + float FNR(); + float falsePositiveRate(); + float FPR(); - - // positive likelihood ratio - float LRplus() { return TPR() / FPR(); }; - // negative likelihood ratio - float LRminus() { return FNR() / TNR(); }; + // Output functions II + float positivePredictiveValue(); + float PPV(); + float negativePredictiveValue(); + float NPV(); + float falseDiscoveryRate(); + float FDR(); + float falseOmissionRate(); + float FOR(); - - float prevalenceThreshold() { return sqrt(FPR()) / (sqrt(TPR()) + sqrt(FPR())); }; - float threatScore() { return TP / (TP + FN + FP); }; - float criticalSuccessIndex() { return threatScore(); }; + float positiveLikelihoodRatio(); + float LRplus(); + float negativeLikelihoodRatio(); + float LRminus(); - - float prevalence() { return P / (P + N); }; - float accuracy() { return (TP + TN) / (P + N); }; - float balancedAccuracy() { return (TPR() + TNR()) / 2; }; - float F1Score() { return (2 * TP)/(2 * TP + FP + FN); }; + float prevalenceThreshold(); + float threatScore(); + float criticalSuccessIndex(); - - // Matthews correlation coefficient - float MCC() { return (TP*TN-FP*FN)/sqrt((TP+FP)*(TP+FN)*(TN+FP)*(TN+FN)); }; - float phi() { return MCC(); }; - // Fowlkes–Mallows index - float FM() { return sqrt(PPV()*TPR()); }; - // Bookmaker informedness - float BM() { return TPR() + TNR() - 1; }; - // markedness - float MK() { return PPV() + NPV() - 1; }; - float deltaP() { return MK(); }; - // diagnostic odds ratio - float DOR() { return LRplus() / LRminus(); }; + float prevalence(); + float accuracy(); + float balancedAccuracy(); + float F1Score(); -private: - float P = 0; - float N = 0; - float TP = 0; - float TN = 0; - float FP = 0; - float FN = 0; + float MatthewsCorrelationCoefficient(); + float phi(); + float MCC(); + float FowlkesMallowsIndex(); + float FM(); + float BookmakerInformedness(); + float BM(); + + + float markedness(); + float deltaP(); + float MK(); + float diagnosticOddsRatio(); + float DOR(); + + +private: + float AP; // actual positive + float AN; // actual negative + float TP; // true positive + float TN; // true negative + float FP; // false positive + float FN; // false positive }; diff --git a/libraries/GST/README.md b/libraries/GST/README.md index 766df6e6..0f339668 100644 --- a/libraries/GST/README.md +++ b/libraries/GST/README.md @@ -13,30 +13,140 @@ Arduino library for Gold Standard Test metrics. ## Description -The GST library is **experimental**. +Note: **experimental** + +The GST library is an implementation of the **Gold Standard Test**. #### Links +These sites describe the functions in more detail. + - https://en.wikipedia.org/wiki/Sensitivity_and_specificity - https://en.wikipedia.org/wiki/Confusion_matrix +#### Performance + +The math functions are from pretty straightforward to rather complex. + +It is possible to optimize functions with intermediate values if needed. +However the right way to optimize depends on the way the library is used. + + +#### Related libraries + +- https://github.com/RobTillaart/Statistic + + ## Interface -See .h file +See .h file for all functions. Many function exist in a long descriptive name and an acronym version. Here only the long names are given. + +For the definitions please check - https://en.wikipedia.org/wiki/Sensitivity_and_specificity or +https://en.wikipedia.org/wiki/Confusion_matrix + + +### Input functions + +These four numbers should all be set before output functions make sense. +The parameter **value** is typical absolute value measured or counted. +If the parameter is omitted, the default 0 will be used to reset the value. + +- **void setTruePositive(float value = 0)** set the internal TP value. +- **void setTrueNegative(float value = 0)** set the internal TN value. +- **void setFalsePositive(float value = 0)** set the internal FP value. +- **void setFalseNegative(float value = 0)** set the internal FN value. +- **void clearAll()** reset all the above to 0. + +In tests one often want to increase / change the numbers. +This can be done with the **addTruePositive()** etc functions. +After every addition all output functions can be called. + +- **float addTruePositive(float value)** increases the internal TP value. +Use a negative value to decrease. +Returns the new value of TP. +- **float addTrueNegative(float value)** increases the internal TN value. +Use a negative value to decrease. +Returns the new value of TN. +- **float addFalsePositive(float value)** increases the internal FP value. +Use a negative value to decrease. +Returns the new value of FP. +- **float addFalseNegative(float value)** increases the internal FN value. +Use a negative value to decrease. +Returns the new value of FN. + + +### Output functions I + +Basic output + +- **float getTruePositive()** returns internal TP. +- **float getTrueNegative()** returns internal TN. +- **float getFalsePositive()** returns internal FP. +- **float getFalseNegative()** returns internal FN. + + +- **float getTotal()** returns total of four numbers. +- **float getActualPositive()** +- **float getActualNegative()** +- **float getTestedPositive()** +- **float getTestedNegative()** + + +- **float sensitivity()** equals truePositiveRate(). +- **float specificity()** equals trueNegativeRate() + + +- **float truePositiveRate()** +- **float trueNegativeRate()** +- **float falseNegativeRate()** +- **float falsePositiveRate()** + + +### Output functions II + +These are the more 'complex' functions. +Read the Wikipedia pages for their uses. + +- **float positivePredictiveValue()** +- **float negativePredictiveValue()** +- **float falseDiscoveryRate()** +- **float falseOmissionRate()** + + +- **float positiveLikelihoodRatio()** +- **float negativeLikelihoodRatio()** + + +- **float prevalenceThreshold()** +- **float threatScore()** +- **float criticalSuccessIndex()** + + +- **float prevalence()** +- **float accuracy()** +- **float balancedAccuracy()** +- **float F1Score()** + + +- **float MatthewsCorrelationCoefficient()** +- **float FowlkesMallowsIndex()** +- **float BookmakerInformedness()** + + +- **float markedness()** +- **float deltaP()** +- **float diagnosticOddsRatio()** ## Future -- documentation - - improve - - more links? +- improve documentation +- add functions + - percentage functions for TP TN FP and FN? - test - complete the CI test coverage. - examples - add real life examples. - combination with a sensor? batch testing? -- code - - full name functions instead of acronyms. (wrap?) - - is GST a good class name? diff --git a/libraries/GST/changelog.md b/libraries/GST/changelog.md index f4983db4..8fcf132b 100644 --- a/libraries/GST/changelog.md +++ b/libraries/GST/changelog.md @@ -2,7 +2,18 @@ # GST Changelog -## 0.1.0 2022-02-25 +## 0.1.1 2022-06-08 + +- add **addTruePositive()** etc functions. +- add defaults for **setTruePositive(value = 0)** etc functions +- add long descriptive names for the short functions. +- add derived class GoldenStandardTest, for descriptive name +- added some documentation +- split off GST.cpp file, prevent - https://github.com/RobTillaart/CRC/issues/21 + + +## 0.1.0 2022-02-25 + - initial version - diff --git a/libraries/GST/examples/GST_add_runtime/GST_add_runtime.ino b/libraries/GST/examples/GST_add_runtime/GST_add_runtime.ino new file mode 100644 index 00000000..15ed248b --- /dev/null +++ b/libraries/GST/examples/GST_add_runtime/GST_add_runtime.ino @@ -0,0 +1,121 @@ +// FILE: GST_add_runtime.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo +// URL: https://github.com/RobTillaart/GST + + +#include "Arduino.h" +#include "GST.h" + + +GST gst; + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + Serial.println(__FILE__); + + gst.setTruePositive(0); + gst.setTrueNegative(0); + gst.setFalsePositive(0); + gst.setFalseNegative(0); + +} + +void loop() +{ + // simulate a test result + delay(500); + int score = random(4); + switch (score) + { + case 0: + gst.addTruePositive(1); + break; + case 1: + gst.addTrueNegative(1); + break; + case 2: + gst.addFalsePositive(1); + break; + case 3: + gst.addFalseNegative(1); + break; + } + confusion_matrix(); + // confusion_matrix_normalized(); + + +} + + +void confusion_matrix() +{ + Serial.println(); + Serial.println(__FUNCTION__); + Serial.println(); + + // PRINTED IN A MATRIX + Serial.print("\t"); + Serial.print(gst.getTotal()); + Serial.print("\t"); + Serial.print(gst.getTestedPositive()); + Serial.print("\t"); + Serial.println(gst.getTestedNegative()); + + Serial.print("\t"); + Serial.print(gst.getActualPositive()); + Serial.print("\t"); + Serial.print(gst.getTruePositive()); + Serial.print("\t"); + Serial.println(gst.getFalseNegative()); + + Serial.print("\t"); + Serial.print(gst.getActualNegative()); + Serial.print("\t"); + Serial.print(gst.getFalsePositive()); + Serial.print("\t"); + Serial.println(gst.getTrueNegative()); + + Serial.println(); + Serial.print("\tSensitivity:\t"); + Serial.println(gst.sensitivity(), 4); + Serial.print("\tSpecificity:\t"); + Serial.println(gst.specificity(), 4); +} + + +void confusion_matrix_normalized() +{ + Serial.println(); + Serial.println(__FUNCTION__); + Serial.println(); + + // PRINTED IN A MATRIX + Serial.print("\t"); + Serial.print("100.00%"); + Serial.print("\t"); + Serial.print(gst.getTestedPositive()); + Serial.print("\t"); + Serial.println(gst.getTestedNegative()); + + Serial.print("\t"); + Serial.print(gst.getActualPositive()); + Serial.print("\t"); + Serial.print(gst.TPR(), 4); + Serial.print("\t"); + Serial.println(gst.FNR(), 4); + + Serial.print("\t"); + Serial.print(gst.getActualNegative()); + Serial.print("\t"); + Serial.print(gst.FPR(), 4); + Serial.print("\t"); + Serial.println(gst.TNR(), 4); +} + + + +// -- END OF FILE -- diff --git a/libraries/GST/keywords.txt b/libraries/GST/keywords.txt index d1a467c3..5c9faac8 100644 --- a/libraries/GST/keywords.txt +++ b/libraries/GST/keywords.txt @@ -9,6 +9,12 @@ setTruePositive KEYWORD2 setTrueNegative KEYWORD2 setFalsePositive KEYWORD2 setFalseNegative KEYWORD2 +clearAll KEYWORD2 + +addTruePositive KEYWORD2 +addTrueNegative KEYWORD2 +addFalsePositive KEYWORD2 +addFalseNegative KEYWORD2 getTruePositive KEYWORD2 getTrueNegative KEYWORD2 @@ -60,6 +66,29 @@ deltaP KEYWORD2 DOR KEYWORD2 +truePositiveRate KEYWORD2 +trueNegativeRate KEYWORD2 +falseNegativeRate KEYWORD2 +falsePositiveRate KEYWORD2 + + +positivePredictiveValue KEYWORD2 +negativePredictiveValue KEYWORD2 +falseDiscoveryRate KEYWORD2 +falseOmissionRate KEYWORD2 + + +positiveLikelihoodRatio KEYWORD2 +negativeLikelihoodRatio KEYWORD2 + + +MatthewsCorrelationCoefficient KEYWORD2 +FowlkesMallowsIndex KEYWORD2 +BookmakerInformedness KEYWORD2 +markedness KEYWORD2 +diagnosticOddsRatio KEYWORD2 + + # Constants (LITERAL1) GST_LIB_VERSION LITERAL1 diff --git a/libraries/GST/library.json b/libraries/GST/library.json index 00317ba8..f5bafe8c 100644 --- a/libraries/GST/library.json +++ b/libraries/GST/library.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/GST.git" }, - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "frameworks": "arduino", "platforms": "*", diff --git a/libraries/GST/library.properties b/libraries/GST/library.properties index dccb3c7c..e9725827 100644 --- a/libraries/GST/library.properties +++ b/libraries/GST/library.properties @@ -1,5 +1,5 @@ name=GST -version=0.1.0 +version=0.1.1 author=Rob Tillaart maintainer=Rob Tillaart sentence=Arduino library for Golden Standard Test, confusion matrix.