Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Condecon committed May 16, 2023
0 parents commit 38d6cfd
Show file tree
Hide file tree
Showing 27 changed files with 1,693 additions and 0 deletions.
1 change: 1 addition & 0 deletions .Rprofile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
source("renv/activate.R")
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.Rproj.user
.Rhistory
.RData
.Ruserdata
.Rbuildignore
.DS_Store
src/*.o
src/*.so
src/*.dll
src/*.pdb
.vscode/
renv.lock
psy_data/
inst/python/__pycache__/
16 changes: 16 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Package: psytoolkittools
Type: Package
Title: psytoolkittools
Version: 0.1.0
Date: 2023-05-11
Author: Jonas Engicht
Maintainer: Jonas Engicht <dev@condecon.de>
Description: Working with PsyToolkit experiments and surveys
License: GPL (>= 2)
RoxygenNote: 7.2.3
Suggests:
testthat (>= 3.0.0)
Imports:
cli, glue, reticulate, stringr
LinkingTo: cpp11
SystemRequirements: C++11
12 changes: 12 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by roxygen2: do not edit by hand

export(load.experiment)
export(load.survey)
export(recode.keys)
export(recode.status)
import("glue")
import("reticulate")
import("stringr")
importFrom("stats","setNames")
importFrom("utils","read.csv")
useDynLib(psytoolkittools, .registration = TRUE)
9 changes: 9 additions & 0 deletions R/cpp11.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Generated by cpp11: do not edit by hand

recode_keys_c <- function(vector, keys) {
.Call(`_psytoolkittools_recode_keys_c`, vector, keys)
}

recode_status_c <- function(status_vector, correct, error, timeout) {
.Call(`_psytoolkittools_recode_status_c`, status_vector, correct, error, timeout)
}
63 changes: 63 additions & 0 deletions R/load.experiment.data.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#' Load Experiment
#' @description This function loads experiment data from a PsyToolkit experiment
#' Therefore, a vector of file names and a vector of labels which represents the
#' structure of the experiment file have to be provided.
#' The experiment data has to be located in the `psy_data/experiment_data` directory.
#' Otherwise, loading the experiment will fail.
#' @param experiment.file.names A vector of experiment file names
#' @param label.structur A vector of labels that represents the structure of the saved experiment value
#' @param merge.dataframe The result of this function can be directly added to a existing data frame (e.g. survey data).
#' The default value is NA.
#'
#' @return data.frame
#' @export
#' @importFrom "stats" "setNames"
#' @importFrom "utils" "read.csv"
#' @examples
load.experiment = function(experiment.file.names,label.structure, merge.dataframe = NA){

#add path to experiment.file.names
experiment.file.names = glue::glue("psy_data\\experiment_data\\{experiment.file.names}")



d = data.frame()

for(i in c(1:length(experiment.file.names))){
data.list = .read.experiment.file(experiment.file.names[i])

d = rbind(d, data.list)
}

d = setNames(d, label.structure)



### add to existing dataframe?
if(missing(merge.dataframe)){
return(d)
}
else{
return(cbind(merge.dataframe, d))
}

}

.read.experiment.file = function(filename){
experiment.data = read.csv(filename, sep =" ", header = FALSE)


data.vector = c()

for(row in c(1:nrow(experiment.data))){
for(entry in c(1:length(experiment.data[row, ]))){

e = experiment.data[row, entry]
if(!is.na(e)){
data.vector = append(data.vector, as.integer(e))
}
}
}

return(data.vector)
}
90 changes: 90 additions & 0 deletions R/load.survey.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#======Basic Load======

#UNZIP-Survey-Data
unzip_tool = function(file_path){
unzip(file_path, exdir = "psy_data")
}

#load survey
load_survey_awnsers = function(){
awnsers = read.csv(glue::glue("psy_data\\data.csv"))
return(awnsers)
}

#========================Survey Parser===============================
#' Parse Survey (Hidden)
#'
#' @import "reticulate"
#' @import "stringr"
#' @import "glue"

parse_survey = function(awnsers){
#get package path
package_path = path.package("psytoolkittools")
#replace / with \\
package_path = gsub("/", "\\\\", package_path)
module_path = glue::glue("{package_path}\\python\\")

#import module
parse_module = reticulate::import_from_path("survey_parser", path = module_path)
#parse document
document = parse_module$parse(".")

list_of_question_labels = c()

for(i in c(1:length(document))){
list_of_question_labels = append(list_of_question_labels, document[[i]]$label)
}

list_of_question_types = c()

for(i in c(1:length(document))){
list_of_question_types = append(list_of_question_types, document[[i]]$type)
}

#remove newline of question_labels
list_of_question_labels = gsub("\n", "", list_of_question_labels)
list_of_question_types = gsub("\n", "", list_of_question_types)



#get index of all radio questions
index_of_radio_questions = which(list_of_question_types == "radio")
#get names of radio questions#
names_of_radio_questions = list_of_question_labels[index_of_radio_questions]


#get survey awnser index of radio questions
survey_radio_index = c()
for(i in c(1:length(names_of_radio_questions))){
res = stringr::str_which(colnames(awnsers), names_of_radio_questions[i])

survey_radio_index = append(res, survey_radio_index)
}


#convert columns which are "radio" questions to factors
for(i in c(1:length(survey_radio_index))){
awnsers[,survey_radio_index[i]] = as.factor(awnsers[,survey_radio_index[i]])
}


return(awnsers)
}


#============Load Survey============
#'Load Survey
#'@description This functions loads the answers of a PsyToolkit survey from the specified .ZIP file.
#'This file will be unzipped in the folder `psy_data`. Do not remove this folder for further use such as reading experiment data.
#'@param filename File name of the .ZIP file that has been downloaded from PsyToolkit
#'@export
#'@return data.frame
load.survey = function(filename){
unzip_tool(filename)

awnsers = load_survey_awnsers()
awnsers = parse_survey(awnsers)

return(awnsers)
}
4 changes: 4 additions & 0 deletions R/psytoolkittools.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#' @useDynLib psytoolkittools, .registration = TRUE
load = function(){

}
17 changes: 17 additions & 0 deletions R/recode.keys.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#' Recode Keys
#'
#' @param vector A vector of key values
#' @param keys A vector of keys. Those keys have to be in the same order than
#' in your experiment code.
#'
#' @return numeric
#'
#' @export
#' @examples
recode.keys = function(vector, keys){
v = as.character(vector)
k = as.character(keys)
result = psytoolkittools:::recode_keys_c(v, k)

return(result)
}
19 changes: 19 additions & 0 deletions R/recode.status.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

#' Recode Status
#' @description This function recodes the status variable to custom values.
#' @param status_vector Numeric vector of statuses. This vector will be recoded.
#' @param correct Numeric value which indicates a correct response.
#' @param error Numeric value which indicates an error.
#' @param timeout Numeric value which indicates a timeout.
#'
#' @return numeric
#' @export
#'
#' @examples
recode.status = function(status_vector, correct = 1, error = 0, timeout = NA){
return(psytoolkittools:::recode_status_c(as.character(status_vector),
as.character(correct),
as.character(error),
as.character(timeout)))

}
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# PSYTOOLKITTOOLS

## Installing

Because the package is currently not avaible on CRAN, you have to download it from GitHub and compile it yourself.
Both can be be easily done by using the following R code.
```r
#if devtools are not installed
install.packages("devtools")

library(devtools)
devtools::install_github("condecon/psytoolkittools")
```

**Dependecies**
- reticulate
- cpp11
- cli
- glue
- devtools

Also, RTools are required for compiling the source code.

## Loading Data

At first, download your survey and experiment results from the
PsyToolkit website. You will receive a .ZIP file. Move this file to your
current R working directory.

In R, load the `psytoolkittools` package and run the `load.survey(filename)` function.
For example:
```r
library(psytoolkittools)
data <- load.survey("data.zip")
```
The function returns a data frame with the survey awnsers.
A folder called "psy_data" is created. Do not delete this folder as it is nessecary for loading experiment data.

### Loading Experiment Data
To easily load the experiment data, the package provides the `load.experiment.data()` function.
This functions requires two arguments:
- Column with the experiment data filenames
- Vector of strings which correspond to the structure of the saved experiment data

The function returns a data frame which can be combined with the survey data frame by passing it to the function.
```r
load.experiment.data(experiment.file.names, label.structure, merge.dataframe = surveydata)
```

# Utilities

## `recode.keys`
In PsyToolkit, when saving keys, the pressed key itself is not saved instead the position in which the key was definied is saved.
For further analysis, it can be helpful to reverse the saved value to the name of the original key that has been pressed by the participant.
In this case `recode.keys()` can be used.
The function takes to parameters. The first is the vector or column that shall be recoded. The second parameter is a vector of characters in which the characters are in the same line up than in the experiment code.
For example:
```
#experiment code
...
keys 1 2 3 4 space
...
```

```r
#R code
recode.keys(data, c("1", "2", "3", "4", "space"))
```
## `recode.status`

In PsyToolkit experiments, a status of 1 represents a correct and 2 an incorrect answer. 3 represents a time out.\
With `recode.status()`, status codes can be recoded to custom values.

*Example:*

```r
recode.status(vector, correct = 1, error = 0, timeout = -99)
```
56 changes: 56 additions & 0 deletions inst/python/survey_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class Question:
label:str = ""
type:str = ""
question:str = ""
awnsers:list[str] = []



def parse(tempdir:str) -> list[Question]:
document:list[Question] = []

file = open(f"psy_data\\survey.txt")
survey_string_list = file.readlines()


#remove all empty lines
survey_string_list = [x for x in survey_string_list if x != "\n"]

question = Question()
#loop document
line_index = 0
for line in survey_string_list:
if line.startswith("l:"):
question_label = line.replace("l: ", "")
#question_label = line.replace("\n", "")
question.label = question_label

if line.startswith("t:"):
question_type = line.replace("t: ", "")
question.type = question_type

if line.startswith("q:"):
question.question = line.replace("q: ", "")

if line.startswith("-"):
question.awnsers.append(line.replace("- ", ""))

#check for question end
#question ended when the beginning of the next line is "l: "
try:
if survey_string_list[line_index + 1].startswith("l:"):
#save question
document.append(question)
#create new question variable
question = Question()
question.awnsers = []
except IndexError:
document.append(question)


line_index = line_index + 1



#return
return document
Loading

0 comments on commit 38d6cfd

Please sign in to comment.