

{"id":3940,"date":"2016-02-14T08:17:25","date_gmt":"2016-02-14T13:17:25","guid":{"rendered":"http:\/\/rud.is\/b\/?p=3940"},"modified":"2018-03-13T07:35:14","modified_gmt":"2018-03-13T12:35:14","slug":"making-faceted-heatmaps-with-ggplot2","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/","title":{"rendered":"Making Faceted Heatmaps with ggplot2"},"content":{"rendered":"<p>We were doing some exploratory data analysis on some attacker data at work and one of the things I was interested is what were &#8220;working hours&#8221; by country. Now, I don&#8217;t put a great deal of faith in the precision of geolocated IP addresses since every geolocation database that exists thinks I live in Vermont (I don&#8217;t) and I know that these databases rely on a pretty &#8220;meh&#8221; distributed process for getting this local data. However, at a country level, the errors are tolerable provided you use a decent geolocation provider. Since a rant about the precision of IP address geolocation was <em>not<\/em> the point of this post, let&#8217;s move on.<\/p>\n<p>One of the best ways to visualize these &#8220;working hours&#8221; is a temporal heatmap. Jay &amp; I <a href=\"http:\/\/datadrivensecurity.info\/blog\/posts\/2014\/Jan\/dds-github\/\">made a couple<\/a> as part of our inaugural Data-Driven Security Book blog post to show how much of our collected lives were lost during the creation of our tome.<\/p>\n<p>I have some paired-down, simulated data based on the attacker data we were looking at. Rather than the complete data set, I&#8217;m providing 200,000 &#8220;events&#8221; (RDP login attempts, to be precise) in the <code>eventlog.csv<\/code> file in the <code>data\/<\/code> directory that have the <code>timestamp<\/code>, and the <code>source_country<\/code> ISO 3166-1 alpha-2 country code (which is the source of the attack) plus the <code>tz<\/code> time zone of the source IP address. Let&#8217;s have a look:<\/p>\n<pre id=\"fhmp01\"><code class=\"language-r\">library(data.table)  # faster fread() and better weekdays()\r\nlibrary(dplyr)       # consistent data.frame operations\r\nlibrary(purrr)       # consistent &amp; safe list\/vector munging\r\nlibrary(tidyr)       # consistent data.frame cleaning\r\nlibrary(lubridate)   # date manipulation\r\nlibrary(countrycode) # turn country codes into pretty names\r\nlibrary(ggplot2)     # base plots are for Coursera professors\r\nlibrary(scales)      # pairs nicely with ggplot2 for plot label formatting\r\nlibrary(gridExtra)   # a helper for arranging individual ggplot objects\r\nlibrary(ggthemes)    # has a clean theme for ggplot2\r\nlibrary(viridis)     # best. color. palette. evar.\r\nlibrary(knitr)       # kable : prettier data.frame output\r\n\r\nattacks &lt;- tbl_df(fread(&quot;data\/eventlog.csv&quot;))\r\n\r\nkable(head(attacks))<\/code><\/pre>\n<table>\n<thead>\n<tr class=\"header\">\n<th align=\"left\">timestamp<\/th>\n<th align=\"left\">source_country<\/th>\n<th align=\"left\">tz<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"odd\">\n<td align=\"left\">2015-03-12T15:59:16.718901Z<\/td>\n<td align=\"left\">CN<\/td>\n<td align=\"left\">Asia\/Shanghai<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">2015-03-12T16:00:48.841746Z<\/td>\n<td align=\"left\">FR<\/td>\n<td align=\"left\">Europe\/Paris<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">2015-03-12T16:02:26.731256Z<\/td>\n<td align=\"left\">CN<\/td>\n<td align=\"left\">Asia\/Shanghai<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">2015-03-12T16:02:38.469907Z<\/td>\n<td align=\"left\">US<\/td>\n<td align=\"left\">America\/Chicago<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">2015-03-12T16:03:22.201903Z<\/td>\n<td align=\"left\">CN<\/td>\n<td align=\"left\">Asia\/Shanghai<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">2015-03-12T16:03:45.984616Z<\/td>\n<td align=\"left\">CN<\/td>\n<td align=\"left\">Asia\/Shanghai<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For a temporal heatmap, we&#8217;re going to need the weekday and hour (or as granular as you want to get). I use a <code>factor<\/code> here so I can have ordered weekdays. I need the source timezone weekday\/hour so we have to get a bit creative since the time zone parameter to virtually every date\/time operation in R only handles a single element vector.<\/p>\n<pre id=\"fhmp02\"><code class=\"language-r\">make_hr_wkday &lt;- function(cc, ts, tz) {\r\n\r\n  real_times &lt;- ymd_hms(ts, tz=tz[1], quiet=TRUE)\r\n\r\n  data_frame(source_country=cc,\r\n             wkday=weekdays(as.Date(real_times, tz=tz[1])),\r\n             hour=format(real_times, &quot;%H&quot;, tz=tz[1]))\r\n\r\n}\r\n\r\ngroup_by(attacks, tz) %&gt;%\r\n  do(make_hr_wkday(.$source_country, .$timestamp, .$tz)) %&gt;%\r\n  ungroup() %&gt;%\r\n  mutate(wkday=factor(wkday,\r\n                      levels=levels(weekdays(0, FALSE)))) -&gt; attacks\r\nkable(head(attacks))<\/code><\/pre>\n<table>\n<thead>\n<tr class=\"header\">\n<th align=\"left\">tz<\/th>\n<th align=\"left\">source_country<\/th>\n<th align=\"left\">wkday<\/th>\n<th align=\"left\">hour<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"odd\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">BG<\/td>\n<td align=\"left\">Saturday<\/td>\n<td align=\"left\">22<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">TW<\/td>\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">08<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">TW<\/td>\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">10<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">CN<\/td>\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">13<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">US<\/td>\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">17<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Africa\/Cairo<\/td>\n<td align=\"left\">CA<\/td>\n<td align=\"left\">Monday<\/td>\n<td align=\"left\">13<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>It&#8217;s pretty straightforward to make an overall heatmap of activity. Group &amp; count the number of &#8220;attacks&#8221; by weekday and hour then use <code>geom_tile()<\/code>. I&#8217;m going to clutter up the pristine ggplot2 commands with some explanation for those still learning ggplot2:<\/p>\n<pre id=\"fhmp03\"><code class=\"language-r\">wkdays &lt;- count(attacks, wkday, hour)\r\n\r\nkable(head(wkdays))<\/code><\/pre>\n<table>\n<thead>\n<tr class=\"header\">\n<th align=\"left\">wkday<\/th>\n<th align=\"left\">hour<\/th>\n<th align=\"right\">n<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"odd\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">00<\/td>\n<td align=\"right\">1076<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">01<\/td>\n<td align=\"right\">1307<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">02<\/td>\n<td align=\"right\">1189<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">03<\/td>\n<td align=\"right\">1301<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">04<\/td>\n<td align=\"right\">1145<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Sunday<\/td>\n<td align=\"left\">05<\/td>\n<td align=\"right\">1313<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Here, we&#8217;re just feeding in the new <code>data.frame<\/code> we just created to ggplot and telling it we want to use the <code>hour<\/code> column for the x-axis, the <code>wkday<\/code> column for the y-axis and that we are doing a continuous scale fill by the <code>n<\/code> aggregated count:<\/p>\n<pre id=\"fhmp04\"><code class=\"language-r\">gg &lt;- ggplot(wkdays, aes(x=hour, y=wkday, fill=n))<\/code><\/pre>\n<p>This does all the hard work. <code>geom_tile()<\/code> will make tiles at each x&amp;y location we&#8217;ve already specified. I knew we had events for every hour, but if you had missing days or hours, you could use <code>tidyr::complete()<\/code> to fill those in. We&#8217;re also telling it to use a thin (0.1 units) white border to separate the tiles.<\/p>\n<pre id=\"fhmp06\"><code class=\"language-r\">gg &lt;- gg + geom_tile(color=&quot;white&quot;, size=0.1)<\/code><\/pre>\n<p>this has some additional magic in that it&#8217;s an awesome color scale. Read the <code>viridis<\/code> package vignette for more info. By specifying a <code>name<\/code> here, we get a nice label on the legend.<\/p>\n<pre id=\"fhmp05\"><code class=\"language-r\">gg &lt;- gg + scale_fill_viridis(name=&quot;# Events&quot;, label=comma)<\/code><\/pre>\n<p>This ensures the plot will have a 1:1 aspect ratio (i.e. <code>geom_tile()<\/code>&#8211;which draws rectangles&#8211;will draw nice squares).<\/p>\n<pre id=\"fhmp07\"><code class=\"language-r\">gg &lt;- gg + coord_equal()<\/code><\/pre>\n<p>This tells ggplot to not use an x- or y-axis label and to also not reserve any space for them. I used a pretty bland but descriptive title. If I worked for some other security company I&#8217;d&#8217;ve added <strong>&#8220;ZOMGOSH CHINA!&#8221;<\/strong> to it.<\/p>\n<pre id=\"fhmp08\"><code class=\"language-r\">gg &lt;- gg + labs(x=NULL, y=NULL, title=&quot;Events per weekday &amp; time of day&quot;)<\/code><\/pre>\n<p>Here&#8217;s what makes the plot look <em>really<\/em> nice. I customize a number of theme elements, starting with a base theme of <code>theme_tufte()<\/code> from the ggthemes package. It removes alot of chart junk without having to do it manually.<\/p>\n<pre id=\"fhmp09\"><code class=\"language-r\">gg &lt;- gg + theme_tufte(base_family=&quot;Helvetica&quot;)<\/code><\/pre>\n<p>I like my plot titles left-aligned. For <code>hjust<\/code>:<\/p>\n<ul>\n<li><code>0<\/code> == left<\/li>\n<li><code>0.5<\/code> == centered<\/li>\n<li><code>1<\/code> == right<\/li>\n<\/ul>\n<pre id=\"fhmp10\"><code class=\"language-r\">gg &lt;- gg + theme(plot.title=element_text(hjust=0))<\/code><\/pre>\n<p>We don&#8217;t want any tick marks on the axes and I want the text to be slightly smaller than the default.<\/p>\n<pre id=\"fhmp11\"><code class=\"language-r\">gg &lt;- gg + theme(axis.ticks=element_blank())\r\ngg &lt;- gg + theme(axis.text=element_text(size=7))<\/code><\/pre>\n<p>For the legend, I just needed to tweak the title and text sizes a wee bit.<\/p>\n<pre id=\"fhmp12\"><code class=\"language-r\">gg &lt;- gg + theme(legend.title=element_text(size=8))\r\ngg &lt;- gg + theme(legend.text=element_text(size=6))\r\ngg<\/code><\/pre>\n<p><img decoding=\"async\" src=\"\/b\/rmd\/fmgp_files\/figure-html\/unnamed-chunk-16-1.png\" alt=\"\" \/><\/p>\n<p>(<strong>NOTE: there&#8217;s an <a href=\"https:\/\/rud.is\/projects\/facetedheatmaps.html\">alternate version<\/a> of this post with SVG graphics and nicer tables<\/strong>)<\/p>\n<p>That&#8217;s great, but what if we wanted the heatmap breakdown by country? We&#8217;ll do this two ways, first with each country&#8217;s heatmap using the same scale, then with each one using it&#8217;s own scale. That will let us compare at a macro and micro level.<\/p>\n<p>For either view, I want to rank-order the countries and want nice country names versus 2-letter abbreviations. We&#8217;ll do that first:<\/p>\n<pre id=\"fhmp13\"><code class=\"language-r\">count(attacks, source_country) %&gt;%\r\n  mutate(percent=percent(n\/sum(n)), count=comma(n)) %&gt;%\r\n  mutate(country=sprintf(&quot;%s (%s)&quot;,\r\n                         countrycode(source_country, &quot;iso2c&quot;, &quot;country.name&quot;),\r\n                         source_country)) %&gt;%\r\n  arrange(desc(n)) -&gt; events_by_country\r\n\r\nkable(events_by_country[,5:3])<\/code><\/pre>\n<table>\n<thead>\n<tr class=\"header\">\n<th align=\"left\">country<\/th>\n<th align=\"left\">count<\/th>\n<th align=\"left\">percent<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"odd\">\n<td align=\"left\">China (CN)<\/td>\n<td align=\"left\">85,243<\/td>\n<td align=\"left\">42.6%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">United States (US)<\/td>\n<td align=\"left\">48,684<\/td>\n<td align=\"left\">24.3%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Korea, Republic of (KR)<\/td>\n<td align=\"left\">12,648<\/td>\n<td align=\"left\">6.3%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Netherlands (NL)<\/td>\n<td align=\"left\">8,572<\/td>\n<td align=\"left\">4.3%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Viet Nam (VN)<\/td>\n<td align=\"left\">6,340<\/td>\n<td align=\"left\">3.2%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Taiwan, Province of China (TW)<\/td>\n<td align=\"left\">3,469<\/td>\n<td align=\"left\">1.7%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">United Kingdom (GB)<\/td>\n<td align=\"left\">3,266<\/td>\n<td align=\"left\">1.6%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">France (FR)<\/td>\n<td align=\"left\">3,252<\/td>\n<td align=\"left\">1.6%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Ukraine (UA)<\/td>\n<td align=\"left\">2,219<\/td>\n<td align=\"left\">1.1%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Germany (DE)<\/td>\n<td align=\"left\">2,055<\/td>\n<td align=\"left\">1.0%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Argentina (AR)<\/td>\n<td align=\"left\">1,793<\/td>\n<td align=\"left\">0.9%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Canada (CA)<\/td>\n<td align=\"left\">1,646<\/td>\n<td align=\"left\">0.8%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Russian Federation (RU)<\/td>\n<td align=\"left\">1,633<\/td>\n<td align=\"left\">0.8%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Japan (JP)<\/td>\n<td align=\"left\">1,476<\/td>\n<td align=\"left\">0.7%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">Singapore (SG)<\/td>\n<td align=\"left\">1,278<\/td>\n<td align=\"left\">0.6%<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">Hong Kong (HK)<\/td>\n<td align=\"left\">1,239<\/td>\n<td align=\"left\">0.6%<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">&hellip;<\/td>\n<td align=\"left\">&hellip;<\/td>\n<td align=\"left\">&hellip;<\/td>\n<\/tr>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Now, we&#8217;ll do a simple ggplot facet, but also exclude the top 2 attacking countries since they skew things a bit (and, we&#8217;ll see them in the last vis):<\/p>\n<pre id=\"fhmp14\"><code class=\"language-r\">filter(attacks, source_country %in% events_by_country$source_country[3:12]) %&gt;%\r\n  count(source_country, wkday, hour) %&gt;%\r\n  ungroup() %&gt;%\r\n  left_join(events_by_country[,c(1,5)]) %&gt;%\r\n  complete(country, wkday, hour, fill=list(n=0)) %&gt;%\r\n  mutate(country=factor(country,\r\n                        levels=events_by_country$country[3:12])) -&gt; cc_heat<\/code><\/pre>\n<p>Before we go all crazy and plot, let me explain ^^ a bit. I&#8217;m filtering by the top 10 (excluding the top 2) countries, then doing the group\/count. I need the pretty country info, so I&#8217;m joining that to the result. Not all countries attacked every day\/hour, so we use that <code>complete()<\/code> operation I mentioned earlier to ensure we have values for all countries for each day\/hour combination. Finally, I want to print the heatmaps in order, so I turn the country into an ordered factor.<\/p>\n<pre id=\"fhmp15\"><code class=\"language-r\">gg &lt;- ggplot(cc_heat, aes(x=hour, y=wkday, fill=n))\r\ngg &lt;- gg + geom_tile(color=&quot;white&quot;, size=0.1)\r\ngg &lt;- gg + scale_fill_viridis(name=&quot;# Events&quot;)\r\ngg &lt;- gg + coord_equal()\r\ngg &lt;- gg + facet_wrap(~country, ncol=2)\r\ngg &lt;- gg + labs(x=NULL, y=NULL, title=&quot;Events per weekday &amp; time of day by country\\n&quot;)\r\ngg &lt;- gg + theme_tufte(base_family=&quot;Helvetica&quot;)\r\ngg &lt;- gg + theme(axis.ticks=element_blank())\r\ngg &lt;- gg + theme(axis.text=element_text(size=5))\r\ngg &lt;- gg + theme(panel.border=element_blank())\r\ngg &lt;- gg + theme(plot.title=element_text(hjust=0))\r\ngg &lt;- gg + theme(strip.text=element_text(hjust=0))\r\ngg &lt;- gg + theme(panel.margin.x=unit(0.5, &quot;cm&quot;))\r\ngg &lt;- gg + theme(panel.margin.y=unit(0.5, &quot;cm&quot;))\r\ngg &lt;- gg + theme(legend.title=element_text(size=6))\r\ngg &lt;- gg + theme(legend.title.align=1)\r\ngg &lt;- gg + theme(legend.text=element_text(size=6))\r\ngg &lt;- gg + theme(legend.position=&quot;bottom&quot;)\r\ngg &lt;- gg + theme(legend.key.size=unit(0.2, &quot;cm&quot;))\r\ngg &lt;- gg + theme(legend.key.width=unit(1, &quot;cm&quot;))\r\ngg<\/code><\/pre>\n<p><img decoding=\"async\" src=\"\/b\/rmd\/fmgp_files\/figure-html\/unnamed-chunk-19-1.png\" alt=\"\" \/><\/p>\n<p>To get individual scales for each country we need to make <code>n<\/code> separate ggplot object and combine then using <code>gridExtra::grid.arrange<\/code>. It&#8217;s pretty much the same setup as before, only without the facet call. We&#8217;ll do the top 16 countries (not excluding anything) this way (pick any number you want, though provided you like scrolling). I didn&#8217;t bother with a legend title since you kinda know what you&#8217;re looking at by now :-)<\/p>\n<pre id=\"fhmp16\"><code class=\"language-r\">count(attacks, source_country, wkday, hour) %&gt;%\r\n  ungroup() %&gt;%\r\n  left_join(events_by_country[,c(1,5)]) %&gt;%\r\n  complete(country, wkday, hour, fill=list(n=0)) %&gt;%\r\n  mutate(country=factor(country,\r\n                        levels=events_by_country$country)) -&gt; cc_heat2\r\n\r\nlapply(events_by_country$country[1:16], function(cc) {\r\n  gg &lt;- ggplot(filter(cc_heat2, country==cc),\r\n               aes(x=hour, y=wkday, fill=n, frame=country))\r\n  gg &lt;- gg + geom_tile(color=&quot;white&quot;, size=0.1)\r\n  gg &lt;- gg + scale_x_discrete(expand=c(0,0))\r\n  gg &lt;- gg + scale_y_discrete(expand=c(0,0))\r\n  gg &lt;- gg + scale_fill_viridis(name=&quot;&quot;)\r\n  gg &lt;- gg + coord_equal()\r\n  gg &lt;- gg + labs(x=NULL, y=NULL,\r\n                  title=sprintf(&quot;%s&quot;, cc))\r\n  gg &lt;- gg + theme_tufte(base_family=&quot;Helvetica&quot;)\r\n  gg &lt;- gg + theme(axis.ticks=element_blank())\r\n  gg &lt;- gg + theme(axis.text=element_text(size=5))\r\n  gg &lt;- gg + theme(panel.border=element_blank())\r\n  gg &lt;- gg + theme(plot.title=element_text(hjust=0, size=6))\r\n  gg &lt;- gg + theme(panel.margin.x=unit(0.5, &quot;cm&quot;))\r\n  gg &lt;- gg + theme(panel.margin.y=unit(0.5, &quot;cm&quot;))\r\n  gg &lt;- gg + theme(legend.title=element_text(size=6))\r\n  gg &lt;- gg + theme(legend.title.align=1)\r\n  gg &lt;- gg + theme(legend.text=element_text(size=6))\r\n  gg &lt;- gg + theme(legend.position=&quot;bottom&quot;)\r\n  gg &lt;- gg + theme(legend.key.size=unit(0.2, &quot;cm&quot;))\r\n  gg &lt;- gg + theme(legend.key.width=unit(1, &quot;cm&quot;))\r\n  gg\r\n}) -&gt; cclist\r\n\r\ncclist[[&quot;ncol&quot;]] &lt;- 2\r\n\r\ndo.call(grid.arrange, cclist)<\/code><\/pre>\n<p><img decoding=\"async\" src=\"\/b\/rmd\/fmgp_files\/figure-html\/unnamed-chunk-20-1.png\" alt=\"\" \/><\/p>\n<p>You can find the data and source for this R markdown document <a href=\"https:\/\/github.com\/hrbrmstr\/facetedcountryheatmaps\">on github<\/a>. You&#8217;ll need to <code>devtools::install_github(\"hrbrmstr\/hrbrmrkdn\")<\/code> first since I&#8217;m using a custom template (or just change the <code>output:<\/code> to <code>html_document<\/code> in the YAML header).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We were doing some exploratory data analysis on some attacker data at work and one of the things I was interested is what were &#8220;working hours&#8221; by country. Now, I don&#8217;t put a great deal of faith in the precision of geolocated IP addresses since every geolocation database that exists thinks I live in Vermont [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3957,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":3,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[753,91],"tags":[810],"class_list":["post-3940","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ggplot","category-r","tag-post"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Making Faceted Heatmaps with ggplot2 - rud.is<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Making Faceted Heatmaps with ggplot2 - rud.is\" \/>\n<meta property=\"og:description\" content=\"We were doing some exploratory data analysis on some attacker data at work and one of the things I was interested is what were &#8220;working hours&#8221; by country. Now, I don&#8217;t put a great deal of faith in the precision of geolocated IP addresses since every geolocation database that exists thinks I live in Vermont [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2016-02-14T13:17:25+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-03-13T12:35:14+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1654\" \/>\n\t<meta property=\"og:image:height\" content=\"1114\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"hrbrmstr\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"hrbrmstr\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Making Faceted Heatmaps with ggplot2\",\"datePublished\":\"2016-02-14T13:17:25+00:00\",\"dateModified\":\"2018-03-13T12:35:14+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/\"},\"wordCount\":1143,\"commentCount\":8,\"publisher\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2016\\\/02\\\/heatmapsample.png?fit=1654%2C1114&ssl=1\",\"keywords\":[\"post\"],\"articleSection\":[\"ggplot\",\"R\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/\",\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/\",\"name\":\"Making Faceted Heatmaps with ggplot2 - rud.is\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2016\\\/02\\\/heatmapsample.png?fit=1654%2C1114&ssl=1\",\"datePublished\":\"2016-02-14T13:17:25+00:00\",\"dateModified\":\"2018-03-13T12:35:14+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2016\\\/02\\\/heatmapsample.png?fit=1654%2C1114&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2016\\\/02\\\/heatmapsample.png?fit=1654%2C1114&ssl=1\",\"width\":1654,\"height\":1114},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2016\\\/02\\\/14\\\/making-faceted-heatmaps-with-ggplot2\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/rud.is\\\/b\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Making Faceted Heatmaps with ggplot2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#website\",\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/\",\"name\":\"rud.is\",\"description\":\"&quot;In God we trust. All others must bring data&quot;\",\"publisher\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/rud.is\\\/b\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\",\"name\":\"hrbrmstr\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"width\":460,\"height\":460,\"caption\":\"hrbrmstr\"},\"logo\":{\"@id\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\"},\"description\":\"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7\",\"sameAs\":[\"http:\\\/\\\/rud.is\"],\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/author\\\/hrbrmstr\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Making Faceted Heatmaps with ggplot2 - rud.is","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/","og_locale":"en_US","og_type":"article","og_title":"Making Faceted Heatmaps with ggplot2 - rud.is","og_description":"We were doing some exploratory data analysis on some attacker data at work and one of the things I was interested is what were &#8220;working hours&#8221; by country. Now, I don&#8217;t put a great deal of faith in the precision of geolocated IP addresses since every geolocation database that exists thinks I live in Vermont [&hellip;]","og_url":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/","og_site_name":"rud.is","article_published_time":"2016-02-14T13:17:25+00:00","article_modified_time":"2018-03-13T12:35:14+00:00","og_image":[{"width":1654,"height":1114,"url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","type":"image\/png"}],"author":"hrbrmstr","twitter_card":"summary_large_image","twitter_misc":{"Written by":"hrbrmstr","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Making Faceted Heatmaps with ggplot2","datePublished":"2016-02-14T13:17:25+00:00","dateModified":"2018-03-13T12:35:14+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/"},"wordCount":1143,"commentCount":8,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"image":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","keywords":["post"],"articleSection":["ggplot","R"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/","url":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/","name":"Making Faceted Heatmaps with ggplot2 - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#primaryimage"},"image":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","datePublished":"2016-02-14T13:17:25+00:00","dateModified":"2018-03-13T12:35:14+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#primaryimage","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","width":1654,"height":1114},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2016\/02\/14\/making-faceted-heatmaps-with-ggplot2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Making Faceted Heatmaps with ggplot2"}]},{"@type":"WebSite","@id":"https:\/\/rud.is\/b\/#website","url":"https:\/\/rud.is\/b\/","name":"rud.is","description":"&quot;In God we trust. All others must bring data&quot;","publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/rud.is\/b\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886","name":"hrbrmstr","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","width":460,"height":460,"caption":"hrbrmstr"},"logo":{"@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1"},"description":"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7","sameAs":["http:\/\/rud.is"],"url":"https:\/\/rud.is\/b\/author\/hrbrmstr\/"}]}},"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/02\/heatmapsample.png?fit=1654%2C1114&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p23idr-11y","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":3770,"url":"https:\/\/rud.is\/b\/2015\/11\/02\/an-ephemeral-update-to-daylight\/","url_meta":{"origin":3940,"position":0},"title":"An Ephemeral Update to daylight()","author":"hrbrmstr","date":"2015-11-02","format":false,"excerpt":"This occurrence of the bi-annual corruption of the space-time continuum (i.e. changing to\/from standard\/daylight time) in the U.S. caused me to make a slight change to the code [from an older post](https:\/\/rud.is\/b\/2014\/09\/23\/seeing-the-daylight-with-r\/). The `daylight()` function now auto-discovers the date and location information (via [telize](http:\/\/www.telize.com\/)) from the caller, which means all\u2026","rel":"","context":"In &quot;Charts &amp; Graphs&quot;","block_context":{"text":"Charts &amp; Graphs","link":"https:\/\/rud.is\/b\/category\/charts-graphs\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/11\/RStudio.png?fit=1200%2C1088&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/11\/RStudio.png?fit=1200%2C1088&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/11\/RStudio.png?fit=1200%2C1088&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/11\/RStudio.png?fit=1200%2C1088&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/11\/RStudio.png?fit=1200%2C1088&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3298,"url":"https:\/\/rud.is\/b\/2015\/03\/09\/new-r-package-ipapi-ipdomain-geolocation\/","url_meta":{"origin":3940,"position":1},"title":"New R Package &#8211; ipapi (IP\/Domain Geolocation)","author":"hrbrmstr","date":"2015-03-09","format":false,"excerpt":"I noticed that the @rOpenSci folks had an interface to [ip-api.com](http:\/\/ip-api.com\/) on their [ToDo](https:\/\/github.com\/ropensci\/webservices\/wiki\/ToDo) list so I whipped up a small R package to fill said gap. Their IP Geolocation API will take an IPv4, IPv6 or FQDN and kick back a ASN, lat\/lon, address and more. The [ipapi package](https:\/\/github.com\/hrbrmstr\/ipapi)\u2026","rel":"","context":"In &quot;cartography&quot;","block_context":{"text":"cartography","link":"https:\/\/rud.is\/b\/category\/cartography\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":7924,"url":"https:\/\/rud.is\/b\/2018\/01\/18\/bitcoin-world-map-bubbles\/","url_meta":{"origin":3940,"position":2},"title":"Bitcoin (World Map) Bubbles","author":"hrbrmstr","date":"2018-01-18","format":false,"excerpt":"We're doing some interesting studies (cybersecurity-wise, not finance-wise) on digital currency networks at work-work and --- while I'm loathe to create a geo-map from IPv4 geolocation data --- we: do get (often, woefully inaccurate) latitude & longitude data from our geolocation service (I won't name-and-shame here); and, there are definite\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/01\/map-1.png?fit=1200%2C960&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/01\/map-1.png?fit=1200%2C960&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/01\/map-1.png?fit=1200%2C960&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/01\/map-1.png?fit=1200%2C960&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/01\/map-1.png?fit=1200%2C960&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":11911,"url":"https:\/\/rud.is\/b\/2019\/02\/14\/using-the-ropendata-r-package-to-access-petabytes-of-free-internet-telemetry-data-from-rapid7\/","url_meta":{"origin":3940,"position":3},"title":"Using the ropendata R Package to Access Petabytes of Free Internet Telemetry Data from Rapid7","author":"hrbrmstr","date":"2019-02-14","format":false,"excerpt":"I've got a post up over at $DAYJOB's blog on using the ropendata? package to access the ginormous and ever-increasing amount of internet telemetry (scan) data via the Rapid7 Open Data API. It's super-R-code-heavy but renders surprisingly well in Ghost (the blogging platform we use at work) and covers everything\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":5178,"url":"https:\/\/rud.is\/b\/2017\/03\/19\/exploring-2017-retail-store-closings-with-r\/","url_meta":{"origin":3940,"position":4},"title":"Exploring 2017 Retail Store Closings with R","author":"hrbrmstr","date":"2017-03-19","format":false,"excerpt":"A story about one of the retail chains (J.C. Penny) releasing their list of stores closing in 2017 crossed paths with my Feedly reading list today and jogged my memory that there were a number of chains closing many of their doors this year, and I wanted to see the\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/03\/bls-1.png?fit=1200%2C1050&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/03\/bls-1.png?fit=1200%2C1050&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/03\/bls-1.png?fit=1200%2C1050&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/03\/bls-1.png?fit=1200%2C1050&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/03\/bls-1.png?fit=1200%2C1050&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":637,"url":"https:\/\/rud.is\/b\/2011\/12\/01\/youre-the-mayor-of-fudville\/","url_meta":{"origin":3940,"position":5},"title":"You&#8217;re The Mayor of FUDville!","author":"hrbrmstr","date":"2011-12-01","format":false,"excerpt":"Rik Ferguson, Director Security Research at Trend Micro, had a great tweet early last Tueday morning calling out potential FUD in an article over at The Metro: Given the plethora of FUD-dropping in the article, I could only think of one way to do it justice, and that was a\u2026","rel":"","context":"In &quot;FUD&quot;","block_context":{"text":"FUD","link":"https:\/\/rud.is\/b\/category\/fud\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/3940","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/comments?post=3940"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/3940\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media\/3957"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=3940"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=3940"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=3940"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}