<p><div><div><div><div data-width="100" data-show-faces="false" data-send="true" data-layout="button_count" data-height="21" data-href="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/"></div></div><div><a data-url="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/" data-counturl="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/" data-count="horizontal" data-text="Make Easy Heatmaps to Visualize your Turnaround Times" class="twitter-share-button" data-via="rbloggers" href="https://twitter.com/share"></a></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://labrtorian.com/2016/09/08/make-easy-heatmaps-to-visualize-your-turnaround-times/"> The Lab-R-torian</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<h2>The Problem</h2>
<p>In two previous posts, I discussed visualizing your turnaround times (TATs). These posts are <a target="_blank" href="http://labrtorian.com/2015/08/11/generating-meaningful-turaround-time-plots-for-clinical-laboratory-medicine/"> here</a> and <a target="_blank" href="http://labrtorian.com/2015/08/28/a-closer-look-at-tat-time-dependence-2/"> here</a>. One other nice way to visualize your TAT is by means of a heatmap. In particular, we would like to look at the TAT for every hour of the week in a single figure. This manner of dataviz bling seems to be particularly attractive to managers because it costs you $0 to do this with R, but with commercial tools like Tableau, you'd have to pay a fortune and, as with Excel, your report would not be readily reproducible. Further, to make it autogenerate a PDF would mean you had to fork out more money for a report-generation module. Pffft.</p>
<h2>The Data</h2>
<p>We're going to read in a year's worth of order times and result times for a stat immunoassay test offered to a particular ward. The data, as I've formatted it, has two columns, ord and res. </p>

<pre>test.data &lt;- read.csv(&quot;test_data.csv&quot;)
head(test.data)</pre>

<pre>##                   ord                 res
## 1 2015-01-01 13:24:00 2015-01-01 14:29:00
## 2 2015-01-01 06:16:00 2015-01-01 07:43:00
## 3 2015-01-01 06:32:00 2015-01-01 07:43:00
## 4 2015-01-01 06:32:00 2015-01-01 07:43:00
## 5 2015-01-01 12:12:00 2015-01-01 13:13:00
## 6 2015-01-01 12:12:00 2015-01-01 13:13:00</pre>

<p>Now, of course, we want to look at data collected from a long period of time so that we can be sure that the observations we are not simply an artifact of recent instrument downtime, maintenance, or whoever happened to be running the instrument. This is why I chose a year's worth of data. We are going to visualize the median order-to-file TAT for this test.</p>
<h2>Formatting and Calculations</h2>
<p>To calculate the hourly medians, we'll need to be able to label every TAT with the day it was run and the hour in the day it was run. This is pretty easy with the lubridate package. We'll do three things:</p>
<ul>
<li>We'll convert the dates to POSIXct objects</li>
<li>We'll use the <code>difftime()</code> function to calculate the TATs</li>
<li>We'll use the <code>wday()</code> function to determine which day of the week the specimen was run on</li>
<li>We'll pull out the hour of the day on which it was run with the <code>format()</code> function.</li>
</ul>
<pre>library(&quot;dplyr&quot;)
library(&quot;lubridate&quot;)
library(&quot;fields&quot;)
library(&quot;magrittr&quot;)

test.data$ord &lt;- ymd_hms(test.data$ord)
test.data$res &lt;- ymd_hms(test.data$res)
test.data &lt;- mutate(test.data,otf = difftime(res,ord,units=&quot;min&quot;))
test.data &lt;- mutate(test.data,dow = wday(ord))
test.data &lt;- mutate(test.data,hod = as.numeric(format(test.data$ord, &quot;%H&quot;)))</pre>

<p>And now the data will look like this:</p>

<pre>head(test.data)</pre>

<pre>##                   ord                 res     otf dow hod
## 1 2015-01-01 13:24:00 2015-01-01 14:29:00 65 mins   5  13
## 2 2015-01-01 06:16:00 2015-01-01 07:43:00 87 mins   5   6
## 3 2015-01-01 06:32:00 2015-01-01 07:43:00 71 mins   5   6
## 4 2015-01-01 06:32:00 2015-01-01 07:43:00 71 mins   5   6
## 5 2015-01-01 12:12:00 2015-01-01 13:13:00 61 mins   5  12
## 6 2015-01-01 12:12:00 2015-01-01 13:13:00 61 mins   5  12</pre>

<p>where the order-to-file TAT is in the <strong>otf</strong> column, the day-of-week is in the <strong>dow</strong> column and the hour-of-day is in the <strong>hod</strong> column. Now we can cycle though the days of the week and the hours of the day and calculate the year's median TAT for each hour, storing it in a matrix:</p>

<pre>#prepare an empty matrix
heat.data &lt;- matrix(rep(NA,7*24),nrow = 7, ncol = 24)
#loop over the days and hours and calculate the median TAT
for(i in 1:7){
  for(j in 0:23){
    heat.data[i,j+1] &lt;- subset(test.data, test.data$dow==i &amp; test.data$hod==j)$otf %>% median
  }
}</pre>

<h2>Making the Heatmap</h2>
<p>There are many ways to make the heatmap but I am particularly fond of the appearance of surface plots made with the <a target="_blank" href="https://cran.r-project.org/web/packages/fields/index.html"> fields</a> package.</p>

<pre>image.plot(1:7,seq(from=0.5, to=23.5, by = 1),heat.data,axes=FALSE, 
           xlab = &quot;Day of Week&quot;, ylab = &quot;Hour of Day&quot;, ylim=c(0,24))
# the following pointless command is necessary to make the custom axis labels non-transparent
# google revealed this among a number of other workarounds.
points(0,0)
# now these will display properly
axis(side=1, at=1:7, labels=as.character(wday(1:7, label=TRUE)), las=2, cex.axis = 0.8)
axis(side=2, at= 0:24, labels=0:24, las=1, cex.axis=0.8)</pre>

<p><img alt="plot of chunk unnamed-chunk-5" src="http://i1.wp.com/labrtorian.com/wp-content/uploads/2016/09/unnamed-chunk-5-1.png?w=604" data-recalc-dims="1"> </p>
<h2>Overlay Printed Times</h2>
<p>We can see that there is a morning slowdown that is particularly bad on Saturday. But what if we wanted to know the exact value for these eye-catching problem times? We'd have trouble, unless we overlaid some text.</p>
<p>It turns out that if you use white printing, you can't read the numbers when the background colour is yellow and green. There is a 64 colour gradient used in the <code>image.plot()</code> function, so I calculated which integers in 0–64 were the problem and found the TATs that would correspond. It turned out that colours 20–45 out of the 64 colours in the gradient are the problem. By this means, I can make the printing black over the yellows and greens but white everywhere else:</p>

<pre>image.plot(1:7,seq(from=0.5, to=23.5, by = 1),heat.data,axes=FALSE, 
           xlab = &quot;Day of Week&quot;, ylab = &quot;Hour of Day&quot;, ylim=c(0,24))
points(0,0) #random command that resets par
axis(side=1, at=1:7, labels=as.character(wday(1:7, label=TRUE)), las=2, cex.axis = 0.8)
axis(side=2, at= 0:24, labels=0:24, las=1, cex.axis=0.8)

# calculate the lowest and highest TAT
min.z &lt;- min(heat.data)
max.z &lt;- max(heat.data)
# determine which TAT's will have yellow to green shading
z.yellows &lt;- min.z + (max.z - min.z)/64*c(20,45) 
# print the labels
for(i in 1:7){
  for(j in 1:24){
    if((heat.data[i,j] > z.yellows[1])&amp;(heat.data[i,j] &lt; z.yellows[2])){
      text(i,j-0.5,heat.data[i,j], col=&quot;black&quot;, cex = 0.8)
    }else{
      text(i,j-0.5,heat.data[i,j], col=&quot;white&quot;, cex = 0.8)     
    }
  }
}</pre>

<p><img alt="plot of chunk unnamed-chunk-6" src="http://i0.wp.com/labrtorian.com/wp-content/uploads/2016/09/unnamed-chunk-6-1.png?w=604" data-recalc-dims="1"> </p>
<p>So, that is not too bad, and if you wanted to look at the 75th percentile instead you would only have to adjust the heat.data calculation as follows:</p>

<pre>#prepare an empty matrix
heat.data &lt;- matrix(rep(NA,7*24),nrow = 7, ncol = 24)
#loop over the days and hours and calculate the median TAT
for(i in 1:7){
  for(j in 0:23){
    heat.data[i,j+1] &lt;- subset(test.data, test.data$dow==i &amp; test.data$hod==j)$otf %>% quantile(.,probs=0.75)
  }
}</pre>

<p>And this is what you will get.</p>
<p><img alt="plot of chunk unnamed-chunk-8" src="http://i0.wp.com/labrtorian.com/wp-content/uploads/2016/09/unnamed-chunk-8-1.png?w=604" data-recalc-dims="1"> </p>
<p>Hmmm…we'd better look at Saturday morning, 6 am. I hope you have found this helpful.</p>
<p><br>
<br>
<strong>And as for heat</strong></p>
<p>“He will sit as a refiner and purifier of silver” </p>
<p>Malachi 3:3</p>
<p><div><div><div><div data-width="55" data-show-faces="false" data-send="true" data-layout="box_count" data-height="62" data-href="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/"></div></div><div><a data-url="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/" data-counturl="https://www.r-bloggers.com/make-easy-heatmaps-to-visualize-your-turnaround-times/" data-count="vertical" data-text="Make Easy Heatmaps to Visualize your Turnaround Times" class="twitter-share-button" data-via="rbloggers" href="https://twitter.com/share"></a></div><div></div></div><div></div></div>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://labrtorian.com/2016/09/08/make-easy-heatmaps-to-visualize-your-turnaround-times/"> The Lab-R-torian</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" rel="nofollow" href="https://www.r-project.org/">R</a> news and <a title="R tutorials" rel="nofollow" href="https://www.r-bloggers.com/?s=tutorial">tutorials</a> on topics such as: <a title="Data science" rel="nofollow" href="https://www.r-bloggers.com/?s=data%20science">Data science</a>, <a title="Big Data" rel="nofollow" href="https://www.r-bloggers.com/?s=Big%20Data">Big Data, <a title="R jobs" rel="nofollow" href="https://www.r-users.com/">R jobs</a>, visualization (<a title="ggplot and ggplot2 tutorials" rel="nofollow" href="https://www.r-bloggers.com/?s=ggplot2">ggplot2</a>, <a title="Boxplots using lattice and ggplot2 tutorials" rel="nofollow" href="https://www.r-bloggers.com/?s=boxplot">Boxplots</a>, <a title="Maps and gis" rel="nofollow" href="https://www.r-bloggers.com/?s=map">maps</a>, <a title="Animation in R" rel="nofollow" href="https://www.r-bloggers.com/?s=animation">animation</a>), programming (<a title="RStudio IDE for R" rel="nofollow" href="https://www.r-bloggers.com/?s=RStudio">RStudio</a>, <a title="Sweave and literate programming" rel="nofollow" href="https://www.r-bloggers.com/?s=sweave">Sweave</a>, <a title="LaTeX in R" rel="nofollow" href="https://www.r-bloggers.com/?s=LaTeX">LaTeX</a>, <a title="SQL and databases" rel="nofollow" href="https://www.r-bloggers.com/?s=SQL">SQL</a>, <a title="Eclipse IDE for R" rel="nofollow" href="https://www.r-bloggers.com/?s=eclipse">Eclipse</a>, <a title="git and github, Version Control System" rel="nofollow" href="https://www.r-bloggers.com/?s=git">git</a>, <a title="Large data in R using Hadoop" rel="nofollow" href="https://www.r-bloggers.com/?s=hadoop">hadoop</a>, <a title="Web Scraping of google, facebook, yahoo, twitter and more using R" rel="nofollow" href="https://www.r-bloggers.com/?s=Web+Scraping">Web Scraping</a>) statistics (<a title="Regressions and ANOVA analysis tutorials" rel="nofollow" href="https://www.r-bloggers.com/?s=regression">regression</a>, <a title="principal component analysis tutorial" rel="nofollow" href="https://www.r-bloggers.com/?s=PCA">PCA</a>, <a title="Time series" rel="nofollow" href="https://www.r-bloggers.com/?s=time+series">time series</a>, <a title="finance trading" rel="nofollow" href="https://www.r-bloggers.com/?s=trading">trading</a>) and more...
<img height="1" alt="" width="1" src="http://feeds.feedburner.com/~r/RBloggers/~4/W5pSHiRy2VA"></a></div>