Skip to content

Commit e350991

Browse files
authored
Merge pull request #97 from ClojureCivitas/hrv-for-macroexpand-noj
Hrv for macroexpand noj
2 parents 30f9c69 + 27c17ea commit e350991

File tree

2 files changed

+595
-165
lines changed

2 files changed

+595
-165
lines changed

site/data_analysis/heart_rate_variability/exploring_heart_rate_variability.qmd

Lines changed: 466 additions & 92 deletions
Large diffs are not rendered by default.

src/data_analysis/heart_rate_variability/exploring_heart_rate_variability.clj

Lines changed: 129 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
[java-time.api :as jt]))
2828

2929

30-
;; # Exploring HRV
30+
;; # Exploring HRV - DRAFT 🛠
3131

3232
(ns data-analysis.heart-rate-variability.exploring-heart-rate-variability
3333
(:require [tech.v3.datatype :as dtype]
@@ -75,8 +75,7 @@
7575
(plotly/layer-bar {:=y :ppi})))
7676

7777

78-
79-
(def compute-spectrograms
78+
(def compute-measures
8079
(fn [ppi-ds {:keys [sampling-rate
8180
window-size-in-sec ]}]
8281
(let [spline (interp/interpolation
@@ -96,13 +95,23 @@
9695
hop-size 8
9796
n-windows (int (/ (- n window-size)
9897
hop-size))
99-
spectrograms (->> (range n-windows)
100-
(pfor/pmap (fn [w]
101-
(let [start-idx (* w hop-size)
102-
window (-> resampled-ppi
103-
:ppi
104-
(dtype/sub-buffer start-idx window-size))
105-
window-standardized (stats/standardize window)
98+
windows (->> (range n-windows)
99+
(map (fn [w]
100+
[w (let [start-idx (* w hop-size)]
101+
(-> resampled-ppi
102+
:ppi
103+
(dtype/sub-buffer start-idx window-size)))])))
104+
rmssds (->> windows
105+
(map (fn [[w window]]
106+
(-> window
107+
(tcc/shift 1)
108+
(tcc/- window)
109+
tcc/sq
110+
tcc/mean
111+
tcc/sqrt))))
112+
spectrograms (->> windows
113+
(pfor/pmap (fn [[w window]]
114+
(let [window-standardized (stats/standardize window)
106115
window-filtered (.bandPassFilter bw
107116
(double-array window-standardized)
108117
4
@@ -117,14 +126,14 @@
117126
vec)]
118127
{:sampling-rate sampling-rate
119128
:resampled-ppi resampled-ppi
129+
:rmssds rmssds
120130
:spectrograms spectrograms})))
121131

122132

123133
(comment
124-
(compute-spectrograms my-ppi
125-
{:sampling-rate 10
126-
:window-size-in-sec 60}))
127-
134+
(compute-measures my-ppi
135+
{:sampling-rate 10
136+
:window-size-in-sec 60}))
128137

129138

130139
;; [An Overview of Heart Rate Variability Metrics and Norms](https://pmc.ncbi.nlm.nih.gov/articles/PMC5624990/)
@@ -145,63 +154,70 @@
145154
tcc/sum))))
146155

147156

148-
149-
(defn plot-with-power-spectrum [{:keys [sampling-rate
150-
resampled-ppi
151-
spectrograms]}]
157+
(defn plot-with-measures [{:keys [sampling-rate
158+
resampled-ppi
159+
rmssds
160+
spectrograms]}]
152161
(when spectrograms
153-
(kind/fragment
154-
(let [n (-> spectrograms first :magnitude count)
155-
Nyquist-freq (/ sampling-rate 2.0)
156-
freq-resolution (/ Nyquist-freq n)
157-
times (map (comp str :t) spectrograms)
158-
freqs (tcc/* (range n)
159-
freq-resolution)]
160-
[(-> resampled-ppi
161-
(plotly/base {:=height 300 :=width 700})
162-
(plotly/layer-bar (merge {:=x :t
163-
:=y :ppi}
164-
(when (:label resampled-ppi)
165-
{:=color :label
166-
:=color-type :nominal}))))
167-
(kind/plotly
168-
{:data [{:x times
169-
:y freqs
170-
:z (-> (mapv :magnitude spectrograms)
171-
(tensor/transpose [1 0]))
172-
:type :heatmap
173-
:showscale false}]
174-
:layout {:height 300
175-
:width 700
176-
:margin {:t 25}
177-
:xaxis {:title {:text "t"}}
178-
:yaxis {:title {:text "freq"}}}})
179-
(-> {:freq freqs
180-
:mean-power (-> spectrograms
181-
(->> (map :magnitude))
182-
tensor/->tensor
183-
(tensor/reduce-axis dfn/mean 0))}
184-
tc/dataset
185-
(plotly/base {:=height 300 :=width 700})
186-
(plotly/layer-bar {:=x :freq
187-
:=y :mean-power}))
188-
(-> {:t times
189-
:LF-to-HF (->> spectrograms
190-
(map (partial LF-to-HF freqs)))}
191-
tc/dataset
192-
(plotly/base {:=height 300 :=width 700})
193-
(plotly/layer-line {:=x :t
194-
:=y :LF-to-HF})
195-
plotly/plot
196-
(assoc-in [:layout :yaxis :range] [0 4])
197-
(assoc-in [:layout :yaxis :title] {:text "LF/HF"}))]))))
162+
(let [n (-> spectrograms first :magnitude count)
163+
Nyquist-freq (/ sampling-rate 2.0)
164+
freq-resolution (/ Nyquist-freq n)
165+
times (map (comp str :t) spectrograms)
166+
freqs (tcc/* (range n)
167+
freq-resolution)]
168+
{:resampled-ppi (-> resampled-ppi
169+
(plotly/base {:=height 300 :=width 700})
170+
(plotly/layer-bar (merge {:=x :t
171+
:=y :ppi}
172+
(when (:label resampled-ppi)
173+
{:=color :label
174+
:=color-type :nominal}))))
175+
:rmssd (-> {:t times
176+
:rmssd rmssds}
177+
tc/dataset
178+
(plotly/base {:=height 300 :=width 700})
179+
(plotly/layer-line {:=x :t
180+
:=y :rmssd})
181+
plotly/plot
182+
(assoc-in [:layout :yaxis :range] [0 0.1]))
183+
:power-spectrum (kind/plotly
184+
{:data [{:x times
185+
:y freqs
186+
:z (-> (mapv :magnitude spectrograms)
187+
(tensor/transpose [1 0]))
188+
:type :heatmap
189+
:showscale false}]
190+
:layout {:height 300
191+
:width 700
192+
:margin {:t 25}
193+
:xaxis {:title {:text "t"}}
194+
:yaxis {:title {:text "freq"}}}})
195+
:mean-power-spectrum (-> {:freq freqs
196+
:mean-power (-> spectrograms
197+
(->> (map :magnitude))
198+
tensor/->tensor
199+
(tensor/reduce-axis dfn/mean 0))}
200+
tc/dataset
201+
(plotly/base {:=height 300 :=width 700})
202+
(plotly/layer-bar {:=x :freq
203+
:=y :mean-power}))
204+
:LF-to-HF-series (-> {:t times
205+
:LF-to-HF (->> spectrograms
206+
(map (partial LF-to-HF freqs)))}
207+
tc/dataset
208+
(plotly/base {:=height 300 :=width 700})
209+
(plotly/layer-line {:=x :t
210+
:=y :LF-to-HF})
211+
plotly/plot
212+
(assoc-in [:layout :yaxis :range] [0 4])
213+
(assoc-in [:layout :yaxis :title] {:text "LF/HF"}))})))
198214

199215

200216
(delay
201217
(-> my-ppi
202-
(compute-spectrograms {:sampling-rate 10
203-
:window-size-in-sec 60})
204-
plot-with-power-spectrum))
218+
(compute-measures {:sampling-rate 10
219+
:window-size-in-sec 60})
220+
plot-with-measures))
205221

206222

207223
;; ## Analysing ECG data
@@ -327,7 +343,6 @@
327343
(tcc/shift (:t %) 1)))
328344
(tc/drop-rows [0]))))
329345

330-
331346
;; ### Plotting the PPI
332347

333348
(delay
@@ -341,22 +356,63 @@
341356
(plotly/layer-bar {:=x :t
342357
:=y :ppi})))
343358

344-
;; ## Spectrograms again
359+
;; ### Measures again
345360

346361
(def WESAD-spectrograms
347362
(memoize
348-
(fn [{:keys [ppi-params spectrogram-params]}]
363+
(fn [{:keys [ppi-params measures-params]}]
349364
(-> ppi-params
350365
extract-ppi
351-
(compute-spectrograms spectrogram-params)))))
366+
(compute-measures measures-params)))))
352367

353368

354369
(delay
355370
(-> {:ppi-params {:subject-id 5
356371
:row-interval [0 1000000]}
357-
:spectrogram-params {:sampling-rate 10
358-
:window-size-in-sec 120}}
372+
:measures-params {:sampling-rate 10
373+
:window-size-in-sec 120}}
359374
WESAD-spectrograms
360-
plot-with-power-spectrum))
375+
plot-with-measures))
376+
377+
;; ## A subject's journey
378+
379+
(def id->label
380+
[:transient, :baseline,
381+
:stress, :amusement, :meditation,
382+
:ignore :ignore :ignore])
383+
384+
(def label-intervals
385+
(memoize
386+
(fn [subject]
387+
(-> (labelled-dataset subject)
388+
:label
389+
(->> (partition-by identity)
390+
(map (fn [part]
391+
[(-> part first int id->label)
392+
(count part)])))
393+
tc/dataset
394+
(tc/rename-columns [:label :n])
395+
(tc/add-column :offset #(cons 0 (reductions + (:n %))))
396+
(tc/select-columns [:offset :n :label])))))
397+
398+
399+
(delay
400+
(label-intervals 5))
361401

362402

403+
(delay
404+
(let [subject 5]
405+
(kind/fragment
406+
(-> (label-intervals subject)
407+
(tc/select-rows #(not= (:label %) :ignore))
408+
#_(tc/select-rows #(= (:label %) :meditation))
409+
(tc/rows :as-maps)
410+
(->> (mapcat (fn [{:keys [offset n label]}]
411+
[label
412+
(try (-> {:ppi-params {:subject-id subject
413+
:row-interval [offset (+ offset n)]}
414+
:measures-params {:sampling-rate 10
415+
:window-size-in-sec 120}}
416+
WESAD-spectrograms
417+
plot-with-measures)
418+
(catch Exception e 'unavailable))])))))))

0 commit comments

Comments
 (0)