了解 PsychoPy 的数据记录
Understanding PsychoPy's data logging
我有一个测试 PsychoPy Builder 脚本,我用它来调查一些违反直觉的行为。结构是四个例程:
"Init",不在循环中,下面代码在"Begin Experiment":
x = 0
y = 0
z = 0
foo = [0, 0, 0]
"One",在一个循环中,"End Routine"中的代码如下:
x = x + 1
foo[0] = foo[0] + 1
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("foo", foo)
"Two",在一个循环中,下面的代码在"End Routine":
y = y + 2
foo[1] = foo[1] + 2
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("fooY", foo[1])
thisExp.addData("foo", foo)
"Three",在一个循环中,"End Routine"中的如下代码:
z = z + 3
foo[2] = foo[2] + 3
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("foo", foo)
没有其他代码,没有其他组件。例程 "One"、"Two" 和 "Three" 形成一个按顺序执行五次的循环。 CSV输出文件的相关列如下:
trials.thisRepN trials.thisTrialN trials.thisN trials.thisIndex x y z foo fooY
0 0 0 0 1 2 3 [5, 10, 15] 2
1 0 1 0 2 4 6 [5, 10, 15] 4
2 0 2 0 3 6 9 [5, 10, 15] 6
3 0 3 0 4 8 12 [5, 10, 15] 8
4 0 4 0 5 10 15 [5, 10, 15] 10
这是预期的输出吗?如果是这样,为什么?请注意,单个变量 x、y 和 z 每次循环(在循环结束时)都显示更新的值,而列表 foo 仅显示循环迭代所有五次后的最终值,但它在每一行中显示这一点。但是调出列表中的单个元素显示为单个变量。
这背后的逻辑和原理是什么?
有没有办法让列表输出像其他输出一样执行?
有没有办法在调用 addData() 时强制输出 capture/display 这些变量中的任何一个,而不是等到循环结束?
我想我知道这里出了什么问题。这可能是因为 python 通过引用而不是复制来分配。这在其他地方有详细解释,但简要说明,
original = [1, 2]
new = original # new is simply a reference to original! It is not a copy.
new[0] = 'Oops' # original is now ['Oops', 2] as is new (which is just a reference or pointer
在您的例子中,TrialHandler 接收引用,它简单地指向 "foo" 变量,该变量在整个实验过程中更新。由于日志仅在实验结束时保存,因此 "foo" 中的所有行现在都指向 "foo variable",它现在包含值 [5, 10, 15]。
这种按引用赋值可以非常漂亮和方便,但有时会像您的示例中那样令人头疼。它适用于所有 python 可变变量:列表、字典、函数和 类。但不适用于不可变对象,例如数字、元组和字符串!这就是为什么您的脚本适用于数字但不适用于列表的原因。
有不同的解决方案。最简单的方法可能是用 thisExp.addData("foo", tuple(foo))
替换 addData
调用,这会将可变列表转换为不可变元组。也可以做thisExp.addData("foo", [x for x in foo])
。对于各种对象更全面的解决方案是在实验开始时运行 import copy
然后在其他代码块中添加thisExp.addData("foo", copy.copy(foo))
之类的数据(如果你有一个复杂的对象, 请改用 copy.deepcopy
).
我有一个测试 PsychoPy Builder 脚本,我用它来调查一些违反直觉的行为。结构是四个例程:
"Init",不在循环中,下面代码在"Begin Experiment":
x = 0
y = 0
z = 0
foo = [0, 0, 0]
"One",在一个循环中,"End Routine"中的代码如下:
x = x + 1
foo[0] = foo[0] + 1
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("foo", foo)
"Two",在一个循环中,下面的代码在"End Routine":
y = y + 2
foo[1] = foo[1] + 2
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("fooY", foo[1])
thisExp.addData("foo", foo)
"Three",在一个循环中,"End Routine"中的如下代码:
z = z + 3
foo[2] = foo[2] + 3
thisExp.addData("x", x)
thisExp.addData("y", y)
thisExp.addData("z", z)
thisExp.addData("foo", foo)
没有其他代码,没有其他组件。例程 "One"、"Two" 和 "Three" 形成一个按顺序执行五次的循环。 CSV输出文件的相关列如下:
trials.thisRepN trials.thisTrialN trials.thisN trials.thisIndex x y z foo fooY
0 0 0 0 1 2 3 [5, 10, 15] 2
1 0 1 0 2 4 6 [5, 10, 15] 4
2 0 2 0 3 6 9 [5, 10, 15] 6
3 0 3 0 4 8 12 [5, 10, 15] 8
4 0 4 0 5 10 15 [5, 10, 15] 10
这是预期的输出吗?如果是这样,为什么?请注意,单个变量 x、y 和 z 每次循环(在循环结束时)都显示更新的值,而列表 foo 仅显示循环迭代所有五次后的最终值,但它在每一行中显示这一点。但是调出列表中的单个元素显示为单个变量。
这背后的逻辑和原理是什么?
有没有办法让列表输出像其他输出一样执行?
有没有办法在调用 addData() 时强制输出 capture/display 这些变量中的任何一个,而不是等到循环结束?
我想我知道这里出了什么问题。这可能是因为 python 通过引用而不是复制来分配。这在其他地方有详细解释,但简要说明,
original = [1, 2]
new = original # new is simply a reference to original! It is not a copy.
new[0] = 'Oops' # original is now ['Oops', 2] as is new (which is just a reference or pointer
在您的例子中,TrialHandler 接收引用,它简单地指向 "foo" 变量,该变量在整个实验过程中更新。由于日志仅在实验结束时保存,因此 "foo" 中的所有行现在都指向 "foo variable",它现在包含值 [5, 10, 15]。
这种按引用赋值可以非常漂亮和方便,但有时会像您的示例中那样令人头疼。它适用于所有 python 可变变量:列表、字典、函数和 类。但不适用于不可变对象,例如数字、元组和字符串!这就是为什么您的脚本适用于数字但不适用于列表的原因。
有不同的解决方案。最简单的方法可能是用 thisExp.addData("foo", tuple(foo))
替换 addData
调用,这会将可变列表转换为不可变元组。也可以做thisExp.addData("foo", [x for x in foo])
。对于各种对象更全面的解决方案是在实验开始时运行 import copy
然后在其他代码块中添加thisExp.addData("foo", copy.copy(foo))
之类的数据(如果你有一个复杂的对象, 请改用 copy.deepcopy
).