Skip to content

Commit 7aa5c0c

Browse files
author
Bart
committed
add peak frequency function
1 parent e9b13ef commit 7aa5c0c

File tree

5 files changed

+171
-3
lines changed

5 files changed

+171
-3
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export(is_acc)
1414
export(is_uniform)
1515
export(n_axis)
1616
export(n_samples)
17+
export(peak_frequency)
1718
export(plot_time)
1819
import(vctrs)
1920
importFrom(stats,na.omit)

R/peak_frequency.R

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
#' Calculate the peak frequency per axis for accelration bursts
3+
#'
4+
#' @param x An `acc` vector
5+
#' @param resolution A scalar with the units Hertz
6+
#'
7+
#' @returns returns a list with the same length as `x` with the peak frequency per axis
8+
#'
9+
#' @details
10+
#'
11+
#' To increase the resolution of the result zero padding can be used. This can be controlled uing the resolution argument.
12+
#' Note that increasing resolution without increasing the number of samples in a acceleration burst has a limited ability to get closer to the true frequency.
13+
#'
14+
#' @export
15+
#'
16+
#' @examples
17+
#' a<-acc(list(cbind(z=cos(1:200/(80/(pi*2))),
18+
#' x=sin(1:200/(5/(pi*2))))), units::set_units(400,'Hz'))
19+
#' peak_frequency(a)
20+
#' peak_frequency(a, units::set_units(.25, "Hz"))
21+
#' # Increasing resolution more
22+
#' peak_frequency(a, units::set_units(.005, "Hz"))
23+
#' a<-acc(list(cbind(z=cos(80+1:200/(80/(pi*2))),
24+
#' x=sin((1:200)/(5/(pi*2))))), units::set_units(400,'Hz'))
25+
#' peak_frequency(a, units::set_units(.005, "Hz"))
26+
27+
28+
29+
peak_frequency<-function(x, resolution=NA){
30+
assertthat::assert_that(inherits(x,'acc'))
31+
s<-!is.na(x)
32+
res<-rep(list(NULL),length(x))
33+
if(all(!s)){
34+
return(res)
35+
}
36+
b<-field(x[s],"bursts")
37+
b<- purrr::map2(b,purrr::map(b, inherits,'units'), ~if(.y){units::drop_units(.x)}else{.x})
38+
b_centered<-purrr::map2(b,purrr::map(b, colMeans), ~ t((.x))-.y)
39+
if(!is.na(resolution)){
40+
to_pad<-units::drop_units(field(x[s],"frequency")/resolution)-n_samples(x[s])
41+
b_centered<-purrr::map2(b_centered, to_pad, ~cbind(.x,matrix(0, ncol=.y, nrow=nrow(.x))))
42+
}
43+
b_mod<-purrr::map(b_centered, ~ do.call(rbind,lapply(apply(.x,1, fft, simplify = F), Mod))[,1:ceiling(ncol(.x)/2), drop=F])
44+
peak<- purrr::map(b_mod, ~ apply(.x,1, which.max ))
45+
peak_freq<-purrr::pmap(list(peak,field(x[s],"frequency"),purrr::map(b_mod, ncol)), ~ (..1-1)*(..2/..3/2))
46+
res[s]<-peak_freq
47+
res
48+
}

man/peak_frequency.Rd

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
test_that("Correct frequency found", {
2+
a<-new_acc(list(matrix(sin(1:200/(50/(pi*2))))), units::set_units(200,'Hz'))
3+
expect_equal(peak_frequency(a), list(units::set_units(4,'Hz')))
4+
})
5+
6+
test_that("Multiple axis peak freq and changing freq", {
7+
a<-new_acc(list(cbind(z=cos(1:200/(100/(pi*2))),
8+
x=sin(1:200/(5/(pi*2))))), units::set_units(200,'Hz'))
9+
expect_equal(peak_frequency(a), list(units::set_units(c(z=2,x=40),'Hz')))
10+
11+
a<-new_acc(list(cbind(z=cos(1:200/(100/(pi*2))),
12+
x=sin(1:200/(5/(pi*2))))), units::set_units(100,'Hz'))
13+
expect_equal(peak_frequency(a), list(units::set_units(c(z=1,x=20),'Hz')))
14+
a<-new_acc(list(cbind(z=cos(1:200/(100/(pi*2))),
15+
x=sin(1:200/(5/(pi*2))))), units::set_units(400,'Hz'))
16+
expect_equal(peak_frequency(a), list(units::set_units(c(z=4,x=80),'Hz')))
17+
})
18+
19+
test_that("length does not influnce result", {
20+
a<-new_acc(list(cbind(z=cos(1:199/(100/(pi*2))),
21+
x=sin(1:199/(5/(pi*2))))), units::set_units(200,'Hz'))
22+
expect_equal(peak_frequency(a), list(units::set_units(c(z=2,x=40),'Hz')))
23+
a<-new_acc(list(cbind(z=cos(1:199/(100/(pi*2))),
24+
x=sin(1:199/(5/(pi*2))))), units::set_units(100,'Hz'))
25+
expect_equal(peak_frequency(a), list(units::set_units(c(z=1,x=20),'Hz')))
26+
a<-new_acc(list(cbind(z=cos(1:199/(100/(pi*2))),
27+
x=sin(1:199/(5/(pi*2))))), units::set_units(400,'Hz'))
28+
expect_equal(peak_frequency(a), list(units::set_units(c(z=4,x=80),'Hz')))
29+
})
30+
31+
test_that("Multiple axis peak freq intercept does not matter", {
32+
a<-new_acc(list(cbind(z=-3+.1*cos(1:200/(100/(pi*2))),x=3*(2+sin(1:200/(50/(pi*2)))))), units::set_units(200,'Hz'))
33+
expect_equal(peak_frequency(a), list(units::set_units(c(z=2,x=4),'Hz')))
34+
})
35+
36+
test_that("NA returns empty",{
37+
expect_equal(peak_frequency(new_acc(list(NULL), frequency = NA)), list(NULL))
38+
expect_equal(peak_frequency(new_acc(list(NULL, NULL), frequency = c(NA, NA))), list(NULL, NULL))
39+
expect_equal(peak_frequency(new_acc(list(NULL,matrix(sin(1:200/(50/(pi*2)))), NULL),
40+
frequency = units::set_units(c(NA,200, NA),'Hz'))),
41+
list(NULL,units::set_units(4,"Hz"), NULL))
42+
43+
})
44+
45+
test_that("Resolution alows to identify partial frequencies", {
46+
a<-new_acc(list(cbind(z=cos(1:200/(80/(pi*2))),
47+
x=sin(1:200/(5/(pi*2))))), units::set_units(200,'Hz'))
48+
expect_equal(peak_frequency(a),
49+
list(units::set_units(c(z=3,x=40),'Hz')))
50+
expect_equal(peak_frequency(a, resolution = units::set_units(.5,'Hz')),
51+
list(units::set_units(c(z=2.5,x=40),'Hz')))
52+
expect_equal(peak_frequency(a, resolution = units::set_units(.25,'Hz')),
53+
list(units::set_units(c(z=2.5,x=40),'Hz')))
54+
})
55+
56+
57+
test_that("Resolution alows to identify partial frequencies", {
58+
a<-new_acc(list(matrix(runif(100), ncol=10)), units::set_units(23,'Hz'))
59+
expect_equal((((unlist(peak_frequency(a, resolution = units::set_units(.005,'Hz')))/.005)+.5)%%1)-.5,
60+
rep(0,10) )
61+
expect_equal((((unlist(peak_frequency(a, resolution = units::set_units(.025,'Hz')))/.025)+.5)%%1)-.5,
62+
rep(0,10) )
63+
# expect_equal((((unlist(peak_frequency(a, resolution = units::set_units(.035,'Hz')))/.035)+.5)%%1)-.5,
64+
# rep(0,10) )
65+
a<-new_acc(list(matrix(runif(1000), ncol=10)), units::set_units(23,'Hz'))
66+
expect_equal((((unlist(peak_frequency(a, resolution = units::set_units(.005,'Hz')))/.005)+.5)%%1)-.5,
67+
rep(0,10) )
68+
expect_equal((((unlist(peak_frequency(a, resolution = units::set_units(.025,'Hz')))/.025)+.5)%%1)-.5,
69+
rep(0,10) )
70+
71+
})
72+

vignettes/moveAcc.Rmd

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ The same can be done with ornitella data.
8383
```{r ornitella}
8484
d <- as.Date("2021-3-3")
8585
86-
lbbg_data <- move2::movebank_download_study("LBBG_ZEEBRUGGE - Lesser black-backed gulls ",
86+
lbbg_data <- movebank_download_study("LBBG_ZEEBRUGGE - Lesser black-backed gulls ",
8787
sensor_type_id = c("gps", "acceleration"),
8888
timestamp_start = as.POSIXct(d),
8989
timestamp_end = as.POSIXct(d + 1)
9090
)
9191
lbbg_data <- lbbg_data %>%
92-
mutate(acceleration = as_acc(.)) %>%
93-
filter(!is.na(acceleration) | sensor_type_id==653)%>%
92+
mutate(acceleration = as_acc(.)) %>%
93+
filter(!is.na(acceleration) | sensor_type_id==653) %>%
9494
select(acceleration)
9595
lbbg_data
9696
```
@@ -113,6 +113,19 @@ Alternatively you can plot one burst directly, in this case showing the clear wi
113113
```{r plot_one, fig.width=6}
114114
plot_time(lbbg_data$acceleration[340], mt_time(lbbg_data)[340])
115115
```
116+
117+
Using the `peak_frequency` function we can identify the wing beat frequency in this acceleration burst.
118+
```{r pf}
119+
peak_frequency(lbbg_data$acceleration[340])
120+
```
121+
122+
Due to the nature of the fast fourier transform there is a limit to the frequencies that can be identified. To resolve this zero padding can be used, this is controlled by the resolution argument. By setting this a more detailed estimate of the wing beat frequency can be obtained.
123+
124+
```{r pf_pad}
125+
peak_frequency(lbbg_data$acceleration[340],
126+
resolution = units::set_units(.01,"Hz"))
127+
```
128+
116129
We can also visualize one burst in three dimensions showing the cyclic patterns in the acceleration.
117130

118131
```{r plot3d, fig.width=6, fig.height=6}

0 commit comments

Comments
 (0)