Extraindo dados de frequência cardíaca (duas maneiras!) De arquivos de exportação XML da Apple Health usando R (também conhecido como The Least Romantic Valentine’s Day R Post Ever)

cupom com desconto - o melhor site de cupom de desconto cupomcomdesconto.com.br

[ad_1]

[This article was first published on R – rud.is, and kindly contributed to R-bloggers]. (Você pode relatar problemas sobre o conteúdo desta página aqui)


Quer compartilhar seu conteúdo em R-bloggers? clique aqui se você tiver um blog, ou aqui se não tiver.

💙 Expandir para o código EKG
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 = "µV"
) %>% 
  mutate(
    idx = 1:n()
  ) -> ekg

ggplot() +
  geom_line_theme(
    data = ekg %>% tail(3000) %>% head(2500),
    aes(idx, µV),
    size = 0.125, color = "#cb181d"
  ) +
  labs(x = NULL, y = NULL) +
  theme_ipsum_inter(grid="") +
  theme(
    panel.background = element_rect(color = NA, fill = "#141414"),
    plot.background = element_rect(color = NA, fill = "#141414")
  ) +
  theme(
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    elementalist.geom_line= element_line_glow()
  )

Os proprietários do Apple Watch têm a capacidade de exportar seus dados rastreados e fazer o que quiserem com eles. Como é Dia dos Namorados, achei que seria divertido mostrar duas maneiras de ler dados de frequência cardíaca dessas exportações.

Por que dois maneiras? Bem, eu tenho um Apple Watch intermitente desde o dispositivo de primeira geração, e quando a Apple diz que você pode exportar todo seus dados, eles significam todo. O apple_health_export.zip o arquivo é gerado acessando o aplicativo iOS “Health”, tocando em seu avatar no canto superior esquerdo, rolando para baixo e tocando no botão de exportação:

imagem de exportação de dados de saúde da apple

(NOTA: sugiro salvá-lo e baixá-lo do iCloud em vez de usar o AirDrop local para o seu sistema.)

Este arquivo compactado tem um tamanho enganadoramente ~ 58 MB. Abri-lo resulta em uma árvore de diretório de quase 3 GB de espaço em disco consumido O_o. Essa árvore tem a seguinte estrutura:

fs::dir_tree("~/Data/apple_health_export", recurse = 1)
## ~/Data/apple_health_export
## ├── electrocardiograms
## │   └── ecg_2020-09-24.csv             # 122 KB
## ├── export.xml                         # 882 MB
## ├── export_cda.xml                     # 950 MB
## └── workout-routes                     #  81 MB
##     ├── ...
##     ├── route_2021-01-28_5.21pm.gpx
##     ├── route_2021-01-31_4.28pm.gpx
##     ├── route_2021-02-02_1.26pm.gpx
##     ├── route_2021-02-04_3.52pm.gpx
##     ├── route_2021-02-06_2.24pm.gpx
##     └── route_2021-02-10_4.54pm.gpx

Os dados de frequência cardíaca estão em pouco menos de 1 GB export.xml e é misturado com todos os outros pontos de dados que a Apple registra. Eles se parecem com isto:


Observe que os registros mais recentes desse tipo não são tags vazias.

cupom com desconto - o melhor site de cupom de desconto cupomcomdesconto.com.br

Embora lidar com arquivos gigabyte + XML não sejam tão insustentáveis ​​como costumavam ser em R, construir uma árvore XML analisada na memória para todos esses registros ocupará uma quantidade não insignificante de RAM (veremos quanto abaixo) . Já que quero começar a brincar com esses dados com mais frequência, decidi tentar duas abordagens: uma que processa o XML em “blocos” de streaming e outra que o faz da maneira que você provavelmente está acostumado (se você tiver o azar de ter trabalhar com XML regularmente).

Transmissão 💙 Beats

Começaremos com a abordagem de streaming, o que significa usar o venerável pacote {XML}, que tem xmlEventParse() que é um orientado por eventos ou o analisador de estilo SAX (API Simples para XML) que processa XML sem construir a árvore, mas identifica tokens no fluxo de caracteres e os passa para manipuladores que podem entendê-los no contexto. Já que estamos indo para a velha escola, também usaremos {data.table} para obter um conjunto de dados organizado para trabalhar.

Encontraremos registros de frequência cardíaca e armazenaremos os dados deles em uma lista, portanto, precisaremos abrir espaço para eles e usar atribuições de valores com base indexada para evitar fazer milhares de cópias com append(). Para descobrir de quanto espaço precisaremos, vou “trapacear” um pouco e usar o ripgrep para contar quantos HKQuantityTypeIdentifierHeartRate registros existem e usam esse resultado para reservar espaço na lista:

library(XML)
library(data.table)

nl 

There are just under 790K records buried in that file. The xmlEventParse() function has a handlers 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 startElement. In it, we’ll only process HKQuantityTypeIdentifierHeartRate records and further only care about data since 2019:

invisible(xmlEventParse(
  file = "~/Data/apple_health_export/export.xml",
  handlers = list(

    # process at element start

    startElement = function(name, attrs) {

      # only care about the heart rate recs

      if ((name == "Record") && (attrs["type"] == "HKQuantityTypeIdentifierHeartRate")) {

        # only care about records >= the year 2019

        if (substr(attrs["endDate"], 1, 4) >= 2019) {

          # if we find them, add them to the list (note the 

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’re eating about ~500 MiB of RAM with that call), and it took around 34 painful seconds to process the XML file.

Now, we’ll use {data.table} to tidy it up:

records 

That took around 4.5 seconds, and when the R garbage collector kicks in we’re now consuming ~695 MiB, so not much more than the previous step.

So, ~38s for the ingestion & conversion, and a maximum of ~695 MiB in play at any time during the R session. Let’s see how the new/modern way (i.e. {xml2}) compares.

Modern 💙

A menos que eu tenha perdido algo na página de índice {xml2}, não há processador de streaming equivalente, então temos que ler todo o documento na RAM ativa:

biblioteca (xml2) registros da biblioteca (tidyverse) 

Esta operação leva 15,7s e a sessão R agora consome ~ 5,8 GiB de RAM. Isso é um “G”, como em gigabyte.

Agora, encontraremos todos os registros de nosso interesse (como acima). Faremos isso por meio de um modesto seletor XPath:

xml_find_all (registros, xpath = ".//Record[
         @type="HKQuantityTypeIdentifierHeartRate" and
         (starts-with(@endDate, '2019') or 
          starts-with(@endDate, '2020') or 
          starts-with(@endDate, '2021'))
      ]") -> registros

Essa operação demorou cerca de 6,5s e ainda estamos consumindo cerca de 6,23 GiB de RAM.

Agora, vamos arrumar isso:

tibble(
  ts = records %>% 
    xml_attr("endDate") %>% 
    as.POSIXct(format = "%Y-%m-%d %H:%M:%S %z"),  
  rate = records %>% 
    xml_attr("value") %>% 
    as.integer()
) -> records

records
## # A tibble: 734,530 x 2
##    ts                   rate
##                  
##  1 2019-02-12 15:19:54    69
##  2 2019-02-12 15:26:11    90
##  3 2019-02-12 15:31:33    92
##  4 2019-02-12 15:34:24    89
##  5 2019-02-12 15:57:33   120
##  6 2019-02-12 15:44:09    80
##  7 2019-02-12 16:03:24   110
##  8 2019-02-12 16:13:08   118
##  9 2019-02-12 16:08:10   100
## 10 2019-02-12 16:15:04    95
## # … with 734,520 more rows

Isso levou cerca de 10,4s e, depois que a coleta de lixo acontece, estamos de volta a um consumo muito mais razoável de ~ 890 MiB de RAM após um fluxo de trabalho máximo de 6 GiB, levando um total de ~ 32,6 segundos.

FIM 💙

Se / quando a memória estiver apertada, é bom ter algumas alternativas além de “pegar uma caixa maior”, e esta é uma abordagem (existem outras) para realizar este tipo de cirurgia XML em R.

Fique seguro / forte, pessoal.



[ad_2]

cupom com desconto - o melhor site de cupom de desconto cupomcomdesconto.com.br
Leia Também  Entrevista com Nontsikelelo Shongwe, co-organizador do Eswatini UseR Meetup Group