{"id":2192,"date":"2018-07-21T13:24:12","date_gmt":"2018-07-21T11:24:12","guid":{"rendered":"https:\/\/blogs.fu-berlin.de\/reseda\/?page_id=2192"},"modified":"2018-09-25T19:38:06","modified_gmt":"2018-09-25T17:38:06","slug":"accuracy-statistics-in-r","status":"publish","type":"page","link":"https:\/\/blogs.fu-berlin.de\/reseda\/accuracy-statistics-in-r\/","title":{"rendered":"Accuracy Statistics in R"},"content":{"rendered":"<p>In this section we will focus on creating an confusion matrix in R. Additionally we will perform a significance test, and calculate confidence intervals as well as the kappa coefficient.<\/p>\n<p><\/br><a name=\"1\"><\/a><\/p>\n<h1>Accuracy Matrix<\/h1>\n<p>A confusion matrix, also known as error or accuracy matrix, is a specific type of table showing the performance of an algorithm. The name <em>confusion matrix<\/em> comes from the fact that you can quickly see where the algorithm confuses two classes, which would indicate a misclassification.<\/p>\n<p>Several metrics can be derived from the table:<\/p>\n<ul>\n<li><strong>User&#8217;s accuracies<\/strong>: Depict how often the class in the classification map will actually be present on the ground. The User&#8217;s Accuracy is complement of the Commission Error (User&#8217;s Accuracy = 100% &#8211; Commission Error)<\/li>\n<li><strong>Producer&#8217;s accuracies<\/strong>: Depict how often real features in the study area are correctly shown on the classication map. The Producer&#8217;s Accuracy is complement of the <em>Omission Error<\/em> (Producer&#8217;s Accuracy = 100% &#8211; Omission Error)<\/li>\n<li><strong>Overall accuracy<\/strong>: Characterize the proportion of all correctly classified validation points in percent.<\/li>\n<\/ul>\n<p><strong>Implementation in R:<\/strong>:<br \/>\nYou need your final classification map, as well as both the training and validation shapefiles created in the course of the <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/classification-in-r\/\" rel=\"noopener\" target=\"_blank\">Classification in R section<\/a>.<br \/>\nGood to go?<\/p>\n<p>Fine, then import your <span class=\"crayon-inline theme:amityreseda\">raster<\/span> library in R as usual:<\/p>\n<pre class=\"theme:amityreseda\">\r\n#install.packages(\"raster\")\r\nlibrary(raster)\r\n<\/pre>\n<p>Then import your classification image <span class=\"crayon-inline theme:amityreseda\">classification_RF.tif<\/span>, your training shapefile <span class=\"crayon-inline theme:amityreseda\">training_data.shp<\/span> and your validation shapefile <span class=\"crayon-inline theme:amityreseda\">validation_RF.shp<\/span>. In this example we use the output of the RF classification. However, the workflow is applicable to every classifier!<\/p>\n<pre class=\"theme:amityreseda\">\r\nsetwd(\"\/media\/sf_exchange\/landsatdata\/\")\r\nimg.classified &lt;- raster(&quot;classification_RF.tif&quot;)\r\nshp.train &lt;- shapefile(&quot;training_data.shp&quot;)\r\nshp.valid &lt;- shapefile(&quot;validation_RF.shp&quot;)\r\n<\/pre>\n<p>Our goal is first to generate two factor vectors, which we then compare in our confusion matrix:<\/p>\n<ol>\n<li><strong>reference <\/strong>: class labels you assigned manually in the previous section<\/li>\n<li><strong>predicted <\/strong>: class labels that resulted in the automatic RF (or SVM) classification<\/li>\n<\/ol>\n<p>For the reference vector, we need to address the <em>validclass <\/em> column of our shapefile (we created this column in <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/label-samples-in-qgis\/\" rel=\"noopener\" target=\"_blank\">this section in QGIS<\/a>). As already mentioned, we need to transform the vectors into factors using <span class=\"crayon-inline theme:amityreseda\">as.factor()<\/span> (it becomes clear why in a second):<\/p>\n<pre class=\"theme:amityreseda\">\r\nreference &lt;- as.factor(shp.valid$validclass)\r\nreference\r\n##   [1] 3    1    4    4    6    4    5    3    6    2    1    5    4    2   \r\n##  [15] 2    4    4    5    4    5    1    3    &lt;NA&gt; 2    6    3    3    6   \r\n##  [29] 1    4    6    6    5    6    2    3    1    1    3    2    4    2   \r\n##  [43] 5    4    2    5    2    4    5    5    3    1    4    1    3    5   \r\n##  [57] 2    3    5    4    5    2    5    6    2    1    5    2    5    2   \r\n##  [71] 2    6    4    1    5    3    6    4    6    6    6    6    1    5   \r\n##  [85] 1    2    5    5    6    4    5    3    5    5    2    2    5    6   \r\n##  [99] 2    3    5    6    6    1    1    2    1    2    1    &lt;NA&gt; 3    4   \r\n## [113] 6    1    6    6    5    5    6    2    3    6    6    5    3    5   \r\n## [127] 2    3    6    4    3    6    6    5    4    6    4    2    6    3   \r\n## [141] 2    2    2    3    2    6    6    5    3    3    4    2    6    &lt;NA&gt;\r\n## [155] 2    5    &lt;NA&gt; 3    6    1    3    6    1    2    5    2    2    5   \r\n## [169] 5    &lt;NA&gt; 4    6    5    5    4    4    2    5    1    6    3    5   \r\n## [183] 5    &lt;NA&gt; 5    5    2    2    2    5    3    2    6    2    5    3   \r\n## [197] 6    4    1    3    5    3    3    3    5    4    4    4    4    2   \r\n## [211] 3    5    2    4    1    1    3    2    4    2    2    3    3    5   \r\n## [225] 4    4    6    2    1    1    2    6    3    4    5    3    6    1   \r\n## [239] 6    5    2    2    6    3    6    5    5    1    4    1    2    5   \r\n## [253] 3    4    5    5    3    5    2    3    2    2    6    3    6    5   \r\n## [267] 5    5    3    4    3    &lt;NA&gt; 3    6    5    6    5    5    5    6   \r\n## [281] 2    3    4    5    1    1    3    4    4    1    4    5    6    4   \r\n## [295] 2    3    4    3    5    4   \r\n## Levels: 1 2 3 4 5 6\r\n<\/pre>\n<p>You may notice some <span class=\"crayon-inline theme:amityreseda\">NA<\/span> entries in your reference values. These arise, for example, if you have skipped some validation points during the <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/label-samples-in-qgis\/\" rel=\"noopener\" target=\"_blank\">labeling in QGIS<\/a> because you could not make a clear classification based on the available image data. We simply ignore them later on in the statistics &#8211; no problem!<\/p>\n<p>For the predicted vector, we need to extract the classification values at the validation coordinates. Here again, we want a factor object via <span class=\"crayon-inline theme:amityreseda\">as.factor()<\/span>:<\/p>\n<pre class=\"theme:amityreseda\">\r\npredicted &lt;- as.factor(extract(img.classified, shp.valid))\r\npredicted\r\n##   [1] 3 1 4 4 6 4 5 3 6 2 1 5 4 2 2 4 4 3 4 5 1 3 4 2 6 3 3 6 1 4 6 6 5 6 2\r\n##  [36] 3 1 1 3 3 1 2 5 4 3 4 2 4 1 5 1 1 4 1 3 5 2 1 5 4 5 2 3 6 2 1 5 3 5 2\r\n##  [71] 2 6 4 1 5 1 6 4 6 6 6 6 1 1 1 2 4 5 6 4 5 3 5 4 2 2 1 2 2 3 5 6 6 1 1\r\n## [106] 2 1 2 1 1 3 4 6 1 6 6 5 4 6 2 1 6 6 5 1 5 2 3 6 4 2 6 6 3 4 6 4 2 6 3\r\n## [141] 2 2 2 3 2 6 6 5 3 3 4 2 6 1 2 5 1 3 6 1 3 6 1 3 4 2 2 5 4 1 1 6 5 4 4\r\n## [176] 4 2 5 1 6 1 5 5 6 4 5 2 2 2 5 3 2 6 2 5 3 6 4 1 5 5 3 1 3 5 4 4 4 4 2\r\n## [211] 3 5 3 4 1 1 3 3 4 2 2 3 3 1 4 4 6 2 1 1 2 6 3 4 5 3 6 1 6 5 2 3 6 3 6\r\n## [246] 5 1 1 4 1 2 4 3 4 5 5 3 5 2 3 2 2 6 3 6 5 5 3 2 4 3 1 3 6 5 6 5 5 5 6\r\n## [281] 2 3 5 5 1 1 3 4 4 1 4 5 6 4 2 3 5 3 5 4\r\n## Levels: 1 2 3 4 5 6\r\n<\/pre>\n<p>Once we prepared both factor vectors, we can utilize the <span class=\"crayon-inline theme:amityreseda\">table<\/span> function in an elegant way to build a contingency table of the counts at each combination of factor levels. We can additionally name the vectors so that they are also displayed in the table accordingly:<\/p>\n<pre class=\"theme:amityreseda\">\r\naccmat &lt;- table(&quot;pred&quot; = predicted, &quot;ref&quot; = reference)\r\naccmat\r\n##     ref\r\n## pred  1  2  3  4  5  6\r\n##    1 31  0  7  2  5  0\r\n##    2  0 47  2  0  0  1\r\n##    3  0  7 39  0  4  0\r\n##    4  0  0  0 40  9  0\r\n##    5  0  0  1  2 47  0\r\n##    6  0  0  0  0  0 49\r\n<\/pre>\n<p>The numbers thus reflect the number of validation pixels. All pixels that have a <span class=\"crayon-inline theme:amityreseda\">NA<\/span> value in either <span class=\"crayon-inline theme:amityreseda\">reference<\/span> or <span class=\"crayon-inline theme:amityreseda\">predicted<\/span> were ignored here.<br \/>\nExcellent! This output already visualize if and where there are misclassifications in our map: all pixels located on the diagonale are correctly classified, all pixels off the diagonal are not.<\/p>\n<p>We can now calculate the user&#8217;s accuracies <span class=\"crayon-inline theme:amityreseda\">UA<\/span>, producer&#8217;s accuracies <span class=\"crayon-inline theme:amityreseda\">PA<\/span>, and the overall accuracy <span class=\"crayon-inline theme:amityreseda\">OA<\/span>:<\/p>\n<pre class=\"theme:amityreseda\">\r\nUA &lt;- diag(accmat) \/ rowSums(accmat) * 100\r\nUA\r\n##         1         2         3         4         5         6 \r\n##  68.88889  94.00000  78.00000  81.63265  94.00000 100.00000\r\n\r\nPA &lt;- diag(accmat) \/ colSums(accmat) * 100\r\nPA\r\n##         1         2         3         4         5         6 \r\n## 100.00000  87.03704  79.59184  90.90909  72.30769  98.00000\r\n\r\nOA &lt;- sum(diag(accmat)) \/ sum(accmat) * 100\r\nOA\r\n## [1] 86.34812\r\n<\/pre>\n<p>Actually we already extracted all information needed for a confusion matrix, so let us form a nicely formatted matrix in R:<\/p>\n<pre class=\"theme:amityreseda\">\r\naccmat.ext &lt;- addmargins(accmat)\r\naccmat.ext &lt;- rbind(accmat.ext, &quot;Users&quot; = c(PA, NA))\r\naccmat.ext &lt;- cbind(accmat.ext, &quot;Producers&quot; = c(UA, NA, OA))\r\ncolnames(accmat.ext) &lt;- c(levels(as.factor(shp.train$classes)), &quot;Sum&quot;, &quot;PA&quot;)\r\nrownames(accmat.ext) &lt;- c(levels(as.factor(shp.train$classes)), &quot;Sum&quot;, &quot;UA&quot;)\r\naccmat.ext &lt;- round(accmat.ext, digits = 1)\r\ndimnames(accmat.ext) &lt;- list(&quot;Prediction&quot; = colnames(accmat.ext),\r\n                             &quot;Reference&quot; = rownames(accmat.ext))\r\nclass(accmat.ext) &lt;- &quot;table&quot;\r\naccmat.ext\r\n##            Reference\r\n## Prediction  baresoil forest grassland urban_hd urban_ld water   Sum    UA\r\n##   baresoil      31.0    0.0       7.0      2.0      5.0   0.0  45.0  68.9\r\n##   forest         0.0   47.0       2.0      0.0      0.0   1.0  50.0  94.0\r\n##   grassland      0.0    7.0      39.0      0.0      4.0   0.0  50.0  78.0\r\n##   urban_hd       0.0    0.0       0.0     40.0      9.0   0.0  49.0  81.6\r\n##   urban_ld       0.0    0.0       1.0      2.0     47.0   0.0  50.0  94.0\r\n##   water          0.0    0.0       0.0      0.0      0.0  49.0  49.0 100.0\r\n##   Sum           31.0   54.0      49.0     44.0     65.0  50.0 293.0      \r\n##   PA           100.0   87.0      79.6     90.9     72.3  98.0        86.3\r\n<\/pre>\n<p><\/br><a name=\"2\"><\/a><\/p>\n<h1>Significance Test<\/h1>\n<p>Furthermore, we can check if the result is purely coincidental, i.e., whether a random classification of the classes could have led to an identical result. We can use a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Binomial_test\" rel=\"noopener\" target=\"_blank\">binomial test<\/a> for this. We only need two values for this test:<br \/>\n<span class=\"crayon-inline theme:amityreseda\">x = <\/span>  total number of correctly classified validation points, and<br \/>\n<span class=\"crayon-inline theme:amityreseda\">n = <\/span> the total number of validation points in our confusion matrix:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsign &lt;- binom.test(x = sum(diag(accmat)),\r\n                   n = sum(accmat),\r\n                   alternative = c(&quot;two.sided&quot;),\r\n                   conf.level = 0.95\r\n                   )\r\n\r\npvalue &lt;- sign$p.value\r\npvalue\r\n## [1] 5.30128e-39\r\n\r\nCI95 &lt;- sign$conf.int[1:2]\r\nCI95\r\n## [1] 0.8187710 0.9006459\r\n<\/pre>\n<p>The p-value is much lower than 0.05, so the classification result is highly significant. If the classification were repeated under the same conditions, it can be assumed that the OA is 95% in the range of 81.2% to 90.0%.<\/p>\n<p><\/br><a name=\"3\"><\/a><\/p>\n<h1>Kappa<\/h1>\n<p>The Kappa Coefficient can be used to evaluate the accuracy of a classification. It evaluates how well the classification performs compared to map, in which all values are just randomly assigned.<br \/>\nThe Kappa coefficient can range from -1 to 1.<br \/>\nA value of 0 indicates that the classification is as good as random values.<br \/>\nA value below 0 indicates the classification is significantly worse than random.<br \/>\nA value greater than 0 indicates that the classification is significantly better than random.<\/p>\n<p>When you have the accuracy matrix as a table \\(m_{i, j}\\) with \\(c\\) different classes, then Kappa is<\/p>\n<p>\\begin{equation} \\label{1}\\tag{1}<br \/>\n   \\kappa = \\frac{N_{o} &#8211; N_{e}}{N &#8211; N_{e}} , \\text{with} \\\\<br \/>\n   N = \\sum_{i,j = 1}^{c} m_{i, j}\\\\<br \/>\n   N_{o} = \\sum_{i=1}^c{m_{i, i}}\\\\<br \/>\n   N_{e} = \\frac{1}{N}\\cdot\\sum_{l=1}^c\\left(\\sum_{j=1}^c{m_{l, j}} \\cdot \\sum_{i=1}^c{m_{i, l}}\\right)<br \/>\n\\end{equation}<\/p>\n<p>That looks pretty complicated. Using R, we can write our own function, which calculates \\(\\kappa\\) for us! The calculation also looks much friendlier:<\/p>\n<pre class=\"theme:amityreseda\">\r\nkappa &lt;- function(m) {\r\n  N &lt;- sum(m)\r\n  No &lt;- sum(diag(m))\r\n  Ne &lt;- 1 \/ N * sum(colSums(m) * rowSums(m))\r\n  return( (No - Ne) \/ (N - Ne) )\r\n}\r\n<\/pre>\n<p>Use the accuracy matrix <span class=\"crayon-inline theme:amityreseda\">accmat<\/span> as argument for our new function to calculate the Kappa coefficient:<\/p>\n<pre class=\"theme:amityreseda\">\r\nkappacoefficient &lt;- kappa(accmat)\r\nkappacoefficient\r\n## [1] 0.8359646\r\n<\/pre>\n<p>However, Kappas use has been questioned by many articles and is therefore not recommended (see <a href=\"https:\/\/sensoremoto2016.files.wordpress.com\/2016\/09\/pontius_death_to_kappa_2011.pdf\" rel=\"noopener\" target=\"_blank\">Pontius Jr and Millones 2011<\/a>).<\/p>\n<p><\/br><\/br><\/p>\n<hr style=\"height:4px;background-color:#6b9e1f\">\n<a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/area-adjusted-accuracies\/\"><br \/>\n<button style=\"width:100%;text-align:right;padding: 10 0;background-color:white;margin:-55px 0 0 0\"><\/p>\n<div style=\"font-family: 'Noto Sans',sans-serif;line-height: 1.2\">\n<span style=\"font-size: 12px;color:#bfbfbf\"><strong><em>NEXT<\/em><\/strong><\/span><br \/>\n<span style=\"font-size: 30px;color:#6b9e1f\"><strong><em>Area Adjusted Accuracies<\/em><\/strong><\/span>\n<\/div>\n<p><\/button><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this section we will focus on creating an confusion matrix in R. Additionally we will perform a significance test, and calculate confidence intervals as well as the kappa coefficient. Accuracy Matrix A confusion matrix, also known as error or accuracy matrix, is a specific type of table showing the performance of an algorithm. The &hellip; <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/accuracy-statistics-in-r\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Accuracy Statistics in R&#8221;<\/span><\/a><\/p>\n","protected":false},"author":3237,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2192","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/2192","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/users\/3237"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/comments?post=2192"}],"version-history":[{"count":34,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/2192\/revisions"}],"predecessor-version":[{"id":2534,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/2192\/revisions\/2534"}],"wp:attachment":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/media?parent=2192"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}