-
Notifications
You must be signed in to change notification settings - Fork 1
/
analysis_utils.py
174 lines (139 loc) · 5.34 KB
/
analysis_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import numpy as np
from matplotlib.mlab import csv2rec
from scipy.optimize import leastsq
import matplotlib.pyplot as plt
def get_data(file_name):
file_read = file(file_name,'r')
l = file_read.readline()
p = {} #This will hold the params
l = file_read.readline()
data_rec = []
if l=='':
return p,l,data_rec
while l[0]=='#':
try:
p[l[1:l.find(':')-1]]=float(l[l.find(':')+1:l.find('\n')])
#Not all the parameters can be cast as float (the task and the
#subject):
except:
p[l[2:l.find(':')-1]]=l[l.find(':')+1:l.find('\n')]
l = file_read.readline()
try:
data_rec = csv2rec(file_name)
except ValueError:
p = []
return p,l,data_rec
# Helper function in order to get rid of small round-off error in the
# representation of trial contrasts in the staircase object:
def defloaterrorize(a):
# Turn into units of % contrast:
a *= 100
# Truncate anything smaller than 1% contrast:
a = a.astype(int)
# Recover the original units (0-1):
a = a/100.0
return a
def analyze(amp, c, guess=0.5, flake=0.01, slope=3.5, fig_name=None,
bootstrap_n=1000, n_up=3):
"""
Perform a psychometric curve analysis of the data in the staircase and
save a figure, if needed.
Parameters
----------
amp: float array
The magnitude of the signal
c: float/int array
The correct value in each trial (0/1), -1 for no response.
guess: The expected hit rate when the subject is blind-folded (default:
0.5)
flake: The expected rate of misses on trials on which the subjects
should actually succeed, if they are really doing the task (default: 0.1)
slope: The slope of the psychometric curve at the inflection point
(default to 3.5)
fig_name: string
A file name for saving a figure. If none provided, don't save the
generated figure
bootstrap_n: int
The number of boot samples to take for the bootstrapping analysis
n_up: int
What kind of staircase was used
Note
----
The fitting procedure is applied to the slope, as well as to the
threshold.
"""
def weibull(x,threshx,slope,guess,flake,threshy=None):
if threshy is None:
threshy = 1-(1-guess)*np.exp(-1)
k = (-np.log( (1-threshy)/(1-guess) ))**(1/slope)
weib = flake - (flake-guess)*np.exp(-(k*x/threshx)**slope)
return weib
def get_thresh(amp,c):
"""Calculate a threshold given amp, c(orrect) values """
#Helper functions for fitting the psychometric curve, need to be
#defined within the local scope, so that they can grok the data:
def weib_fit(pars):
thresh,slope = pars
return weibull(x,thresh,slope,guess,flake)
def err_func(pars):
return y-weib_fit(pars)
#Throw away the None's:
hit_amps = amp[c==1]
miss_amps = amp[c==0]
# Get rid of floating point error:
hit_amps = defloaterrorize(hit_amps)
miss_amps = defloaterrorize(miss_amps)
all_amps = np.hstack([hit_amps,miss_amps])
stim_intensities = np.unique(all_amps)
n_correct = [len(np.where(hit_amps==i)[0]) for i in stim_intensities]
n_trials = [len(np.where(all_amps==i)[0]) for i in stim_intensities]
Data = zip(stim_intensities,n_correct,n_trials)
x = []
y = []
n = []
for idx,this in enumerate(Data):
#Take only cases where there were at least n_up observations:
if n_trials[idx]>=n_up:
#Contrast values:
x = np.hstack([x,this[2] * [this[0]]])
#% correct:
y = np.hstack([y,this[2] * [this[1]/float(this[2])]])
initial = np.mean(x),slope
this_fit , msg = leastsq(err_func,initial)
return this_fit,x,y
#Convert the flake into the expected format for the weibull function:
flake = 1-flake
this_fit,keep_x,keep_y = get_thresh(amp,c)
#print keep_x
#print keep_y
bootstrap_th = []
bootstrap_slope = []
keep_amp = amp
keep_c = c
keep_slope = this_fit[1]
keep_th = this_fit[0]
for b in xrange(bootstrap_n):
b_idx = np.random.randint(0,c.shape[0],c.shape[0])
amp = keep_amp[b_idx]
c = keep_c[b_idx]
this_fit,x,y = get_thresh(amp,c)
bootstrap_th.append(this_fit[0])
upper = np.sort(bootstrap_th)[bootstrap_n*0.975]
lower = np.sort(bootstrap_th)[bootstrap_n*0.025]
#Make a figure, if required:
if fig_name is not None:
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
for idx,this_x in enumerate(keep_x):
n = np.sum(keep_x==this_x) # How many trials, sets the markersize
ax.plot(this_x,keep_y[idx],'o',color = 'b',markersize = n)
x_for_plot = np.linspace(np.min(keep_x)-0.05,np.max(keep_x)+0.05,100)
ax.plot(x_for_plot,weibull(x_for_plot,keep_th,
keep_slope,
guess,
flake),
color = 'g')
ax.set_title('Threshold=%1.2f +/- %1.2f ::Slope=%1.2f'
%(keep_th,(upper-lower)/2,keep_slope))
fig.savefig(fig_name)
return keep_th,lower,upper