
diegr
diegr.RmdThe package diegr displays high-density
electroencephalography (HD-EEG) data in different ways using interactive
elements or animations for a comprehensive overview of data.
Installation
install.packages("diegr")
# or for development version:
# install.packages("pak")
pak::pak("gerslovaz/diegr") Data
The package contains built-in datasets. Due to the nature of the experiment and the size of the data, this is only a small section which serves as a demonstration of the implemented functions and it is not possible to reconstruct the real signal of a specific sensor or subject from it. Details of the experiment from which the original data comes can be found in Madetko-Alster et al. (2025).
epochdata
The brain signal was originally measured by 256-channel HydroCel Geodesic Sensor Net with a sampling frequency of 250 Hz. The example dataset contains amplitude values from 204 selected channels at 50 time points (stimulus at time point 10) for 2 representative subjects (one healthy control, one patient). Out of 50 epochs per subject, 14 (or 15, respectively) were selected.
The data is organized into a data frame with the following columns:
-
time: Numbers of time points (as sampling indices, not in ms). Number 10 indicates the time of stimulus (0 ms), the interval between two time points corresponds to 4 ms (sampling frequency 250 Hz). -
signal: HD-EEG signal amplitude (in microvolts). -
epoch: Epoch numbers (there are 14 epochs for subject one, 15 epochs for subject two). -
sensor: Sensor labels (labeling corresponds to the Geodesic Sensor Net Technical Manual, 2024). -
subject: Subject IDs (1 - representative healthy control subject, 2 - representative patient subject).
rtdata
The response time values in individual experiment epochs for 2
representative subjects (one from patient, one from healthy control
group). The epochs and subjects correspond to the sample dataset
epochdata.
The data is organized into a data frame with the following columns:
-
subject: Subject ID (1 - representative healthy control subject, 2 - representative patient subject). -
epoch: Epoch number (there are 14 epochs for subject one, 15 epochs for subject two). -
RT: Response time in ms.
Sensor position templates
For each template, the data is organized into a list with the following elements:
-
D2: A tibble with coordinates and labels of sensors in 2D. -
D3: A tibble with coordinates and labels of sensors in 3D. -
ROI: A vector with the region of interest (ROI) assignment for each sensor (“central”, “frontal”, “occipital”, “parietal” or “temporal” according to the brain regions, possibly “face” for the face area).
Axis orientation (3D): - x: left (-) to right (+); - y: posterior (-) to anterior (+); - z: inferior (-) to superior (+).
HCGSN256
The Cartesian coordinates of HD-EEG sensor positions in 3D space on the scalp surface and their projection into 2D space according to 256-channel HydroCel Geodesic Sensor Net average template montage. Sensors are labeled according to Geodesic Sensor Net Technical Manual, 2024.
biosemi128
The Cartesian coordinates of HD-EEG sensor positions in 3D space on an idealized spherical surface and their projection into 2D space according to 128-channel BioSemi system. Sensors are labeled according to BioSemi website (https://www.biosemi.com/headcap2.htm).
biosemi256
The Cartesian coordinates of HD-EEG sensor positions in 3D space on an idealized spherical surface and their projection into 2D space according to 256-channel BioSemi system. Sensors are labeled according to BioSemi website (https://www.biosemi.com/headcap2.htm).
Tools useful in preprocessing
The package assumes that the input data have already been preprocessed (artifact removal, independent component analysis - ICA, segmentation, …), but may still contain outliers that should be excluded for specific statistical analyses.
A useful tool for outlier detection is interactive plot with boxplots
of amplitudes (boxplot_epoch()) or response times
(boxplot_rt()) in individual epochs. The
plotly output makes it easy to determine the epoch number
from which outliers come and additionally allows editing of the output
image layout.
There is also an interactive graph of amplitudes in individual epochs
with (or without) the average plotted
(interactive_waveforms()). This function can also be used
to compare signals across different experimental levels (e.g., sensor,
subject, group, or condition) as specified by the level
argument.
# The function for interactive boxplot of signal assumes
# pre-filtered data input
# example: subject 1 and channel E34 in time points from 10 to 20
subdata <- epochdata |>
pick_data(subject_rg = 1, sensor_rg = "E34")
boxplot_epoch(subdata, amplitude = "signal", time_lim = 10:20)
# customizing the output title and axis labels using |> operator
p1 <- boxplot_epoch(subdata, amplitude = "signal", time_lim = 10:20)
p1 |> plotly::layout(xaxis = list(title = ""),
yaxis = list(title = "Amplitude"),
title = list(text = "Outlier detection for subject 1 and channel E34",
font = list(size = 11, color = "darkblue"))
)
# note: the output is not plotted to reduce the vignette file sizeOutlier epochs can be easily detected using the hover text in the
plotly output. In the case above, the outlier is epoch
14.
The results can be supported computationally by the
outliers_epoch() function, which allows the evaluation of
outliers using one of three criteria (interquartile range - IQR,
percentile approach or Hampel filter).
The output of this function is a table containing epoch IDs and the
number of time points in which the epochs were evaluated as outliers
(according to chosen criterion) for unique combinations of input
variables (e.g. group, subject, sensor).
# Example: outlier detection for both subjects and channel E34 using IQR method
# The column in which we are looking for outliers is specified by the `amplitude` argument
epochdata |>
pick_data(sensor_rg = "E34") |>
outliers_epoch(amplitude = "signal", method = "iqr")The results correspond with findings from
boxplot_epoch() - for subject 1, epoch 14 was evaluated as
an outlier in all 50 time points.
Analogously to function boxplot_epoch(), function
boxplot_subject() works to identify outliers at the subject
level.
Preparing signal for visualisation
The main focus of the package is interactive visualization and animation of the HD-EEG data. This often requires first baseline correcting the data or averaging the signal across epochs, sensors/time points, or subjects.
The baseline correction of the amplitude is enabled through the
function baseline_correction(). In the first release of the
package, only the most commonly used absolute correction
(type = "absolute"), which subtracts the mean over
base_int, is available.
The calculation of the average signal can be performed using the
function compute_mean(), which computes pointwise
standard/weighted or jackknife (leave-one-out) mean of the signal at
different levels. In addition to the mean itself, the function also
calculates the boundaries of the pointwise confidence interval
(ci_low and ci_up in the output). The argument
domain determines whether the average is calculated within
individual time points (for curves) - domain = "time" or
sensors (for topographic maps) - domain = "space".
# preparing data for later visualisation
# filtering signal without outlier epochs 14 and 15
# and computing epoch level jackknife average from baseline corrected data
# Note: data filtering is possible both using the pick_data() function or dplyr commands
edata <- epochdata |>
pick_data(subject_rg = 2, epoch_rg = 1:13) |>
baseline_correction(baseline_range = 1:9)
s1 <- edata |>
dplyr::filter(time == 10) |>
compute_mean(amplitude = "signal_base", level = "epoch",
domain = "space", type = "jack")Temporal domain visualization
The interactive_waveforms() and
plot_time_mean() can be used to display the time course of
the signal. While function interactive_waveforms()
interactively displays individual waveforms at different levels via
plotly, function plot_time_mean() is adapted
directly to display the average signal including the pointwise
confidence interval as a ggplot object.
# computing jackknife average from channel E65
s2 <- edata |>
pick_data(sensor_rg = "E65") |>
compute_mean(amplitude = "signal_base", level = "epoch",
domain = "time", type = "jack")
# plotting the average, the zero time point (stimulus) is set to 10
plot_time_mean(s2, t0 = 10)
Spatial domain visualization
Mesh Generation
The original electrode grid is too coarse for graphical rendering. To
achieve smoother and more visually appealing results in plots, it’s
necessary to generate a denser mesh of points. This refined mesh
provides better spatial resolution and enhances the quality of
visualizations. Creating a new mesh is available through
point_mesh() function. This function computes the
coordinates of the mesh in 2D and/or 3D and allows control of the
density (argument n) or the shape (argument
type) of the result mesh.
The default setting creates a polygonal mesh with an initial 10,000
points and computes the coordinates in both dimensions, which are
available in $D2 and $D3 parts of a result
list (class "mesh").
To plot a created mesh use the function
plot_point_mesh(), which results in two-dimensional
ggplot of point mesh or three-dimensional rgl
plot depending on the input dimension.
Notes:
- Creating and plotting a mesh for a subset of the full template
montage is available through
sensor_selectparameter. For consistency between the created mesh and its plot use the same vector of sensor labels in both functions. - The input of
plot_point_mesh()must contain the relevant part with coordinates -$D2for two dimensional and$D3for three dimensional plot. - If the number of mesh points is large, they are no longer visible in the plot and the mesh looks like a solid surface (as can be seen below in first plot).
- Depending on the hardware used, the computational cost of default
n = 10000can be high. In this case we recommend to reducenand to reuse a precomputed mesh across plots for speed.
# creating a mesh with default settings
sensors204 <- unique(epochdata$sensor)
M1 <- point_mesh(template = "HCGSN256", sensor_select = sensors204)
# plot output in 2D
plot_point_mesh(M1, sensor_select = sensors204, plot_dim = 2)
# creating a circular mesh, only 2D coordinates
M2 <- point_mesh(dimension = 2, n = 3000, template = "HCGSN256",
sensor_select = sensors204, type = "circle")
# plotting a mesh - function allows different options of the result plot
plot_point_mesh(M2, sensor_select = sensors204, col_sensors = "purple",
label_sensors = TRUE, cex = 0.1)

diegr’s plotting functions also allow users to input a
custom mesh created in another way, it is only necessary to maintain the
structure following a "mesh" object.
If the mesh input is not specified, the graphic functions
automatically use the mesh created by point_mesh() with
default settings.
Topographical maps in 2D projection
The topo_plot() function is used to plot a topographic
map of the brain signal, the amplitude values are colored by default
using a topographic colour scale. An alternative to this function
adapted directly to plot the mean along with the confidence interval
bounds is plot_topo_mean(). The create_scale()
function generates two built-in colour palettes used for topographical
plots. The default "redblue" palette maps negative values
to shades of blue and positive values to shades of red, with light grey
shades around zero. In the topographic colour scale
(type = "topo"), amplitude = 0 is mapped to the blue–green
boundary; positive values trend towards yellow/red, and negative values
trend towards dark blue. Additionally, users can choose their own
palettes for plots using the col_scale argument.
Both functions (topo_plot(),
plot_topo_mean()) allow setting some optional arguments for
editing the output appearance (with or without contours, sensor names,
etc.) and return a ggplot object for further
customization.
Notes:
The input values of amplitude (or also CI bounds in the case of the
plot_topo_mean()function) outside the chosencol_rangewill cause “holes” in the resulting plot.To compare results for different subjects or conditions, setting the same values of
col_rangeandcol_scalearguments in all cases is necessary.
# Plot average topographic map of baseline corrected signal for subject 1
# from the time point 10 (the time of the stimulus)
# the outlier epoch 14 is extracted before computing average
# preparing data
data_base <- epochdata |>
pick_data(subject_rg = 1, time_rg = 1:10) |>
baseline_correction(baseline_range = 1:9)
data_mean <- data_base |>
pick_data(epoch_rg = 1:13, time_rg = 10) |>
compute_mean(amplitude = "signal_base", type = "point",
domain = "space")
# create a topographic scale for plotting
# interval (-10,10) is selected in consideration of the signal progress
CS_topo <- create_scale(col_range = c(-10, 10), type = "topo")
# plotting the base topographic polygon map with contours and legend
topo_plot(data = data_mean, amplitude = "average",
col_scale = CS_topo, contour = TRUE)
# plotting the same map without contours and legend
# but with sensor labels and adding the title
g1 <- topo_plot(data = data_mean, amplitude = "average",
col_scale = CS_topo,
label_sensors = TRUE, show_legend = FALSE)
g1 + ggplot2::ggtitle("Subject 1, time of the stimulus")
# plotting the average together with CI bounds using plot_topo_mean
plot_topo_mean(data = data_mean, template = "HCGSN256", col_scale = CS_topo)


Topographical maps on the scalp
The scalp_plot() function is created for plotting a
scalp polygon map of the EEG signal amplitude using, by default, the
same colour scale as in topo_plot(). The result plot is
rendered using rgl::shape3d function and the signal
interpolation between sensor locations is based on thin-plate spline
interpolation model.
Correct mesh triangulation is required for appropriate colour rendering.
This triangulation is constructed by make_triangulation(),
which creates a Delaunay type-I triangulation (Lai & Schumaker,
2007) with consistently oriented edges (Schneider & Eberly,
2003).
Notes:
The input values of amplitude outside the chosen
col_rangewill cause “holes” in the resulting plot.To obtain comparable results between different subjects or conditions, setting the same values of
col_rangeandcol_scalearguments in all cases is necessary.
# plotting the scalp polygon map of previously prepared signal s1
#open3d()
scalp_plot(s1, amplitude = "average", col_range = c(-30, 15))
# note: the output is not plotted to reduce the vignette file sizeThe rgl output allows users to zoom and rotate the image
with a default view of the back of the head.
Surface plots
At the intersection of temporal and spatial visualization is the
interactive_surfaceplot_curves(), which allows users to
display the time courses of all (or selected) electrodes in a single
figure.
# computing mean across epochs 1:13 for Subject 2
edata <- pick_data(epochdata, subject_rg = 2, epoch_rg = 1:13)
data_base <- baseline_correction(edata, baseline_range = 1:9)
data_mean <- compute_mean(data_base, amplitude = "signal_base", type = "point", domain = "time")
# select sensor labels to display on the axis
selected_sensors <- c("E1", "E21", "E41", "E61", "E81", "E101",
"E121", "E141", "E161", "E181", "E201", "E221")
# render the interactive plot
interactive_surfaceplot_curves(data_mean, amplitude = "average", sensor_ticks = selected_sensors)
# note: the output is not plotted to reduce the vignette file sizeAnimations
The package includes the animate_topo() and
animate_topo_mean() functions for 2D topographic animation
in time and the animate_scalp() function for 3D scalp
animation in time. Both of these functions allow to export the output, a
GIF animation using gifski package for the 2D case and a
.mp4 video format using av package for the 3D case. The
topographic map animation uses gganimate and accepts
additional parameters passed to gganimate::animate() via
.... Most input parameters of animate_topo()
are the same as in topo_plot() function, parameters related
to animation properties and export are added and also the
t_lim parameter for setting the length of the timeline
displayed below the animation. The animate_topo_mean()
function is customized for topographic maps of average signal together
with pointwise confidence interval bounds displayed as topographic maps
on the sides of the average map. Usage is almost identical to the
animate_topo() function, only input data must contain
columns average, ci_up and ci_low
(like the output from the compute_mean() function).
Example: The animation of time course of the EEG signal for subject 1
and epoch 5 (from epochdata) between time points 10 and 20
with the whole range of time 0:50 is created as follows (the output is
not rendered to reduce the vignette file size):
# filtering the example data
s1e05 <- epochdata |>
pick_data(subject_rg = 1, epoch_rg = 5, time_rg = 10:20)
# Plot animation with setting the range of time and t0 = 10 indicating the time of the stimulus:
animate_topo(s1e05, amplitude = "signal", t_lim = c(0,50), t0 = 10)
# Export the gif animation - this code will create the animation inside your working directory.
# If you want to export it elsewhere, set the whole path in output_path parameter.
animate_topo(s1e05, amplitude = "signal", t_lim = c(0,50),
t0 = 10, output_path = "example_animation.gif")The scalp animation allows users to export a video in
.mp4 format using the av package or save a
sequence of frames to a chosen directory setting the
frames_dir parameter. The animation speed can be controlled
by the sec parameter in the viewer or by the
framerate parameter for the exported video. The video
resolution quality can be affected using par3d function,
for example rgl::par3d(windowRect = c(100, 100, 800, 800)).
Example of scalp animation with the same data as above (the output is
not rendered to reduce the vignette file size):
# Plot animation with default mesh and triangulation:
animate_scalp(s1e05, amplitude = "signal")
# export the video - the .mp4 extension is required for correct functioning
rgl::par3d(windowRect = c(100, 100, 800, 800))
animate_scalp(s1e05, amplitude = "signal",
frames_dir = "your_frames_dir_path",
output_path = "your_created_video_path.mp4")If you are interested in animation of rotating scalp visualization at
a fixed time point, you can easily achieve this by using the combination
of rgl::play3d and rgl::spin3d functions
according to the following code.
#open3d()
scalp_plot(s1, amplitude = "average", col_range = c(-30,15))
rgl::play3d(rgl::spin3d(axis = c(0, 0, 1), rpm = 10), duration = 10)
# note: the output is not plotted to reduce the vignette file sizeStep-by-step example: Comparing Topographic Maps of Two Subjects
In this section we present a sample code for analyzing topographic maps across subjects:
- Outlier detection
# using IQR method for selected sensor from central region
dataE59 <- epochdata |>
pick_data(sensor_rg = "E59")
outliers_epoch(dataE59, method = "iqr")
# verify results by visual inspection (for each subject separately)
dataE59 |>
dplyr::filter(.data$subject == 1) |>
interactive_waveforms(amplitude = "signal", level = "epoch",
t0 = 10)
dataE59 |>
dplyr::filter(.data$subject == 2) |>
interactive_waveforms(amplitude = "signal", level = "epoch",
t0 = 10)Subject 1: epoch 14 is an obvious outlier. Subject 2: epoch 15 is an obvious outlier, epoch 14 was identified as an outlier for 37 time points - we exclude both of them from further analysis.
- Baseline correction
We exclude outlier epochs before baseline correction, otherwise they would affect computation of the baseline mean.
Note: If there are multiple subjects (or other variables) in the
input data, the baseline_correction() adds them into
grouping structure, so the baseline is calculated separately for unique
combinations of these variables. This procedure allows to compute
baseline correction for both subjects in one step.
subjects_base <- epochdata |>
pick_data(epoch_rg = 1:13) |>
baseline_correction(baseline_range = 1:9)- Computing the average
We compute average topographic map at time point 35 (100 ms after stimulus) for both subjects. We use standard (unweighted) pointwise average with bootstrap confidence intervals.
subjects_mean <- subjects_base |>
dplyr::filter(time == 35) |>
compute_mean(amplitude = "signal_base", domain = "space",
level = "epoch", R = 1000)- Average plotting
To maintain consistency between both subjects, we first construct a mesh and set colour range that we will use in both figures:
# prepare a mesh for plotting
M <- point_mesh(dimension = 2, n = 4000, template = "HCGSN256",
sensor_select = unique(subjects_mean$sensor),
type = "polygon")
# compute consistent col_range across subjects
# a) find the range of average amplitude
range(subjects_mean$average)
#> [1] -15.962526 8.671741
# b) make the range symmetric
cr_subjects <- c(-16,16)For plotting, we will use the plot_topo_mean() function,
which is directly adapted for rendering topographic map of the average
EEG amplitude together with its lower and upper confidence interval (CI)
bounds.
# plot the average topographic maps with the same color range for both subjects
subjects_mean |>
dplyr::filter(.data$subject == 1) |>
plot_topo_mean(mesh = M, template = "HCGSN256",
col_range = cr_subjects, contour = TRUE)
subjects_mean |>
dplyr::filter(.data$subject == 2) |>
plot_topo_mean(mesh = M, template = "HCGSN256",
col_range = cr_subjects, contour = TRUE)
# note: the output is not plotted to reduce the vignette file size- Average difference plot
To display the difference between the two subject averages considered
in the example, we can use the topo_plot() function with
the built-in red-blue colour palette.
# compute difference
subjects_diff <- subjects_mean |>
dplyr::group_by(.data$sensor) |>
dplyr::reframe(
diff = .data$average[.data$subject == "1"] - .data$average[.data$subject == "2"]
)
# create colour scale
CS1 <- create_scale(col_range = range(subjects_diff$diff), type = "redblue")
# display the plot: positive difference values (i.e., average for subject 1 higher than for subject 2)
# are displayed in shades of red, negative values in shades of blue
topo_plot(subjects_diff, amplitude = "diff", mesh = M,
template = "HCGSN256", col_scale = CS1)
References
Lai M-J, Schumaker LL. Spline functions on triangulations. Cambridge University Press; 2007.
Schneider PJ, Eberly DH. Geometric Tools for Computer Graphics. The Morgan Kaufmann Series in Computer Graphics. San Francisco: Morgan Kaufmann, 2003.
Madetko-Alster N., Alster P., Lamoš M., Šmahovská L., Boušek T., Rektor I. and Bočková M. The role of the somatosensory cortex in self-paced movement impairment in Parkinson’s disease. Clinical Neurophysiology. 2025, vol. 171, 11-17. https://doi.org/10.1016/j.clinph.2025.01.001
Wickham H, Bryan J. R Packages (2e). O’Reilly Media; 2023.
Wickham H. ggplot2: Elegant Graphics for Data Analysis. Springer; 2016.
Electrical Geodesics, Inc.: Geodesic Sensor Net Technical Manual. 2024. https://www.egi.com/knowledge-center
Oostenveld, R., Praamstra, P. The five percent electrode system for high-resolution EEG and ERP measurements. Clinical Neurophysiology. 2001, 112(4), 713-719. https://doi.org/10.1016/s1388-2457(00)00527-7
Reproducibility, System Requirements & Troubleshooting
This document was compiled on 2026-08-20 using R version 4.6.1 and the following package versions:
Primary Packages:
diegr(version 0.3.1)dplyr(version 1.2.1)rlang(version 1.3.0)ggplot2(version 4.0.3)rgl(version 1.3.36)plotly(version 4.12.1)gganimate(version 1.0.11)gifski(version 1.32.0.2)av(version 0.9.6)OpenGL and
rgl: Interactive 3D visualizations rely on therglpackage and OpenGL support. If rendering fails, verify that OpenGL (or WebGL) is available on your system. On Linux, additional OpenGL libraries may need to be installed.rgl on Headless Systems: The
rglpackage requires specific setup for graphics on systems without a display (headless servers, such as those often used for package checks or continuous integration). If you are reproducing this vignette on a headless system, ensure the following command is run before loadingrgl:options(rgl.useNULL = TRUE) # Alternatively, set the environment variable RGL_USE_NULL="true"Browser-based Widgets and Media Requirements: Interactive plots (from
plotly) require a modern WebGL-enabled browser. If a widget does not display correctly in the RStudio Viewer, try opening it in an external browser. Exporting widgets as static images may require thewebshot2package together with a Chrome- or Chromium-based browser.Animations (from
gganimateandgifski/av) require the respective R packages and, for some output formats, additional system dependencies. In particular, exporting video formats requires FFmpeg to be installed and available on the system.LaTeX Labels: Figures containing LaTeX mathematical labels require a working TeX distribution. Installing TinyTeX via the
tinytexpackage is recommended:install.packages("tinytex") tinytex::install_tinytex()