

{"id":12951,"date":"2021-02-14T13:11:24","date_gmt":"2021-02-14T18:11:24","guid":{"rendered":"https:\/\/rud.is\/b\/?p=12951"},"modified":"2021-02-14T13:13:45","modified_gmt":"2021-02-14T18:13:45","slug":"extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/","title":{"rendered":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#8217;s Day R Post Ever)"},"content":{"rendered":"<details>\n<summary>? Expand for EKG code<\/summary>\n<pre><code class=\"language-r\">library(hrbrthemes)\nlibrary(elementalist) # remotes::install_github(\"teunbrand\/elementalist\")\nlibrary(ggplot2)\n\nread_csv(\n  file = \"~\/Data\/apple_health_export\/electrocardiograms\/ecg_2020-09-24.csv\", # this is extracted below\n  skip = 12,\n  col_names = \"\u00b5V\"\n) %&gt;% \n  mutate(\n    idx = 1:n()\n  ) -&gt; ekg\n\nggplot() +\n  geom_line_theme(\n    data = ekg %&gt;% tail(3000) %&gt;% head(2500),\n    aes(idx, \u00b5V),\n    size = 0.125, color = \"#cb181d\"\n  ) +\n  labs(x = NULL, y = NULL) +\n  theme_ipsum_inter(grid=\"\") +\n  theme(\n    panel.background = element_rect(color = NA, fill = \"#141414\"),\n    plot.background = element_rect(color = NA, fill = \"#141414\")\n  ) +\n  theme(\n    axis.text.x = element_blank(),\n    axis.text.y = element_blank(),\n    elementalist.geom_line= element_line_glow()\n  )\n<\/code><\/pre>\n<\/details>\n<p>Apple Watch owners have the ability to export their tracked data and do whatever they like with it. Since it&#8217;s Valentine&#8217;s Day, I thought it might be fun to show two ways to read heart rate data from these exports.<\/p>\n<p>Why <em>two<\/em> ways? Well, I&#8217;ve owned an Apple Watch off-and-on ever since the first generation device, and when Apple says you can export <em>all<\/em> your data, they mean <em>all<\/em>. The <code>apple_health_export.zip<\/code> archive is generated by going to the &#8220;Health&#8221; iOS app, tapping your avatar in the upper left, then scrolling down and tapping the export button:<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"12952\" data-permalink=\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/health-data-export\/\" data-orig-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?fit=291%2C600&amp;ssl=1\" data-orig-size=\"291,600\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"health-data-export\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?fit=146%2C300&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?fit=291%2C600&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?resize=291%2C600&#038;ssl=1\" alt=\"apple health data export screenshot\" width=\"291\" height=\"600\" class=\"aligncenter size-full wp-image-12952\" srcset=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?w=291&amp;ssl=1 291w, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?resize=146%2C300&amp;ssl=1 146w, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?resize=73%2C150&amp;ssl=1 73w, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?resize=150%2C309&amp;ssl=1 150w, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/health-data-export.png?resize=200%2C412&amp;ssl=1 200w\" sizes=\"auto, (max-width: 291px) 100vw, 291px\" \/><\/a><\/p>\n<p>(NOTE: I suggest saving it to and then downloading it from iCloud vs using local AirDrop to your system.)<\/p>\n<p>This compressed file is a deceivingly ~58 MB in size. Opening it up results in a directory tree of nearly 3 GB of consumed drive space O_o. That tree has the following structure:<\/p>\n<pre><code class=\"language-r\">fs::dir_tree(\"~\/Data\/apple_health_export\", recurse = 1)\n## ~\/Data\/apple_health_export\n## \u251c\u2500\u2500 electrocardiograms\n## \u2502   \u2514\u2500\u2500 ecg_2020-09-24.csv             # 122 KB\n## \u251c\u2500\u2500 export.xml                         # 882 MB\n## \u251c\u2500\u2500 export_cda.xml                     # 950 MB\n## \u2514\u2500\u2500 workout-routes                     #  81 MB\n##     \u251c\u2500\u2500 ...\n##     \u251c\u2500\u2500 route_2021-01-28_5.21pm.gpx\n##     \u251c\u2500\u2500 route_2021-01-31_4.28pm.gpx\n##     \u251c\u2500\u2500 route_2021-02-02_1.26pm.gpx\n##     \u251c\u2500\u2500 route_2021-02-04_3.52pm.gpx\n##     \u251c\u2500\u2500 route_2021-02-06_2.24pm.gpx\n##     \u2514\u2500\u2500 route_2021-02-10_4.54pm.gpx\n<\/code><\/pre>\n<p>The heart rate data is in the just-under 1 GB <code>export.xml<\/code> and is mixed in with all the other data points Apple records. They look like this:<\/p>\n<pre><code class=\"language-xml\">&lt;Record \n  type=\"HKQuantityTypeIdentifierHeartRate\" \n  sourceName=\"Apple\u00a0Watch\" \n  sourceVersion=\"3.2\" \n  device=\"&lt;&lt;HKDevice: 0x2812d8a00&gt;, name:Apple Watch, manufacturer:Apple, model:Watch, hardware:Watch1,2, software:3.2&gt;\" \n  unit=\"count\/min\" \n  creationDate=\"2017-04-29 12:21:15 -0500\" \n  startDate=\"2017-04-29 12:21:15 -0500\" \n  endDate=\"2017-04-29 12:21:15 -0500\" \n  value=\"102\"\n\/&gt;\n<\/code><\/pre>\n<p>Note that newer records of this type are not empty tags.<\/p>\n<p>While dealing with gigabyte+ XML files are not nearly as untenable as they used to be in R, building a parsed XML tree in memory for all of those records will take up a non-insignificant amount of RAM (we&#8217;ll see how much below). Since I want to start playing with this data more often I decided to try two approaches: one that processes the XML in streaming &#8220;chunks&#8221; and one that does it the way you&#8217;re likely used to (if you&#8217;re unfortunate enough to have to work with XML regularly).<\/p>\n<h3>Streaming ? Beats<\/h3>\n<p>We&#8217;ll start with the streaming approach, which means using the venerable {XML} package, which has <code>xmlEventParse()<\/code> which is an <em>event-driven<\/em> or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Simple_API_for_XML\">SAX (Simple API for XML)<\/a> style parser which process XML without building the tree but rather identifies tokens in the stream of characters and passes them to handlers which can make sense of them in context. Since we&#8217;re going old-school, we&#8217;ll also use {data.table} to get a tidy dataset to work with.<\/p>\n<p>We&#8217;re going to be finding heart rate records and storing the data from them into a list, so we&#8217;ll need to make room for them and use indexed-based value assignments to avoid making thousands of copies with <code>append()<\/code>. To figure out how much room we&#8217;ll need I&#8217;m going to &#8220;cheat&#8221; a bit and use <a href=\"https:\/\/github.com\/BurntSushi\/ripgrep\">ripgrep<\/a> to count how many <code>HKQuantityTypeIdentifierHeartRate<\/code> records exist and use that result to reserve list space:<\/p>\n<pre><code class=\"language-r\">library(XML)\nlibrary(data.table)\n\nnl &lt;- system(\"rg -c 'type=\\\"HKQuantityTypeIdentifierHeartRate' ~\/Data\/apple_health_export\/export.xml\", intern = TRUE)\nrecords &lt;- vector(mode = \"list\", as.numeric(nl))\nidx &lt;- 1\n<\/code><\/pre>\n<p>There are just under 790K records buried in that file. The <code>xmlEventParse()<\/code> function has a <code>handlers<\/code> parameter which takes a list named functions for various events. The event we care about is the one where we start processing an XML element, which is unsurprisingly called <code>startElement<\/code>. In it, we&#8217;ll only process <code>HKQuantityTypeIdentifierHeartRate<\/code> records and further only care about data since 2019:<\/p>\n<pre><code class=\"language-r\">invisible(xmlEventParse(\n  file = \"~\/Data\/apple_health_export\/export.xml\",\n  handlers = list(\n\n    # process at element start\n\n    startElement = function(name, attrs) {\n\n      # only care about the heart rate recs\n\n      if ((name == \"Record\") &amp;&amp; (attrs[\"type\"] == \"HKQuantityTypeIdentifierHeartRate\")) {\n\n        # only care about records &gt;= the year 2019\n\n        if (substr(attrs[\"endDate\"], 1, 4) &gt;= 2019) {\n\n          # if we find them, add them to the list (note the &lt;&lt;-)\n          records[idx] &lt;&lt;- list(as.list(unname(attrs[c(\"endDate\", \"value\")]))) # not using names reduces memory\n          idx &lt;&lt;- idx + 1\n\n        }\n      }\n    }\n  )\n))\n<\/code><\/pre>\n<p>At this point we have a list of all those records and have taken the R session memory from 131 MiB to 629 MiB (so, we&#8217;re eating about ~500 MiB of RAM with that call), and it took around 34 painful seconds to process the XML file.<\/p>\n<p>Now, we&#8217;ll use {data.table} to tidy it up:<\/p>\n<pre><code class=\"language-r\">records &lt;- records[lengths(records) != 0]         # get rid of any list elements we didn't use\n\nrecords &lt;- rbindlist(records, use.names = FALSE)  # make a data frame\nsetattr(records, 'names', c(\"ts\", \"rate\"))\n\nrecords[, c(\"ts\", \"rate\") := list(\n  as.POSIXct(ts, format = \"%Y-%m-%d %H:%M:%S %z\"),\n  as.integer(rate)\n)]  \n##                          ts rate\n##      1: 2019-02-12 15:19:54   69\n##      2: 2019-02-12 15:26:11   90\n##      3: 2019-02-12 15:31:33   92\n##      4: 2019-02-12 15:34:24   89\n##      5: 2019-02-12 15:57:33  120\n##     ---                         \n## 734526: 2021-02-13 10:17:08  118\n## 734527: 2021-02-13 10:26:50  124\n## 734528: 2021-02-13 10:22:56  110\n## 734529: 2021-02-13 10:34:56   98\n## 734530: 2021-02-13 10:39:34   99\n<\/code><\/pre>\n<p>That took around 4.5 seconds, and when the R garbage collector kicks in we&#8217;re now consuming ~695 MiB, so not much more than the previous step.<\/p>\n<p>So, ~38s for the ingestion &amp; conversion, and a maximum of ~695 MiB in play at any time during the R session. Let&#8217;s see how the new\/modern way (i.e. {xml2}) compares.<\/p>\n<h3>Modern ?<\/h3>\n<p>Unless I missed something in the {xml2} index page, there is no equivalent streaming processor, so we have to read the entire document into active RAM:<\/p>\n<pre><code class=\"language-r\">library(xml2)\nlibrary(tidyverse)\n\nrecords &lt;- xml2::read_xml(\"~\/Data\/apple_health_export\/export.xml\")\n<\/code><\/pre>\n<p>This operation takes 15.7s and the R session now consumes ~5.8 GiB of RAM. That is a &#8220;G&#8221;, as in gigabyte.<\/p>\n<p>Now, we&#8217;ll find all the records that we care about (as above). We&#8217;ll do this via a modest XPath selector:<\/p>\n<pre><code class=\"language-r\">xml_find_all(\n  records,\n  xpath = \"\n    .\/\/Record[\n         @type = 'HKQuantityTypeIdentifierHeartRate' and\n         (starts-with(@endDate, '2019') or \n          starts-with(@endDate, '2020') or \n          starts-with(@endDate, '2021'))\n      ]\"\n) -&gt; records\n<\/code><\/pre>\n<p>That operation took around ~6.5s and we&#8217;re still consuming around 6.23 GiB of RAM.<\/p>\n<p>Now, we&#8217;ll tidy that up:<\/p>\n<pre><code class=\"language-r\">tibble(\n  ts = records %&gt;% \n    xml_attr(\"endDate\") %&gt;% \n    as.POSIXct(format = \"%Y-%m-%d %H:%M:%S %z\"),  \n  rate = records %&gt;% \n    xml_attr(\"value\") %&gt;% \n    as.integer()\n) -&gt; records\n\nrecords\n## # A tibble: 734,530 x 2\n##    ts                   rate\n##    &lt;dttm&gt;              &lt;int&gt;\n##  1 2019-02-12 15:19:54    69\n##  2 2019-02-12 15:26:11    90\n##  3 2019-02-12 15:31:33    92\n##  4 2019-02-12 15:34:24    89\n##  5 2019-02-12 15:57:33   120\n##  6 2019-02-12 15:44:09    80\n##  7 2019-02-12 16:03:24   110\n##  8 2019-02-12 16:13:08   118\n##  9 2019-02-12 16:08:10   100\n## 10 2019-02-12 16:15:04    95\n## # \u2026 with 734,520 more rows\n<\/code><\/pre>\n<p>That took around 10.4s and, after garbage collection happens, we&#8217;re back to a much more reasonable ~890 MiB of consumed RAM after a workflow maximum of over 6 GiB, taking a total of ~32.6 seconds.<\/p>\n<h3>FIN ?<\/h3>\n<p>If\/when memory is tight, it&#8217;s nice to have some alternatives besides &#8220;get a bigger box&#8221;, and this is one approach (there are others) for performing this type of XML surgery in R.<\/p>\n<p>Stay safe\/strong, folks.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>? Expand for EKG code library(hrbrthemes) library(elementalist) # remotes::install_github(&#8220;teunbrand\/elementalist&#8221;) library(ggplot2) read_csv( file = &#8220;~\/Data\/apple_health_export\/electrocardiograms\/ecg_2020-09-24.csv&#8221;, # this is extracted below skip = 12, col_names = &#8220;\u00b5V&#8221; ) %&gt;% mutate( idx = 1:n() ) -&gt; ekg ggplot() + geom_line_theme( data = ekg %&gt;% tail(3000) %&gt;% head(2500), aes(idx, \u00b5V), size = 0.125, color = &#8220;#cb181d&#8221; ) + labs(x [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":12953,"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],"tags":[],"class_list":["post-12951","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-r"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#039;s Day R Post Ever) - 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\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#039;s Day R Post Ever) - rud.is\" \/>\n<meta property=\"og:description\" content=\"? Expand for EKG code library(hrbrthemes) library(elementalist) # remotes::install_github(&quot;teunbrand\/elementalist&quot;) library(ggplot2) read_csv( file = &quot;~\/Data\/apple_health_export\/electrocardiograms\/ecg_2020-09-24.csv&quot;, # this is extracted below skip = 12, col_names = &quot;\u00b5V&quot; ) %&gt;% mutate( idx = 1:n() ) -&gt; ekg ggplot() + geom_line_theme( data = ekg %&gt;% tail(3000) %&gt;% head(2500), aes(idx, \u00b5V), size = 0.125, color = &quot;#cb181d&quot; ) + labs(x [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2021-02-14T18:11:24+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-02-14T18:13:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1852\" \/>\n\t<meta property=\"og:image:height\" content=\"674\" \/>\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\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#8217;s Day R Post Ever)\",\"datePublished\":\"2021-02-14T18:11:24+00:00\",\"dateModified\":\"2021-02-14T18:13:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\"},\"wordCount\":788,\"commentCount\":2,\"publisher\":{\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"image\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1\",\"articleSection\":[\"R\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\",\"url\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\",\"name\":\"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine's Day R Post Ever) - rud.is\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1\",\"datePublished\":\"2021-02-14T18:11:24+00:00\",\"dateModified\":\"2021-02-14T18:13:45+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage\",\"url\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1\",\"width\":1852,\"height\":674},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/rud.is\/b\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#8217;s Day R Post Ever)\"}]},{\"@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":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine's Day R Post Ever) - 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\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/","og_locale":"en_US","og_type":"article","og_title":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine's Day R Post Ever) - rud.is","og_description":"? Expand for EKG code library(hrbrthemes) library(elementalist) # remotes::install_github(\"teunbrand\/elementalist\") library(ggplot2) read_csv( file = \"~\/Data\/apple_health_export\/electrocardiograms\/ecg_2020-09-24.csv\", # this is extracted below skip = 12, col_names = \"\u00b5V\" ) %&gt;% mutate( idx = 1:n() ) -&gt; ekg ggplot() + geom_line_theme( data = ekg %&gt;% tail(3000) %&gt;% head(2500), aes(idx, \u00b5V), size = 0.125, color = \"#cb181d\" ) + labs(x [&hellip;]","og_url":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/","og_site_name":"rud.is","article_published_time":"2021-02-14T18:11:24+00:00","article_modified_time":"2021-02-14T18:13:45+00:00","og_image":[{"width":1852,"height":674,"url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&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\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#8217;s Day R Post Ever)","datePublished":"2021-02-14T18:11:24+00:00","dateModified":"2021-02-14T18:13:45+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/"},"wordCount":788,"commentCount":2,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"image":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1","articleSection":["R"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/","url":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/","name":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine's Day R Post Ever) - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage"},"image":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1","datePublished":"2021-02-14T18:11:24+00:00","dateModified":"2021-02-14T18:13:45+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#primaryimage","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1","width":1852,"height":674},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2021\/02\/14\/extracting-heart-rate-data-from-apple-health-xml-export-files-using-r-a-k-a-the-least-romantic-valentines-day-r-post\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Extracting Heart Rate Data (Two Ways!) from Apple Health XML Export Files Using R (a.k.a. The Least Romantic Valentine&#8217;s Day R Post Ever)"}]},{"@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\/2021\/02\/ekg.png?fit=1852%2C674&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p23idr-3mT","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":12576,"url":"https:\/\/rud.is\/b\/2019\/12\/27\/short-attention-span-theatre-reproducing-axios-1-big-thing-google-trends-2019-news-in-review-with-ggplot2\/","url_meta":{"origin":12951,"position":0},"title":"Short Attention Span Theatre: Reproducing Axios&#8217; &#8220;1 Big Thing&#8221; Google Trends 2019 News In Review with {ggplot2}","author":"hrbrmstr","date":"2019-12-27","format":false,"excerpt":"I woke up to Axios' \"1 Big Thing\" ridgeline chart showing the crazy that was the 2019 news cycle: and, I decided to reproduce it in {ggplot2}. Getting The Data First, I had to find the data. The Axios chart is interactive, so I assumed the visualization was built on-load.\u2026","rel":"","context":"In &quot;ggplot&quot;","block_context":{"text":"ggplot","link":"https:\/\/rud.is\/b\/category\/ggplot\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":11840,"url":"https:\/\/rud.is\/b\/2019\/01\/30\/fast-static-maps-built-with-r\/","url_meta":{"origin":12951,"position":1},"title":"Fast Static Maps Built with R","author":"hrbrmstr","date":"2019-01-30","format":false,"excerpt":"Luke Whyte posted an article (apologies for a Medium link) over on Towards Data Science showing how to use a command line workflow involving curl, node and various D3 libraries and javascript source files to build a series of SVG static maps. It's well written and you should give it\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\/2019\/01\/fast-static-maps.png?fit=1200%2C806&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/01\/fast-static-maps.png?fit=1200%2C806&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/01\/fast-static-maps.png?fit=1200%2C806&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/01\/fast-static-maps.png?fit=1200%2C806&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/01\/fast-static-maps.png?fit=1200%2C806&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":11102,"url":"https:\/\/rud.is\/b\/2018\/07\/29\/ggplot-doodling-with-hibp-breaches\/","url_meta":{"origin":12951,"position":2},"title":"ggplot &#8220;Doodling&#8221; with HIBP Breaches","author":"hrbrmstr","date":"2018-07-29","format":false,"excerpt":"After reading this interesting analysis of \"How Often Are Americans' Accounts Breached?\" by Gaurav Sood (which we need more of in cyber-land) I gave in to the impulse to do some gg-doodling with the \"Have I Been Pwnd\" JSON data he used. It's just some basic data manipulation with some\u2026","rel":"","context":"In &quot;ggplot&quot;","block_context":{"text":"ggplot","link":"https:\/\/rud.is\/b\/category\/ggplot\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/07\/hibp-lines.png?fit=1200%2C588&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/07\/hibp-lines.png?fit=1200%2C588&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/07\/hibp-lines.png?fit=1200%2C588&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/07\/hibp-lines.png?fit=1200%2C588&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/07\/hibp-lines.png?fit=1200%2C588&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":12667,"url":"https:\/\/rud.is\/b\/2020\/03\/02\/make-wsj-esque-uber-tuesday-democrat-delegate-cartograms-in-r-with-catchpole\/","url_meta":{"origin":12951,"position":3},"title":"Make WSJ-esque \u00dcber Tuesday Democrat Delegate Cartograms in R with {catchpole}","author":"hrbrmstr","date":"2020-03-02","format":false,"excerpt":"For folks who are smart enough not to go near Twitter, I've been on a hiatus from the platform insofar as reading the Twitter feed goes. \"Why\" isn't the subject of this post so I won't go into it, but I've broken this half-NYE resolution on more than one occasion\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\/2020\/03\/my-map-2.png?fit=1200%2C784&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2020\/03\/my-map-2.png?fit=1200%2C784&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2020\/03\/my-map-2.png?fit=1200%2C784&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2020\/03\/my-map-2.png?fit=1200%2C784&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2020\/03\/my-map-2.png?fit=1200%2C784&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":1880,"url":"https:\/\/rud.is\/b\/2013\/01\/11\/slopegraphs-in-r\/","url_meta":{"origin":12951,"position":4},"title":"Slopegraphs in R","author":"hrbrmstr","date":"2013-01-11","format":false,"excerpt":"I updated the code to use ggsave and tweaked some of the font & line size values for more consistent (and pretty) output. This also means that I really need to get this up on github. If you even remotely follow this blog, you'll see that I'm kinda obsessed with\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":12366,"url":"https:\/\/rud.is\/b\/2019\/06\/26\/quick-hit-above-the-fold-hard-wrapping-text-at-n-characters\/","url_meta":{"origin":12951,"position":5},"title":"Quick Hit: Above the Fold; Hard wrapping text at &#8216;n&#8217; characters","author":"hrbrmstr","date":"2019-06-26","format":false,"excerpt":"Despite being on holiday I'm getting in a bit of non-work R coding since the fam has a greater ability to sleep late than I do. Apart from other things I've been working on a PR into {lutz}, a package by @andyteucher that turns lat\/lng pairs into timezone strings. 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\/2019\/06\/lutz-widths-02.png?fit=1200%2C628&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/06\/lutz-widths-02.png?fit=1200%2C628&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/06\/lutz-widths-02.png?fit=1200%2C628&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/06\/lutz-widths-02.png?fit=1200%2C628&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2019\/06\/lutz-widths-02.png?fit=1200%2C628&ssl=1&resize=1050%2C600 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/12951","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=12951"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/12951\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media\/12953"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=12951"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=12951"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=12951"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}