| Title: | Muscle Near-Infrared Spectroscopy Processing and Analysis |
|---|---|
| Description: | Read, process, and analyse data from muscle near-infrared spectroscopy (mNIRS) devices. Import raw data from .csv or .xls(x) files and return time-series data and metadata. Includes standardised methods for cleaning, filtering, and pre-processing mNIRS data for subsequent analysis. Also includes a custom plot theme and colour palette. Intended for mNIRS researchers and practitioners in exercise physiology, sports science, and clinical rehabilitation with minimal coding experience required. |
| Authors: | Jem Arnold [aut, cre, cph] (ORCID: <https://orcid.org/0000-0003-3908-9447>) |
| Maintainer: | Jem Arnold <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.6.5 |
| Built: | 2026-06-03 03:08:25 UTC |
| Source: | https://github.com/jemarnold/mnirs |
Exported from Artinis Oxysoft, recorded on Oxymon MKIII at 50 Hz and exported at 10 Hz. Containing two 5-minute cycling work intervals and an ischaemic occlusion, placed on the vastus lateralis muscle site.
.xlsx file with header metadata and five columns and 20919 rows:
Sample index (divide by sample rate for seconds).
O2Hb: oxyhaemoglobin concentration change (M).
HHb: deoxyhaemoglobin concentration change (M).
Event marker (character).
Unmarked event label (character).
Channel mapping for read_mnirs():
nirs_channels = c(O2Hb = 2, HHb = 3)
time_channel = c(sample = 1)
event_channel = c(event = 4, label = "col_5")
interval_times = list(start = c(158, 999, 1750) end = c(493, 1333, 1961)) two intervals and post-exercise occlusion
Artinis Medical Systems. Oxymon MKIII, exported via Oxysoft desktop software (https://artinis.com/)
example_mnirs("artinis_intervals")example_mnirs("artinis_intervals")
Pretty time span breaks for plotting in units of 5, 15, 30, 60 sec, etc.
Modified from scales::breaks_timespan().
breaks_timespan(unit = c("secs", "mins", "hours", "days", "weeks"), n = 5)breaks_timespan(unit = c("secs", "mins", "hours", "days", "weeks"), n = 5)
unit |
The time unit used to interpret numeric data input (defaults to "secs"). |
n |
Desired number of breaks. You may get slightly more or fewer breaks than requested. |
Returns a function for generating breaks.
x <- 0:120 y <- sin(2 * pi * x / 15) + rnorm(length(x), 0, 0.2) ggplot2::ggplot(data.frame(x, y), ggplot2::aes(x, y)) + theme_mnirs() + ggplot2::scale_x_continuous(breaks = breaks_timespan()) + ggplot2::geom_line()x <- 0:120 y <- sin(2 * pi * x / 15) + rnorm(length(x), 0, 0.2) ggplot2::ggplot(data.frame(x, y), ggplot2::aes(x, y)) + theme_mnirs() + ggplot2::scale_x_continuous(breaks = breaks_timespan()) + ggplot2::geom_line()
Helper functions to define interval start or end boundaries for
extract_intervals().
by_time(...) by_label(..., ignore_case = FALSE, fixed = FALSE) by_lap(...) by_sample(...)by_time(...) by_label(..., ignore_case = FALSE, fixed = FALSE) by_lap(...) by_sample(...)
... |
Specify start or end boundaries.
|
ignore_case |
For |
fixed |
For |
These helpers can be used explicitly for arguments start/end, or raw
values can be passed directly:
Numeric -> by_time()
Character -> by_label(),
Explicit integer (e.g. 2L) -> by_lap().
Use by_sample() explicitly for sample indices.
An object of class "mnirs_interval" for use with the start
and end arguments of extract_intervals().
## read example data data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c( smo2_left = "SmO2 unfiltered", smo2_right = "SmO2 unfiltered" ), time_channel = c(time = "Timestamp (seconds passed)"), event_channel = c(lap = "Lap/Event"), zero_time = TRUE, verbose = FALSE ) ## start and end by time extract_intervals(data, start = by_time(66), end = by_time(357)) ## start by lap extract_intervals(data, start = by_lap(2, 4), span = 0) ## introduce event_channel with "start" string data$event <- NA_character_ data$event[1000] <- "start" data <- create_mnirs_data(data, event_channel = "event") ## start by label, end by time extract_intervals(data, start = by_label("start"), end = by_time(1500)) ## case-insensitive label match extract_intervals(data, start = by_label("START", ignore_case = TRUE)) ## literal-string label match (regex metacharacters treated as text) data$event[1000] <- "lap.1" data <- create_mnirs_data(data, event_channel = "event") extract_intervals(data, start = by_label("lap.1", fixed = TRUE)) ## multiple intervals by sample index extract_intervals(data, start = by_sample(1000, 1500))## read example data data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c( smo2_left = "SmO2 unfiltered", smo2_right = "SmO2 unfiltered" ), time_channel = c(time = "Timestamp (seconds passed)"), event_channel = c(lap = "Lap/Event"), zero_time = TRUE, verbose = FALSE ) ## start and end by time extract_intervals(data, start = by_time(66), end = by_time(357)) ## start by lap extract_intervals(data, start = by_lap(2, 4), span = 0) ## introduce event_channel with "start" string data$event <- NA_character_ data$event[1000] <- "start" data <- create_mnirs_data(data, event_channel = "event") ## start by label, end by time extract_intervals(data, start = by_label("start"), end = by_time(1500)) ## case-insensitive label match extract_intervals(data, start = by_label("START", ignore_case = TRUE)) ## literal-string label match (regex metacharacters treated as text) data$event[1000] <- "lap.1" data <- create_mnirs_data(data, event_channel = "event") extract_intervals(data, start = by_label("lap.1", fixed = TRUE)) ## multiple intervals by sample index extract_intervals(data, start = by_sample(1000, 1500))
Manually add class "mnirs" and metadata to an existing data frame.
create_mnirs_data(data, ...)create_mnirs_data(data, ...)
data |
A data frame with existing metadata (accessed with
|
... |
Additional arguments with metadata to add to the data frame. Can be either seperate named arguments or a list of named values.
|
Typically will only be called internally, but can be used to inject mnirs metadata into any data frame.
A tibble of class "mnirs". Metadata are stored
as attributes and can be accessed with attributes(data).
data <- data.frame( A = 1:3, B = seq(10, 30, 10), C = seq(11, 33, 11) ) attributes(data) ## inject metadata nirs_data <- create_mnirs_data( data, nirs_channels = c("B", "C"), time_channel = "A", sample_rate = 1 ) attributes(nirs_data)data <- data.frame( A = 1:3, B = seq(10, 30, 10), C = seq(11, 33, 11) ) attributes(data) ## inject metadata nirs_data <- create_mnirs_data( data, nirs_channels = c("B", "C"), time_channel = "A", sample_rate = 1 ) attributes(nirs_data)
Get path to mnirs example files
example_mnirs(file = NULL)example_mnirs(file = NULL)
file |
Name of file as character string. If |
A file path character string for selected example files stored in this package.
## lists all files example_mnirs() ## partial matching will error if matches multiple try(example_mnirs("moxy")) example_mnirs("moxy_ramp")## lists all files example_mnirs() ## partial matching will error if matches multiple try(example_mnirs("moxy")) example_mnirs("moxy_ramp")
Extract intervals from "mnirs" time series data, specifying interval start and end boundaries by time value, event label, lap number, or sample index.
extract_intervals( data, nirs_channels = NULL, time_channel = NULL, event_channel = NULL, sample_rate = NULL, start = NULL, end = NULL, span = list(c(-60, 60)), event_groups = c("distinct", "ensemble"), zero_time = FALSE, verbose = TRUE )extract_intervals( data, nirs_channels = NULL, time_channel = NULL, event_channel = NULL, sample_rate = NULL, start = NULL, end = NULL, span = list(c(-60, 60)), event_groups = c("distinct", "ensemble"), zero_time = FALSE, verbose = TRUE )
data |
A data frame of class "mnirs" containing time series data and metadata. |
nirs_channels |
A character vector or a
|
time_channel |
A character string naming the time or sample column.
Must match a column name in
|
event_channel |
An optional character string giving the name of an event/lap column. The column may contain character event labels or integer lap numbers.
|
sample_rate |
An optional numeric sample rate (Hz) used to bin time
values for ensemble-averaging. If |
start |
Specifies where intervals begin. Either raw values – numeric
for time values, character for event labels, explicit integer (e.g. |
end |
Specifies where intervals end. Either raw values – numeric for
time values, character for event labels, explicit integer (e.g. |
span |
A one- or two-element numeric vector
|
event_groups |
Either a character string or a
|
zero_time |
Logical. Default is |
verbose |
Logical. Default is |
Interval start and end boundaries are specified using helper functions,
or by passing raw values directly:
by_time()Time values in units of time_channel.
by_label()Strings to match in event_channel.
All matching occurrences are returned.
by_lap()Lap numbers to match in event_channel. Resolves to the
first sample of each lap for start, and the last lap sample for end
by_sample()Integer sample indices (row numbers).
Raw values supplied to start/end are auto-coerced:
Numeric -> by_time()
Character -> by_label(),
Explicit integer (e.g. 2L) -> by_lap().
Use by_sample() explicitly for sample indices.
start and end can use different specification types (e.g., start by
label, end by time). When lengths differ, the shorter is recycled.
span additively expands the time span window around interval boundaries.
A two-value vector expands the start and end, respectively:
span = c(-60, 60) expands the start earlier by 60, and the end
later by 60. For example,
start = by_time(30), end = by_time(60), span = c(-5, 10) returns an
interval of [25, 70].
A single numeric value is recycled according to the sign: span = -60
becomes c(-60, 0) to expand the start earlier. span = 60 becomes
c(0, 60) to expand the end later.
If only start is specified alone, both span values expand the single
boundary window: start = by_time(30), span = c(-5, 60) returns
[25, 90].
When event_groups = "ensemble" or a list of numeric grouped intervals,
nirs_channels can be specified as a list of column names to override
ensemble-averaging across interval. For example, to exclude a channel
in one interval:
nirs_channels = list( c(A, B, C), c(A, C) ## channel "B" is excluded )
If all grouped intervals can include all nirs_channels, or if
event_groups = "distinct", a single nirs_channels character vector can
be supplied and recycled to all groups, or left as NULL for channels to
be taken from "mnirs" metadata.
event_groups controls whether extracted intervals are returned as distinct
data frames or ensemble-averaged.
"distinct"The default. Extract each interval and return a list of independent data frames.
"ensemble"Ensemble-average each specified nirs_channel across
all detected intervals and return a one-item list with a single data
frame.
list(c(1, 2), c(3, 4))Ensemble-average each specified
nirs_channel within each group and return a list with one data frame
for each group. Any intervals detected but not specified in
event_groups are returned as distinct.
event_groups lists can be named (e.g.
list(low = c(1, 2), high = c(3, 4))) and will pass those names to the
returned list of data frames.
When event_groups is a list of numeric interval numbers, list items in
nirs_channels and span are recycled to the number of groups. If lists
are only partially specified, the final item is recycled forward as needed.
Extra items are ignored.
A named list() of tibbles of class
"mnirs", each with metadata available via attributes().
## read example data data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c( smo2_left = "SmO2 unfiltered", smo2_right = "SmO2 unfiltered" ), time_channel = c(time = "Timestamp (seconds passed)"), zero_time = TRUE, verbose = FALSE ) |> ## avoid issues ensemble-averaging irregular samples resample_mnirs(method = "linear", verbose = FALSE) ## ensemble-average across multiple intervals interval_list <- extract_intervals( data, ## channels recycled to all intervals by default nirs_channels = c(smo2_left, smo2_right), start = by_time(368, 1084), ## manually identified interval start times span = c(-20, 90), ## include the last 180-sec of each interval (recycled) event_groups = "ensemble", ## ensemble-average across two intervals zero_time = TRUE ## re-calculate common time to start from `0` ) interval_list[[1L]] if (requireNamespace("ggplot2", quietly = TRUE)) { plot(interval_list[[1L]], time_labels = TRUE) + ggplot2::geom_vline(xintercept = 0, linetype = "dotted") }## read example data data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c( smo2_left = "SmO2 unfiltered", smo2_right = "SmO2 unfiltered" ), time_channel = c(time = "Timestamp (seconds passed)"), zero_time = TRUE, verbose = FALSE ) |> ## avoid issues ensemble-averaging irregular samples resample_mnirs(method = "linear", verbose = FALSE) ## ensemble-average across multiple intervals interval_list <- extract_intervals( data, ## channels recycled to all intervals by default nirs_channels = c(smo2_left, smo2_right), start = by_time(368, 1084), ## manually identified interval start times span = c(-20, 90), ## include the last 180-sec of each interval (recycled) event_groups = "ensemble", ## ensemble-average across two intervals zero_time = TRUE ## re-calculate common time to start from `0` ) interval_list[[1L]] if (requireNamespace("ggplot2", quietly = TRUE)) { plot(interval_list[[1L]], time_labels = TRUE) + ggplot2::geom_vline(xintercept = 0, linetype = "dotted") }
Apply a Butterworth digital filter to vector data with signal::butter()
and signal::filtfilt() which handles 'edges' better at the start and end
of the data.
filter_butter( x, order = 2L, W, type = c("low", "high", "stop", "pass"), edges = c("rev", "rep1", "none"), na.rm = FALSE, ... )filter_butter( x, order = 2L, W, type = c("low", "high", "stop", "pass"), edges = c("rev", "rep1", "none"), na.rm = FALSE, ... )
x |
A numeric vector. |
order |
An integer defining the filter order (default |
W |
A one- or two-element numeric vector defining the filter cutoff frequency(ies) as a fraction of the Nyquist frequency (see Details). |
type |
A character string indicating the digital filter type (see Details).
|
edges |
A character string indicating edge detection padding for
|
na.rm |
Logical; default is |
... |
Additional method-specific arguments must be specified (see Details). |
Applies a centred (two-pass symmetrical) Butterworth digital filter from
signal::butter() and signal::filtfilt().
Filter type defines how the desired signal frequencies are either
passed or rejected from the output signal. Low-pass and high-pass
filters allow only frequencies lower or higher than the cutoff
frequency W to be passed through as the output signal, respectively.
Stop-band defines a critical range of frequencies which are rejected
from the output signal. Pass-band defines a critical range of
frequencies which are passed through as the output signal.
The filter order (number of passes) is defined by order, typically in
the range order = [1, 10]. Higher filter order tends to capture more
rapid changes in amplitude, but also causes more distortion around
those change points in the signal. General advice is to use the
lowest filter order which sufficiently captures the desired rapid
responses in the data.
The critical (cutoff) frequency is defined by W, a numeric value for
low-pass and high-pass filters, or a two-element vector
c(low, high) defining the lower and upper bands for stop-band and
pass-band filters. W represents the desired fractional cutoff
frequency in the range W = [0, 1], where 1 is the Nyquist
frequency, i.e., half the sample rate of the data in Hz.
Missing values (NA) in x will cause an error unless na.rm = TRUE.
Then NAs will be ignored and passed through to the returned vector.
A numeric vector the same length as x.
signal::filtfilt(), signal::butter()
set.seed(13) sin <- sin(2 * pi * 1:150 / 50) * 20 + 40 noise <- rnorm(150, mean = 0, sd = 6) noisy_sin <- sin + noise without_edge_detection <- filter_butter( x = noisy_sin, order = 2, W = 0.1, edges = "none" ) with_edge_detection <- filter_butter( x = noisy_sin, order = 2, W = 0.1, edges = "rep1" ) ggplot2::ggplot(data.frame(), ggplot2::aes(x = seq_along(noise))) + theme_mnirs() + scale_colour_mnirs(name = NULL) + ggplot2::geom_line(ggplot2::aes(y = noisy_sin)) + ggplot2::geom_line( ggplot2::aes(y = without_edge_detection, colour = "without_edge_detection") ) + ggplot2::geom_line( ggplot2::aes(y = with_edge_detection, colour = "with_edge_detection") )set.seed(13) sin <- sin(2 * pi * 1:150 / 50) * 20 + 40 noise <- rnorm(150, mean = 0, sd = 6) noisy_sin <- sin + noise without_edge_detection <- filter_butter( x = noisy_sin, order = 2, W = 0.1, edges = "none" ) with_edge_detection <- filter_butter( x = noisy_sin, order = 2, W = 0.1, edges = "rep1" ) ggplot2::ggplot(data.frame(), ggplot2::aes(x = seq_along(noise))) + theme_mnirs() + scale_colour_mnirs(name = NULL) + ggplot2::geom_line(ggplot2::aes(y = noisy_sin)) + ggplot2::geom_line( ggplot2::aes(y = without_edge_detection, colour = "without_edge_detection") ) + ggplot2::geom_line( ggplot2::aes(y = with_edge_detection, colour = "with_edge_detection") )
Apply a simple moving average smoothing filter to vector data.
filter_moving_average() is an alias of filter_ma().
filter_ma( x, t = seq_along(x), width = NULL, span = NULL, partial = FALSE, na.rm = FALSE, verbose = TRUE, ... ) filter_moving_average( x, t = seq_along(x), width = NULL, span = NULL, partial = FALSE, na.rm = FALSE, verbose = TRUE, ... )filter_ma( x, t = seq_along(x), width = NULL, span = NULL, partial = FALSE, na.rm = FALSE, verbose = TRUE, ... ) filter_moving_average( x, t = seq_along(x), width = NULL, span = NULL, partial = FALSE, na.rm = FALSE, verbose = TRUE, ... )
x |
A numeric vector of the response variable. |
t |
An optional numeric vector of the predictor variable (e.g. time).
Default is |
width |
An integer defining the local window in number of samples
centred on |
span |
A numeric value defining the local window time span around
|
partial |
Logical; default is |
na.rm |
Logical; default is |
verbose |
Logical. Default is |
... |
Additional arguments. |
Applies a centred (symmetrical) moving average filter in a local
window, defined by either width as the number of samples around
idx between [idx - floor(width/2), idx + floor(width/2)]. Or by
span as the timespan in units of time_channel between
[t - span/2, t + span/2].
The default partial = FALSE requires a complete number of samples
specified by width or span (estimated from the sample rate of t when
span is used). NA is returned if fewer samples are present in the
local window.
Setting partial = TRUE allows computation with only a single valid sample,
such as at edge conditions. But these values will be more sensitive to
noise and should be used with caution.
na.rm controls whether missing values (NAs) within each local window are
either propagated to the returned vector when na.rm = FALSE (the default),
or ignored before processing if na.rm = TRUE.
A numeric vector the same length as x.
x <- c(1, 3, 2, 5, 4, 6, 5, 7) t <- c(0, 1, 2, 4, 5, 6, 7, 10) ## irregular time with gaps ## width: centred window of 3 samples filter_ma(x, width = 3) ## partial = TRUE fills edge values with a narrower window filter_ma(x, width = 3, partial = TRUE) ## span: centred window of 2 time-units (accounts for irregular sampling) filter_ma(x, t, span = 2) ## na.rm = FALSE (default): any NA in the window propagates to the result x_na <- c(1, NA, 3, 4, 5, NA, 7, 8) filter_ma(x_na, width = 3, na.rm = FALSE) ## na.rm = TRUE: skip NAs and return the local mean of local valid values filter_ma(x_na, width = 3, partial = TRUE, na.rm = TRUE)x <- c(1, 3, 2, 5, 4, 6, 5, 7) t <- c(0, 1, 2, 4, 5, 6, 7, 10) ## irregular time with gaps ## width: centred window of 3 samples filter_ma(x, width = 3) ## partial = TRUE fills edge values with a narrower window filter_ma(x, width = 3, partial = TRUE) ## span: centred window of 2 time-units (accounts for irregular sampling) filter_ma(x, t, span = 2) ## na.rm = FALSE (default): any NA in the window propagates to the result x_na <- c(1, NA, 3, 4, 5, NA, 7, 8) filter_ma(x_na, width = 3, na.rm = FALSE) ## na.rm = TRUE: skip NAs and return the local mean of local valid values filter_ma(x_na, width = 3, partial = TRUE, na.rm = TRUE)
Apply digital filtering/smoothing to numeric vector data within a data frame using either:
A cubic smoothing spline.
A Butterworth digital filter.
A simple moving average.
filter_mnirs( data, nirs_channels = NULL, time_channel = NULL, method = c("smooth_spline", "butterworth", "moving_average"), na.rm = FALSE, verbose = TRUE, ... )filter_mnirs( data, nirs_channels = NULL, time_channel = NULL, method = c("smooth_spline", "butterworth", "moving_average"), na.rm = FALSE, verbose = TRUE, ... )
data |
A data frame of class "mnirs" containing time series data and metadata. |
nirs_channels |
A character vector giving the names of mNIRS columns to
operate on. Must match column names in
|
time_channel |
A character string naming the time or sample column.
Must match a column name in
|
method |
A character string indicating how to filter the data (see Details).
|
na.rm |
Logical; default is |
verbose |
Logical. Default is |
... |
Additional method-specific arguments must be specified (see Details). |
Aliases: method = c("smooth spline", "spline")
Applies a non-parametric cubic smoothing spline from
stats::smooth.spline(). Smoothing is defined by the parameter spar,
which can be left as NULL and automatically determined via penalised
log likelihood. This usually works well for responses occurring on the
order of minutes or longer. spar can be specified typically, but not
necessarily, in the range spar = [0, 1].
Additional arguments (...) accepted when method = "smooth_spline":
sparA numeric smoothing parameter passed to
stats::smooth.spline(). If NULL (default), automatically
determined via penalised log likelihood.
Aliases: method = c("butter")
Applies a centred (two-pass symmetrical) Butterworth digital filter
from signal::butter() and signal::filtfilt().
Filter type defines how the desired signal frequencies are either
passed or rejected from the output signal. Low-pass and high-pass
filters allow only frequencies lower or higher than the cutoff
frequency, respectively to be passed through to the output signal.
Stop-band defines a critical range of frequencies which are rejected
from the output signal. Pass-band defines a critical range of
frequencies which are passed through as the output signal.
The filter order (number of passes) is defined by order, typically
in the range order = [1, 10]. Higher filter order tends to capture
more rapid changes in amplitude, but also causes more distortion
around those change points in the signal. General advice is to use
the lowest filter order which sufficiently captures the desired rapid
responses in the data.
The critical (cutoff) frequency can be defined by W, a numeric value
for low-pass and high-pass filters, or a two-element vector
c(low, high) defining the lower and upper bands for stop-band
and pass-band filters. W represents the desired fractional cutoff
frequency in the range W = [0, 1], where 1 is the Nyquist
frequency, i.e., half the sample_rate of the data in Hz.
Alternatively, the cutoff frequency can be defined by fc and
sample_rate together. fc represents the desired cutoff frequency
directly in Hz, and sample_rate is the sample rate of the recorded data
in Hz. Where W = fc / (sample_rate / 2).
Only one of either W or fc should be defined. If both are
defined, W will be preferred over fc.
Additional arguments (...) accepted when method = "butterworth":
orderAn integer for the filter order (default 2).
WA numeric fractional cutoff frequency within [0, 1]. One
of either W or fc must be specified.
fcA numeric absolute cutoff frequency in Hz. Used with
sample_rate to compute W.
sample_rateA numeric sample rate in Hz. Will be taken from
metadata or estimated from time_channel if not defined.
typeA character string specifying filter type, one of:
c("low", "high", "stop", "pass") ("low" is the default).
edgesA character string specifying the edge padding, one of:
c("rev", "rep1", "none") ("rev" is the default).
See filter_butter().
Aliases: method = c("moving average", "ma")
Applies a centred (symmetrical) moving average filter in a local
window, defined by either width as the number of samples around
idx between [idx - floor(width/2), idx + floor(width/2)]. Or by
span as the timespan in units of time_channel between
[t - span/2, t + span/2].
Additional arguments (...) accepted when method = "moving_average":
width or span
Either an integer number of samples, or a
numeric time duration in units of time_channel within the local
window. One of either width or span must be specified.
partialLogical; FALSE by default, only returns values
where a full window of valid (non-NA) samples are available.
If TRUE, ignores NA and allows calculation over partial windows
at the edges of the data.
Missing values (NA) in nirs_channels will cause an error for
method = "smooth_spline" or "butterworth", unless na.rm = TRUE.
Then NAs will be ignored and passed through to the returned data.
For method = "moving_average", na.rm controls whether NAs within
each local window are either propagated to the returned vector when
na.rm = FALSE (the default), or ignored before processing if
na.rm = TRUE.
A tibble of class "mnirs" with metadata
available with attributes().
## read example data and clean for outliers data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> replace_mnirs( invalid_values = c(0, 100), outlier_cutoff = 3, width = 7, verbose = FALSE ) data data_filtered <- filter_mnirs( data, ## blank channels will be retrieved from metadata method = "butterworth", ## Butterworth digital filter is a common choice order = 2, ## filter order number W = 0.02, ## filter fractional critical frequency `[0, 1]` type = "low", ## specify a "low-pass" filter na.rm = TRUE ## explicitly ignore NAs ) ## note the smoothed `smo2` values data_filtered if (requireNamespace("ggplot2", quietly = TRUE)) { ## plot filtered data and add the raw data back to the plot to compare plot(data_filtered, time_labels = TRUE) + ggplot2::geom_line( data = data, ggplot2::aes(y = smo2, colour = "smo2"), alpha = 0.4 ) }## read example data and clean for outliers data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> replace_mnirs( invalid_values = c(0, 100), outlier_cutoff = 3, width = 7, verbose = FALSE ) data data_filtered <- filter_mnirs( data, ## blank channels will be retrieved from metadata method = "butterworth", ## Butterworth digital filter is a common choice order = 2, ## filter order number W = 0.02, ## filter fractional critical frequency `[0, 1]` type = "low", ## specify a "low-pass" filter na.rm = TRUE ## explicitly ignore NAs ) ## note the smoothed `smo2` values data_filtered if (requireNamespace("ggplot2", quietly = TRUE)) { ## plot filtered data and add the raw data back to the plot to compare plot(data_filtered, time_labels = TRUE) + ggplot2::geom_line( data = data, ggplot2::aes(y = smo2, colour = "smo2"), alpha = 0.4 ) }
Convert numeric time span data to h:mm:ss format for pretty plotting.
Inspired by ggplot2::scale_x_time().
format_hmmss(x)format_hmmss(x)
x |
A numeric vector. |
If all values are less than 3600 (1 hour), then format is returned as
mm:ss. If any value is greater than 3600, format is returned as
h:mm:ss with leading zeroes.
A character vector the same length as x.
x <- 0:120 y <- sin(2 * pi * x / 15) + rnorm(length(x), 0, 0.2) ggplot2::ggplot(data.frame(x, y), ggplot2::aes(x, y)) + theme_mnirs() + ggplot2::scale_x_continuous( breaks = breaks_timespan(), labels = format_hmmss ) + ggplot2::geom_line()x <- 0:120 y <- sin(2 * pi * x / 15) + rnorm(length(x), 0, 0.2) ggplot2::ggplot(data.frame(x, y), ggplot2::aes(x, y)) + theme_mnirs() + ggplot2::scale_x_continuous( breaks = breaks_timespan(), labels = format_hmmss ) + ggplot2::geom_line()
Exported from Moxy onboard recording at 0.5 Hz no smoothing. Containing four 4-minute cycling work intervals, placed on the vastus lateralis muscle site.
.csv file with seven columns and 936 rows:
Recording date (dd-MMM format).
Recording time of day (hh:mm:ss format).
Muscle oxygen saturation, raw signal (%).
Muscle oxygen saturation, rolling average (%).
Total haemoglobin (arbitrary units).
Lap marker (integer). Not typically in use.
Session count of recordings.
Channel mapping for read_mnirs():
nirs_channels = c("SmO2 Live", "SmO2 Averaged", "THb")
time_channel = c("hh:mm:ss")
interval_times = list(start = c(124, 486, 848, 1210), end = c(364, 726, 1088, 1450))
Moxy Monitor (Fortiori Design LLC), exported via Moxy Portal App. (https://www.moxymonitor.com/)
example_mnirs("moxy_intervals")example_mnirs("moxy_intervals")
Exported from PerfPro Studio software, recorded
at 0.5 Hz no smoothing and exported at 2 Hz. Containing a
ramp incremental cycling protocol, placed on bilateral
vastus lateralis muscle sites. Intentional data errors
(outliers, invalid values, and missing NA values) have
been introduced to demonstrate mnirs cleaning
functions.
.xlsx file with five columns and 2202 rows:
Recording date (dd-MMM format).
Time of day (hh:mm:ss format).
Muscle oxygen saturation, left leg (%). Contains simulated erroneous and missing samples.
Muscle oxygen saturation, right leg (%).
Lap marker (integer).
Channel mapping for read_mnirs():
nirs_channels = c("SmO2 Live", "SmO2 Live(2)")
time_channel = c("hh:mm:ss")
event_channel = c("Lap")
interval_times = list(start = c(204, 878)) (start and end of exercise)
Moxy Monitor (Fortiori Design LLC), exported via PerfPro Studio desktop software (https://perfprostudio.com/).
example_mnirs("moxy_ramp")example_mnirs("moxy_ramp")
Custom mnirs colour palette
palette_mnirs(...)palette_mnirs(...)
... |
Either a single numeric specifying the number of colours to return, or character strings specifying colour names. If empty, all colours are returned. |
Named (when selecting by name) or unnamed character vector of hex colours.
theme_mnirs(), scale_colour_mnirs()
scales::show_col(palette_mnirs()) scales::show_col(palette_mnirs(2)) scales::show_col(palette_mnirs("red", "orange"))scales::show_col(palette_mnirs()) scales::show_col(palette_mnirs(2)) scales::show_col(palette_mnirs("red", "orange"))
Create a base plot for data frames or lists of data frames with class "mnirs".
## S3 method for class 'mnirs' plot(x, points = FALSE, time_labels = FALSE, na.omit = FALSE, ...)## S3 method for class 'mnirs' plot(x, points = FALSE, time_labels = FALSE, na.omit = FALSE, ...)
x |
Data frame or list of data frames of class "mnirs" (e.g. from
|
points |
Logical. Default is |
time_labels |
Logical. Default is |
na.omit |
Logical. Default is |
... |
Additional arguments. |
When x is a named list of "mnirs" data frames, elements are bound into a
single data frame and displayed as faceted panels via
ggplot2::facet_wrap().
Accepts some arguments in ..., such as nrow, ncol, and scales
passed to ggplot2::facet_wrap(). n.breaks overrides the default number
of y-axis breaks. breaks overrides the x-axis breaks directly.
A ggplot2 object.
data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c(smo2 = "SmO2"), time_channel = c(time = "Timestamp (seconds passed)"), verbose = FALSE ) ## plot time labels as "hh:mm:ss" plot(data, time_labels = TRUE) data_list <- extract_intervals( data, start = by_time(2452, 3168), span = c(-60, 120), verbose = FALSE ) ## plot a list of mnirs data frames as faceted panels plot(data_list, time_labels = TRUE)data <- read_mnirs( example_mnirs("train.red"), nirs_channels = c(smo2 = "SmO2"), time_channel = c(time = "Timestamp (seconds passed)"), verbose = FALSE ) ## plot time labels as "hh:mm:ss" plot(data, time_labels = TRUE) data_list <- extract_intervals( data, start = by_time(2452, 3168), span = c(-60, 120), verbose = FALSE ) ## plot a list of mnirs data frames as faceted panels plot(data_list, time_labels = TRUE)
Exported from Artinis Oxysoft, recorded on Portamon at 10 Hz on the vastus lateralis muscle. Containing two trials of repeated occlusion oxidative capacity testing, each with 17 occlusions.
.xlsx file with header metadata and six columns and 7943 rows:
Sample index (divide by sample rate for seconds).
tHb: total haemoglobin concentration change
(M).
HHb: deoxyhaemoglobin concentration change (M).
O2Hb: oxyhaemoglobin concentration change (M).
Event marker (character).
Unmarked event label (character).
Channel mapping for read_mnirs():
nirs_channels = c(THb = 2, HHb = 3, O2Hb = 4)
time_channel = c(sample = 1)
event_channel = c(event = 5, label = "col_6")
Artinis Medical Systems. Portamon, exported via Oxysoft desktop software (https://artinis.com/)
example_mnirs("portamon")example_mnirs("portamon")
Generic methods for objects of class "mnirs".
## S3 method for class 'mnirs' print(x, ...)## S3 method for class 'mnirs' print(x, ...)
x |
Object of class |
... |
Additional arguments passed to |
print |
Returns |
x <- read_mnirs( example_mnirs("train.red"), nirs_channels = c(smo2 = "SmO2"), time_channel = c(time = "Timestamp (seconds passed)"), verbose = FALSE ) |> resample_mnirs(method = "linear", verbose = FALSE) |> extract_intervals( start = by_time(2452, 3168), span = c(-60, 120), verbose = FALSE ) print(x)x <- read_mnirs( example_mnirs("train.red"), nirs_channels = c(smo2 = "SmO2"), time_channel = c(time = "Timestamp (seconds passed)"), verbose = FALSE ) |> resample_mnirs(method = "linear", verbose = FALSE) |> extract_intervals( start = by_time(2452, 3168), span = c(-60, 120), verbose = FALSE ) print(x)
Import time-series data exported from common muscle NIRS (mNIRS) devices and
return a tibble of class "mnirs" with the selected signal channels and
metadata.
read_mnirs( file_path, nirs_channels = NULL, time_channel = NULL, event_channel = NULL, sample_rate = NULL, add_timestamp = FALSE, zero_time = FALSE, keep_all = FALSE, verbose = TRUE )read_mnirs( file_path, nirs_channels = NULL, time_channel = NULL, event_channel = NULL, sample_rate = NULL, add_timestamp = FALSE, zero_time = FALSE, keep_all = FALSE, verbose = TRUE )
file_path |
Path of the data file to import. Supported file extensions
are |
nirs_channels |
A character vector of one or more column names containing mNIRS signals to import. Names must match the file header exactly.
|
time_channel |
A character string giving the name of the time (or sample) column to import. The name must match the file header exactly.
|
event_channel |
An optional character string giving the name of an
event/lap column to import. Names must match the file header exactly.
A named character vector can be used to rename the column on import in
the form |
sample_rate |
An optional numeric sample rate in Hz. If left blank
( |
add_timestamp |
A logical. Default is |
zero_time |
Logical. Default is |
keep_all |
Logical. Default is
|
verbose |
Logical. Default is |
read_mnirs() searches the file for a header row containing the requested
channel names. The header row does not need to be the first row in the file.
If duplicate column names exist, columns are matched in the order they appear and renamed with unique strings.
Columns without a header name in the source file will be renamed to
col_*, where * is the numeric column number in which they appear in
the file (e.g. col_6). This applies to Artinis Oxysoft event label
columns, which do not have a column header and must be identified manually.
A named character vector can be specified to rename nirs_channels,
time_channel, and event_channel, in the form
c(renamed = "original_name"). The "original_name" must match the
contents of the file data table header row exactly.
time_channel will be converted to numeric for analysis.
If time_channel is a date-time (POSIXct) format, it will be converted
to numeric and re-based to start from 0, regardless of zero_time.
Some devices export a sample index rather than time values. In those
cases, if an export sample_rate is detected in the file metadata (e.g.
Artinis Oxysoft exports), read_mnirs() will create or overwrite a
"time" column in seconds derived from the sample index and the detected
sample_rate.
If sample_rate is not specified, it is estimated from differences in
time_channel. If time_channel is actually a sample index, as described
above, this may erroneously be estimated at 1 Hz. sample_rate should be
specified explicitly in this case.
Entirely empty rows and columns are removed. Invalid values (e.g.
c(NaN, Inf)) are standardized to NA. A warning will be displayed when
irregular sampling is detected (e.g. non-monotonic, repeated, or unequal
time values), if verbose = TRUE.
A tibble of class "mnirs". Metadata are stored
as attributes and can be accessed with attributes(data).
read_mnirs( file_path = example_mnirs("moxy_ramp"), ## call an example data file nirs_channels = c( smo2_left = "SmO2 Live", ## identify and rename channels smo2_right = "SmO2 Live(2)" ), time_channel = c(time = "hh:mm:ss"), ## date-time format will be converted to numeric event_channel = NULL, ## leave blank if unused sample_rate = NULL, ## if blank, will be estimated from time_channel add_timestamp = FALSE, ## omit a date-time timestamp column zero_time = TRUE, ## recalculate time values from zero keep_all = FALSE, ## return only the specified data channels verbose = TRUE ## show warnings & messages )read_mnirs( file_path = example_mnirs("moxy_ramp"), ## call an example data file nirs_channels = c( smo2_left = "SmO2 Live", ## identify and rename channels smo2_right = "SmO2 Live(2)" ), time_channel = c(time = "hh:mm:ss"), ## date-time format will be converted to numeric event_channel = NULL, ## leave blank if unused sample_rate = NULL, ## if blank, will be estimated from time_channel add_timestamp = FALSE, ## omit a date-time timestamp column zero_time = TRUE, ## recalculate time values from zero keep_all = FALSE, ## return only the specified data channels verbose = TRUE ## show warnings & messages )
Detect and replace local outliers, specified invalid values, and missing
NA values across nirs_channels within an "mnirs" data frame.
replace_mnirs() operates on a data frame, extending the vectorised
functions:.
replace_invalid() detects specified invalid values or range cutoffs in a
numeric vector and replace them with the local median value or NA.
replace_outliers() detects local outliers in a numeric vector using a Hampel filter and replaces with the local median value or NA.
replace_missing() detects missing (NA) values in a numeric vector and
replaces via interpolation.
replace_mnirs( data, nirs_channels = NULL, time_channel = NULL, invalid_values = NULL, invalid_above = NULL, invalid_below = NULL, outlier_cutoff = NULL, width = NULL, span = NULL, method = c("linear", "median", "locf", "none"), verbose = TRUE ) replace_invalid( x, t = seq_along(x), invalid_values = NULL, invalid_above = NULL, invalid_below = NULL, width = NULL, span = NULL, method = c("median", "none"), verbose = TRUE, ... ) replace_outliers( x, t = seq_along(x), outlier_cutoff = 3, width = NULL, span = NULL, method = c("median", "none"), verbose = TRUE, ... ) replace_missing( x, t = seq_along(x), width = NULL, span = NULL, method = c("linear", "median", "locf"), verbose = TRUE, ... )replace_mnirs( data, nirs_channels = NULL, time_channel = NULL, invalid_values = NULL, invalid_above = NULL, invalid_below = NULL, outlier_cutoff = NULL, width = NULL, span = NULL, method = c("linear", "median", "locf", "none"), verbose = TRUE ) replace_invalid( x, t = seq_along(x), invalid_values = NULL, invalid_above = NULL, invalid_below = NULL, width = NULL, span = NULL, method = c("median", "none"), verbose = TRUE, ... ) replace_outliers( x, t = seq_along(x), outlier_cutoff = 3, width = NULL, span = NULL, method = c("median", "none"), verbose = TRUE, ... ) replace_missing( x, t = seq_along(x), width = NULL, span = NULL, method = c("linear", "median", "locf"), verbose = TRUE, ... )
data |
A data frame of class "mnirs" containing time series data and metadata. |
nirs_channels |
A character vector giving the names of mNIRS columns to
operate on. Must match column names in
|
time_channel |
A character string naming the time or sample column.
Must match a column name in
|
invalid_values |
A numeric vector of invalid values to be replaced,
e.g. |
invalid_above, invalid_below
|
Numeric values each specifying cutoff values, above or below which (respectively) will be replaced, inclusive of the specified cutoff values. |
outlier_cutoff |
A numeric value for the local outlier threshold, as the number of standard deviations from the local median.
|
width |
An integer defining the local window in number of samples
centred on |
span |
A numeric value defining the local window time span around
|
method |
A character string indicating how to handle
|
verbose |
Logical. Default is |
x |
A numeric vector of the response variable. |
t |
An optional numeric vector of the predictor variable (e.g. time).
Default is |
... |
Additional arguments. |
nirs_channels and time_channel are retrieved automatically from
"mnirs" metadata if not specified explicitly. Columns in data not
listed in nirs_channels are passed through unprocessed.
replace_outliers() and replace_missing() (when method = "median")
operate over a local rolling window for outlier detection and median
interpolation. The window is specified by either width as the number
of samples, or span as the time span in units of time_channel.
A partial window is calculated at the edges of the data.
Specific invalid_values can be replaced, such as c(0, 100, 102.3).
Data ranges can be replaced with cutoff values specified by invalid_above
and invalid_below, where any values higher or lower than the specified
cutoff values (respectively) will be replaced, inclusive of the cutoff
values themselves.
Rolling local medians are computed across x within a window defined
by width (number of samples) or span (time span in units of t).
Outliers are detected with robust median absolute deviation (MAD),
adapted from pracma::hampel(). Deviations equal to or less than the
smallest absolute time series difference in x are excluded, to avoid
flagging negligible differences where local data have minimal or zero
variation.
Values of x outside the local bounds defined by outlier_cutoff are
identified as outliers and either replaced with the local median
(method = "median", the default) or set to NA (method = "none").
Existing NA values in x are not replaced. They are passed
through to the returned vector. See replace_missing().
outlier_cutoff is the number of (MAD-normalised) standard deviations
from the local median. Higher values are more conservative; lower
values flag more outliers.
outlier_cutoff = 3 – Pearson's 3 sigma edit rule (default).
outlier_cutoff = 2 – approximately Tukey-style 1.5*IQR rule.
outlier_cutoff = 0 – Tukey's median filter (every point
replaced by local median).
method = "linear" and method = "locf" use stats::approx() with
rule = 2, so leading NAs are filled by "nocb"
("next observation carried backward") and trailing NAs by "locf".
method = "median" calculates the local median of valid (non-NA)
values to either side of NAs, within a window defined by width
(number of samples) or span (time span in units of t). Sequential
NAs are all replaced by the same median value.
If there are no valid values within span to one side of the NA,
the median of the other side is used (i.e. for leading and trailing
NAs). If there are no valid values within either side, the first
valid sample on either side is used (equivalent to
replace_missing(x, width = 1)).
replace_mnirs() return a tibble of
class "mnirs" with metadata available via attributes().
replace_invalid() returns a numeric vector the same length as
x with invalid values replaced.
replace_outliers() returns a numeric vector the same length as
x with outliers replaced.
replace_missing() returns a numeric vector the same length as
x with missing values replaced.
## vectorised operations x <- c(1, 999, 3, 4, 999, 6) replace_invalid(x, invalid_values = 999, width = 3, method = "median") (x_na <- replace_outliers(x, outlier_cutoff = 3, width = 3, method = "none")) replace_missing(x_na, method = "linear") ## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) ## clean data data_clean <- replace_mnirs( data, ## channels retrieved from metadata invalid_values = 0, ## known invalid values in the data invalid_above = 90, ## remove data spikes above 90 outlier_cutoff = 3, ## Pearson's 3 sigma edit rule width = 7, ## window for outlier detection and interpolation method = "linear" ## linear interpolation over NAs ) if (requireNamespace("ggplot2", quietly = TRUE)) { ## plot original and show where values have been replaced ## ignore warning about replacing the existing colour scale plot(data, time_labels = TRUE) + ggplot2::scale_colour_manual( name = NULL, breaks = c("smo2", "replaced"), values = palette_mnirs(2) ) + ggplot2::geom_point( data = data[data_clean$smo2 != data$smo2, ], ggplot2::aes(y = smo2, colour = "replaced"), na.rm = TRUE ) + ggplot2::geom_line( data = { data_clean[!is.na(data$smo2), "smo2"] <- NA data_clean }, ggplot2::aes(y = smo2, colour = "replaced"), linewidth = 1, na.rm = TRUE ) }## vectorised operations x <- c(1, 999, 3, 4, 999, 6) replace_invalid(x, invalid_values = 999, width = 3, method = "median") (x_na <- replace_outliers(x, outlier_cutoff = 3, width = 3, method = "none")) replace_missing(x_na, method = "linear") ## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) ## clean data data_clean <- replace_mnirs( data, ## channels retrieved from metadata invalid_values = 0, ## known invalid values in the data invalid_above = 90, ## remove data spikes above 90 outlier_cutoff = 3, ## Pearson's 3 sigma edit rule width = 7, ## window for outlier detection and interpolation method = "linear" ## linear interpolation over NAs ) if (requireNamespace("ggplot2", quietly = TRUE)) { ## plot original and show where values have been replaced ## ignore warning about replacing the existing colour scale plot(data, time_labels = TRUE) + ggplot2::scale_colour_manual( name = NULL, breaks = c("smo2", "replaced"), values = palette_mnirs(2) ) + ggplot2::geom_point( data = data[data_clean$smo2 != data$smo2, ], ggplot2::aes(y = smo2, colour = "replaced"), na.rm = TRUE ) + ggplot2::geom_line( data = { data_clean[!is.na(data$smo2), "smo2"] <- NA data_clean }, ggplot2::aes(y = smo2, colour = "replaced"), linewidth = 1, na.rm = TRUE ) }
Up- or down-sample an "mnirs" data frame to a new sample rate, filling new samples via nearest-neighbour matching or interpolation.
resample_mnirs( data, time_channel = NULL, sample_rate = NULL, resample_rate = sample_rate, method = c("none", "linear", "locf"), verbose = TRUE )resample_mnirs( data, time_channel = NULL, sample_rate = NULL, resample_rate = sample_rate, method = c("none", "linear", "locf"), verbose = TRUE )
data |
A data frame of class "mnirs" containing time series data and metadata. |
time_channel |
A character string naming the time or sample column.
Must match a column name in
|
sample_rate |
A numeric sample rate in Hz.
|
resample_rate |
An optional sample rate (Hz) for the output data
frame. If |
method |
A character string specifying how new samples are filled. Default is "none". Filling must be opted into explicitly (see Details):
|
verbose |
Logical. Default is |
This function uses replace_missing() (based on stats::approx()) to
interpolate across new samples in the resampled data range.
time_channel and sample_rate are retrieved automatically from data
of class "mnirs", if not defined explicitly.
Otherwise, sample_rate will be estimated from the values in time_channel.
However, this may return unexpected values, and it is safer to define
sample_rate explicitly or retrieve it from "mnirs" metadata.
When resample_rate is omitted, the output has the same sample_rate as
the input but with a regular, evenly-spaced time_channel. This is useful
for regularising data that contains missing or repeated samples without
changing the nominal rate.
Numeric columns are interpolated according to method (see
?replace_missing). Non-numeric columns (e.g. character event labels,
integer lap numbers) are always filled by last-observation-carried-forward,
regardless of method:
For method = "none", existing rows are matched to the nearest original
values of time_channel without interpolation or filling, meaning newly
created samples and any NAs in the original data are returned as NA.
When down-sampling, numeric columns use time-weighted averaging. Non-numeric columns use the first valid value in each output bin.
A tibble of class "mnirs". Metadata are
stored as attributes and can be accessed with attributes(data).
## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = TRUE ) ## note warning about irregular sampling data data_resampled <- resample_mnirs( data, ## blank channels will be retrieved from metadata resample_rate = 2, ## blank by default will resample to `sample_rate` method = "linear", ## linear interpolation across resampled indices verbose = TRUE ) ## note the altered `time` values resolving the above warning data_resampled## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2 = "SmO2 Live"), time_channel = c(time = "hh:mm:ss"), verbose = TRUE ) ## note warning about irregular sampling data data_resampled <- resample_mnirs( data, ## blank channels will be retrieved from metadata resample_rate = 2, ## blank by default will resample to `sample_rate` method = "linear", ## linear interpolation across resampled indices verbose = TRUE ) ## note the altered `time` values resolving the above warning data_resampled
Expand or reduce the range (min and max values) of data channels to a new
amplitude/dynamic range, e.g. re-scale the range of NIRS data to c(0, 100).
rescale_mnirs( data, nirs_channels = list(), range, verbose = TRUE )rescale_mnirs( data, nirs_channels = list(), range, verbose = TRUE )
data |
A data frame of class "mnirs" containing time series data and metadata. |
nirs_channels |
A
|
range |
A numeric vector in the form |
verbose |
Logical. Default is |
nirs_channels = list() can be used to group data channels (column names)
to preserve absolute or relative scaling.
Channels grouped together in a vector (e.g. list(c("A", "B"))) will be
re-scaled to a common range, and the relative scaling within that group
will be preserved.
Channels in separate list vectors (e.g. list("A", "B")) will be
re-scaled independently, and relative scaling between groups will be lost.
A single vector of channel names (e.g. c("A", "B")) will group
channels together.
Channels (columns) in data not explicitly defined in nirs_channels
will be passed through untouched to the output data frame.
nirs_channels can be retrieved automatically from data of class
"mnirs" which has been processed with {mnirs}, if not defined
explicitly. This will default to returning all nirs_channels grouped
together, and should be defined explicitly for other grouping arrangements.
A tibble of class "mnirs" with metadata
available with attributes().
## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> rescale_mnirs( ## un-grouped nirs channels to rescale separately nirs_channels = list(smo2_left, smo2_right), range = c(0, 100) ## rescale to a 0-100% functional exercise range ) data if (requireNamespace("ggplot2", quietly = TRUE)) { plot(data, time_labels = TRUE) + ggplot2::geom_hline(yintercept = c(0, 100), linetype = "dotted") }## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> rescale_mnirs( ## un-grouped nirs channels to rescale separately nirs_channels = list(smo2_left, smo2_right), range = c(0, 100) ## rescale to a 0-100% functional exercise range ) data if (requireNamespace("ggplot2", quietly = TRUE)) { plot(data, time_labels = TRUE) + ggplot2::geom_hline(yintercept = c(0, 100), linetype = "dotted") }
Scales for custom mnirs palette
scale_colour_mnirs(..., aesthetics = "colour") scale_color_mnirs(..., aesthetics = "colour") scale_fill_mnirs(..., aesthetics = "fill")scale_colour_mnirs(..., aesthetics = "colour") scale_color_mnirs(..., aesthetics = "colour") scale_fill_mnirs(..., aesthetics = "fill")
... |
Arguments passed to |
aesthetics |
A character vector with aesthetic(s) passed to
|
A ggplot2 scale object.
theme_mnirs(), palette_mnirs()
## plot example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) ggplot2::ggplot(data, ggplot2::aes(x = time)) + theme_mnirs() + scale_colour_mnirs() + ggplot2::geom_line(ggplot2::aes(y = smo2_left, colour = "smo2_left")) + ggplot2::geom_line(ggplot2::aes(y = smo2_right, colour = "smo2_right"))## plot example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) ggplot2::ggplot(data, ggplot2::aes(x = time)) + theme_mnirs() + scale_colour_mnirs() + ggplot2::geom_line(ggplot2::aes(y = smo2_left, colour = "smo2_left")) + ggplot2::geom_line(ggplot2::aes(y = smo2_right, colour = "smo2_right"))
Move the range of data channels in a data frame up or down, while preserving the absolute amplitude/dynamic range of each channel, and the relative scaling across channels. e.g. shift the minimum data value to zero for all positive values, or shift the mean of the first time span in a recording to zero.
shift_mnirs( data, nirs_channels = list(), time_channel = NULL, to = NULL, by = NULL, width = NULL, span = NULL, position = c("min", "max", "first"), verbose = TRUE )shift_mnirs( data, nirs_channels = list(), time_channel = NULL, to = NULL, by = NULL, width = NULL, span = NULL, position = c("min", "max", "first"), verbose = TRUE )
data |
A data frame of class "mnirs" containing time series data and metadata. |
nirs_channels |
A character vector giving the names of mNIRS columns to
operate on. Must match column names in
|
time_channel |
A character string naming the time or sample column.
Must match a column name in
|
to |
A numeric value in units of |
by |
A numeric value in units of |
width |
An integer defining the local window in number of samples
centred on |
span |
A numeric value defining the local window time span around
|
position |
Indicates where the reference values will be shifted from.
|
verbose |
Logical. Default is |
nirs_channels = list() can be used to group data channels (column names)
to preserve absolute or relative scaling.
Channels grouped together in a vector (e.g. list(c("A", "B"))) will be
shifted to a common value, and the relative scaling within that group
will be preserved.
Channels in separate list vectors (e.g. list("A", "B")) will be
shifted independently, and relative scaling between groups will be lost.
A single vector of channel names (e.g. c("A", "B")) will group
channels together.
Channels (columns) in data not explicitly defined in nirs_channels
will be passed through untouched to the output data frame.
nirs_channels and time_channel can be retrieved automatically from
data of class "mnirs" which has been processed with {mnirs},
if not defined explicitly. This will default to returning all
nirs_channels grouped together, and should be defined explicitly
for other grouping arrangements.
Only one of either to or by and one of either width or span should
be defined. If both of either pairing are defined, to will be preferred
over by, and width will be preferred over span.
A tibble of class "mnirs" with metadata
available with attributes().
## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> shift_mnirs( ## un-grouped nirs channels to shift separately nirs_channels = list(smo2_left, smo2_right), to = 0, ## NIRS values will be shifted to zero span = 120, ## shift the *first* 120 sec of data to zero position = "first" ) data if (requireNamespace("ggplot2", quietly = TRUE)) { plot(data, time_labels = TRUE) + ggplot2::geom_hline(yintercept = 0, linetype = "dotted") }## read example data data <- read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> shift_mnirs( ## un-grouped nirs channels to shift separately nirs_channels = list(smo2_left, smo2_right), to = 0, ## NIRS values will be shifted to zero span = 120, ## shift the *first* 120 sec of data to zero position = "first" ) data if (requireNamespace("ggplot2", quietly = TRUE)) { plot(data, time_labels = TRUE) + ggplot2::geom_hline(yintercept = 0, linetype = "dotted") }
A [ggplot2][ggplot2::ggplot2-package] theme for display.
theme_mnirs( base_size = 14, base_family = "sans", border = c("partial", "full"), ink = "black", paper = "white", accent = "#0080ff", ... )theme_mnirs( base_size = 14, base_family = "sans", border = c("partial", "full"), ink = "black", paper = "white", accent = "#0080ff", ... )
base_size |
Base font size, given in pts. |
base_family |
Base font family. |
border |
Define either a partial or full border around plots. |
ink |
Colour for text and lines. Default is "black". |
paper |
Background colour. Default is "white". |
accent |
Accent colour for highlights. Default is "#0080ff". |
... |
Additional arguments to add to |
axis.title = element_text(face = "bold") by default Modify to "plain".
panel.grid.major & panel.grid.major set to blank. Modify to
= element_line() for visible grid lines.
legend.position = "top" by default Modify "none" to remove legend
entirely.
border = "partial" uses panel.border = element_blank() and
axis.line = element_line().
border = "full" uses panel.border = element_rect(colour = "black",
linewidth = 1) and axis.line = element_line().
base_family = "sans" by default.
A ggplot2 theme object.
palette_mnirs(), scale_colour_mnirs()
## plot example data read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> plot(time_labels = TRUE)## plot example data read_mnirs( file_path = example_mnirs("moxy_ramp"), nirs_channels = c(smo2_left = "SmO2 Live", smo2_right = "SmO2 Live(2)"), time_channel = c(time = "hh:mm:ss"), verbose = FALSE ) |> plot(time_labels = TRUE)
Exported from Train.Red app, recorded at 10 Hz. Containing two 5-minute cycling work intervals, placed on bilateral vastus lateralis muscle sites. Some data channels have been omitted to reduce file size.
.csv file with header metadata and 10 columns and 11995 rows:
Elapsed time (s).
Lap number (numeric).
Muscle oxygen saturation, filtered (%). Two channels have
duplicated names. If both are called, the second will be renamed to
SmO2_1.
Muscle oxygen saturation, raw signal (%). Two
channels have duplicated names. If both are called, the second will
be renamed to SmO2 unfiltered_1.
Oxyhaemoglobin concentration, raw signal
(arbitrary units). Two channels have duplicated names. If both are
called, the second will be renamed to O2HB unfiltered_1.
Deoxyhaemoglobin concentration, raw signal
(arbitrary units). Two channels have duplicated names. If both are
called, the second will be renamed to HHb unfiltered_1.
Channel mapping for read_mnirs():
nirs_channels = c("SmO2", "SmO2 unfiltered", "O2HB unfiltered", "HHb unfiltered")
time_channel = c("Timestamp (seconds passed)")
event_channel = c("Lap/Event")
interval_times = list(start = c(2150.09, 2872.28), end = c(2452.26, 3167.98))
interval_times = list(start = c(65.94, 788.13), end = c(368.11, 1083.83)) from zero_time
Train.Red (Train.Red B.V.), exported via Train.Red app (https://train.red/)
example_mnirs("train.red")example_mnirs("train.red")