Programming Language/Python

애플워치로 HRV 데이터 추출하기

깜태 2021. 7. 2. 11:45
728x90

HRV란 Heart Rate Variability의 약자로, 심박변이도라고 말하기도 한다.

쉽게 생각하면 심장은 항상 안정적으로 뛰기 때문에 변이가 적을수록 건강한 것이 아닌가 싶겠지만,

심장은 오히려 상황에 따라 변화하면서 불규칙하게 뛰기 때문에

건강할 사람일 수록 HRV가 더 높다고 한다.

 

검색해보던 중에 애플워치를 착용하고 있던 나에게
애플워치로도 HRV를 추출할 수 있다고 해서 검색해보고 시도해봤다.

 

1. 애플워치 데이터 추출하기

 

애플워치 공식홈페이지에 따라 행동하면 건강 데이터를 추출해낼 수 있다.

https://support.apple.com/ko-kr/guide/iphone/iph27f6325b2/ios

 

애플워치에서 얻을 수 있는 생체 정보는 다음과 같다.

  1. 분당 심장박동수
  2. 휴식 시 심장박동수
  3. 운동 시 심장박동수
  4. SDNN 데이터
  5. 산소포화도
  6. 신체온도
  7. 이완시혈압
  8. 수축시혈압
  9. 호흡 주기

 

이 중 HRV와 관련된 데이터는 1, 2, 3, 4번이 있는데
1, 2, 3번 분당 심장박동수는 HRV를 측정하기엔 측정간격이 길다. (대략 5초 간격)

4번은 SDNN데이터로 HRV를 측정하도록 초점이 되어져 나온 데이터로 볼 수 있고,

위의 데이터들보다 측정주기가 짧다(1초 안팎)

 

애플워치에서 주어지는 SDNN 데이터는 후처리 되어 나온 데이터로 HRV를 얻어내는 과정은 거꾸로 계산이 필요하다.

그 과정은 bpm과 상관없이 아래의 InstantaneousBeatsPerMinute 데이터 마다 시간차를 계산한 뒤,

표준편차를 계산하는데, 이 때 노이즈가 들어가 시간 편차가 매우 큰 경우도 있다.

애플워치에서는 안좋은 예시의 데이터를 전부 사용하면 SDNN의 value 값이 나올 수 없는 것으로 보아,

시간차가 큰 데이터는 생략하거나 다른 알고리즘이 있어보인다.

 

다음은 내가 뽑은 데이터 중 일부로 좋은 예시 1개와 안 좋은 예시 1개를 골랐다.

<좋은 예시 1>

<Record type="HKQuantityTypeIdentifierHeartRateVariabilitySDNN" value="68.0652">
  <HeartRateVariabilityMetadataList>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 5:01:58.38"/>
  <InstantaneousBeatsPerMinute bpm="74" time="오후 5:01:59.19"/>
  <InstantaneousBeatsPerMinute bpm="72" time="오후 5:02:00.02"/>
  <InstantaneousBeatsPerMinute bpm="68" time="오후 5:02:00.90"/>
  <InstantaneousBeatsPerMinute bpm="62" time="오후 5:02:01.86"/>
  <InstantaneousBeatsPerMinute bpm="61" time="오후 5:02:02.85"/>
  <InstantaneousBeatsPerMinute bpm="62" time="오후 5:02:03.81"/>
  <InstantaneousBeatsPerMinute bpm="68" time="오후 5:02:06.52"/>
  <InstantaneousBeatsPerMinute bpm="71" time="오후 5:02:07.36"/>
  <InstantaneousBeatsPerMinute bpm="71" time="오후 5:02:08.20"/>
  <InstantaneousBeatsPerMinute bpm="68" time="오후 5:02:10.67"/>
  <InstantaneousBeatsPerMinute bpm="63" time="오후 5:02:11.61"/>
  <InstantaneousBeatsPerMinute bpm="63" time="오후 5:02:12.56"/>
  <InstantaneousBeatsPerMinute bpm="67" time="오후 5:02:13.45"/>
  <InstantaneousBeatsPerMinute bpm="71" time="오후 5:02:14.30"/>
  <InstantaneousBeatsPerMinute bpm="75" time="오후 5:02:15.10"/>
  <InstantaneousBeatsPerMinute bpm="80" time="오후 5:02:15.84"/>
  <InstantaneousBeatsPerMinute bpm="78" time="오후 5:02:16.62"/>
  <InstantaneousBeatsPerMinute bpm="76" time="오후 5:02:17.40"/>
  <InstantaneousBeatsPerMinute bpm="71" time="오후 5:02:18.24"/>
  <InstantaneousBeatsPerMinute bpm="68" time="오후 5:02:19.12"/>
  <InstantaneousBeatsPerMinute bpm="69" time="오후 5:02:25.47"/>
  <InstantaneousBeatsPerMinute bpm="70" time="오후 5:02:26.32"/>
  <InstantaneousBeatsPerMinute bpm="66" time="오후 5:02:27.22"/>
  <InstantaneousBeatsPerMinute bpm="63" time="오후 5:02:28.18"/>
  <InstantaneousBeatsPerMinute bpm="60" time="오후 5:02:29.17"/>
  <InstantaneousBeatsPerMinute bpm="62" time="오후 5:02:30.14"/>
  <InstantaneousBeatsPerMinute bpm="65" time="오후 5:02:31.06"/>
  <InstantaneousBeatsPerMinute bpm="67" time="오후 5:02:31.96"/>
  <InstantaneousBeatsPerMinute bpm="70" time="오후 5:02:32.81"/>
  <InstantaneousBeatsPerMinute bpm="72" time="오후 5:02:33.65"/>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 5:02:42.49"/>
  <InstantaneousBeatsPerMinute bpm="63" time="오후 5:02:43.44"/>
  <InstantaneousBeatsPerMinute bpm="60" time="오후 5:02:44.44"/>
</HeartRateVariabilityMetadataList>

 

<안 좋은 예시1>

<Record type="HKQuantityTypeIdentifierHeartRateVariabilitySDNN" value="30.0895">
  <HeartRateVariabilityMetadataList>
  <InstantaneousBeatsPerMinute bpm="76" time="오후 2:54:46.29"/>
  <InstantaneousBeatsPerMinute bpm="72" time="오후 2:55:12.66"/>
  <InstantaneousBeatsPerMinute bpm="72" time="오후 2:55:13.50"/>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 2:55:14.32"/>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 2:55:15.14"/>
  <InstantaneousBeatsPerMinute bpm="77" time="오후 2:55:15.92"/>
  <InstantaneousBeatsPerMinute bpm="75" time="오후 2:55:16.72"/>
  <InstantaneousBeatsPerMinute bpm="75" time="오후 2:55:17.52"/>
  <InstantaneousBeatsPerMinute bpm="79" time="오후 2:55:18.27"/>
  <InstantaneousBeatsPerMinute bpm="78" time="오후 2:55:19.04"/>
  <InstantaneousBeatsPerMinute bpm="77" time="오후 2:55:19.82"/>
  <InstantaneousBeatsPerMinute bpm="77" time="오후 2:55:23.74"/>
  <InstantaneousBeatsPerMinute bpm="74" time="오후 2:55:24.55"/>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 2:55:25.37"/>
  <InstantaneousBeatsPerMinute bpm="78" time="오후 2:55:26.14"/>
  <InstantaneousBeatsPerMinute bpm="80" time="오후 2:55:26.89"/>
  <InstantaneousBeatsPerMinute bpm="81" time="오후 2:55:27.63"/>
  <InstantaneousBeatsPerMinute bpm="80" time="오후 2:55:28.38"/>
  <InstantaneousBeatsPerMinute bpm="73" time="오후 2:55:29.20"/>
</HeartRateVariabilityMetadataList>

 

왜 안좋냐고 했냐면 안 좋은 데이터의 1행과 2행을 보면 시간차의 간격(2:54:46.29, 2:55:12.66)이 컸기 때문인데,

그 이유로 시간을 가지고 측정하는 HRV는 30초가 넘는 간격으로 주어지면 노이즈로 볼 수 있기 때문이다.

무튼, 위의 데이터들을 가지고 matplotlib을 이용해 RR interval을 그려봤다.

아래는 진행한 코드

 

import xml.etree.ElementTree as etree
from datetime import datetime, timedelta
import matplotlib.pyplot as plt

import numpy as np
import pandas as pd
from pandas.plotting import autocorrelation_plot
xDoc = etree.parse('export_d2.xml')
records = list(xDoc.getroot())
record_type_identifier = 'HKQuantityTypeIdentifierHeartRateVariabilitySDNN'

HRV_plot_data_list = []

for i, record in enumerate(records):
    if 'type' in record.attrib and record.attrib['type'] == record_type_identifier:
        for data_list in record:
            HRV_data_list = []
            RMSSD_list = []
            for idx, data in enumerate(data_list[:-1]):
                bpm_instance = data_list[idx].attrib['time'].replace('오전', '').replace('오후', '').strip()
                bpm_instance = datetime.strptime(bpm_instance, '%H:%M:%S.%f')
                bpm_instance2 = data_list[idx+1].attrib['time'].replace('오전', '').replace('오후', '').strip()
                bpm_instance2 = datetime.strptime(bpm_instance2, '%H:%M:%S.%f')
                RR_interval_sec = (bpm_instance2 - bpm_instance).total_seconds()
                if RR_interval_sec > 1.2:
                    continue

                RMSSD = pow(RR_interval_sec, 2) * 1000
                RMSSD_list.append(RMSSD)
                HRV_data_list.append(RR_interval_sec*1000)
                BPM_data_list.append(int(data_list[idx].attrib['bpm']))
            HRV_plot_data_list.extend(HRV_data_list)

plt.plot([x+1 for x in range(len(HRV_plot_data_list))], np.array(HRV_plot_data_list)/1000)
plt.xlabel('Samples')
plt.ylabel('RR Interval (seconds)')
plt.show()

 

728x90