summaryrefslogtreecommitdiff
path: root/experiment
diff options
context:
space:
mode:
authorNiclas Dobbertin <niclas.dobbertin@mailbox.org>2023-11-03 08:29:49 +0100
committerNiclas Dobbertin <niclas.dobbertin@mailbox.org>2023-11-03 08:29:49 +0100
commitd94e638f98c599b7c151927d504a474705ae9bca (patch)
tree128765ac1e9cd9413323820f596ced3730091dea /experiment
parentc05a2a127449595f1e62b99adb7aa3a0ded8ec27 (diff)
parent4c71eec3cd5f5f36c1cdc6d2284f6dd93facc193 (diff)
Merge branch 'master' into 1920x1080
Diffstat (limited to 'experiment')
-rw-r--r--experiment/EK_Muster-Einverstaendniserklaerung_2022.odtbin0 -> 120847 bytes
-rw-r--r--experiment/EK_Muster-Einverstaendniserklaerung_2022.pdfbin0 -> 95322 bytes
-rw-r--r--experiment/analysis/analysis.org103
-rw-r--r--experiment/analysis/analysis.pdfbin0 -> 128522 bytes
-rw-r--r--experiment/analysis/analysis.tex150
-rw-r--r--experiment/analysis/tools.py60
-rw-r--r--experiment/frensch_procedures.py150
-rw-r--r--experiment/frensch_task.py430
8 files changed, 893 insertions, 0 deletions
diff --git a/experiment/EK_Muster-Einverstaendniserklaerung_2022.odt b/experiment/EK_Muster-Einverstaendniserklaerung_2022.odt
new file mode 100644
index 0000000..8634478
--- /dev/null
+++ b/experiment/EK_Muster-Einverstaendniserklaerung_2022.odt
Binary files differ
diff --git a/experiment/EK_Muster-Einverstaendniserklaerung_2022.pdf b/experiment/EK_Muster-Einverstaendniserklaerung_2022.pdf
new file mode 100644
index 0000000..c21c686
--- /dev/null
+++ b/experiment/EK_Muster-Einverstaendniserklaerung_2022.pdf
Binary files differ
diff --git a/experiment/analysis/analysis.org b/experiment/analysis/analysis.org
new file mode 100644
index 0000000..e726046
--- /dev/null
+++ b/experiment/analysis/analysis.org
@@ -0,0 +1,103 @@
+#+title: Analysis
+#+PROPERTY: header-args:python+ :session *python* :exports both :tangle yes
+
+* Imports
+#+begin_src python :results none
+import pandas as pd
+from pathlib import Path
+from pprint import pprint
+
+import tools
+
+#+end_src
+
+* Constants
+#+begin_src python :results none
+data_path = Path("/home/niclas/repos/uni/master_thesis/experiment/data")
+
+procedures = ["1", "2", "3", "4", "5", "6", "overall"]
+#+end_src
+
+* Import Data
+** Conditions
+#+begin_src python
+conditions = [x.stem for x in data_path.iterdir() if x.is_dir()]
+conditions
+#+end_src
+
+#+RESULTS:
+| random | fixed | blocked |
+
+** Data
+#+begin_src python :results none
+data = {}
+for condition in conditions:
+ data[condition] = {}
+ for vp in (data_path / condition).iterdir():
+ data[condition][vp.stem] = tools.unpickle(vp / "vp.pkl")
+
+data_train, data_test = tools.train_test_split(data)
+#+end_src
+
+* Basic statistics
+** Total percent correct
+To find out how well VP solved the tasked, we calculate the accuracy for train
+and test phase.
+
+#+begin_src python
+condition = "random"
+df = pd.DataFrame([tools.total_accuracy(data[condition][vp], procedures) for vp in data[condition].keys()], index=data[condition].keys(), columns=["train", "test"])
+df
+#+end_src
+
+#+RESULTS:
+#+begin_example
+ train test
+vp12 0.822222 0.820000
+vp19 0.966667 0.800000
+vp15 0.973333 0.980000
+vp17 0.911111 0.960000
+vp20 0.906667 0.980000
+vp10 0.924444 0.943333
+vp16 0.957778 0.926667
+vp13 0.857778 0.946667
+vp18 0.962222 0.970000
+vp14 0.982222 0.986667
+#+end_example
+
+Most subjects have an accuracy of over 95% in both training and test phase.
+Some however are notably lower, under 90% in either training or test phase, or
+both.
+This could be a systematic misunderstanding of specific equations, that are
+present in both, or only one of the two phases.
+To investigate, we look at the per procedure accuracy per subject.
+
+#+begin_src python
+condition = "random"
+proc_accs = [
+ tools.count_correct(data[condition][vp], data[condition][vp].keys(), procedures)
+ for vp in data[condition].keys()
+]
+for vp in proc_accs:
+ for proc in vp.keys():
+ vp[proc] /= len(next(iter(data[condition].values())).keys())
+df = pd.DataFrame(proc_accs, index=data[condition].keys())
+df
+#+end_src
+
+#+RESULTS:
+#+begin_example
+ 1 2 3 4 5 6 overall
+vp12 0.992 0.592 0.392 0.976 0.960 1.000 0.016
+vp19 1.000 0.992 0.000 0.576 0.992 0.992 0.848
+vp15 0.992 0.992 0.960 0.392 0.592 1.000 0.928
+vp17 0.392 0.968 0.584 1.000 1.000 0.992 0.648
+vp20 0.992 0.376 0.952 0.976 0.976 0.560 0.784
+vp10 0.968 0.360 0.592 0.984 0.984 0.992 0.712
+vp16 0.976 0.600 0.376 0.976 0.992 1.000 0.752
+vp13 0.384 0.960 0.928 0.560 0.992 0.968 0.568
+vp18 0.976 0.976 0.960 0.392 0.600 0.984 0.904
+vp14 0.992 0.976 0.992 0.976 0.400 0.600 0.968
+#+end_example
+
+We can see that most vp have around 2 procedures with accuracy of around 50%
diff --git a/experiment/analysis/analysis.pdf b/experiment/analysis/analysis.pdf
new file mode 100644
index 0000000..751625d
--- /dev/null
+++ b/experiment/analysis/analysis.pdf
Binary files differ
diff --git a/experiment/analysis/analysis.tex b/experiment/analysis/analysis.tex
new file mode 100644
index 0000000..5b56bc8
--- /dev/null
+++ b/experiment/analysis/analysis.tex
@@ -0,0 +1,150 @@
+% Created 2023-10-28 Sat 19:43
+% Intended LaTeX compiler: pdflatex
+\documentclass[11pt]{article}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{graphicx}
+\usepackage{longtable}
+\usepackage{wrapfig}
+\usepackage{rotating}
+\usepackage[normalem]{ulem}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{capt-of}
+\usepackage{hyperref}
+\author{Niclas Dobbertin}
+\date{\today}
+\title{Analysis}
+\hypersetup{
+ pdfauthor={Niclas Dobbertin},
+ pdftitle={Analysis},
+ pdfkeywords={},
+ pdfsubject={},
+ pdfcreator={Emacs 29.1 (Org mode 9.7)},
+ pdflang={English}}
+\usepackage{biblatex}
+\addbibresource{/home/niclas/bib/references.bib}
+\begin{document}
+
+\maketitle
+\tableofcontents
+
+\section{Imports}
+\label{sec:orgbdc2c77}
+\begin{verbatim}
+import pandas as pd
+from pathlib import Path
+from pprint import pprint
+
+import tools
+
+\end{verbatim}
+\section{Constants}
+\label{sec:orgcb8c537}
+\begin{verbatim}
+data_path = Path("/home/niclas/repos/uni/master_thesis/experiment/data")
+
+procedures = ["1", "2", "3", "4", "5", "6", "overall"]
+\end{verbatim}
+\section{Import Data}
+\label{sec:org87e67b0}
+\subsection{Conditions}
+\label{sec:orga12f2b6}
+\begin{verbatim}
+conditions = [x.stem for x in data_path.iterdir() if x.is_dir()]
+conditions
+\end{verbatim}
+
+\begin{center}
+\begin{tabular}{lll}
+random & fixed & blocked\\[0pt]
+\end{tabular}
+\end{center}
+\subsection{Data}
+\label{sec:orgcac95cb}
+\begin{verbatim}
+data = {}
+for condition in conditions:
+ data[condition] = {}
+ for vp in (data_path / condition).iterdir():
+ data[condition][vp.stem] = tools.unpickle(vp / "vp.pkl")
+\end{verbatim}
+
+\begin{verbatim}
+None
+\end{verbatim}
+\subsection{Useful Subdata}
+\label{sec:org4384120}
+\begin{verbatim}
+# data_correct = {conditons[0]: {}, conditons[1]: {}, conditons[2]: {}}
+pass
+# for condition in conditions:
+# data_correct[condition] = None
+\end{verbatim}
+
+\begin{verbatim}
+None
+\end{verbatim}
+\section{Basic statistics}
+\label{sec:org44d0851}
+\subsection{Total percent correct}
+\label{sec:org461b551}
+To find out how well VP solved the tasked, we calculate the accuracy for train
+and test phase.
+
+\begin{verbatim}
+condition = "random"
+df = pd.DataFrame([tools.total_accuracy(data[condition][vp], procedures) for vp in data[condition].keys()], index=data[condition].keys(), columns=["train", "test"])
+df
+\end{verbatim}
+
+\begin{verbatim}
+ train test
+vp12 0.822222 0.820000
+vp19 0.966667 0.800000
+vp15 0.973333 0.980000
+vp17 0.911111 0.960000
+vp20 0.906667 0.980000
+vp10 0.924444 0.943333
+vp16 0.957778 0.926667
+vp13 0.857778 0.946667
+vp18 0.962222 0.970000
+vp14 0.982222 0.986667
+\end{verbatim}
+
+Most subjects have an accuracy of over 95\% in both training and test phase.
+Some however are notably lower, under 90\% in either training or test phase, or
+both.
+This could be a systematic misunderstanding of specific equations, that are
+present in both, or only one of the two phases.
+To investigate, we look at the per procedure accuracy per subject.
+
+\begin{verbatim}
+condition = "random"
+proc_accs = [
+ tools.count_correct(data[condition][vp], data[condition][vp].keys(), procedures)
+ for vp in data[condition].keys()
+]
+for vp in proc_accs:
+ for proc in vp.keys():
+ vp[proc] /= len(next(iter(data[condition].values())).keys())
+df = pd.DataFrame(proc_accs, index=data[condition].keys())
+df
+\end{verbatim}
+
+\begin{verbatim}
+ 1 2 3 4 5 6 overall
+vp12 0.992 0.592 0.392 0.976 0.960 1.000 0.016
+vp19 1.000 0.992 0.000 0.576 0.992 0.992 0.848
+vp15 0.992 0.992 0.960 0.392 0.592 1.000 0.928
+vp17 0.392 0.968 0.584 1.000 1.000 0.992 0.648
+vp20 0.992 0.376 0.952 0.976 0.976 0.560 0.784
+vp10 0.968 0.360 0.592 0.984 0.984 0.992 0.712
+vp16 0.976 0.600 0.376 0.976 0.992 1.000 0.752
+vp13 0.384 0.960 0.928 0.560 0.992 0.968 0.568
+vp18 0.976 0.976 0.960 0.392 0.600 0.984 0.904
+vp14 0.992 0.976 0.992 0.976 0.400 0.600 0.968
+\end{verbatim}
+
+We can see that most vp have around 2 procedures with accuracy of around 50\%
+\end{document} \ No newline at end of file
diff --git a/experiment/analysis/tools.py b/experiment/analysis/tools.py
new file mode 100644
index 0000000..d32ccd3
--- /dev/null
+++ b/experiment/analysis/tools.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+
+import pickle
+from copy import deepcopy
+
+def unpickle(pkl):
+ with open(pkl, "rb") as f:
+ data = pickle.load(f)
+ return data
+
+
+def count_correct(vp, trials, procedures):
+ trials_correct = {}
+ for proc in procedures:
+ trials_correct[proc] = 0
+ for sample in trials:
+ for proc in vp[sample]["procedure_order"]:
+ vp_ans = vp[sample][proc]["answer"]
+ for c in vp_ans:
+ if not c.isdigit():
+ vp_ans = vp_ans.replace(c, "")
+ vp_ans = int(vp_ans)
+ if vp_ans == vp[sample]["water_sample"][proc][0]:
+ trials_correct[proc] += 1
+ return trials_correct
+
+
+def total_accuracy(vp, procedures):
+ train = [x for x in vp.keys() if "train" in x]
+ test = [x for x in vp.keys() if "test" in x]
+
+ train_total = len(train) * len(vp[train[0]]["procedure_order"])
+ test_total = len(test) * len(vp[test[0]]["procedure_order"])
+
+ acc_train = count_correct(vp, train, procedures)
+ acc_test = count_correct(vp, test, procedures)
+
+ acc_train = sum([acc_train[x] for x in acc_train.keys()]) / train_total
+ acc_test = sum([acc_test[x] for x in acc_test.keys()]) / test_total
+
+ return acc_train, acc_test
+
+
+def train_test_split(data):
+ def delete_trials(data, string):
+ new_dict = {}
+ for cond in data.keys():
+ new_dict[cond] = {}
+ for vp in data[cond].keys():
+ new_dict[cond][vp] = {}
+ for trial in data[cond][vp].keys():
+ if string in trial:
+ new_dict[cond][vp][trial] = data[cond][vp][trial]
+ return new_dict
+ data_train = delete_trials(data, "train")
+ data_test = delete_trials(data, "test")
+
+ return data_train, data_test
+
+print("imported tools")
diff --git a/experiment/frensch_procedures.py b/experiment/frensch_procedures.py
new file mode 100644
index 0000000..3c64471
--- /dev/null
+++ b/experiment/frensch_procedures.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+import random
+
+
+class WaterSample:
+ def __init__(
+ self,
+ solid: int,
+ algae: int,
+ lime: tuple[int, int, int, int],
+ toxin: tuple[int, int, int, int],
+ ):
+ self.solid = solid
+ self.algae = algae
+ self.lime = lime
+ self.toxin = toxin
+
+ def procedure_dict(self):
+ procedures = {
+ "1": (self.index1(), self.index1_str()),
+ "2": (self.index2(), self.index2_str()),
+ "3": (self.index3(), self.index3_str()),
+ "4": (self.index4(), self.index4_str()),
+ "5": (self.index5(), self.index5_str()),
+ "6": (self.index6(), self.index6_str()),
+ "overall": (self.overall(), self.overall_str()),
+ }
+
+ return procedures
+
+ def water_sample_dict(self):
+ sample = {
+ "1": (self.index1(), self.index1_str()),
+ "2": (self.index2(), self.index2_str()),
+ "3": (self.index3(), self.index3_str()),
+ "4": (self.index4(), self.index4_str()),
+ "5": (self.index5(), self.index5_str()),
+ "6": (self.index6(), self.index6_str()),
+ "overall": (self.overall(), self.overall_str()),
+ "solid": self.solid,
+ "algae": self.algae,
+ "lime": self.lime,
+ "toxin": self.toxin,
+ }
+ return sample
+
+ def select_procedures(self):
+ procedures = [
+ (self.index1, self.index1_str),
+ (self.index2, self.index2_str),
+ (self.index3, self.index3_str),
+ (self.index4, self.index4_str),
+ (self.index5, self.index5_str),
+ (self.index6, self.index6_str),
+ ]
+ random.shuffle(procedures)
+ training_procedures = procedures[:-1]
+ training_procedures.append((self.overall, self.overall_str))
+
+ return training_procedures, procedures[-1]
+
+ def index1(self):
+ return (self.lime[3] - self.lime[1]) * self.solid
+
+ def index1_str(self):
+ return "(Sandstein_4 - Sandstein_2) * Mineralien"
+
+ def index2(self):
+ return (2 * self.algae) + min(self.lime)
+
+ def index2_str(self):
+ return "(2 * Algen) + Sandstein_min"
+
+ def index3(self):
+ return max(self.toxin) + min(self.toxin)
+
+ def index3_str(self):
+ return "Gifte_max + Gifte_min"
+
+ def index4(self):
+ return (self.solid * 2) - self.toxin[3]
+
+ def index4_str(self):
+ return "(Mineralien * 2) - Gifte_4"
+
+ def index5(self):
+ return max(self.lime[2], (self.toxin[2] - self.toxin[1]))
+
+ def index5_str(self):
+ return "Das Höhere von (Gifte_3 - Gifte_2), (Sandstein_3)"
+
+ def index6(self):
+ return min(self.algae, (self.lime[0] + self.toxin[0]))
+
+ def index6_str(self):
+ return "Das Kleinere von (Sandstein_1 + Gifte_1), (Algen)"
+
+ def overall(self):
+ return 100 - max(
+ self.index1(), self.index2(), self.index3(), self.index4(), self.index5()
+ )
+
+ def overall_str(self):
+ return "100 - dem Höchstem aller Ergebnisse"
+
+ def print_all(self):
+ print(f"Solid: {self.solid}")
+ print(f"Algae: {self.algae}")
+ print(f"Lime: {self.lime}")
+ print(f"Toxin: {self.toxin}")
+ print(f"Index 1: {self.index1_str()} = {self.index1()}")
+ print(f"Index 2: {self.index2_str()} = {self.index2()}")
+ print(f"Index 3: {self.index3_str()} = {self.index3()}")
+ print(f"Index 4: {self.index4_str()} = {self.index4()}")
+ print(f"Index 5: {self.index5_str()} = {self.index5()}")
+ print(f"Index 6: {self.index6_str()} = {self.index6()}")
+ print(f"Overall Quality: {self.overall_str()} = {self.overall()}")
+
+
+# No step should produce a negative number, greater/lesser of comparisons should not
+# use equal numbers
+def constrained_WaterSample():
+ water_sample = random_WaterSample()
+ resample = True
+ while resample:
+ water_sample = random_WaterSample()
+ resample = False
+ # check for negative results
+ for proc in water_sample.procedure_dict().keys():
+ if water_sample.procedure_dict()[proc][0] < 0:
+ resample = True
+ # check for greater/lesser equality
+ # procedure 5
+ if (water_sample.toxin[2] - water_sample.toxin[1]) == water_sample.lime[2]:
+ resample = True
+ # procedure 6
+ if (water_sample.lime[0] + water_sample.toxin[0]) == water_sample.algae:
+ resample = True
+ return water_sample
+
+
+def random_WaterSample():
+ solid = random.randint(1, 9)
+ algae = random.randint(1, 9)
+ lime = tuple(random.randint(1, 9) for _ in range(4))
+ toxin = tuple(random.randint(1, 9) for _ in range(4))
+
+ return WaterSample(solid, algae, lime, toxin)
diff --git a/experiment/frensch_task.py b/experiment/frensch_task.py
new file mode 100644
index 0000000..1949d51
--- /dev/null
+++ b/experiment/frensch_task.py
@@ -0,0 +1,430 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import pickle
+import random
+from collections import namedtuple
+from pprint import pprint
+
+import pandas as pd
+from psychopy import core, event, gui, visual
+
+import frensch_procedures
+
+DisplayVariable = namedtuple("DisplayVariable", ["name", "values"])
+DisplayProcedure = namedtuple("DisplayProcedure", ["procedure", "solution"])
+
+intro_text = """Vielen Dank dass Sie bei unserem Experiment zum menschlichen Lernen teilnehmen!
+
+In diesem Experiment arbeiten Sie in einem Labor, welches die Wasserqualität analysiert.
+Dafür bekommen Sie einige Wasserproben und müssen für jede Probe verschiedene Kennwerte ermitteln.
+
+Jede Wasserprobe besitzt bereits verschiedene gemessene Werte, wie der Algengehalt, welche für die Berechnungen benutzt werden.
+
+
+(Leertaste zum Fortfahren)
+"""
+
+intro2_text = """Im folgenden müssen sie verschiedene Rechenaufgaben lösen um die Kennwerte zu berechnen.
+Verwenden Sie die gewohnten Rechenregeln und geben sie Ihre Lösung bitte immer als zweistellige Zahl ein, und bestätigen mit Enter.
+Die Werte der Variablen (z.B. Algen) werden oben am Bildschirm angezeigt.
+
+Manche Variablen haben mehrere mögliche Werte; "Gifte_2" besagt z.B., dass der zweite Wert der Giftwerte zu verwenden ist.
+"_max/_min" besagt, dass der maximale/minimale Wert dieser Variable zu verwenden ist.
+
+Als letzte Berechnung müssen Sie den Gesamtwert der Wasserqualität aus ihren Ergebnissen berechnen.
+
+Nach jeder Aufgabe können Sie kurz pausieren.
+
+Drücken Sie die Leertaste um zu beginnen"""
+
+
+def experiment_shutdown():
+ WIN.close()
+ core.quit()
+
+
+WIN = visual.Window((1920, 1080), fullscr=True, units="pix")
+MONITOR_FPS = 60
+TRAIN_TRIALS = 75
+TEST_TRIALS = 50
+# TRAIN_TRIALS = 3
+# TEST_TRIALS = 3
+ORDER_CONDITIONS = ["fixed", "random", "blocked"]
+PROCEDURE_KEYS = ["1", "2", "3", "4", "5", "6", "overall"]
+
+# Cancel experiment anytime with Esc
+event.globalKeys.add(key="escape", func=experiment_shutdown, name="shutdown")
+
+
+def generate_variable_display(varx: list[DisplayVariable], x_positions: list[int]):
+ assert len(varx) == len(x_positions)
+
+ stims = []
+
+ def gen_value_stims(values, x, y, offset):
+ for value in values:
+ y -= offset
+ value_stim = visual.TextBox2(
+ WIN,
+ pos=(x, y),
+ text=value,
+ # size=200,
+ letterHeight=75,
+ alignment="center",
+ )
+ stims.append(value_stim)
+
+ y = 550
+ offset = 100
+
+ for var, x_pos in zip(varx, x_positions):
+ stim_var = visual.TextBox2(
+ WIN,
+ pos=[x_pos, y],
+ text=var.name,
+ # size=[1000, 1000],
+ letterHeight=40,
+ alignment="center",
+ )
+ stims.append(stim_var)
+
+ gen_value_stims(var.values, x_pos, y, offset)
+
+ return stims
+
+
+def generate_procedure_display(procedure: DisplayProcedure, position):
+ stim_procedure = visual.TextBox2(
+ WIN,
+ pos=position,
+ text=procedure.procedure,
+ size=[1000000, 1000],
+ letterHeight=40,
+ alignment="center",
+ )
+ return stim_procedure
+
+
+def generate_all_watersamples(n):
+ samples = []
+ for _ in range(n):
+ samples.append(frensch_procedures.constrained_WaterSample())
+ return samples
+
+
+def run_blocked_trials(water_samples, procedure_keys):
+ results = {}
+ for proc_idx, proc in enumerate(procedure_keys):
+ for sample_idx, sample in enumerate(water_samples):
+
+ if sample_idx % 6 == 0:
+ pause_after_trial.draw()
+ WIN.flip()
+ event.waitKeys(keyList=["space"])
+
+ cur_key = f"train_{sample_idx}"
+ if not cur_key in results.keys():
+ results[cur_key] = {}
+ results[cur_key]["procedure_order"] = tuple(procedure_keys)
+ results[cur_key]["water_sample"] = sample.water_sample_dict()
+
+ solid = DisplayVariable("Mineralien", [sample.solid])
+ algae = DisplayVariable("Algen", [sample.algae])
+ lime = DisplayVariable("Sandstein", sample.lime)
+ toxin = DisplayVariable("Gifte", sample.toxin)
+ x_positions = [-800, -400, 400, 800]
+
+ stims = generate_variable_display([solid, algae, lime, toxin], x_positions)
+
+ procedures = sample.procedure_dict()
+
+ proc_x = -600
+ proc_y = -100
+ answ_x = 200
+ answ_y = -100
+ y_offset = 80
+
+ for prev in procedure_keys[:proc_idx]:
+ if not prev:
+ continue
+ p = DisplayProcedure(procedures[prev][1], procedures[prev][0])
+ p = generate_procedure_display(p, (proc_x, proc_y))
+ stims.append(p)
+ proc_y -= y_offset
+
+ stim_answer_equals = visual.TextBox2(
+ WIN,
+ "=",
+ letterHeight=50,
+ pos=(answ_x - 100, answ_y),
+ size=[150, 70],
+ alignment="center",
+ )
+ stims.append(stim_answer_equals)
+
+ stim_answer_box = visual.TextBox2(
+ WIN,
+ results[f"train_{sample_idx}"][prev]["answer"],
+ letterHeight=50,
+ pos=(answ_x, answ_y),
+ size=[150, 70],
+ editable=True,
+ fillColor="white",
+ color="black",
+ alignment="center",
+ )
+ stims.append(stim_answer_box)
+ answ_y -= y_offset
+
+ p = DisplayProcedure(procedures[proc][1], procedures[proc][0])
+ p = generate_procedure_display(p, (proc_x, proc_y))
+ stims.append(p)
+ proc_y -= y_offset
+
+ stim_answer_equals = visual.TextBox2(
+ WIN,
+ "=",
+ letterHeight=50,
+ pos=(answ_x - 100, answ_y),
+ size=[150, 70],
+ alignment="center",
+ )
+ stims.append(stim_answer_equals)
+
+ print(sample_idx)
+ stim_answer_box = visual.TextBox2(
+ WIN,
+ "",
+ letterHeight=50,
+ pos=(answ_x, answ_y),
+ size=[150, 70],
+ editable=True,
+ fillColor="white",
+ color="black",
+ alignment="center",
+ )
+ stims.append(stim_answer_box)
+ answ_y -= y_offset
+
+ not_finished = True
+ answer = "not answered"
+ start_time = core.monotonicClock.getTime()
+ while not_finished:
+ stim_answer_box.hasFocus = True
+ for stim in stims:
+ stim.draw()
+ WIN.flip()
+ answer = stim_answer_box.text
+ if "\n" in answer:
+ if answer[0].isdigit() and answer[1].isdigit():
+ not_finished = False
+ else:
+ stim_answer_box.text = answer[:-1]
+ if len(answer) > 2:
+ stim_answer_box.text = stim_answer_box.text[:2]
+ answer_time = core.monotonicClock.getTime() - start_time
+ answer = (answer.replace("\n", ""), answer_time)
+ results[cur_key][proc] = {"answer": answer[0], "time": answer[1]}
+
+ pause_new_proc.draw()
+ WIN.flip()
+ event.waitKeys(keyList=["space"])
+
+ return results
+
+
+def run_trial(water_sample, procedure_keys: list, condition):
+ water_sample.print_all()
+
+ if condition == "random":
+ overall = procedure_keys.pop()
+ random.shuffle(procedure_keys)
+ procedure_keys.append(overall)
+
+ solid = DisplayVariable("Mineralien", [water_sample.solid])
+ algae = DisplayVariable("Algen", [water_sample.algae])
+ lime = DisplayVariable("Sandstein", water_sample.lime)
+ toxin = DisplayVariable("Gifte", water_sample.toxin)
+ x_positions = [-800, -400, 400, 800]
+
+ stims = generate_variable_display([solid, algae, lime, toxin], x_positions)
+
+ procedures = water_sample.procedure_dict()
+
+ answers = []
+ proc_x = -600
+ proc_y = -100
+ answ_x = 200
+ answ_y = -100
+ y_offset = 80
+ for proc in procedure_keys:
+ p = DisplayProcedure(procedures[proc][1], procedures[proc][0])
+ p = generate_procedure_display(p, (proc_x, proc_y))
+ stims.append(p)
+ proc_y -= y_offset
+
+ stim_answer_equals = visual.TextBox2(
+ WIN,
+ "=",
+ letterHeight=50,
+ pos=(answ_x - 100, answ_y),
+ size=[150, 70],
+ alignment="center",
+ )
+ stims.append(stim_answer_equals)
+
+ stim_answer_box = visual.TextBox2(
+ WIN,
+ "",
+ letterHeight=50,
+ pos=(answ_x, answ_y),
+ size=[150, 70],
+ editable=True,
+ fillColor="white",
+ color="black",
+ alignment="center",
+ )
+ stims.append(stim_answer_box)
+ answ_y -= y_offset
+
+ not_finished = True
+ answer = "not answered"
+ start_time = core.monotonicClock.getTime()
+ while not_finished:
+ stim_answer_box.hasFocus = True
+ for stim in stims:
+ stim.draw()
+ WIN.flip()
+ answer = stim_answer_box.text
+ if "\n" in answer:
+ if answer[0].isdigit() and answer[1].isdigit():
+ not_finished = False
+ else:
+ stim_answer_box.text = answer[:-1]
+ if len(answer) > 2:
+ stim_answer_box.text = stim_answer_box.text[:2]
+ answer_time = core.monotonicClock.getTime() - start_time
+ answers.append((answer.replace("\n", ""), answer_time))
+
+ # event.waitKeys(keyList=["space"])
+
+ return tuple(answers), tuple(procedure_keys)
+
+
+condition_dlg = gui.Dlg(title="Experiment Condition")
+condition_dlg.addText("Condition")
+condition_dlg.addField("condition")
+CONDITION = condition_dlg.show()[0]
+
+assert CONDITION in ORDER_CONDITIONS
+
+pause = visual.TextBox2(
+ WIN,
+ """Drücken Sie die Leertaste um mit der nächsten Wasserprobe fortzufahren""",
+ letterHeight=50,
+ alignment="center",
+)
+
+pause_new_proc = visual.TextBox2(
+ WIN,
+ """Drücken Sie die Leertaste um mit dem nächsten Kennwert fortzufahren""",
+ letterHeight=50,
+ alignment="center",
+)
+
+pause_after_trial = visual.TextBox2(
+ WIN,
+ """Drücken Sie die Leertaste um fortzufahren""",
+ letterHeight=50,
+ alignment="center",
+)
+
+intro = visual.TextBox2(
+ WIN, intro_text, letterHeight=40, alignment="center", size=(100000, 100000)
+)
+intro.draw()
+WIN.flip()
+event.waitKeys(keyList=["space"])
+
+intro2 = visual.TextBox2(
+ WIN, intro2_text, letterHeight=40, alignment="center", size=(100000, 100000)
+)
+intro2.draw()
+WIN.flip()
+event.waitKeys(keyList=["space"])
+
+train_procedures = PROCEDURE_KEYS[:-1]
+random.shuffle(train_procedures)
+transfer_procedure = train_procedures[-1]
+train_procedures = train_procedures[:-1]
+train_procedures.append(PROCEDURE_KEYS[-1])
+
+all_samples = generate_all_watersamples(TRAIN_TRIALS + TEST_TRIALS)
+
+if CONDITION != "blocked":
+ results = {}
+ for i in range(TRAIN_TRIALS):
+ print(train_procedures)
+ answer, procedure_keys = run_trial(all_samples[i], train_procedures, CONDITION)
+ answer_dict = {}
+ answer_dict["procedure_order"] = procedure_keys
+ answer_dict["water_sample"] = all_samples[i].water_sample_dict()
+ for proc, key in zip(answer, procedure_keys):
+ answer_dict[key] = {"answer": proc[0], "time": proc[1]}
+
+ results[f"train_{i}"] = answer_dict
+
+ pause.draw()
+ WIN.flip()
+ event.waitKeys(keyList=["space"])
+else:
+ results = run_blocked_trials(all_samples[:TRAIN_TRIALS], train_procedures)
+
+
+phase = visual.TextBox2(
+ WIN,
+ """Sie haben den ersten Teil geschafft! Der zweite Teil ist etwas kürzer als der erste
+
+Drücken Sie die Leertaste um anzufangen.""",
+ letterHeight=40,
+ alignment="center",
+)
+phase.draw()
+WIN.flip()
+event.waitKeys(keyList=["space"])
+
+train_procedures[2] = transfer_procedure
+for i in range(TEST_TRIALS):
+ print(train_procedures)
+ answer, procedure_keys = run_trial(
+ all_samples[TRAIN_TRIALS + i], train_procedures, "fixed"
+ )
+ print(procedure_keys)
+ answer_dict = {}
+ answer_dict["procedure_order"] = procedure_keys
+ answer_dict["water_sample"] = all_samples[TRAIN_TRIALS + i].water_sample_dict()
+ for proc, key in zip(answer, procedure_keys):
+ answer_dict[key] = {"answer": proc[0], "time": proc[1]}
+
+ results[f"test_{i}"] = answer_dict
+
+ pause.draw()
+ WIN.flip()
+ event.waitKeys(keyList=["space"])
+
+pprint(results)
+
+df = pd.DataFrame.from_dict(results, orient="index")
+df.to_csv("vp_results.csv")
+
+with open("vp.pkl", "wb") as file:
+ pickle.dump(results, file)
+
+outro_text = "Das Experiment ist nun vorüber.\n\nVielen Dank für Ihre Teilnahme!"
+outro = visual.TextBox2(
+ WIN, outro_text, letterHeight=40, alignment="center", size=(100000, 100000)
+)
+outro.draw()
+WIN.flip()
+event.waitKeys(keyList=["space"])