

{"id":3558,"date":"2015-07-25T09:49:19","date_gmt":"2015-07-25T14:49:19","guid":{"rendered":"http:\/\/rud.is\/b\/?p=3558"},"modified":"2018-03-07T16:43:48","modified_gmt":"2018-03-07T21:43:48","slug":"roll-your-own-gist-comments-notifier-in-r","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/","title":{"rendered":"Roll Your Own Gist Comments Notifier in R"},"content":{"rendered":"<p>As I was putting together the [coord_proj](https:\/\/rud.is\/b\/2015\/07\/24\/a-path-towards-easier-map-projection-machinations-with-ggplot2\/) ggplot2 extension I had posted a <!-- Missing Gist ID -->(https:\/\/gist.github.com\/hrbrmstr\/363e33f74e2972c93ca7) that I shared on Twitter. Said gist received a comment (several, in fact) and a bunch of us were painfully reminded of the fact that there is no built-in way to receive notifications from said comment activity.<\/p>\n<p>@jennybryan posited that it could be possible to use IFTTT as a broker for these notifications, but after some checking that ended up not being directly doable since there are no &#8220;gist comment&#8221; triggers to act upon in IFTTT.<\/p>\n<p>There are a few standalone Ruby gems that programmatically retrieve gist comments but I wasn&#8217;t interested in managing a Ruby workflow [ugh]. I did find a Heroku-hosted service &#8211; https:\/\/gh-rss.herokuapp.com\/ &#8211; that will turn gist comments into an RSS\/Atom feed (based on Ruby again). I gave it a shot and hooked it up to IFTTT but my feed is far enough down on the food chain there that it never gets updated. It was possible to deploy that app on my own Heroku instance, but&mdash;again&mdash;I&#8217;m not interested in managing a Ruby workflow.<\/p>\n<p>The Ruby scripts pretty much:<\/p>\n<p>&#8211; grab your main gist RSS\/Atom feed<br \/>\n&#8211; visit each gist in the feed<br \/>\n&#8211; extract comments &#038; comment metadata from them (if any)<br \/>\n&#8211; return a composite data structure you can do anything with<\/p>\n<p>That&#8217;s super-easy to duplicate in R, so I decided to build a small R script that does all that and generates an RSS\/Atom file which I added to my Feedly feeds (I&#8217;m pretty much always scanning RSS, so really didn&#8217;t need the IFTTT notification setup). I put it into a `cron` job that runs every hour. When Feedly refreshes the feed, a new entry will appear whenever there&#8217;s a new comment.<\/p>\n<p>The script is below and [on github](https:\/\/gist.github.com\/hrbrmstr\/0ad1ced217edd137de27) (ironically as a gist). Here&#8217;s what you&#8217;ll grok from the code:<\/p>\n<p>&#8211; one way to deal with the &#8220;default namespace&#8221; issue in R+XML<br \/>\n&#8211; one way to deal with error checking for scraping<br \/>\n&#8211; how to build an XML file (and, specifically, an RSS\/Atom feed) with R<br \/>\n&#8211; how to escape XML entities with R<br \/>\n&#8211; how to get an XML object as a character string in R<\/p>\n<p>You&#8217;ll definitely need to tweak this a bit for your own setup, but it should be a fairly complete starting point for you to work from. To see the output, grab the [generated feed](http:\/\/dds.ec\/hrbrmstrgcfeed.xml).<\/p>\n<pre lang=\"rsplus\"># Roll your own GitHub Gist Comments Feed in R\r\n\r\nlibrary(xml2)    # github version\r\nlibrary(rvest)   # github version\r\nlibrary(stringr) # for str_trim & str_replace\r\nlibrary(dplyr)   # for data_frame & bind_rows\r\nlibrary(pbapply) # free progress bars for everyone!\r\nlibrary(XML)     # to build the RSS feed\r\n\r\nwho <- \"hrbrmstr\" # CHANGE ME!\r\n\r\n# Grab the user's gist feed -----------------------------------------------\r\n\r\ngist_feed <- sprintf(\"https:\/\/gist.github.com\/%s.atom\", who)\r\nfeed_pg <- read_xml(gist_feed)\r\nns <- xml_ns_rename(xml_ns(feed_pg), d1 = \"feed\")\r\n\r\n# Extract the links &#038; titles of the gists in the feed ---------------------\r\n\r\nlinks <-  xml_attr(xml_find_all(feed_pg, \"\/\/feed:entry\/feed:link\", ns), \"href\")\r\ntitles <-  xml_text(xml_find_all(feed_pg, \"\/\/feed:entry\/feed:title\", ns))\r\n\r\n#' This function does the hard part by iterating over the\r\n#' links\/titles and building a tbl_df of all the comments per-gist\r\nget_comments <- function(links, titles) {\r\n\r\n  bind_rows(pblapply(1:length(links), function(i) {\r\n\r\n    # get gist\r\n\r\n    pg <- read_html(links[i])\r\n\r\n    # look for comments\r\n\r\n    ref <- tryCatch(html_attr(html_nodes(pg, \"div.timeline-comment-wrapper a[href^='#gistcomment']\"), \"href\"),\r\n                    error=function(e) character(0))\r\n\r\n    # in theory if 'ref' exists then the rest will\r\n\r\n    if (length(ref) != 0) {\r\n\r\n      # if there were comments, get all the metadata we care about\r\n\r\n      author <- html_text(html_nodes(pg, \"div.timeline-comment-wrapper a.author\"))\r\n      timestamp <- html_attr(html_nodes(pg, \"div.timeline-comment-wrapper time\"), \"datetime\")\r\n      contentpg <- str_trim(html_text(html_nodes(pg, \"div.timeline-comment-wrapper div.comment-body\")))\r\n\r\n    } else {\r\n      ref <- author <- timestamp <- contentpg <- character(0)\r\n    }\r\n\r\n    # bind_rows ignores length 0 tbl_df's\r\n    if (sum(lengths(list(ref, author, timestamp, contentpg))==0)) {\r\n      return(data_frame())\r\n    }\r\n\r\n    return(data_frame(title=titles[i], link=links[i],\r\n                      ref=ref, author=author,\r\n                      timestamp=timestamp, contentpg=contentpg))\r\n\r\n  }))\r\n\r\n}\r\n\r\ncomments <- get_comments(links, titles)\r\n\r\nfeed <- xmlTree(\"feed\")\r\nfeed$addNode(\"id\", sprintf(\"user:%s\", who))\r\nfeed$addNode(\"title\", sprintf(\"%s's gist comments\", who))\r\nfeed$addNode(\"icon\", \"https:\/\/assets-cdn.github.com\/favicon.ico\")\r\nfeed$addNode(\"link\", attrs=list(href=sprintf(\"https:\/\/github.com\/%s\", who)))\r\nfeed$addNode(\"updated\", format(Sys.time(), \"%Y-%m-%dT%H:%M:%SZ\", tz=\"GMT\"))\r\n\r\nfor (i in 1:nrow(comments)) {\r\n\r\n  feed$addNode(\"entry\", close=FALSE)\r\n    feed$addNode(\"id\", sprintf(\"gist:comment:%s:%s\", who, comments[i, \"timestamp\"]))\r\n    feed$addNode(\"link\", attrs=list(href=sprintf(\"%s%s\", comments[i, \"link\"], comments[i, \"ref\"])))\r\n    feed$addNode(\"title\", sprintf(\"Comment by %s\", comments[i, \"author\"]))\r\n    feed$addNode(\"updated\", comments[i, \"timestamp\"])\r\n    feed$addNode(\"author\", close=FALSE)\r\n      feed$addNode(\"name\", comments[i, \"author\"])\r\n    feed$closeTag()\r\n    feed$addNode(\"content\", saveXML(xmlTextNode(as.character(comments[i, \"contentpg\"])), prefix=\"\"), \r\n                 attrs=list(type=\"html\"))\r\n  feed$closeTag()\r\n\r\n}\r\n\r\nrss <- str_replace(saveXML(feed), \"<feed>\", '<feed xmlns=\"http:\/\/www.w3.org\/2005\/Atom\">')\r\n\r\nwriteLines(rss, con=\"feed.xml\")<\/pre>\n<p>To get that RSS feed into something that an internet service can process you have to make sure that `feed.xml` is being written to a directory that translates to a publicly accessible web location (mine is at [http:\/\/dds.ec\/hrbrmstrgcfeed.xml](http:\/\/dds.ec\/hrbrmstrgcfeed.xml) if you want to see it).<\/p>\n<p>On the internet-facing Ubuntu box that generated the feed I&#8217;ve got a `cron` entry:<\/p>\n<pre lang=\"cron\">30  * * * * \/home\/bob\/bin\/gengcfeed.R<\/pre>\n<p>which means it&#8217;s going to check github every 30 minutes for comment updates. Tune said parameters to your liking.<\/p>\n<p>At the top of `gengcfeed.R` I have an `Rscript` shebang:<\/p>\n<pre lang=\"bash\">#!\/usr\/bin\/Rscript<\/pre>\n<p>and the execute bit is set on the file.<\/p>\n<p>Run the file by hand, first, and then test the feed via [https:\/\/validator.w3.org\/feed\/](https:\/\/validator.w3.org\/feed\/) to ensure it&#8217;s accessible and that it validates correctly. Now you can enter that feed URL into your favorite newsfeed reader (I use @feedly).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As I was putting together the [coord_proj](https:\/\/rud.is\/b\/2015\/07\/24\/a-path-towards-easier-map-projection-machinations-with-ggplot2\/) ggplot2 extension I had posted a (https:\/\/gist.github.com\/hrbrmstr\/363e33f74e2972c93ca7) that I shared on Twitter. Said gist received a comment (several, in fact) and a bunch of us were painfully reminded of the fact that there is no built-in way to receive notifications from said comment activity. @jennybryan posited that it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"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":[91,725,732],"tags":[810,748,733,734,401],"class_list":["post-3558","post","type-post","status-publish","format-standard","hentry","category-r","category-web-scraping","category-xml","tag-post","tag-r","tag-rstats","tag-web-scraping","tag-xml"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Roll Your Own Gist Comments Notifier in R - 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\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Roll Your Own Gist Comments Notifier in R - rud.is\" \/>\n<meta property=\"og:description\" content=\"As I was putting together the [coord_proj](https:\/\/rud.is\/b\/2015\/07\/24\/a-path-towards-easier-map-projection-machinations-with-ggplot2\/) ggplot2 extension I had posted a (https:\/\/gist.github.com\/hrbrmstr\/363e33f74e2972c93ca7) that I shared on Twitter. Said gist received a comment (several, in fact) and a bunch of us were painfully reminded of the fact that there is no built-in way to receive notifications from said comment activity. @jennybryan posited that it [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2015-07-25T14:49:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-03-07T21:43:48+00:00\" \/>\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=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Roll Your Own Gist Comments Notifier in R\",\"datePublished\":\"2015-07-25T14:49:19+00:00\",\"dateModified\":\"2018-03-07T21:43:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\"},\"wordCount\":615,\"commentCount\":2,\"publisher\":{\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"keywords\":[\"post\",\"R\",\"rstats\",\"web scraping\",\"XML\"],\"articleSection\":[\"R\",\"web scraping\",\"xml\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\",\"url\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\",\"name\":\"Roll Your Own Gist Comments Notifier in R - rud.is\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/#website\"},\"datePublished\":\"2015-07-25T14:49:19+00:00\",\"dateModified\":\"2018-03-07T21:43:48+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/rud.is\/b\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Roll Your Own Gist Comments Notifier in R\"}]},{\"@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":"Roll Your Own Gist Comments Notifier in R - 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\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/","og_locale":"en_US","og_type":"article","og_title":"Roll Your Own Gist Comments Notifier in R - rud.is","og_description":"As I was putting together the [coord_proj](https:\/\/rud.is\/b\/2015\/07\/24\/a-path-towards-easier-map-projection-machinations-with-ggplot2\/) ggplot2 extension I had posted a (https:\/\/gist.github.com\/hrbrmstr\/363e33f74e2972c93ca7) that I shared on Twitter. Said gist received a comment (several, in fact) and a bunch of us were painfully reminded of the fact that there is no built-in way to receive notifications from said comment activity. @jennybryan posited that it [&hellip;]","og_url":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/","og_site_name":"rud.is","article_published_time":"2015-07-25T14:49:19+00:00","article_modified_time":"2018-03-07T21:43:48+00:00","author":"hrbrmstr","twitter_card":"summary_large_image","twitter_misc":{"Written by":"hrbrmstr","Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Roll Your Own Gist Comments Notifier in R","datePublished":"2015-07-25T14:49:19+00:00","dateModified":"2018-03-07T21:43:48+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/"},"wordCount":615,"commentCount":2,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"keywords":["post","R","rstats","web scraping","XML"],"articleSection":["R","web scraping","xml"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/","url":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/","name":"Roll Your Own Gist Comments Notifier in R - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"datePublished":"2015-07-25T14:49:19+00:00","dateModified":"2018-03-07T21:43:48+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2015\/07\/25\/roll-your-own-gist-comments-notifier-in-r\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Roll Your Own Gist Comments Notifier in R"}]},{"@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":"","jetpack_shortlink":"https:\/\/wp.me\/p23idr-Vo","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":4133,"url":"https:\/\/rud.is\/b\/2016\/03\/16\/supreme-annotations\/","url_meta":{"origin":3558,"position":0},"title":"Supreme Annotations","author":"hrbrmstr","date":"2016-03-16","format":false,"excerpt":"This is a follow up to a twitter-gist post & to the annotation party we're having this week I had not intended this to be \"Annotation Week\" but there was a large, positive response to my annotation \"hack\" post. This reaction surprised me, then someone pointed me to this link\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\/2016\/03\/supremes.png?fit=1200%2C987&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/03\/supremes.png?fit=1200%2C987&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/03\/supremes.png?fit=1200%2C987&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/03\/supremes.png?fit=1200%2C987&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/03\/supremes.png?fit=1200%2C987&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":2643,"url":"https:\/\/rud.is\/b\/2013\/09\/12\/send-mac-os-notifications-from-r\/","url_meta":{"origin":3558,"position":1},"title":"Send Mac OS Notifications From R","author":"hrbrmstr","date":"2013-09-12","format":false,"excerpt":"2013-09-16 UPDATE: I took suggestions from a couple comments, expanded the function a bit and stuck it in a gist. See this comment for details. The data retrieval and computation operations are taking longer and longer as we start cranking through more security data and I'll often let tasks run\u2026","rel":"","context":"In &quot;Development&quot;","block_context":{"text":"Development","link":"https:\/\/rud.is\/b\/category\/development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3700,"url":"https:\/\/rud.is\/b\/2015\/10\/04\/replicating-natgeos-proper-earthquake-map-in-r\/","url_meta":{"origin":3558,"position":2},"title":"Replicating NatGeo&#8217;s &#8220;Proper&#8221; Earthquake Map in R","author":"hrbrmstr","date":"2015-10-04","format":false,"excerpt":"I saw this post over at NatGeo over the weekend and felt compelled to replicate this: with ggplot2. Three shapefiles later and we have it close enough to toss into a post (and I really don't believe the continent names are necessary). library(rgdal) library(ggplot2) library(ggthemes) library(ggalt) # devtools::install_github(\"hrbrmstr\/ggalt\") # grab\u2026","rel":"","context":"In &quot;cartography&quot;","block_context":{"text":"cartography","link":"https:\/\/rud.is\/b\/category\/cartography\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/10\/unnamed-chunk-1-1.png?fit=1200%2C685&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/10\/unnamed-chunk-1-1.png?fit=1200%2C685&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/10\/unnamed-chunk-1-1.png?fit=1200%2C685&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/10\/unnamed-chunk-1-1.png?fit=1200%2C685&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2015\/10\/unnamed-chunk-1-1.png?fit=1200%2C685&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3236,"url":"https:\/\/rud.is\/b\/2015\/01\/18\/nasa-gisss-annual-global-temperature-anomaly-trends-dplyrggplot-version\/","url_meta":{"origin":3558,"position":3},"title":"NASA GISS\u2019s Annual Global Temperature Anomaly Trends (dplyr\/ggplot version)","author":"hrbrmstr","date":"2015-01-18","format":false,"excerpt":"D Kelly O\u2019Day did a [great post](https:\/\/chartsgraphs.wordpress.com\/2015\/01\/16\/nasa-gisss-annual-global-temperature-anomaly-trends\/) on charting NASA\u2019s Goddard Institute for Space Studies (GISS) temperature anomaly data, but it sticks with base R for data munging & plotting. While there's absolutely nothing wrong with base R operations, I thought a modern take on the chart using `dplyr`, `magrittr`\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":"","width":0,"height":0},"classes":[]},{"id":7924,"url":"https:\/\/rud.is\/b\/2018\/01\/18\/bitcoin-world-map-bubbles\/","url_meta":{"origin":3558,"position":4},"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":4051,"url":"https:\/\/rud.is\/b\/2016\/03\/07\/os-x-xquartz-vulnerability-test-using-r\/","url_meta":{"origin":3558,"position":5},"title":"OS X XQuartz Vulnerability Test Using R","author":"hrbrmstr","date":"2016-03-07","format":false,"excerpt":"It's usually a good thing when my #rstats and infosec worlds collide. Unfortunately, this time it's a script that R folk running on OS X can use to see if they are using a version of XQuartz that has a nasty vulnerability in the framework it uses to auto-update. If\u2026","rel":"","context":"In &quot;AppSec&quot;","block_context":{"text":"AppSec","link":"https:\/\/rud.is\/b\/category\/appsec\/"},"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\/3558","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=3558"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/3558\/revisions"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=3558"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=3558"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=3558"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}