查看原文
其他

Psychopy | 第4期:实验数据的收集与处理

喵君姐姐 壹脑云科研圈 2022-10-07




Hello,
这里是行上行下,我是喵君姐姐~

最近在家实在无聊,所以只好安安心心学习啦。你最近在家干什么呢?

今天,继续邀请阿槑给你带来Psychopy系列教程,带来实验数据的收集与处理,希望你会继续喜欢并且一直支持哟~

Part1 
相关概念的简单引入


在前几期,我们已经学习了Psychopy入门数据类型与运算符条件与循环flanker范式的完整编程

但是,在心理学实验中,除了要在屏幕上呈现想要的刺激,我们还希望计算机能够帮助我们收集被试基本信息、反应时以及正确率等指标。

那么如何达到这一目的呢?


Part2 
被试信息录入

对于被试信息,我们将使用 psychopy 中的 gui 来进行收集,所谓 gui ,就是一个被试可以在其中填写信息的对话框,如图:

 


在导入 gui 后,我们使用 gui 中的 DlgFromDict 来进行功能的实现:


from psychopy import core, gui #导入  info = {'observer':'01_xxx','gender':['m','f'],'age':18}  dlg = gui.DlgFromDict(info, title='flanker',                         order=['observer','gender','age']) #设置gui  if dlg.OK: #单击 OK 后的操作      pass #占位符  else:      core.quit()  


可以看到,我们先定义了一个字典来储存需要填写信息的标题以及对应的默认内容,之后我们使用 DlgFromDict 将字典导入 gui 并设置 gui 的标题(title)以及内容顺序(order)。

最后用一个 if 函数来控制 OK 按键使其继续后面的操作,或者 Cancel 按键退出整个程序。因为这里的退出需要使用 core,因此同样需要在开头进行导入。
 
下一步,我们需要确定一下我们最终记录的数据文档的输出路径及文件名。

首先,我们在程序所在路径,也就是我们程序脚本所在的位置建立一个 data 文件夹用于存放数据。

之后,我们先以字符串的形式定义文件名,为了防止文件重复,将会通过 time 获取当前系统时间并添加到文件名中。


import time  date = time.strftime("_20%y_%m_%d_%H%M", time.localtime()) #获取当前时间  file = 'data/'+info['observer']+'_'+info['gender']+          '_'+str(info['age'])+date #文档名(含路径)  


这里的 '%y' , '%m', '%d', '%H%M' 被称为占位符所谓占位符,可以理解为先占据一个位置,以便之后添加需要填进去的内容。当需要填入的内容过长或需要进行实时获取时,我们常用占位符来暂时补齐位置。

不同性质的内容需要不同的占位符,常见的占位符包括 '%s' 字符串,'%d' 整数,'%f' 浮点数。

当我们获得了文件名以后,使用 with open...as... 进行相应的文档操作。


with open("%s.csv"%(file),'a') as D: #写入表头      D.write('flanker'+','+'center'+','+'RT'+','+'accuracy'+'\n') 


简要分析如下:

 


一般来讲,实验过程中所输出的数据均写入 csv 文档中,csv 文档可以使用 excel 打开并比我们平时用的 xls 要更加简单。在 csv 文档中,不同单元格之间仅仅以逗号作为分隔符来进行分隔。

该段代码的输出效果如下:

 


除了所介绍的这种简便的方法,对于文件夹、文档的操作还可以使用 python 中的 os 来进行操作,但是 os 对于含有中文的路径不是特别友好,因此这里将不再介绍。
 
当我们准备好文档后,就可以使用 with open...as... 不断地对文档内容进行写入,只需要与表头相对应即可。

对于我们的例子 flanker 范式来说,我们所要收集的是被试的每次反应的反应时以及正确与否。

 

Part3
反应时记录

在 psychopy 的 Window 中,flip() 方法正好含有该方法运行时距离整个程序开始时的时间,因此我们只需要找到反应屏的 flip() 时间与前一个屏的 flip() 时间做差,就可以得到被试的反应时间。
from psychopy import event  for frame in range(fix_times): #注视点呈现      fix.draw()      time0 = Win.flip()      print(time0)            while True:      [s.draw() for s in stims] #刺激呈现      time1 = Win.flip()      if len(event.getKeys(['left','right'])) > 0: break    RT = time1 - time0 #计算反应时  


Part4
正确率记录

我们要获得正确率,记录每一次被试的反应时否正确即可,我们默认正确记为1,错误或未反应记为0。


from psychopy import event  b=0 #设置变量 b 的起始值用于后面控制呈现中止  while True: #呈现刺激      [s.draw() for s in stims]      time1 = Win.flip()                for key in event.getKeys(): #收集按键          if key in ['left', 'right']:              RT = round((time1-time0)*1000) #计算反应时              b=1              break #跳出收集按键的 for 循环      if b==1:          b=0 #b设置回默认          break #跳出呈现的 while 循环        if (stims[2].flipHoriz==True and key=='right') or\      (stims[2].flipHoriz==False and key=='left'): #判断按键是否正确      acc=1  else:      acc=0  


我们把单纯的监测按键加上了判断,这里的判断方法为判断中间的箭头 stims[2] 是否进行了翻转,如果发生了翻转那么呈现出来的是 →,则被试反应为 'right' 时为正确反应,反之亦然。

同时设置变量 b 以控制跳出呈现屏。
 
到此,我们获得了每个试次的被试反应时以及其判断正确与否的情况,我们下面只需要将其输出到文档即可,同样可用with open。


with open("%s.csv"%(file),'a') as D:      D.write(trial[0]+','+trial[1]+','+str(RT)+','+str(acc)+'\n')  


如此,我们完整的 flanker 范式也就完成了,其输出效果如下:




Part5
完整代码展示

最后我们附上完整代码:


# -*- coding: utf-8 -*-  """  The demo of flanker    @author: A Mei  """        from psychopy import visual, event, gui, core    import time, random    #导入#设置guiinfo = {'observer':'01_xxx','gender':['m','f'],'age':18}    dlg = gui.DlgFromDict(info, title='flanker',                        order=['observer','gender','age'])  if dlg.OK:        pass    else:        core.quit()    #设置数据文档date = time.strftime("_20%y_%m_%d_%H%M", time.localtime())  #设置文件file = 'data/'+info['observer']+'_'+info['gender']+'_'+str(info['age'])+datewith open("%s.csv"%(file),'a') as D:        D.write('flanker'+','+'center'+','+'RT'+','+'accuracy'+'\n') #设置窗口、注视点、结束语        Win = visual.Window((1024,768), color=(128,128,128), fullscr=False,                        units='pix',colorSpace='rgb255')      fix = visual.TextStim(Win, text='+', color='black', height=50,bold=True)    endPrompt = visual.TextStim(Win, text='实验结束,谢谢!',                               color='black', height=60)    #设置各部分呈现时间    Rate = 60    Dura = 1000/Rate    fix_Dura = 300    blank_Dura = 500    fix_times = int(round(fix_Dura/Dura))    blank_times = int(round(blank_Dura/Dura))    #设置自变量 var = []    for flanker in ['left', 'right']:        for center in ['same','diff']:            var.append([flanker, center])    random.shuffle(var)    #设置刺激位置sites = []    for site in range(5):        sites.append((-100+50*site,0))    #开始循环   a = 0    b = 0    for trial in var:        a+=1        #设置是否翻转    stims = []        for stim in range(5):                        if (trial[0] == 'left' and trial[1] == 'diff' and stim == 2) or\          (trial[0] == 'right' and trial[1] == 'diff' and stim != 2) or\          (trial[0] == 'right' and trial[1] == 'same'):                Horiz = True            else:                Horiz = False            #定义箭头                arr = visual.TextStim(Win, text='←', color='black', height=50,                                  pos=(-200+100*stim,0),flipHoriz=Horiz,                                bold=True)            stims.append(arr)        #呈现注视点    for frame in range(fix_times):            fix.draw()            time0 = Win.flip()        #呈现刺激并收集反应时            while True:            [s.draw() for s in stims]            time1 = Win.flip()                        for key in event.getKeys():                if key in ['left', 'right']:                    RT = round((time1-time0)*1000)                    b=1                    break            if b==1:                b=0                break        #判断被试反应是否正确        if (stims[2].flipHoriz==True and key=='right') or\          (stims[2].flipHoriz==False and key=='left'):                acc=1        else:            acc=0        #呈现空屏            for frame in range(blank_times):            Win.flip()        #写入数据        with open("%s.csv"%(file),'a') as D:            D.write(trial[0]+','+trial[1]+','+str(RT)+','+str(acc)+'\n')        #呈现结束语        if a==len(var):            while True:                endPrompt.draw()                Win.flip()                if len(event.getKeys()) > 0: break            Win.close()    

(点击左右滑动可看完整代码)


到现在,我们的 psychopy 系列就要告一段落了。虽然相较于 builder(类似e-prime),coder 更为繁琐,难度也略大。但是其精确性与自由性是 builder 无法比拟的。

当然,本系列只是以实现想要的功能为目的,因此并没有介绍特别多的方法。同时,如果想要比较熟练地进行 psychopy 程序的编写,大量的练习是必不可少的。

因此,感兴趣的同学可以以本系列为入门,在掌握本系列所介绍的基础知识前提下掌握更多 python,psychopy 的方法,从而可以更好地应对不同的实验范式要求。

下一期写,如何自主学习Psychopy以及介绍一些相关的网站,大家可以持续关注我们哟~
 
Part6
系列课程的总结
 
至此,我们已经学习了Psychopy入门数据类型与运算符条件与循环flanker范式的完整编程、实验数据的收集与处理。

基本学完了 Python 在 Psychopy 中需要用到的大多数知识,虽然难度不是很大,但是比较繁杂,建议通过练习以熟悉这些基本的语句和方法。

如果想要了解其他有关 Python 的基础知识,也可以通过更多阅读的到达前几期的传送门来进行学习~

PS:后台回复关键词“psychopy第4期”即可获得所述的资料及代码啦!

作者:阿槑
排版:喵君姐姐
参考文献:
Eriksen, B. A., & Eriksen, C. W. (1974). Effects of noise letters upon the identification of a target letter in a nonsearch task. Perception & Psychophysics, 16(1), 143-149.


第0期:psychopy coder入门
第1期 | psychopy:数据类型及运算符
Psychopy | 第2期:从Stroop看条件与循环
Psychopy | 第3期: 从 flanker 范式看完整的程序

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存