Although the package ships with its own set of feature detectors. It is actually not that difficult to incorporate your own. All the shipped feature detectors are functions whose name starts with .dt
. See ?definitions
.
In principle, feature detectors are just functions which take a data.frame and return another with columns curve_id
, Cl:xxx
, and fname
, where Cl:xxx
is the clause component of the feature definition, and fname
is the machine-friendly feature name. So, for example,
library(gaitFeature)
myRandomDetector=function(x){
detected=sample(0:1,nrow(x),TRUE)
data_frame(curve_id=x$curve_id,
"Cl:random"=as.logical(detected),
Random=detected)
}
myRandomDetector(kinematics)
#> # A tibble: 180 x 3
#> curve_id `Cl:random` Random
#> <int> <lgl> <int>
#> 1 1 T 1
#> 2 2 T 1
#> 3 3 F 0
#> 4 4 F 0
#> 5 5 F 0
#> 6 6 T 1
#> 7 7 T 1
#> 8 8 F 0
#> 9 9 F 0
#> 10 10 F 0
#> # ... with 170 more rows
And you can use the detectAll
function with it.
detectAll(kinematics,fDict=data.frame(fn="myRandomDetector"))%>%
select(-(`0`:`100`))
#> # A tibble: 180 x 6
#> curve_id joint side plane ofs Random
#> <int> <chr> <chr> <chr> <dbl> <int>
#> 1 1 Pel L sag 49.0 0
#> 2 2 Pel R sag 52.0 1
#> 3 3 Hip L sag 49.0 0
#> 4 4 Hip R sag 52.0 1
#> 5 5 Knee L sag 49.0 1
#> 6 6 Knee R sag 52.0 0
#> 7 7 Ankle L sag 49.0 0
#> 8 8 Ankle R sag 52.0 0
#> 9 9 FootProgress L sag 49.0 0
#> 10 10 FootProgress R sag 52.0 0
#> # ... with 170 more rows
However, a useful feature detector will probably be more sophisticated than that. And this package provides some useful tool to construct these complicated features. But these tools assume a particular way of constructing feature detectors. In particular, a feature detector function should consist of 3 components:
A feature should only apply to a particular joint and plane, so we recommend that you explicitly incorporate a filter in the definition.
A feature definition should consists of one or more clauses. The most commonly used clause is what we called a univariate statistics clauses.
And it works as followed:
*The definition of extreme is user-definable. For example, it can be > mean + 2 standard deviation.
Step 3 above calls for a set of reference curve. This packages ships with such a set, and can be called by the getRC()
function. See also ?.rc
.
This clause is implemented in the function unistat
. See ?unistat
.
For example unistat(x,`60`:`100`, .f=mean, .dir=">", .k=2)
reads the mean angle at t in [60,100] should be greater than mean + 2SD of the same mean copmuted from the typical reference data.
.f
can be any function which returns a single number statistics. See ?.tp
for commonly used functions that ships with this package..dir
are: >(=)
, <(=)
, %w%
, and %o%
, where the last two stands for ‘within’ and ‘outside’..c=xxx
argument instead of .k=xx
.finish
A feature detector function should finish off with the finish
function, which does several house-keeping functions, such as checking all clauses are passed before the feature is deemed present, and setting various attributes appropriately.
If we look at the detector for increased ankle rotation.
.dtAIR
#> function (df)
#> {
#> df %>% filter(joint == "Ankle" & plane == "tra") %>% unistat(`0`:`60`,
#> .f = mean, .dir = ">") %>% finish(featurename = "Ankle Internal Rotation",
#> fname = "AIR")
#> }
#> <environment: namespace:gaitFeature>
We will see that,
Multiple clauses can be chained together by the pipe %>%
operator. For example,
.dtDecPelTiltIncROM
#> function (df)
#> {
#> df %>% filter(joint == "Pel" & plane == "sag") %>% unistat(`0`:`100`,
#> .f = mean, .dir = "<") %>% unistat(`0`:`100`, .f = .ROM,
#> .dir = ">", k = 2) %>% finish(featurename = "Decreased Pelvic Tilt + \n Increased ROM",
#> fname = "TiltdecAndROMinc")
#> }
#> <environment: namespace:gaitFeature>
has 2 clauses.
In addition to unistat
, we provide two more clause functions: corr
and custom
. These clauses become useful when the feature is difficult to define through unistat
.
corr
A correlation clause works as follow:
Therefore, in order to use this clause, a set of target curves needs to be supplied (using the corr(..., TC=x,...)
).
This package ships with 5 sets of target curves, which can be assessed by the .tc()
function. They are: .tc()$ett
, .tc()$uni
, .tc()$s2r
, .tc()$revrom
, and .tc()$dblbump
, which are target curves for ‘External Tibial Torsion’, ‘Uni Bump’, ‘Short 2nd rocker’, ‘Reverse ROM’, and ‘Double Bump’.
Since all these curves have domain 0:100, the same domain should be specified in the corr
function.
Straightly speaking, we could use the unistat
clause to achieve the same result. That is,
corr(...,TC=x,">=",0.8)
is equivalent to
unistat(`0`:`100`,.f=function(x)max(apply(t(.tc()$dblbump),2,cor,x)),.dir=">=",.c=0.8)
(the apply
function is only needed because we have a set of TC
. Otherwise a simple cor
will suffice.)
However, the corr
clause is just more literal and thus easier to interpret.
custom
The custom clause function custom
allows one to supply a custom condition through as a string custom(...,cond)
. It is particularly useful for clauses that require data other than the kinematics, such as timing related clauses. For example,
.dtDelayPkKnFx
#> function (df)
#> {
#> df %>% filter(joint == "Knee" & plane == "sag") %>% custom(`60`:`100`,
#> cond = "t[which.max(angle)]>75") %>% finish(featurename = "Delayed Peak Knee Flexion",
#> fname = "DlyPkKFx")
#> }
#> <environment: namespace:gaitFeature>
has a clause which says the peak angle after t=60
must occur after t=75
.
Again, straightly speaking, this clause can be expressed as a univariate clause. That is,
custom(`60`:`100`, cond="t[which.max(angle)]>75")
is equivalent to
unistat(`60`:`100`,.f=function(x)which.max(x),.dir=">",.c=75-60+1)
.
However, the unistat
version relies on the assumption that x
in .f
is sorted inn t
, whereas custom
is explicitly extracting t
.
detectAll
Suppose you have created your own detector using the steps outlined above. For illustration we simply copy the .dtAIR
detector.
myDetector=function(df){
df%>%
filter(joint=="Ankle"&plane=="tra")%>%
unistat(`0`:`60`,.f=mean,.dir=">")%>%
finish(featurename = "My Detector. Should be same as .dtAIR",fname = "MyDetector")
}
You can ask detectAll
to run it by constructing the corresponding feature dictionary.
myDict=makeFDict("myDetector")
kinematics%>%
filter(joint=="Ankle",plane=="tra")%>%
detectAll(fDict=myDict)%>%
select(-(`0`:`100`))
#> # A tibble: 12 x 6
#> curve_id joint side plane ofs MyDetector
#> <int> <chr> <chr> <chr> <dbl> <dbl>
#> 1 27 Ankle L tra 49.0 0
#> 2 28 Ankle R tra 52.0 0
#> 3 57 Ankle L tra 48.0 0
#> 4 58 Ankle R tra 54.0 0
#> 5 87 Ankle L tra 50.0 1.00
#> 6 88 Ankle R tra 49.0 0
#> 7 117 Ankle L tra 50.0 0
#> 8 118 Ankle R tra 50.0 0
#> 9 147 Ankle L tra 50.0 0
#> 10 148 Ankle R tra 51.0 0
#> 11 177 Ankle L tra 50.0 0
#> 12 178 Ankle R tra 49.0 0
You can also add it to an existing (e.g. the default) dictionary.
defaultDict=makeFDict(".dtAIR")
newDict=defaultDict%>%
rbind(myDict)
kinematics%>%
filter(joint=="Ankle",plane=="tra")%>%
detectAll(fDict=newDict)%>%
select(-(`0`:`100`))
#> # A tibble: 12 x 7
#> curve_id joint side plane ofs AIR MyDetector
#> <int> <chr> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 27 Ankle L tra 49.0 0 0
#> 2 28 Ankle R tra 52.0 0 0
#> 3 57 Ankle L tra 48.0 0 0
#> 4 58 Ankle R tra 54.0 0 0
#> 5 87 Ankle L tra 50.0 1.00 1.00
#> 6 88 Ankle R tra 49.0 0 0
#> 7 117 Ankle L tra 50.0 0 0
#> 8 118 Ankle R tra 50.0 0 0
#> 9 147 Ankle L tra 50.0 0 0
#> 10 148 Ankle R tra 51.0 0 0
#> 11 177 Ankle L tra 50.0 0 0
#> 12 178 Ankle R tra 49.0 0 0