Binary classifier performance evaluation

Whether you are building a binary classifier or need to audit one built by someone else, there are many things we'd like to know about its performance. The following sections describe functions that are designed to let you easily get at commonly used binary classifier performance diagnostic metrics.

The functions are relatively performant and are capable of handling millions of rows of data.

kstest

The two sample Kolmogorov-Smirnov test is a statistical test of whether two empirical distributions are the same. The test is based on finding the maximum separation between the two cumulative distribution functions (CDF) and determining the p-value of the test statistic.

For binary classifiers, the predicted probabilities of the two classes should be different, thus the interest isn't whether the probability distributions are different, rather, it is how large is the maximal separation and where does it occur.

Let's generate some data to illustrate the idea.

using Random, Distributions, Plots

Random.seed!(123)

n100 = rand(Normal(100, 10), 1000)
n100a = rand(Normal(100, 10), 1000)
n120 = rand(Normal(120, 10), 1000)
n140 = rand(Normal(140, 10), 1000)

histogram(n100, nbins = 50, opacity= 0.3)
histogram!(n100a, nbins = 50, opacity= 0.3, legend = nothing)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

We can use the kstest function to find the maximum separation and its location. The required input is a vector designating the two classes and another vector of the values, this is the typical data structure of model scoring on development or validation data.

using DSUtils

cls = [fill(0, length(n100)); fill(1, length(n100a))]
values = [n100; n100a]
kstest(cls, values)
(n = 2000, n1 = 1000, n0 = 1000, baserate = 0.5, ks = 0.03400000000000003, ksarg = 93.43534653113403, ksdep = 0.746)

kstest returns results in a named tuple:

  • n, total number of observations
  • n1, total number of observations in class 1
  • n0, total number of observations in class 0
  • baserate, n1 / n, the incidence rate of class 1
  • ks, the maximum separation between CDF1 and CDF0, a value between [0, 1]
  • ksarg, argmax, the value where maximum separation is achieved
  • ksdep, depth of argmax in the sorted values (default sort is from high to low)

ks of 0 means the distributions are indistinguishable, ks of 1 says the two distributions are complete separable. These two distributions have negligible separation since they are drawn from the same distribution.

We now test on moderate separation:

histogram(n100, nbins = 50, opacity= 0.3)
histogram!(n120, nbins = 50, opacity= 0.3, legend = nothing)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

cls = [fill(0, length(n100)); fill(1, length(n120))]
values = [n100; n120]
kstest(cls, values)
(n = 2000, n1 = 1000, n0 = 1000, baserate = 0.5, ks = 0.6779999999999999, ksarg = 110.85777553733867, ksdep = 0.485)

There's considerable separation between the classes, and ks is larger than before.

Let's test on widely separately data:

histogram(n100, nbins = 50, opacity= 0.3)
histogram!(n140, nbins = 50, opacity= 0.3, legend = nothing)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

cls = [fill(0, length(n100)); fill(1, length(n140))]
values = [n100; n140]
kstest(cls, values)
(n = 2000, n1 = 1000, n0 = 1000, baserate = 0.5, ks = 0.949, ksarg = 121.57678388253254, ksdep = 0.4955)

We can see that the two classes are nearly separable and ks is now quite high at 0.949. These examples illustrate how ks can serve as an indicator of the ability to separate the two classes.

auroc

A good binary classifier would have high sensitivity (able to recognize True Positive) and high specificity (able to recognize True Negatives, hence have low False Positive). A plot of the trade-off curve of True Positive Rate versus False Positive Rate at various cutoff probabilities is called the Receiver Operating Characteristics (ROC) curve. One way to quantify performance is by the area under the ROC curve, often abbreviated as AUC or C, many packages would compute AUC via numeric integration of the ROC curve. AUC is in the range [0, 1], a perfect model has AUC of 1, a random model has AUC of 0.5, and a perfectly backwards model would have AUC of -1.

There is another interpretation of AUC which provides more intuition than simply as the area under a curve. If we make all possible pair-wise comparisons between the probabilities of class 1 with class 0, we can count the incidences of:

  • Concordant: class 1 probability > class 0 probability
  • Tied: class 1 probability ≈ class 0 probability
  • Discordant: class 1 probability < class 0 probability

Then we can compute:

  • AUC: (Concordant + 0.5 Tied) / (N1 * N0)
  • Gini: 2AUC - 1, or (Concordant - Discordant) / (N1 * N0)
  • Goodman-Kruskal Gamma: (Concordant - Discordant) / (Concordant + Discordant),

no penalty for Tied

  • Kendall's Tau: (Concordant - Discordant) / (0.5 * (N1+N0) * (N1+N0-1))

We can interpret AUC as the percentage of time class 1 probabilities is larger than class 0 probabilities (ignoring ties).

The mathematical proof can be found at Stack Exchange and Professor David J. Hand's article.

cls = [fill(0, length(n100)); fill(1, length(n140))]
values = [n100; n140]
auroc(cls, values)
(conc = 997398, tied = 0, disc = 2602, auc = 0.997398, gini = 0.994796)

auroc returns results in a named tuple:

  • conc, number of concordant comparisons
  • tied, number of tied comparisons
  • disc, number of discordant comparisons
  • auc, area under ROC curve, or just area under curve
  • gini, 2auc - 1

bcdiag

While kstest and auroc provide diagnostic measures for comparing model performance, when there is a model of interest, it is likely that we need to produce many graphs and table to understand and document its performance, bcdiag allows us to do this easily.

using Random
using GLM
using DSUtils

function logitgen(intercept::Real, slope::Real, len::Int; seed = 888)
    Random.seed!(seed)
    x = 10 .* rand(len)                     # random uniform [0, 10)
    # sort!(x)                                # x ascending
    logit = @. intercept + slope * x        # logit(prob) = ln(p / (1 + p)) = linear equation
    prob = @. 1. / (1. + exp(-logit))       # probability
    y = rand(len) .<= prob
    y, x
end

m = DataFrame(logitgen(-3, 0.6, 100_000), (:target, :x))
m_logistic = glm(@formula(target ~ x), m, Binomial(), LogitLink())
m.pred = predict(m_logistic)

kstest(m.target, m.pred)
(n = 100000, n1 = 49994, n0 = 50006, baserate = 0.49994, ks = 0.5720001714368025, ksarg = 0.500818560199364, ksdep = 0.50068)
auroc(m.target, m.pred)
(conc = 2149668685, tied = 3448, disc = 350327831, auc = 0.8598681759821017, gini = 0.7197363519642035)

Running bcdiag prints a quick summary:

mdiag = bcdiag(m.target, m.pred)
Base rate: 0.4999   n: 100000   n1: 49994   n0: 50006
ks:        0.572   occurs at value of 0.500818560199364 depth of 0.50068
auroc:     0.8599   concordant: 2149668685   tied: 3448   discordant: 350327831
Gini:      0.7197

The output structure allows us to create the following plots and tables to understand:

  • the ability of the model to separate the two classes
  • the accuracy of the probability point estimates
  • how to set cutoff for maximum accuracy
  • performance of the model at varying cutoff depth

ksplot

ksplot plots the cumulative distribution of class 1 (true positive rate) and class 0 (false positive rate) versus depth.

ksplot(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

It shows where the maximum separation of the two distributions occur.

rocplot

rocplot plots the true positive rate vs. false positive rate (depth is implicit).

rocplot(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

A perfect model has auc of 1, a random model has auc of 0.5.

biasplot

Both ksplot and rocplot rely on the ability of the model to rank order the observations, the score value itself doesn't matter. For example, if you took the score and perform any monotonic transform, ks and auc wouldn't change. There are occasions where the score value does matter, where the probabilities need to be accurate, for example, in expected return calculations. Thus, we need to understand whether the probabilities are accurate, biasplot does this by plotting the observed response rate versus predicted response rate to look for systemic bias. This is also called the calibration graph.

biasplot(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

An unbiased model would lie on the diagnonal, systemic shift off the diagonal represents over or under estimate of the true probability.

accuracyplot

People often refer to (TP + TN) / N as accuracy of the model, that is, the ability to correctly identify correct cases. It is used to compare model performance as well - model with higher accuracy is a better model. For a probability based classifier, a cutoff is required to turn probability to predicted class. So, what is the cutoff value to use to achieve maximum accuracy?

There are many approaches to setting the best cutoff, one way is to assign utility values to the four outcomes of [TP, FP, FN, TN] and maximize the sum across different cutoff's. Accuracy measure uses the utility values of [1, 0, 0, 1] giving TP + TN. You can assign negative penalty terms for misclassification as well.

Note that this is different from kstest - maximum separation on cumulative distribution (normalized to 100%) does not account for class size difference, e.g., class 1 may be only 2% of the cases.

accuracyplot(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

liftcurve

liftcurve plots the actual response and predicted response versus depth, with baserate as 1.

liftcurve(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

We can easily see where the model is performing better than average, approximately the same as average, or below average.

cumliftcurve

cumliftcurve is similar to liftcurve, the difference is it is a plot of cumulative response rate from the top of the model.

cumliftcurve(mdiag)
/home/runner/.julia/packages/GR/RlE5Y/src/../deps/gr/bin/gksqt: error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file: No such file or directory
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

liftable

liftable is the table from which liftcurve is plotted.

liftable(mdiag)

100 rows × 9 columns

grpdepthcountcntObscntPrdrrObsrrPredliftObsliftPrd
Int32Float64Int64Int64Float64Float64Float64Float64Float64
100.011000938950.2440.9380.9502441.876231.90072
210.021000944947.3750.9440.9473751.888231.89498
320.031000941944.280.9410.944281.882231.88879
430.041000942941.1840.9420.9411841.884231.88259
540.051000934937.8590.9340.9378591.868221.87594
650.061000941934.3020.9410.9343021.882231.86883
760.071000920930.4350.920.9304351.840221.86109
870.081000935926.4170.9350.9264171.870221.85306
980.091000917922.1720.9170.9221721.834221.84457
1090.11000921917.70.9210.91771.842221.83562
11100.111000902913.0380.9020.9130381.804221.82629
12110.121000927908.1910.9270.9081911.854221.8166
13120.131000888903.2620.8880.9032621.776211.80674
14130.141000878897.7210.8780.8977211.756211.79566
15140.151000874892.2540.8740.8922541.748211.78472
16150.161000886886.4750.8860.8864751.772211.77316
17160.171000872880.3560.8720.8803561.744211.76092
18170.181000872873.8560.8720.8738561.744211.74792
19180.191000866867.0310.8660.8670311.732211.73427
20190.21000866860.1570.8660.8601571.732211.72052
21200.211000853852.9020.8530.8529021.70621.70601
22210.221000865845.2080.8650.8452081.730211.69062
23220.231000830837.0750.830.8370751.66021.67435
24230.241000827828.9890.8270.8289891.65421.65818
25240.251000819819.9820.8190.8199821.63821.64016
26250.261000811811.2420.8110.8112421.622191.62268
27260.271000818802.2320.8180.8022321.63621.60466
28270.281000812792.4470.8120.7924471.624191.58508
29280.291000816782.550.8160.782551.63221.56529
30290.31000780772.5210.780.7725211.560191.54523
31300.311000748762.0090.7480.7620091.496181.5242
32310.321000755751.6650.7550.7516651.510181.50351
33320.331000744740.30.7440.74031.488181.48078
34330.341000716728.730.7160.728731.432171.45763
35340.351000736717.0270.7360.7170271.472181.43423
36350.361000727704.8750.7270.7048751.454171.40992
37360.371000702692.2750.7020.6922751.404171.38472
38370.381000684679.5190.6840.6795191.368161.3592
39380.391000685666.8490.6850.6668491.370161.33386
40390.41000660653.3220.660.6533221.320161.3068
41400.411000640640.2060.640.6402061.280151.28057
42410.421000632626.5810.6320.6265811.264151.25331
43420.431000589612.6670.5890.6126671.178141.22548
44430.441000599598.1750.5990.5981751.198141.19649
45440.451000570583.7250.570.5837251.140141.16759
46450.461000568568.840.5680.568841.136141.13782
47460.471000549554.3060.5490.5543061.098131.10874
48470.481000532539.7630.5320.5397631.064131.07966
49480.491000532525.1890.5320.5251891.064131.0505
50490.51000528509.6390.5280.5096391.056131.0194
51500.511000462494.0590.4620.4940590.9241110.988237
52510.521000475478.6950.4750.4786950.9501140.957504
53520.531000468464.320.4680.464320.9361120.928751
54530.541000452448.630.4520.448630.9041080.897368
55540.551000437433.7130.4370.4337130.8741050.86753
56550.561000435419.6790.4350.4196790.8701040.839459
57560.571000405404.5880.4050.4045880.8100970.809273
58570.581000421389.9640.4210.3899640.8421010.780022
59580.591000349375.9860.3490.3759860.6980840.752062
60590.61000343361.4790.3430.3614790.6860820.723045
61600.611000332347.4770.3320.3474770.664080.695036
62610.621000326334.0060.3260.3340060.6520780.668091
63620.631000318320.7240.3180.3207240.6360760.641525
64630.641000320307.7670.320.3077670.6400770.615608
65640.651000288295.3190.2880.2953190.5760690.59071
66650.661000279283.0220.2790.2830220.5580670.566113
67660.671000259271.0010.2590.2710010.5180620.542068
68670.681000242258.9360.2420.2589360.4840580.517934
69680.691000265247.6160.2650.2476160.5300640.495291
70690.71000228236.8130.2280.2368130.4560550.473683
71700.711000237226.0880.2370.2260880.4740570.45223
72710.721000216215.7810.2160.2157810.4320520.431613
73720.731000229205.4350.2290.2054350.4580550.41092
74730.741000196195.8320.1960.1958320.3920470.39171
75740.751000197186.680.1970.186680.3940470.373406
76750.761000176177.2510.1760.1772510.3520420.354544
77760.771000163168.8080.1630.1688080.3260390.337656
78770.781000158160.7980.1580.1607980.3160380.321634
79780.791000158153.4110.1580.1534110.3160380.306858
80790.81000143145.950.1430.145950.2860340.291934
81800.811000110138.6410.110.1386410.2200260.277315
82810.821000128131.6660.1280.1316660.2560310.263363
83820.831000129124.7220.1290.1247220.2580310.249473
84830.841000127118.2220.1270.1182220.254030.236472
85840.851000104112.1440.1040.1121440.2080250.224315
86850.86100095106.2880.0950.1062880.1900230.212601
87860.87100080100.8820.080.1008820.1600190.201788
88870.88100010395.63060.1030.09563060.2060250.191284
89880.89100010990.74350.1090.09074350.2180260.181509
90890.910008086.190.080.086190.1600190.172401
91900.9110007181.56560.0710.08156560.1420170.163151
92910.9210007777.22780.0770.07722780.1540180.154474
93920.9310006673.06570.0660.07306570.1320160.146149
94930.9410008669.28590.0860.06928590.1720210.138588
95940.9510006665.5770.0660.0655770.1320160.13117
96950.9610007161.93190.0710.06193190.1420170.123879
97960.9710005558.50820.0550.05850820.1100130.11703
98970.9810006255.25870.0620.05525870.1240150.110531
99980.9910005052.18950.050.05218950.1000120.104391
100991.010005749.31880.0570.04931880.1140140.0986494

cumliftable

cumliftable is the cumulative version of liftable.

cumliftable(mdiag)

100 rows × 9 columns

grpdepthcountcumObscumPrdcrObscrPrdliftObsliftPrd
Int32Float64Int64Int64Float64Float64Float64Float64Float64
100.011000938950.2440.9380.9502441.876231.90072
210.02200018821897.620.9410.9488091.882231.89785
320.03300028232841.90.9410.9472991.882231.89483
430.04400037653783.080.941250.945771.882731.89177
540.05500046994720.940.93980.9441881.879831.8886
650.06600056405655.240.940.9425411.880231.88531
760.07700065606585.680.9371430.9408111.874511.88185
870.08800074957512.10.9368750.9390121.873971.87825
980.09900084128434.270.9346670.9371411.869561.87451
1090.11000093339351.970.93330.9351971.866821.87062
11100.11110001023510265.00.9304550.9331821.861131.86659
12110.12120001116211173.20.9301670.93111.860561.86242
13120.13130001205012076.50.9269230.9289581.854071.85814
14130.14140001292812974.20.9234290.9267271.847081.85368
15140.15150001380213866.40.9201330.9244291.840491.84908
16150.16160001468814752.90.9180.9220571.836221.84433
17160.17170001556015633.30.9152940.9196041.830811.83943
18170.18180001643216507.10.9128890.9170621.8261.83434
19180.19190001729817374.20.9104210.9144291.821061.82908
20190.2200001816418234.30.90820.9117151.816621.82365
21200.21210001901719087.20.9055710.9089151.811361.81805
22210.22220001988219932.40.9037270.9060191.807671.81226
23220.23230002071220769.50.9005220.9030221.801261.80626
24230.24240002153921598.50.8974580.8999371.795131.80009
25240.25250002235822418.50.894320.8967391.788851.79369
26250.26260002316923229.70.8911150.893451.782441.78712
27260.27270002398724031.90.8884070.8900721.777031.78036
28270.28280002479924824.40.8856790.8865851.771571.77338
29280.29290002561525606.90.8832760.8829981.766761.76621
30290.3300002639526379.50.8798330.8793151.759881.75884
31300.31310002714327141.50.8755810.8755311.751371.75127
32310.32320002789827893.10.8718120.871661.743831.74353
33320.33330002864228633.40.8679390.867681.736091.73557
34330.34340002935829362.20.8634710.8635931.727151.72739
35340.35350003009430079.20.8598290.8594051.719861.71902
36350.36360003082130784.10.8561390.8551131.712481.71043
37360.37370003152331476.30.8519730.8507121.704151.70163
38370.38380003220732155.90.8475530.8462071.695311.69262
39380.39390003289232822.70.8433850.8416081.686971.68342
40390.4400003355233476.00.83880.8369011.67781.674
41400.41410003419234116.20.8339510.8321031.66811.66441
42410.42420003482434742.80.8291430.827211.658481.65462
43420.43430003541335355.50.8235580.8222211.647311.64464
44430.44440003601235953.70.8184550.8171291.637111.63445
45440.45450003658236537.40.8129330.8119421.626061.62408
46450.46460003715037106.20.8076090.8066571.615411.61351
47460.47470003769937660.50.8021060.8012881.604411.60277
48470.48480003823138200.30.7964790.7958391.593151.59187
49480.49490003876338725.50.7910820.7903161.582351.58082
50490.5500003929139235.10.785820.7847021.571831.56959
51500.51510003975339729.20.7794710.7790031.559131.55819
52510.52520004022840207.90.7736150.7732281.547421.54664
53520.53530004069640672.20.7678490.76741.535881.53498
54530.54540004114841120.80.7620.7614971.524181.52318
55540.55550004158541554.50.7560910.7555371.512361.51126
56550.56560004202041974.20.7503570.749541.500891.49926
57560.57570004242542378.80.7442980.7434881.488781.48715
58570.58580004284642768.80.7387240.7373931.477631.47496
59580.59590004319543144.80.7321190.7312671.464411.46271
60590.6600004353843506.20.7256330.7251041.451441.45038
61600.61610004387043853.70.719180.7189131.438531.438
62610.62620004419644187.70.7128390.7127051.425851.42558
63620.63630004451444508.40.7065710.7064831.413311.41314
64630.64640004483444816.20.7005310.7002531.401231.40067
65640.65650004512245111.50.6941850.6940231.388541.38821
66650.66660004540145394.50.6878940.6877961.375951.37576
67660.67670004566045665.50.6814930.6815751.363151.36331
68670.68680004590245924.50.6750290.675361.350221.35088
69680.69690004616746172.10.6690870.6691611.338331.33848
70690.7700004639546408.90.6627860.6629841.325731.32613
71700.71710004663246635.00.6567890.6568311.313741.31382
72710.72720004684846850.80.6506670.6507051.301491.30157
73720.73730004707747056.20.644890.6446061.289941.28937
74730.74740004727347252.00.6388240.6385411.27781.27724
75740.75750004747047438.70.6329330.6325161.266021.26518
76750.76760004764647616.00.6269210.6265261.253991.2532
77760.77770004780947784.80.6208960.6205821.241941.24131
78770.78780004796747945.60.6149620.6146871.230071.22952
79780.79790004812548099.00.6091770.6088481.21851.21784
80790.8800004826848244.90.603350.6030621.206841.20627
81800.81810004837848383.60.5972590.5973281.194661.1948
82810.82820004850648515.20.5915370.5916491.183221.18344
83820.83830004863548640.00.5859640.5860241.172071.17219
84830.84840004876248758.20.58050.5804551.161141.16105
85840.85850004886648870.30.5748940.5749451.149931.15003
86850.86860004896148976.60.5693140.5694961.138761.13913
87860.87870004904149077.50.563690.5641091.127511.12835
88870.88880004914449173.10.5584550.5587861.117041.11771
89880.89890004925349263.90.5534040.5535271.106941.10719
90890.9900004933349350.10.5481440.5483341.096421.0968
91900.91910004940449431.60.5429010.5432051.085931.08654
92910.92920004948149508.90.5378370.538141.07581.07641
93920.93930004954749581.90.5327630.5331391.065651.06641
94930.94940004963349651.20.5280110.5282041.056151.05654
95940.95950004969949716.80.5231470.5233351.046421.04679
96950.96960004977049778.70.5184370.5185281.0371.03718
97960.97970004982549837.20.513660.5137861.027441.0277
98970.98980004988749892.50.5090510.5091071.018221.01834
99980.99990004993749944.70.5044140.5044921.008951.0091
100991.01000004999449994.00.499940.499941.01.0