I have written a small program in C# to plot ROC, PR (equal to AP) and PRI (equal to API) curves. (The program also calculates the approximate AUC of the curves, but that isn't the main point. The main purpose is to plot the curves.)
Plotting ROC and PR curves in C# appears to be a task which has not yet been done effectively. Therefore, this algorithm is written from scratch. I also hope this algorithm will be useful for those interested in data science and machine learning using, especially those using C#.
Please review and suggest any improvements in the algorithm. (In particular, I am curious about better methods of averaging the x,y coordinates for the outer-cross-validation results (i.e. nested-cross-validation), considering the variable decision boundary thresholds, i.e. the input of x,y can never be pre-determined, and using the standard 11 point averaging does not give accurate results; rather a very poor approximation. Still, it is supported by the method.)
The whole (working) algorithm is in the following file - plot.cs.
It can be tested using the static plot.test_plot() method which contains some real sample data. The generated bmp images will be loaded in the default program after execution.
Note that this was written using Visual Studio 2017, C# 7.3 and .NET 4.7.2. It may be incompatible with some lower C# or .NET versions. Please also note that this algorithm purposely ignores standard C# naming conventions.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Linq;
namespace svm_compute
{
public static class plot
{
public static void test_plot()
{
// this function plots sample ROC, PR and PRI (approximated interpolated) curves, using all thresholds and 11 point thresholds.
var encoded_roc = @"
FPR;TPR/0;0/0;0.022222/0;0.044444/0.030303;0.044444/0.030303;0.066667/0.030303;0.088889/0.030303;0.111111/0.030303;0.133333/0.030303;0.155556/0.030303;0.177778/0.060606;0.177778/0.060606;0.2/0.060606;0.222222/0.060606;0.244444/0.060606;0.266667/0.060606;0.288889/0.090909;0.288889/0.090909;0.311111/0.090909;0.333333/0.121212;0.333333/0.121212;0.355556/0.151515;0.355556/0.151515;0.377778/0.181818;0.377778/0.181818;0.4/0.181818;0.422222/0.181818;0.444444/0.181818;0.466667/0.212121;0.466667/0.212121;0.488889/0.242424;0.488889/0.242424;0.511111/0.242424;0.533333/0.242424;0.555556/0.242424;0.577778/0.242424;0.6/0.242424;0.622222/0.242424;0.644444/0.242424;0.666667/0.272727;0.666667/0.272727;0.688889/0.30303;0.688889/0.333333;0.688889/0.333333;0.711111/0.333333;0.733333/0.333333;0.755556/0.363636;0.755556/0.363636;0.777778/0.363636;0.8/0.363636;0.822222/0.393939;0.822222/0.393939;0.844444/0.393939;0.866667/0.393939;0.888889/0.424242;0.888889/0.424242;0.911111/0.424242;0.933333/0.454545;0.933333/0.484848;0.933333/0.515152;0.933333/0.515152;0.955556/0.515152;0.977778/0.545455;0.977778/0.545455;1/0.606061;1/0.636364;1/0.666667;1/0.69697;1/0.727273;1/0.757576;1/0.787879;1/0.818182;1/0.848485;1/0.878788;1/0.909091;1/0.939394;1/0.969697;1/1;1
FPR;TPR/0;0/0;0.030303/0;0.060606/0;0.090909/0;0.121212/0;0.151515/0;0.181818/0;0.212121/0;0.242424/0;0.272727/0;0.30303/0;0.333333/0;0.363636/0;0.393939/0;0.454545/0.022222;0.454545/0.022222;0.484848/0.044444;0.484848/0.066667;0.484848/0.066667;0.515152/0.066667;0.545455/0.066667;0.575758/0.088889;0.575758/0.111111;0.575758/0.111111;0.606061/0.133333;0.606061/0.155556;0.606061/0.177778;0.606061/0.177778;0.636364/0.2;0.636364/0.222222;0.636364/0.244444;0.636364/0.244444;0.666667/0.266667;0.666667/0.288889;0.666667/0.311111;0.666667/0.311111;0.69697/0.311111;0.727273/0.333333;0.727273/0.333333;0.757576/0.355556;0.757576/0.377778;0.757576/0.4;0.757576/0.422222;0.757576/0.444444;0.757576/0.466667;0.757576/0.488889;0.757576/0.511111;0.757576/0.511111;0.787879/0.533333;0.787879/0.533333;0.818182/0.555556;0.818182/0.577778;0.818182/0.6;0.818182/0.622222;0.818182/0.622222;0.848485/0.644444;0.848485/0.644444;0.878788/0.666667;0.878788/0.666667;0.909091/0.688889;0.909091/0.711111;0.909091/0.711111;0.939394/0.733333;0.939394/0.755556;0.939394/0.777778;0.939394/0.8;0.939394/0.822222;0.939394/0.822222;0.969697/0.844444;0.969697/0.866667;0.969697/0.888889;0.969697/0.911111;0.969697/0.933333;0.969697/0.955556;0.969697/0.955556;1/0.977778;1/1;1
FPR;TPR/0;0/0;0.022222/0;0.044444/0.030303;0.044444/0.030303;0.066667/0.030303;0.088889/0.030303;0.111111/0.030303;0.133333/0.030303;0.155556/0.030303;0.177778/0.030303;0.2/0.060606;0.2/0.060606;0.222222/0.060606;0.244444/0.060606;0.266667/0.060606;0.288889/0.060606;0.311111/0.090909;0.311111/0.121212;0.311111/0.121212;0.333333/0.121212;0.355556/0.121212;0.377778/0.121212;0.4/0.121212;0.422222/0.121212;0.444444/0.151515;0.444444/0.151515;0.466667/0.151515;0.488889/0.181818;0.488889/0.181818;0.511111/0.212121;0.511111/0.242424;0.511111/0.242424;0.533333/0.242424;0.555556/0.242424;0.577778/0.242424;0.6/0.272727;0.6/0.272727;0.622222/0.272727;0.644444/0.272727;0.666667/0.272727;0.688889/0.30303;0.688889/0.30303;0.711111/0.333333;0.711111/0.333333;0.733333/0.363636;0.733333/0.393939;0.733333/0.424242;0.733333/0.424242;0.755556/0.424242;0.777778/0.424242;0.8/0.424242;0.822222/0.424242;0.844444/0.424242;0.866667/0.424242;0.888889/0.454545;0.888889/0.484848;0.888889/0.484848;0.911111/0.515152;0.911111/0.545455;0.911111/0.545455;0.933333/0.575758;0.933333/0.606061;0.933333/0.606061;0.955556/0.666667;0.955556/0.69697;0.955556/0.69697;0.977778/0.727273;0.977778/0.757576;0.977778/0.787879;0.977778/0.787879;1/0.818182;1/0.848485;1/0.878788;1/0.909091;1/0.939394;1/0.969697;1/1;1
FPR;TPR/0;0/0;0.030303/0;0.060606/0;0.090909/0;0.121212/0;0.151515/0;0.181818/0;0.212121/0.022222;0.212121/0.022222;0.242424/0.022222;0.272727/0.022222;0.30303/0.044444;0.30303/0.044444;0.333333/0.044444;0.393939/0.066667;0.393939/0.066667;0.424242/0.066667;0.454545/0.088889;0.454545/0.088889;0.484848/0.088889;0.515152/0.111111;0.515152/0.111111;0.545455/0.111111;0.575758/0.133333;0.575758/0.155556;0.575758/0.177778;0.575758/0.2;0.575758/0.222222;0.575758/0.244444;0.575758/0.266667;0.575758/0.266667;0.606061/0.266667;0.636364/0.266667;0.666667/0.288889;0.666667/0.288889;0.69697/0.311111;0.69697/0.311111;0.727273/0.333333;0.727273/0.355556;0.727273/0.377778;0.727273/0.4;0.727273/0.4;0.757576/0.422222;0.757576/0.444444;0.757576/0.466667;0.757576/0.488889;0.757576/0.488889;0.787879/0.488889;0.818182/0.511111;0.818182/0.511111;0.848485/0.533333;0.848485/0.555556;0.848485/0.555556;0.878788/0.577778;0.878788/0.6;0.878788/0.622222;0.878788/0.644444;0.878788/0.666667;0.878788/0.688889;0.878788/0.688889;0.909091/0.688889;0.939394/0.711111;0.939394/0.733333;0.939394/0.755556;0.939394/0.777778;0.939394/0.8;0.939394/0.8;0.969697/0.822222;0.969697/0.844444;0.969697/0.866667;0.969697/0.888889;0.969697/0.911111;0.969697/0.933333;0.969697/0.955556;0.969697/0.955556;1/0.977778;1/1;1
FPR;TPR/0;0/0;0.022222/0;0.044444/0;0.066667/0;0.088889/0;0.111111/0;0.133333/0;0.155556/0;0.177778/0.030303;0.177778/0.030303;0.2/0.060606;0.2/0.060606;0.222222/0.060606;0.244444/0.060606;0.266667/0.090909;0.266667/0.090909;0.288889/0.090909;0.311111/0.090909;0.333333/0.121212;0.333333/0.121212;0.355556/0.121212;0.377778/0.121212;0.4/0.121212;0.422222/0.151515;0.422222/0.151515;0.444444/0.151515;0.466667/0.151515;0.488889/0.151515;0.511111/0.151515;0.533333/0.151515;0.555556/0.151515;0.577778/0.181818;0.577778/0.181818;0.6/0.181818;0.622222/0.212121;0.622222/0.212121;0.644444/0.242424;0.644444/0.272727;0.644444/0.272727;0.666667/0.272727;0.688889/0.30303;0.688889/0.333333;0.688889/0.333333;0.711111/0.333333;0.733333/0.333333;0.755556/0.333333;0.777778/0.363636;0.777778/0.393939;0.777778/0.424242;0.777778/0.424242;0.8/0.454545;0.8/0.454545;0.822222/0.454545;0.844444/0.454545;0.866667/0.454545;0.888889/0.484848;0.888889/0.484848;0.911111/0.515152;0.911111/0.545455;0.911111/0.545455;0.933333/0.575758;0.933333/0.575758;0.955556/0.606061;0.955556/0.636364;0.955556/0.666667;0.955556/0.666667;0.977778/0.69697;0.977778/0.727273;0.977778/0.757576;0.977778/0.787879;0.977778/0.818182;0.977778/0.848485;0.977778/0.878788;0.977778/0.909091;0.977778/0.909091;1/0.939394;1/0.969697;1/1;1
FPR;TPR/0;0/0;0.030303/0;0.060606/0;0.090909/0.022222;0.090909/0.022222;0.121212/0.022222;0.151515/0.022222;0.181818/0.022222;0.212121/0.022222;0.242424/0.022222;0.272727/0.022222;0.30303/0.022222;0.333333/0.044444;0.333333/0.044444;0.363636/0.044444;0.393939/0.044444;0.424242/0.066667;0.424242/0.066667;0.454545/0.088889;0.454545/0.088889;0.484848/0.088889;0.515152/0.111111;0.515152/0.111111;0.545455/0.133333;0.545455/0.155556;0.545455/0.177778;0.545455/0.2;0.545455/0.2;0.575758/0.222222;0.575758/0.222222;0.606061/0.222222;0.636364/0.222222;0.666667/0.244444;0.666667/0.266667;0.666667/0.288889;0.666667/0.311111;0.666667/0.311111;0.69697/0.311111;0.727273/0.333333;0.727273/0.355556;0.727273/0.355556;0.757576/0.355556;0.787879/0.377778;0.787879/0.377778;0.818182/0.4;0.818182/0.422222;0.818182/0.422222;0.848485/0.444444;0.848485/0.466667;0.848485/0.488889;0.848485/0.511111;0.848485/0.533333;0.848485/0.555556;0.848485/0.577778;0.848485/0.577778;0.878788/0.6;0.878788/0.622222;0.878788/0.644444;0.878788/0.666667;0.878788/0.666667;0.909091/0.688889;0.909091/0.711111;0.909091/0.733333;0.909091/0.733333;0.939394/0.755556;0.939394/0.777778;0.939394/0.8;0.939394/0.8;0.969697/0.822222;0.969697/0.822222;1/0.844444;1/0.866667;1/0.888889;1/0.911111;1/0.933333;1/0.955556;1/0.977778;1/1;1
FPR;TPR/0;0/0;0.022222/0;0.044444/0;0.066667/0;0.088889/0;0.111111/0;0.133333/0;0.155556/0;0.177778/0;0.2/0;0.222222/0;0.244444/0;0.266667/0;0.288889/0;0.311111/0;0.333333/0.030303;0.333333/0.030303;0.355556/0.030303;0.377778/0.030303;0.4/0.030303;0.422222/0.060606;0.422222/0.060606;0.444444/0.060606;0.466667/0.060606;0.488889/0.090909;0.488889/0.090909;0.511111/0.090909;0.533333/0.090909;0.555556/0.121212;0.555556/0.121212;0.577778/0.151515;0.577778/0.151515;0.6/0.151515;0.622222/0.181818;0.622222/0.181818;0.644444/0.181818;0.666667/0.212121;0.666667/0.212121;0.688889/0.242424;0.688889/0.242424;0.711111/0.272727;0.711111/0.30303;0.711111/0.333333;0.711111/0.363636;0.711111/0.363636;0.733333/0.363636;0.755556/0.393939;0.755556/0.393939;0.777778/0.424242;0.777778/0.424242;0.8/0.454545;0.8/0.454545;0.822222/0.484848;0.822222/0.515152;0.822222/0.545455;0.822222/0.545455;0.844444/0.545455;0.866667/0.545455;0.888889/0.545455;0.911111/0.575758;0.911111/0.606061;0.911111/0.606061;0.933333/0.636364;0.933333/0.666667;0.933333/0.666667;0.955556/0.69697;0.955556/0.727273;0.955556/0.727273;0.977778/0.757576;0.977778/0.757576;1/0.787879;1/0.818182;1/0.848485;1/0.878788;1/0.909091;1/0.939394;1/0.969697;1/1;1
FPR;TPR/0;0/0;0.030303/0;0.060606/0;0.090909/0;0.121212/0;0.151515/0;0.181818/0;0.212121/0;0.242424/0.022222;0.242424/0.022222;0.272727/0.044444;0.272727/0.044444;0.30303/0.044444;0.333333/0.066667;0.333333/0.066667;0.363636/0.066667;0.393939/0.088889;0.393939/0.088889;0.424242/0.088889;0.454545/0.111111;0.454545/0.133333;0.454545/0.155556;0.454545/0.177778;0.454545/0.177778;0.484848/0.177778;0.515152/0.177778;0.545455/0.2;0.545455/0.2;0.575758/0.222222;0.575758/0.222222;0.606061/0.244444;0.606061/0.244444;0.636364/0.266667;0.636364/0.288889;0.636364/0.288889;0.666667/0.288889;0.69697/0.288889;0.727273/0.288889;0.757576/0.311111;0.757576/0.311111;0.787879/0.333333;0.787879/0.333333;0.818182/0.355556;0.818182/0.377778;0.818182/0.377778;0.848485/0.4;0.848485/0.422222;0.848485/0.422222;0.878788/0.444444;0.878788/0.444444;0.909091/0.466667;0.909091/0.488889;0.909091/0.511111;0.909091/0.511111;0.939394/0.533333;0.939394/0.555556;0.939394/0.577778;0.939394/0.577778;0.969697/0.6;0.969697/0.622222;0.969697/0.644444;0.969697/0.666667;0.969697/0.666667;1/0.688889;1/0.711111;1/0.733333;1/0.755556;1/0.777778;1/0.8;1/0.822222;1/0.844444;1/0.866667;1/0.888889;1/0.911111;1/0.933333;1/0.955556;1/0.977778;1/1;1
";
var encoded_pr = @"
TPR;PPV/0;1/0.022222;1/0.044444;1/0.044444;0.666667/0.066667;0.75/0.088889;0.8/0.111111;0.833333/0.133333;0.857143/0.155556;0.875/0.177778;0.888889/0.177778;0.8/0.2;0.818182/0.222222;0.833333/0.244444;0.846154/0.266667;0.857143/0.288889;0.866667/0.288889;0.8125/0.311111;0.823529/0.333333;0.833333/0.333333;0.789474/0.355556;0.8/0.355556;0.761905/0.377778;0.772727/0.377778;0.73913/0.4;0.75/0.422222;0.76/0.444444;0.769231/0.466667;0.777778/0.466667;0.75/0.488889;0.758621/0.488889;0.733333/0.511111;0.741935/0.533333;0.75/0.555556;0.757576/0.577778;0.764706/0.6;0.771429/0.622222;0.777778/0.644444;0.783784/0.666667;0.789474/0.666667;0.769231/0.688889;0.775/0.688889;0.756098/0.688889;0.738095/0.711111;0.744186/0.733333;0.75/0.755556;0.755556/0.755556;0.73913/0.777778;0.744681/0.8;0.75/0.822222;0.755102/0.822222;0.74/0.844444;0.745098/0.866667;0.75/0.888889;0.754717/0.888889;0.740741/0.911111;0.745455/0.933333;0.75/0.933333;0.736842/0.933333;0.724138/0.933333;0.711864/0.955556;0.716667/0.977778;0.721311/0.977778;0.709677/1;0.714286/1;0.692308/1;0.681818/1;0.671642/1;0.661765/1;0.652174/1;0.642857/1;0.633803/1;0.625/1;0.616438/1;0.608108/1;0.6/1;0.592105/1;0.584416/1;0.576923
TPR;PPV/0;1/0.030303;1/0.060606;1/0.090909;1/0.121212;1/0.151515;1/0.181818;1/0.212121;1/0.242424;1/0.272727;1/0.30303;1/0.333333;1/0.363636;1/0.393939;1/0.454545;1/0.454545;0.9375/0.484848;0.941176/0.484848;0.888889/0.484848;0.842105/0.515152;0.85/0.545455;0.857143/0.575758;0.863636/0.575758;0.826087/0.575758;0.791667/0.606061;0.8/0.606061;0.769231/0.606061;0.740741/0.606061;0.714286/0.636364;0.724138/0.636364;0.7/0.636364;0.677419/0.636364;0.65625/0.666667;0.666667/0.666667;0.647059/0.666667;0.628571/0.666667;0.611111/0.69697;0.621622/0.727273;0.631579/0.727273;0.615385/0.757576;0.625/0.757576;0.609756/0.757576;0.595238/0.757576;0.581395/0.757576;0.568182/0.757576;0.555556/0.757576;0.543478/0.757576;0.531915/0.757576;0.520833/0.787879;0.530612/0.787879;0.52/0.818182;0.529412/0.818182;0.519231/0.818182;0.509434/0.818182;0.5/0.818182;0.490909/0.848485;0.5/0.848485;0.491228/0.878788;0.5/0.878788;0.491525/0.909091;0.5/0.909091;0.491803/0.909091;0.483871/0.939394;0.492063/0.939394;0.484375/0.939394;0.476923/0.939394;0.469697/0.939394;0.462687/0.939394;0.455882/0.969697;0.463768/0.969697;0.457143/0.969697;0.450704/0.969697;0.444444/0.969697;0.438356/0.969697;0.432432/0.969697;0.426667/1;0.434211/1;0.428571/1;0.423077
TPR;PPV/0;1/0.022222;1/0.044444;1/0.044444;0.666667/0.066667;0.75/0.088889;0.8/0.111111;0.833333/0.133333;0.857143/0.155556;0.875/0.177778;0.888889/0.2;0.9/0.2;0.818182/0.222222;0.833333/0.244444;0.846154/0.266667;0.857143/0.288889;0.866667/0.311111;0.875/0.311111;0.823529/0.311111;0.777778/0.333333;0.789474/0.355556;0.8/0.377778;0.809524/0.4;0.818182/0.422222;0.826087/0.444444;0.833333/0.444444;0.8/0.466667;0.807692/0.488889;0.814815/0.488889;0.785714/0.511111;0.793103/0.511111;0.766667/0.511111;0.741935/0.533333;0.75/0.555556;0.757576/0.577778;0.764706/0.6;0.771429/0.6;0.75/0.622222;0.756757/0.644444;0.763158/0.666667;0.769231/0.688889;0.775/0.688889;0.756098/0.711111;0.761905/0.711111;0.744186/0.733333;0.75/0.733333;0.733333/0.733333;0.717391/0.733333;0.702128/0.755556;0.708333/0.777778;0.714286/0.8;0.72/0.822222;0.72549/0.844444;0.730769/0.866667;0.735849/0.888889;0.740741/0.888889;0.727273/0.888889;0.714286/0.911111;0.719298/0.911111;0.706897/0.911111;0.694915/0.933333;0.7/0.933333;0.688525/0.933333;0.677419/0.955556;0.68254/0.955556;0.661538/0.955556;0.651515/0.977778;0.656716/0.977778;0.647059/0.977778;0.637681/0.977778;0.628571/1;0.633803/1;0.625/1;0.616438/1;0.608108/1;0.6/1;0.592105/1;0.584416/1;0.576923
TPR;PPV/0;1/0.030303;1/0.060606;1/0.090909;1/0.121212;1/0.151515;1/0.181818;1/0.212121;1/0.212121;0.875/0.242424;0.888889/0.272727;0.9/0.30303;0.909091/0.30303;0.833333/0.333333;0.846154/0.393939;0.866667/0.393939;0.8125/0.424242;0.823529/0.454545;0.833333/0.454545;0.789474/0.484848;0.8/0.515152;0.809524/0.515152;0.772727/0.545455;0.782609/0.575758;0.791667/0.575758;0.76/0.575758;0.730769/0.575758;0.703704/0.575758;0.678571/0.575758;0.655172/0.575758;0.633333/0.575758;0.612903/0.606061;0.625/0.636364;0.636364/0.666667;0.647059/0.666667;0.628571/0.69697;0.638889/0.69697;0.621622/0.727273;0.631579/0.727273;0.615385/0.727273;0.6/0.727273;0.585366/0.727273;0.571429/0.757576;0.581395/0.757576;0.568182/0.757576;0.555556/0.757576;0.543478/0.757576;0.531915/0.787879;0.541667/0.818182;0.55102/0.818182;0.54/0.848485;0.54902/0.848485;0.538462/0.848485;0.528302/0.878788;0.537037/0.878788;0.527273/0.878788;0.517857/0.878788;0.508772/0.878788;0.5/0.878788;0.491525/0.878788;0.483333/0.909091;0.491803/0.939394;0.5/0.939394;0.492063/0.939394;0.484375/0.939394;0.476923/0.939394;0.469697/0.939394;0.462687/0.969697;0.470588/0.969697;0.463768/0.969697;0.457143/0.969697;0.450704/0.969697;0.444444/0.969697;0.438356/0.969697;0.432432/0.969697;0.426667/1;0.434211/1;0.428571/1;0.423077
TPR;PPV/0;1/0.022222;1/0.044444;1/0.066667;1/0.088889;1/0.111111;1/0.133333;1/0.155556;1/0.177778;1/0.177778;0.888889/0.2;0.9/0.2;0.818182/0.222222;0.833333/0.244444;0.846154/0.266667;0.857143/0.266667;0.8/0.288889;0.8125/0.311111;0.823529/0.333333;0.833333/0.333333;0.789474/0.355556;0.8/0.377778;0.809524/0.4;0.818182/0.422222;0.826087/0.422222;0.791667/0.444444;0.8/0.466667;0.807692/0.488889;0.814815/0.511111;0.821429/0.533333;0.827586/0.555556;0.833333/0.577778;0.83871/0.577778;0.8125/0.6;0.818182/0.622222;0.823529/0.622222;0.8/0.644444;0.805556/0.644444;0.783784/0.644444;0.763158/0.666667;0.769231/0.688889;0.775/0.688889;0.756098/0.688889;0.738095/0.711111;0.744186/0.733333;0.75/0.755556;0.755556/0.777778;0.76087/0.777778;0.744681/0.777778;0.729167/0.777778;0.714286/0.8;0.72/0.8;0.705882/0.822222;0.711538/0.844444;0.716981/0.866667;0.722222/0.888889;0.727273/0.888889;0.714286/0.911111;0.719298/0.911111;0.706897/0.911111;0.694915/0.933333;0.7/0.933333;0.688525/0.955556;0.693548/0.955556;0.68254/0.955556;0.671875/0.955556;0.661538/0.977778;0.666667/0.977778;0.656716/0.977778;0.647059/0.977778;0.637681/0.977778;0.628571/0.977778;0.619718/0.977778;0.611111/0.977778;0.60274/0.977778;0.594595/1;0.6/1;0.592105/1;0.584416/1;0.576923
TPR;PPV/0;1/0.030303;1/0.060606;1/0.090909;1/0.090909;0.75/0.121212;0.8/0.151515;0.833333/0.181818;0.857143/0.212121;0.875/0.242424;0.888889/0.272727;0.9/0.30303;0.909091/0.333333;0.916667/0.333333;0.846154/0.363636;0.857143/0.393939;0.866667/0.424242;0.875/0.424242;0.823529/0.454545;0.833333/0.454545;0.789474/0.484848;0.8/0.515152;0.809524/0.515152;0.772727/0.545455;0.782609/0.545455;0.75/0.545455;0.72/0.545455;0.692308/0.545455;0.666667/0.575758;0.678571/0.575758;0.655172/0.606061;0.666667/0.636364;0.677419/0.666667;0.6875/0.666667;0.666667/0.666667;0.647059/0.666667;0.628571/0.666667;0.611111/0.69697;0.621622/0.727273;0.631579/0.727273;0.615385/0.727273;0.6/0.757576;0.609756/0.787879;0.619048/0.787879;0.604651/0.818182;0.613636/0.818182;0.6/0.818182;0.586957/0.848485;0.595745/0.848485;0.583333/0.848485;0.571429/0.848485;0.56/0.848485;0.54902/0.848485;0.538462/0.848485;0.528302/0.848485;0.518519/0.878788;0.527273/0.878788;0.517857/0.878788;0.508772/0.878788;0.5/0.878788;0.491525/0.909091;0.5/0.909091;0.491803/0.909091;0.483871/0.909091;0.47619/0.939394;0.484375/0.939394;0.476923/0.939394;0.469697/0.939394;0.462687/0.969697;0.470588/0.969697;0.463768/1;0.471429/1;0.464789/1;0.458333/1;0.452055/1;0.445946/1;0.44/1;0.434211/1;0.428571/1;0.423077
TPR;PPV/0;1/0.022222;1/0.044444;1/0.066667;1/0.088889;1/0.111111;1/0.133333;1/0.155556;1/0.177778;1/0.2;1/0.222222;1/0.244444;1/0.266667;1/0.288889;1/0.311111;1/0.333333;1/0.333333;0.9375/0.355556;0.941176/0.377778;0.944444/0.4;0.947368/0.422222;0.95/0.422222;0.904762/0.444444;0.909091/0.466667;0.913043/0.488889;0.916667/0.488889;0.88/0.511111;0.884615/0.533333;0.888889/0.555556;0.892857/0.555556;0.862069/0.577778;0.866667/0.577778;0.83871/0.6;0.84375/0.622222;0.848485/0.622222;0.823529/0.644444;0.828571/0.666667;0.833333/0.666667;0.810811/0.688889;0.815789/0.688889;0.794872/0.711111;0.8/0.711111;0.780488/0.711111;0.761905/0.711111;0.744186/0.711111;0.727273/0.733333;0.733333/0.755556;0.73913/0.755556;0.723404/0.777778;0.729167/0.777778;0.714286/0.8;0.72/0.8;0.705882/0.822222;0.711538/0.822222;0.698113/0.822222;0.685185/0.822222;0.672727/0.844444;0.678571/0.866667;0.684211/0.888889;0.689655/0.911111;0.694915/0.911111;0.683333/0.911111;0.672131/0.933333;0.677419/0.933333;0.666667/0.933333;0.65625/0.955556;0.661538/0.955556;0.651515/0.955556;0.641791/0.977778;0.647059/0.977778;0.637681/1;0.642857/1;0.633803/1;0.625/1;0.616438/1;0.608108/1;0.6/1;0.592105/1;0.584416/1;0.576923
TPR;PPV/0;1/0.030303;1/0.060606;1/0.090909;1/0.121212;1/0.151515;1/0.181818;1/0.212121;1/0.242424;1/0.242424;0.888889/0.272727;0.9/0.272727;0.818182/0.30303;0.833333/0.333333;0.846154/0.333333;0.785714/0.363636;0.8/0.393939;0.8125/0.393939;0.764706/0.424242;0.777778/0.454545;0.789474/0.454545;0.75/0.454545;0.714286/0.454545;0.681818/0.454545;0.652174/0.484848;0.666667/0.515152;0.68/0.545455;0.692308/0.545455;0.666667/0.575758;0.678571/0.575758;0.655172/0.606061;0.666667/0.606061;0.645161/0.636364;0.65625/0.636364;0.636364/0.636364;0.617647/0.666667;0.628571/0.69697;0.638889/0.727273;0.648649/0.757576;0.657895/0.757576;0.641026/0.787879;0.65/0.787879;0.634146/0.818182;0.642857/0.818182;0.627907/0.818182;0.613636/0.848485;0.622222/0.848485;0.608696/0.848485;0.595745/0.878788;0.604167/0.878788;0.591837/0.909091;0.6/0.909091;0.588235/0.909091;0.576923/0.909091;0.566038/0.939394;0.574074/0.939394;0.563636/0.939394;0.553571/0.939394;0.54386/0.969697;0.551724/0.969697;0.542373/0.969697;0.533333/0.969697;0.52459/0.969697;0.516129/1;0.52381/1;0.515625/1;0.507692/1;0.5/1;0.492537/1;0.485294/1;0.478261/1;0.471429/1;0.464789/1;0.458333/1;0.452055/1;0.445946/1;0.44/1;0.434211/1;0.428571/1;0.423077
";
var p11 = false;
var average_for_closest_points = true;
var open_bmp = true;
plot_curve(perf_curve_types.roc, encoded_roc, $@"c:\svm_compute\charts\{nameof(perf_curve_types.roc)}.bmp", p11, average_for_closest_points, open_bmp);
plot_curve(perf_curve_types.pr, encoded_pr, $@"c:\svm_compute\charts\{nameof(perf_curve_types.pr)}.bmp", p11, average_for_closest_points, open_bmp);
plot_curve(perf_curve_types.pri_from_pr, encoded_pr, $@"c:\svm_compute\charts\{nameof(perf_curve_types.pri_from_pr)}.bmp", p11, average_for_closest_points, open_bmp);
p11 = true;
plot_curve(perf_curve_types.roc, encoded_roc, $@"c:\svm_compute\charts\{nameof(perf_curve_types.roc)}{(p11 ? "_p11" : "")}.bmp", p11, average_for_closest_points, open_bmp);
plot_curve(perf_curve_types.pr, encoded_pr, $@"c:\svm_compute\charts\{nameof(perf_curve_types.pr)}{(p11 ? "_p11" : "")}.bmp", p11, average_for_closest_points, open_bmp);
plot_curve(perf_curve_types.pri_from_pr, encoded_pr, $@"c:\svm_compute\charts\{nameof(perf_curve_types.pri_from_pr)}{(p11 ? "_p11" : "")}.bmp", p11, average_for_closest_points, open_bmp);
}
public enum perf_curve_types : int
{
pr = 1,
pri = 2,
pri_from_pr = 3,
roc = 4
}
public static double scale(double value, double min, double max, double min_scale, double max_scale)
{
return (max_scale - min_scale) * ((value - min) / (max - min)) + min_scale;
}
public static double area_under_curve_trapz(List<(double x, double y)> coordinate_list)
{
coordinate_list = coordinate_list.Distinct().ToList();
coordinate_list = coordinate_list.OrderBy(a => a.x).ThenBy(a => a.y).ToList();
var auc = coordinate_list.Select((c, i) => i >= coordinate_list.Count - 1 ? 0 : (coordinate_list[i + 1].x - coordinate_list[i].x) * ((coordinate_list[i].y + coordinate_list[i + 1].y) / 2)).Sum();
return auc;
}
public static void plot_curve(perf_curve_types perf_curve_type, string encoded_plot_data, string save_filename, bool convert_to_p11 = false, bool average_for_closest_points = true, bool open_bmp = true)
{
// the data is in the format of x_title;y_title/x;y/x;y/x;y/...
// each line represents a different curve. usually from outer-cross-validation, there would be 5 or 10 curves (although it could be any number >= 2). if the input the average value from cross-validation, there would only be one curve.
// note, it is not necessary that the number of values is equal per curve. however, they should all be x,y values of either ROC or PR curves - other types of curves are not necessarily supported as there is code here specific to ROC and PR.
if (perf_curve_type == 0)
{
throw new ArgumentException("Invalid performance curve type specified.", nameof(perf_curve_type));
}
if (string.IsNullOrWhiteSpace(save_filename))
{
throw new ArgumentException("BMP filename required to save curve.", nameof(save_filename));
}
if (string.IsNullOrWhiteSpace(encoded_plot_data))
{
throw new ArgumentException("Plot x,y coordinate data is missing.", nameof(encoded_plot_data));
}
List<(string x_title, string y_title, List<(double x, double y)> xy)> parsed_xy = encoded_plot_data.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(line =>
{
var headers = line.Trim().Split('/').First().Split(';');
var r = (x_title: headers[0], y_title: headers[1], xy: line.Trim().Split('/').Skip(1).Select(b => (x: double.Parse("" + b.Split(';')[0]), y: double.Parse("" + b.Split(';')[1]))).ToList());
r.xy = r.xy.OrderBy(a => a.x).ThenBy(a => perf_curve_type == perf_curve_types.roc ? a.y : 1 - a.y).ToList();
return r;
}).ToList();
if (convert_to_p11)
{
// convert the x,y coordinate to the 11 point thresholds (useful for comparison of different classification models, where thresholds are unlikely to be meaningful)
var points11 = new[] { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
for (var index = 0; index < parsed_xy.Count; index++)
{
if (perf_curve_type == perf_curve_types.roc)
{
parsed_xy[index] = (parsed_xy[index].x_title, parsed_xy[index].y_title, points11.Select(t => (t, parsed_xy[index].xy.Where(a => a.x <= t).Max(a => a.y))).ToList());
}
else
{
parsed_xy[index] = (parsed_xy[index].x_title, parsed_xy[index].y_title, points11.Select(t => (t, parsed_xy[index].xy.Where(a => a.x >= t).Max(a => a.y))).ToList());
}
}
}
if (perf_curve_type == perf_curve_types.pri_from_pr)
{
// interpolate the x,y values (also for comparison of classifiation models)
var last_y = 0d;
parsed_xy = parsed_xy.Select(a => (x_title: a.x_title, y_title: a.y_title, xy: a.xy.Select((b, i) =>
{
//x = TPR; y = PPV
var max_ppv = a.xy.Where(c => c.x >= b.x).Max(c => c.y);
if (double.IsNaN(max_ppv)) max_ppv = last_y;
last_y = max_ppv;
return (x: b.x, y: max_ppv);
}).ToList())).ToList();
}
(string x_title, string y_title, List<(double x, double y)> xy) average_xy2 = (parsed_xy.First().x_title, parsed_xy.First().y_title, new List<(double x, double y)>());
if (average_for_closest_points)
{
// make average curve for all outer-cv curves
var all_xy = parsed_xy.SelectMany(a => a.xy).OrderBy(a => a.x).ThenBy(a => perf_curve_type == perf_curve_types.roc ? a.y : 1 - a.y).Distinct().ToList();
var closest_to_all_xy = all_xy.Select(z => parsed_xy.Select(a => a.xy.OrderBy(b => Math.Abs(b.x - z.x) + Math.Abs(b.y - z.y)).First()).ToList()).ToList();
average_xy2.xy = closest_to_all_xy.Select(a => (x: a.Average(b => b.x), y: a.Average(b => b.y))).Distinct().OrderBy(a => a.x).ThenBy(a => perf_curve_type == perf_curve_types.roc ? a.y : 1 - a.y).ToList();
average_xy2.xy = average_xy2.xy.Distinct().OrderBy(a => a.x).ThenBy(a => perf_curve_type == perf_curve_types.roc ? a.y : 1 - a.y).ToList();
}
List<(string x_title, string y_title, List<(double x, double y)> xy)> data = parsed_xy;
var average_curves = 0;
if (average_xy2.xy != null && average_xy2.xy.Count > 0) { data.Add(average_xy2); average_curves++; }
var graph_title = perf_curve_type.ToString().Replace("_", " ").ToUpperInvariant() + " " + string.Join(" ", string.Join("|", data.Select(a => a.x_title + "/" + a.y_title).Distinct().ToList()), "Curve");
var axis_x_title = string.Join(" ", string.Join("|", data.Select(a => a.x_title).Distinct().ToList()), "(X)");
var axis_y_title = string.Join(" ", string.Join("|", data.Select(a => a.y_title).Distinct().ToList()), "(Y)");
var width_x = 2000; // could be specified as a parameter
var height_y = 2000; // could be specified as a parameter
var bg_color = Color.Transparent;
var fg_color = Color.White; // not in use - using transparent instead
var grid_color = Color.LightSlateGray;
var title_color = Color.White;
var axis_start_color = Color.Blue;
var axis_end_color = Color.Red;
var bmp = new Bitmap(width_x, height_y);
var graph_width_x = (double)bmp.Width * 0.8; // could be specified as a parameter
var graph_height_y = (double)bmp.Height * 0.8; // could be specified as a parameter
var graph_start_x = ((double)width_x - (double)graph_width_x) / (double)2;
var graph_end_x = (double)graph_start_x + (double)graph_width_x;
var graph_start_y = ((double)height_y - (double)graph_height_y) / (double)2;
var graph_end_y = (double)graph_start_y + (double)graph_height_y;
program.WriteLine($@"{nameof(graph_start_x)}={graph_start_x}, {nameof(graph_start_y)}={graph_start_y}, {nameof(graph_end_x)}={graph_end_x}, {nameof(graph_end_y)}={graph_end_y}");
using (var bmp_gfx = Graphics.FromImage(bmp))
{
/////////////////////////////////////////////////////////
// set bg colour, and other gfx parameters
bmp_gfx.Clear(bg_color);
bmp_gfx.SmoothingMode = SmoothingMode.AntiAlias;
bmp_gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
bmp_gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
bmp_gfx.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
StringFormat format = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
};
/////////////////////////////////////////////////////////
// draw title
{
var title_font = new Font("Tahoma", (int)graph_start_y / 4, GraphicsUnit.Pixel);
var title_brush = new SolidBrush(title_color);
bmp_gfx.DrawString(graph_title, title_font, title_brush, new RectangleF(0, 0, bmp.Width, (int)graph_start_y), format);
}
/////////////////////////////////////////////////////////
// draw diagonal graph grid lines
{
bmp_gfx.DrawLine(new Pen(grid_color, 2), (float)graph_start_x, (float)graph_start_y, (float)graph_end_x, (float)graph_end_y);
bmp_gfx.DrawLine(new Pen(grid_color, 2), (float)graph_start_x, (float)graph_end_y, (float)graph_end_x, (float)graph_start_y);
}
/////////////////////////////////////////////////////////
// draw x axis title
{
var axis_x_font = new Font("Tahoma", (int)((height_y - graph_end_y) / 4), GraphicsUnit.Pixel);
var axis_x_title_size = bmp_gfx.MeasureString(axis_x_title, axis_x_font);
var axis_x_title_start_x = (float)(((double)width_x / (double)2) - ((double)axis_x_title_size.Width / (double)2));
var axis_x_title_start_y = (float)((double)height_y - ((double)axis_x_title_size.Height));
var axis_x_brush = new LinearGradientBrush(new RectangleF(axis_x_title_start_x, axis_x_title_start_y, axis_x_title_size.Width, axis_x_title_size.Height), axis_start_color, axis_end_color, LinearGradientMode.Horizontal);
bmp_gfx.DrawString(axis_x_title, axis_x_font, axis_x_brush, axis_x_title_start_x, axis_x_title_start_y);
}
/////////////////////////////////////////////////////////
// draw y axis title
{
var axis_y_font = new Font("Tahoma", (int)graph_start_x / 4, GraphicsUnit.Pixel);
var axis_y_title_size = bmp_gfx.MeasureString(axis_y_title, axis_y_font); //, new StringFormat(StringFormatFlags.DirectionVertical));
var axis_y_title_start_x = (float)0; // (axis_y_title_size.Height/2);
var axis_y_title_start_y = (float)((bmp.Height / 2) + (axis_y_title_size.Width / 2));
var axis_y_title_end_x = axis_y_title_start_x + axis_y_title_size.Height;
var axis_y_title_end_y = axis_y_title_start_y + axis_y_title_size.Width;
bmp_gfx.TranslateTransform(axis_y_title_start_x, axis_y_title_start_y);
bmp_gfx.RotateTransform(90 * 3);
var axis_y_brush = new LinearGradientBrush(new RectangleF(0, 0, axis_y_title_size.Width, axis_y_title_size.Height), axis_start_color, axis_end_color, LinearGradientMode.Horizontal);
bmp_gfx.DrawString(axis_y_title, axis_y_font, (Brush)axis_y_brush, 0, 0);
bmp_gfx.ResetTransform();
}
/////////////////////////////////////////////////////////
// draw grid lines horizonal (x changes, y in min to max)
{
var ticks_axis_x_brush = new LinearGradientBrush(new Rectangle((int)0, (int)0, (int)bmp.Width, bmp.Height), axis_start_color, axis_end_color, LinearGradientMode.Horizontal);
var ticks_axis_x_font = new Font("Tahoma", (int)((height_y - graph_end_y) / 4), GraphicsUnit.Pixel);
for (var x = 0.0; x <= 1.0; x += 0.1)
{
var x1 = scale(x, 0.0, 1.0, graph_start_x, graph_end_x);
var x2 = scale(x, 0.0, 1.0, graph_start_x, graph_end_x);
var y1 = scale(0.0, 0.0, 1.0, graph_start_y, graph_end_y);
var y2 = scale(1.0, 0.0, 1.0, graph_start_y, graph_end_y);
program.WriteLine($@"{nameof(x1)}={x1}, {nameof(y1)}={y1}, {nameof(x2)}={x2}, {nameof(y2)}={y2}");
bmp_gfx.DrawLine(new Pen(grid_color, 2), (float)x1, (float)y1, (float)x2, (float)y2);
var tick_text_size = bmp_gfx.MeasureString(x.ToString("0.0"), ticks_axis_x_font); // swap width and height, since it is rotated to side
bmp_gfx.TranslateTransform((float)x1 - (tick_text_size.Height / 2), (float)(graph_end_y + (tick_text_size.Width * 1.1)));
bmp_gfx.RotateTransform(90 * 3);
bmp_gfx.DrawString(x.ToString("0.0"), ticks_axis_x_font, ticks_axis_x_brush, 0, 0); // tick_text_size.Height);//, new StringFormat(StringFormatFlags.DirectionVertical));
bmp_gfx.ResetTransform();
}
}
/////////////////////////////////////////////////////////
// draw grid lines vertical (y changes, x is min to max)
{
var ticks_axis_y_brush = new LinearGradientBrush(new Rectangle((int)0, (int)0, (int)bmp.Width, bmp.Height), axis_start_color, axis_end_color, LinearGradientMode.Horizontal);
var ticks_axis_y_font = new Font("Tahoma", (int)((graph_start_x) / 4), GraphicsUnit.Pixel);
for (var y = 0.0; y <= 1.0; y += 0.1)
{
var x1 = scale(0.0, 0.0, 1.0, graph_start_x, graph_end_x);
var x2 = scale(1.0, 0.0, 1.0, graph_start_x, graph_end_x);
var y1 = scale(y, 0.0, 1.0, graph_start_y, graph_end_y);
var y2 = scale(y, 0.0, 1.0, graph_start_y, graph_end_y);
program.WriteLine($@"{nameof(x1)}={x1}, {nameof(y1)}={y1}, {nameof(x2)}={x2}, {nameof(y2)}={y2}");
bmp_gfx.DrawLine(new Pen(grid_color, 2), (float)x1, (float)y1, (float)x2, (float)y2);
var tick_text_size = bmp_gfx.MeasureString((1 - y).ToString("0.0"), ticks_axis_y_font);
bmp_gfx.DrawString((1 - y).ToString("0.0"), ticks_axis_y_font, ticks_axis_y_brush, (float)(graph_start_x - (tick_text_size.Width * 1.0)), (float)(y2 - (tick_text_size.Height / 2)));
}
}
/////////////////////////////////////////////////////////
// draw data
var color_rnd = new Random(2);
for (var i = 0; i < data.Count; i++)
{
var is_average_curve = i >= data.Count - average_curves;
var c = is_average_curve ? Color.White : Color.FromArgb(color_rnd.Next(0, 255), color_rnd.Next(0, 255), color_rnd.Next(0, 255));
var auc = area_under_curve_trapz(data[i].xy);
auc = Math.Round(auc, 2);
var auc_str = "AUC " + auc.ToString("0.00");
var auc_str_font = new Font("Tahoma", (int)((graph_start_x) / 4), GraphicsUnit.Pixel);
var auc_str_size = bmp_gfx.MeasureString(auc_str, auc_str_font);
bmp_gfx.DrawString(auc_str, auc_str_font, new SolidBrush(c), (int)(width_x - auc_str_size.Width), (int)(graph_start_y + (auc_str_size.Height * i)));
for (var a = 1; a < data[i].xy.Count; a++)
{
var xy1 = data[i].xy[a - 1];
var xy2 = data[i].xy[a];
var x1 = scale(xy1.x, 0.0, 1.0, graph_start_x, graph_end_x);
var y1 = scale((1 - xy1.y), 0.0, 1.0, graph_start_y, graph_end_y);
var x2 = scale(xy2.x, 0.0, 1.0, graph_start_x, graph_end_x);
var y2 = scale((1 - xy2.y), 0.0, 1.0, graph_start_y, graph_end_y);
var point_size = 20;
var pen_size = 4;
bmp_gfx.DrawLine(new Pen(c, pen_size), (float)x1, (float)y1, (float)x2, (float)y2);
if (a == 1)
{
bmp_gfx.DrawEllipse(new Pen(c, pen_size), (int)x1 - (point_size / 2), (int)y1 - (point_size / 2), point_size, point_size);
}
bmp_gfx.DrawEllipse(new Pen(c, pen_size), (int)x2 - (point_size / 2), (int)y2 - (point_size / 2), point_size, point_size);
}
}
}
Directory.CreateDirectory(Path.GetDirectoryName(save_filename));
bmp.Save(save_filename, ImageFormat.Bmp);
if (open_bmp)
{
Process.Start(save_filename);
}
}
}
}