Skills
Coding
I have multiple years of experience accessing data, including data from databases using MySQL and SQLite as well as restructuring and cleaning data using Numpy, Pandas and Scipy in Python and the amazing Tidyverse in R, often as part of larger projects organised through Jupyter notebooks or R Packages.
In the following code, I use a combination of base R and Tidyverse functional programming to make a useful fuction to carry out an operation that often occurs in wrangling. It summarises a dataframe based on a metric, grouping based on a kind apart from some exceptions. The summary is a mean by default, but it could take more advanced functions too.
summary_kind_metric <- function(df, kind, except, metric, type = "mean") {
result <- df %>%
# Excluding unwanted instances of kind
filter(!({{kind}} %in% except)) %>%
# Grouping for summary
group_by({{kind}}) %>%
# Lazy evaluation of type used to form a summary call and NA removal
summarise({{type}} := match.fun(type)({{metric}}, na.rm = TRUE)) %>%
# Ungrouping output for subsequent compatibility
ungroup()
return(result)
}
# Example: variability in human reaction times on a boring task
summary_df <- summary_kind_metric(df = boring_task,
kind = participant,
except = c("spy1","spy2"),
metric = reaction_time,
type = "sd")
Data Analysis
For data depiction, I rely on Matplotlib and PyQt6 in Python and ggplot2 and Shiny apps in R. I primarily analyse human behavioural and neural data using various versions of Generalised Linear Models but also an array of other methods such as permutation testing and also some deep learning using Keras.
In the code bellow, I code a matplotlib animation at 25fps embedded within a larger PyQt GUI used to show the unfolding signal of a single EEG electrode.
# A matplotlib frame redraw function
def animate(self, i, xc: list, yc: list):
# Quits if run out of recording
if (self.recording_length - self.change) < SLIDE:
self.ani.event_source.stop()
# Tracks the portion of signal on display
self.counter += self.change
self.addition = tracked_signal[self.counter-self.change:self.counter]
# To maximise performance, axis limits are only adjusted reactively
if max(self.addition) > self.limit:
self.limit = max(self.addition)
self.ax.set_ylim([0, self.limit])
# Add x and y to lists
self.xc.extend(range(self.counter-self.change, self.counter))
self.yc.extend(self.addition)
# Limit x and y lists to constant sampling rate
xc, yc = self.xc[-SRATE:], self.yc[-SRATE:]
# Draw x and y
self.ax.clear()
self.ax.plot(xc, yc)
# An animation triggered by outside events within a PyQt widget
def display(self):
# Runs the animation
self.ani = animation.FuncAnimation(self.fig, self.animate,
fargs=(self.xc, self.yc), interval=40)
# Plot formatting
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.20)
self.ax.set(title='Signal plot', xlabel='Sample', ylabel='mV', ylim=[0, 1])
plt.show()
Communication
I communicate the resuls of my analysis online using Hugo, Github pages powered by Github actions and Netlify. I have presented findings of my research at several international conferences (EPS, VSS, OHBM and these are pending a publication in a peer-reviewed journal. As an associate fellow of the DAT HE, I have 3-year long teaching experience in higher education, developing students’ ability to code and conduct psychological data analysis in various formats: individual consulation and supervision, small and large group work. Finally, I have taken part in multiple public engagement initiatives which lead to improvement of data commmunication impact and fed back into academic research and practice.
Here, I use a custom Hugo script to tweak article desplay by retrieving and showing its tag pages with added multilingual support (i18n).
{{- $tags := .Params.tags -}}
{{- if $tags -}}
<p>{{ i18n "TAG_LABEL" . }}</p>
{{ end }}
{{ partial "tags.html" .}}
You can see more of my work in my project portfolio or on my Github page.