blib/lib/Statistics/SDT.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 244 | 292 | 83.5 |
branch | 93 | 140 | 66.4 |
condition | 24 | 61 | 39.3 |
subroutine | 49 | 56 | 87.5 |
pod | 11 | 11 | 100.0 |
total | 421 | 560 | 75.1 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Statistics::SDT; | ||||||
2 | 8 | 8 | 439452 | use strict; | |||
8 | 60 | ||||||
8 | 192 | ||||||
3 | 8 | 8 | 41 | use warnings; | |||
8 | 11 | ||||||
8 | 173 | ||||||
4 | 8 | 8 | 30 | use Carp qw(carp croak); | |||
8 | 11 | ||||||
8 | 330 | ||||||
5 | 8 | 8 | 3656 | use List::AllUtils qw(all any); | |||
8 | 106455 | ||||||
8 | 572 | ||||||
6 | 8 | 8 | 2979 | use Math::Cephes qw(:dists :explog); | |||
8 | 36305 | ||||||
8 | 1940 | ||||||
7 | 8 | 8 | 3031 | use String::Numeric qw(is_int is_float); | |||
8 | 16824 | ||||||
8 | 392 | ||||||
8 | 8 | 8 | 3168 | use String::Util qw(hascontent nocontent); | |||
8 | 34368 | ||||||
8 | 26224 | ||||||
9 | $Statistics::SDT::VERSION = '0.07'; | ||||||
10 | |||||||
11 | my %counts_dep = ( | ||||||
12 | hits => [qw/signal_trials misses/], | ||||||
13 | false_alarms => [qw/noise_trials correct_rejections/], | ||||||
14 | misses => [qw/signal_trials hits/], | ||||||
15 | correct_rejections => [qw/noise_trials false_alarms/], | ||||||
16 | ); | ||||||
17 | my %trials_dep = ( | ||||||
18 | signal_trials => [qw/hits misses/], | ||||||
19 | noise_trials => [qw/false_alarms correct_rejections/] | ||||||
20 | ); | ||||||
21 | my %rates_dep = ( | ||||||
22 | hr => [qw/hits signal_trials/], | ||||||
23 | far => [qw/false_alarms noise_trials/] | ||||||
24 | ); | ||||||
25 | |||||||
26 | =head1 NAME | ||||||
27 | |||||||
28 | Statistics::SDT - Signal detection theory (SDT) measures of sensitivity and bias in frequency data | ||||||
29 | |||||||
30 | =head1 VERSION | ||||||
31 | |||||||
32 | This is documentation for B |
||||||
33 | |||||||
34 | =head1 SYNOPSIS | ||||||
35 | |||||||
36 | use Statistics::SDT 0.07; | ||||||
37 | use feature qw{say}; | ||||||
38 | |||||||
39 | my $sdt = Statistics::SDT->new( | ||||||
40 | correction => 1, | ||||||
41 | precision_s => 2, | ||||||
42 | ); | ||||||
43 | |||||||
44 | $sdt->init( | ||||||
45 | hits => 50, | ||||||
46 | signal_trials => 50, # or misses => 0, | ||||||
47 | false_alarms => 17, | ||||||
48 | noise_trials => 25, # or correct_rejections => 8 | ||||||
49 | ); # or init these into 'new' &/or pass their values as 2nd arg. hashrefs in calling the following methods | ||||||
50 | |||||||
51 | say 'Hit rate = ', $sdt->rate('hr'); # or 'far', 'mr', 'crr' | ||||||
52 | say 'Sensitivity d = ', $sdt->sens('d'); # or 'Ad', 'A' | ||||||
53 | say 'Bias beta = ', $sdt->bias('b'); # or 'log', 'c', 'griers' | ||||||
54 | say 'Criterion k = ', $sdt->crit(); # -0.47 | ||||||
55 | say 'Hit rate by d & c = ', $sdt->dc2hr(); # .99 | ||||||
56 | say 'FAR by d & c = ', $sdt->dc2far(); # .68 | ||||||
57 | say 'LogBeta by d & c = ', $sdt->dc2logbeta(); # -2.60 | ||||||
58 | |||||||
59 | # m-AFC: | ||||||
60 | say 'd_fc = ', $sdt->sens('f' => {hr => .866, alternatives => 3, correction => 0, method => 'alexander'})); # or 'smith' | ||||||
61 | |||||||
62 | =head1 DESCRIPTION | ||||||
63 | |||||||
64 | This module implements algorithms for Signal Detection Theory (SDT) measures of sensitivity and response-bias, e.g., I |
||||||
65 | |||||||
66 | For any particular analysis, (1) create the SDT object with L |
||||||
67 | |||||||
68 | For those measures that involve I |
||||||
69 | |||||||
70 | Most methods assume a yes/no rather than I |
||||||
71 | |||||||
72 | =head1 PARAMETERS | ||||||
73 | |||||||
74 | The following named parameters need to be given as a hash or hash-reference: either to the L |
||||||
75 | |||||||
76 | =over 4 | ||||||
77 | |||||||
78 | =item hits => POSINT | ||||||
79 | |||||||
80 | The number of hits. | ||||||
81 | |||||||
82 | =item false_alarms => POSINT | ||||||
83 | |||||||
84 | The number of false alarms. | ||||||
85 | |||||||
86 | =item signal_trials => POSINT | ||||||
87 | |||||||
88 | The number of signal trials. The hit-rate is derived by dividing the number of hits by the number of signal trials. | ||||||
89 | |||||||
90 | =item noise_trials => POSINT | ||||||
91 | |||||||
92 | The number of noise trials. The false-alarm-rate is derived by dividing the number of false-alarms by the number of noise trials. | ||||||
93 | |||||||
94 | =item hr => FLOAT [0 .. 1] | ||||||
95 | |||||||
96 | The hit-rate -- instead of passing the number of hits and signal trials, give the hit-rate directly. | ||||||
97 | |||||||
98 | =item far => FLOAT [0 .. 1] | ||||||
99 | |||||||
100 | The false-alarm-rate -- instead of passing the number of false alarms and noise trials, give the false-alarm-rate directly. | ||||||
101 | |||||||
102 | =item alternatives => POSINT | ||||||
103 | |||||||
104 | The number of response alternatives; when estimating for a forced-choice rather than yes/no design. If defined (and greater than or equal to 2), then, by default, Smith's (1982) estimate of I |
||||||
105 | |||||||
106 | =item correction => POSINT [0, 1, 2, undef] | ||||||
107 | |||||||
108 | Indicate whether or not to perform a correction on the number of hits and false-alarms when the hit-rate or false-alarm-rate equals 0 or 1 (due, e.g., to strong inducements against false-alarms, or easy discrimination between signals and noise). This is relevant to all functions that make use of the I |
||||||
109 | |||||||
110 | If B |
||||||
111 | |||||||
112 | If B |
||||||
113 | |||||||
114 | If B |
||||||
115 | |||||||
116 | If B |
||||||
117 | |||||||
118 | =item precision_s => POSINT | ||||||
119 | |||||||
120 | Precision (I |
||||||
121 | |||||||
122 | =item method => STR ['smith', 'alexander'] | ||||||
123 | |||||||
124 | Method for estimating I |
||||||
125 | |||||||
126 | =back | ||||||
127 | |||||||
128 | =head1 SUBROUTINES/METHODS | ||||||
129 | |||||||
130 | =head2 new | ||||||
131 | |||||||
132 | Creates the class object that holds the values of the parameters, as above, and accesses the following methods (without having to pass the all values again). | ||||||
133 | |||||||
134 | As well as storing parameter values, the class-object returned by C , the hit-rate, and B |
||||||
135 | |||||||
136 | =cut | ||||||
137 | |||||||
138 | sub new { | ||||||
139 | 5 | 5 | 1 | 335 | my ( $class, @args ) = @_; | ||
140 | 5 | 12 | my $self = {}; | ||||
141 | 5 | 10 | bless $self, $class; | ||||
142 | 5 | 28 | $self->init(@args); | ||||
143 | 5 | 11 | return $self; | ||||
144 | } | ||||||
145 | |||||||
146 | =head2 init | ||||||
147 | |||||||
148 | $sdt->init(...) | ||||||
149 | |||||||
150 | Instead of passing the number of hits, signal-trials, etc., with every call to the measure-functions, or creating a new class object for every set of data, initialise the class object with the values for parameters, key => value pairs, as defined above. This method is called by L |
||||||
151 | |||||||
152 | Each L |
||||||
153 | |||||||
154 | The method also stores any given values for L |
||||||
155 | |||||||
156 | =cut | ||||||
157 | |||||||
158 | sub init { | ||||||
159 | 32 | 32 | 1 | 1351 | my ( $self, @args ) = @_; | ||
160 | 32 | 100 | 68 | if ( scalar @args ) { # have some params? | |||
161 | 18 | 50 | 57 | my $href = ref $args[0] ? $args[0] : {@args}; | |||
162 | |||||||
163 | # Initialise any given counts and arguments: | ||||||
164 | 18 | 86 | foreach my $arg ( | ||||
165 | qw/hits false_alarms misses correct_rejections signal_trials noise_trials hr far alternatives states correction precision_s method/ | ||||||
166 | ) | ||||||
167 | { | ||||||
168 | 234 | 100 | 351 | if ( defined $href->{$arg} ) { | |||
169 | 59 | 50 | 81 | if ( $arg eq 'states' ) { | |||
170 | 0 | 0 | carp | ||||
171 | 'Argument named |
||||||
172 | 0 | 0 | $self->{'alternatives'} = $href->{$arg}; | ||||
173 | } | ||||||
174 | else { | ||||||
175 | 59 | 113 | $self->{$arg} = $href->{$arg}; | ||||
176 | } | ||||||
177 | } | ||||||
178 | } | ||||||
179 | 18 | 100 | 54 | $self->{'method'} ||= 'smith'; | |||
180 | 18 | 100 | 49 | $self->{'precision_s'} ||= 0; | |||
181 | |||||||
182 | 18 | 38 | _init_performance_counts($self); | ||||
183 | 18 | 32 | _init_trial_counts($self); | ||||
184 | 18 | 29 | _init_hr_far($self); | ||||
185 | } | ||||||
186 | |||||||
187 | # no params - assume the values are already in $self | ||||||
188 | 32 | 94 | return ( $self->{'hr'}, $self->{'far'}, $self->{'alternatives'} ); | ||||
189 | } | ||||||
190 | |||||||
191 | # Initialise any missing performance counts of hits, false-alarms, misses & correct rejections | ||||||
192 | ## from what has just been given (just initialised) | ||||||
193 | ## e.g., number of hits from the given number of signal-trials and misses: | ||||||
194 | sub _init_performance_counts { | ||||||
195 | 18 | 18 | 26 | my $self = shift; | |||
196 | 18 | 44 | foreach ( keys %counts_dep ) { | ||||
197 | 72 | 100 | 115 | if ( !defined $self->{$_} ) { | |||
198 | 43 | 100 | 66 | 107 | if ( is_float( $self->{ $counts_dep{$_}->[0] } ) | ||
199 | && is_float( $self->{ $counts_dep{$_}->[1] } ) ) | ||||||
200 | { | ||||||
201 | $self->{$_} = | ||||||
202 | $self->{ $counts_dep{$_}->[0] } - | ||||||
203 | 1 | 21 | $self->{ $counts_dep{$_}->[1] }; | ||||
204 | } | ||||||
205 | else { | ||||||
206 | 42 | 264 | $self->{$_} = 0; | ||||
207 | } | ||||||
208 | } | ||||||
209 | } | ||||||
210 | 18 | 28 | return; | ||||
211 | } | ||||||
212 | |||||||
213 | # Initialise any missing trial counts (of number of signal or noise trials) from what has been given, | ||||||
214 | ## e.g., number of signal trials from the sum of hits and misses: | ||||||
215 | sub _init_trial_counts { | ||||||
216 | 18 | 18 | 23 | my $self = shift; | |||
217 | 18 | 36 | foreach ( keys %trials_dep ) { | ||||
218 | 36 | 100 | 66 | if ( !defined $self->{$_} ) { | |||
219 | 21 | 50 | 33 | 50 | if ( is_float( $self->{ $trials_dep{$_}->[0] } ) | ||
220 | && is_float( $self->{ $trials_dep{$_}->[1] } ) ) | ||||||
221 | { | ||||||
222 | $self->{$_} = | ||||||
223 | $self->{ $trials_dep{$_}->[0] } + | ||||||
224 | 21 | 346 | $self->{ $trials_dep{$_}->[1] }; | ||||
225 | } | ||||||
226 | else { | ||||||
227 | 0 | 0 | $self->{$_} = 0; | ||||
228 | } | ||||||
229 | } | ||||||
230 | } | ||||||
231 | 18 | 23 | return; | ||||
232 | } | ||||||
233 | |||||||
234 | # Initialise the rates of hits and false-alarms if not already done | ||||||
235 | ## by given counts, e.g., HR from number of hits and signal trials: | ||||||
236 | sub _init_hr_far { | ||||||
237 | 18 | 18 | 25 | my $self = shift; | |||
238 | 18 | 30 | foreach ( keys %rates_dep ) { | ||||
239 | 36 | 100 | 66 | 162 | if ( !defined $self->{$_} | ||
100 | |||||||
240 | && defined $self->{ $rates_dep{$_}->[0] } | ||||||
241 | && $self->{ $rates_dep{$_}->[1] } ) | ||||||
242 | { | ||||||
243 | $self->{$_} = _init_rate( | ||||||
244 | $self->{ $rates_dep{$_}->[0] }, | ||||||
245 | $self->{ $rates_dep{$_}->[1] }, | ||||||
246 | 9 | 33 | $self->{'correction'} | ||||
247 | ); | ||||||
248 | } | ||||||
249 | } | ||||||
250 | 18 | 36 | return; | ||||
251 | } | ||||||
252 | |||||||
253 | =head2 clear | ||||||
254 | |||||||
255 | $sdt->clear() | ||||||
256 | |||||||
257 | Sets all attributes to undef: C , C |
||||||
258 | |||||||
259 | =cut | ||||||
260 | |||||||
261 | sub clear { | ||||||
262 | 10 | 10 | 1 | 2173 | my $self = shift; | ||
263 | 10 | 20 | foreach ( | ||||
264 | qw/hits false_alarms misses correct_rejections signal_trials noise_trials hr far alternatives correction precision_s method/ | ||||||
265 | ) | ||||||
266 | { | ||||||
267 | 120 | 143 | $self->{$_} = undef; | ||||
268 | } | ||||||
269 | 10 | 14 | return; | ||||
270 | } | ||||||
271 | |||||||
272 | =head2 rate | ||||||
273 | |||||||
274 | $sdt->rate('hr|far|mr|crr') # return the indicated rate | ||||||
275 | $sdt->rate(hr => PROB, far => PROB, mr => PROB, crr => PROB) # set 1 or more rate => probability pairs | ||||||
276 | $sdt->rate('hr' => {signal_trials => INT, hits => INT}) # or misses instead of hits | ||||||
277 | $sdt->rate('far' => {noise_trials => INT, false_alarms => INT}) # or correct_rejections instead of false_alarms | ||||||
278 | $sdt->rate('mr' => {signal_trials => INT, misses => INT}) # or hits instead of misses | ||||||
279 | $sdt->rate('crr' => {noise_trials => INT, correct_rejections => INT}) # or false_alarms instead of correct_rejections | ||||||
280 | |||||||
281 | Generic method to get or set the conditional response proportions: | ||||||
282 | |||||||
283 | =for html HR (hit-rate) = N(Rs|Ss) / N(Ss) |
||||||
284 | |||||||
285 | =for html FAR (false-alarm-rate) = N(Rs|Sn) / N(Sn) |
||||||
286 | |||||||
287 | =for html MR (miss-rate) = N(Rn|Ss) / N(Ss) |
||||||
288 | |||||||
289 | =for html CRR (correct-rejection-rate) = N(Rn|Sn) / N(Sn) |
||||||
290 | |||||||
291 | where S = stimulus (trial-type, expected response), R = response, subscript I |
||||||
292 | |||||||
293 | To I |
||||||
294 | |||||||
295 | To I |
||||||
296 | |||||||
297 | Also performs any required or requested corrections, depending on value of L |
||||||
298 | |||||||
299 | Unless the values of the rates are directly given, then they will be calculated from the presently passed counts and trial-numbers, or whatever has been cached of these values. For the hit-rate, there must be a value for L |
||||||
300 | |||||||
301 | =cut | ||||||
302 | |||||||
303 | sub rate { | ||||||
304 | 13 | 13 | 1 | 1238 | my ( $self, @args ) = @_; | ||
305 | 13 | 18 | my $rate; | ||||
306 | 13 | 100 | 40 | if ( scalar @args == 1 ) { # Get the rate: | |||
50 | |||||||
307 | 8 | 12 | local $_ = $args[0]; | ||||
308 | CASE: { | ||||||
309 | 8 | 100 | 12 | /^h/ixsm && do { $rate = $self->_hr(); }; | |||
8 | 25 | ||||||
2 | 5 | ||||||
310 | 8 | 100 | 21 | /^f/ixsm && do { $rate = $self->_far(); }; | |||
6 | 25 | ||||||
311 | 8 | 50 | 17 | /^m/ixsm && do { $rate = $self->_mr(); }; | |||
0 | 0 | ||||||
312 | 8 | 50 | 17 | /^c/ixsm && do { $rate = $self->_crr() }; | |||
0 | 0 | ||||||
313 | } #end CASE | ||||||
314 | } | ||||||
315 | ##else { | ||||||
316 | elsif ( scalar @args > 1 ) { # Set the rate: | ||||||
317 | 5 | 13 | my %params = @args; | ||||
318 | 5 | 12 | foreach ( keys %params ) { | ||||
319 | my @args2 = | ||||||
320 | ref $params{$_} | ||||||
321 | 2 | 6 | ? %{ $params{$_} } | ||||
322 | 6 | 100 | 23 | : $params{$_}; # hash(ref) to ari | |||
323 | CASE: { | ||||||
324 | 6 | 100 | 9 | /^h/ixsm && do { $rate = $self->_hr(@args2); last CASE; }; | |||
6 | 20 | ||||||
3 | 8 | ||||||
3 | 10 | ||||||
325 | 3 | 50 | 10 | /^f/ixsm && do { $rate = $self->_far(@args2); last CASE; }; | |||
3 | 9 | ||||||
3 | 7 | ||||||
326 | 0 | 0 | 0 | /^m/ixsm && do { $rate = $self->_mr(@args2); last CASE; }; | |||
0 | 0 | ||||||
0 | 0 | ||||||
327 | 0 | 0 | 0 | /^c/ixsm && do { $rate = $self->_crr(@args2) }; | |||
0 | 0 | ||||||
328 | } #end CASE | ||||||
329 | } | ||||||
330 | } | ||||||
331 | 13 | 28 | return _precisioned( $self->{'precision_s'}, $rate ); | ||||
332 | } | ||||||
333 | |||||||
334 | sub _hr { | ||||||
335 | 5 | 5 | 11 | my ( $self, @args ) = @_; | |||
336 | 5 | 100 | 31 | if ( scalar @args > 1 ) { # set the rate via params | |||
100 | |||||||
337 | 1 | 4 | my (%params) = @args; | ||||
338 | 1 | 3 | foreach ( keys %params ) { | ||||
339 | 4 | 6 | $self->{$_} = $params{$_}; | ||||
340 | } | ||||||
341 | $self->{'hr'} = _init_rate( $self->{'hits'}, $self->{'signal_trials'}, | ||||||
342 | 1 | 3 | $self->{'correction'} ); | ||||
343 | } | ||||||
344 | elsif ( scalar @args == 1 ) { # set the rate as given | ||||||
345 | 2 | 50 | 7 | $self->{'hr'} = _valid_p( $args[0] ) ? $args[0] : croak __PACKAGE__, | |||
346 | ' Rate needs to be between 0 and 1 inclusive'; | ||||||
347 | } | ||||||
348 | 5 | 17 | return $self->{'hr'}; | ||||
349 | } | ||||||
350 | |||||||
351 | sub _far { | ||||||
352 | 9 | 9 | 48 | my ( $self, @args ) = @_; | |||
353 | 9 | 100 | 28 | if ( scalar @args > 1 ) { # set the rate via params | |||
100 | |||||||
354 | 1 | 3 | my %params = @args; | ||||
355 | 1 | 2 | foreach ( keys %params ) { | ||||
356 | 4 | 6 | $self->{$_} = $params{$_}; | ||||
357 | } | ||||||
358 | $self->{'far'} = _init_rate( | ||||||
359 | $self->{'false_alarms'}, | ||||||
360 | $self->{'noise_trials'}, | ||||||
361 | 1 | 4 | $self->{'correction'} | ||||
362 | ); | ||||||
363 | } | ||||||
364 | elsif ( scalar @args == 1 ) { # set the rate as given | ||||||
365 | 2 | 50 | 6 | $self->{'far'} = _valid_p( $args[0] ) ? $args[0] : croak __PACKAGE__, | |||
366 | ' Rate needs to be between 0 and 1 inclusive'; | ||||||
367 | } | ||||||
368 | 9 | 21 | return $self->{'far'}; | ||||
369 | } | ||||||
370 | |||||||
371 | sub _mr { | ||||||
372 | 0 | 0 | 0 | my ( $self, %params ) = @_; | |||
373 | 0 | 0 | foreach ( keys %params ) { | ||||
374 | 0 | 0 | $self->{$_} = $params{$_}; | ||||
375 | } | ||||||
376 | 0 | 0 | 0 | 0 | if ( !is_float( $self->{'signal_trials'} ) | ||
377 | || !is_float( $self->{'misses'} ) ) | ||||||
378 | { | ||||||
379 | #if ( !$self->{'signal_trials'} || !defined $self->{'misses'} ) { | ||||||
380 | 0 | 0 | carp 'Uninitialised counts for calculating MR'; | ||||
381 | 0 | 0 | return q{}; | ||||
382 | } | ||||||
383 | 0 | 0 | return $self->{'misses'} / $self->{'signal_trials'}; | ||||
384 | } | ||||||
385 | |||||||
386 | sub _crr { | ||||||
387 | 0 | 0 | 0 | my ( $self, %params ) = @_; | |||
388 | 0 | 0 | foreach ( keys %params ) { | ||||
389 | 0 | 0 | $self->{$_} = $params{$_}; | ||||
390 | } | ||||||
391 | 0 | 0 | 0 | 0 | if ( !is_float( $self->{'signal_trials'} ) | ||
392 | || !is_float( $self->{'correct_rejections'} ) ) | ||||||
393 | { | ||||||
394 | #if ( !$self->{'signal_trials'} || !defined $self->{'correct_rejections'} ) { | ||||||
395 | 0 | 0 | carp 'Uninitialised counts for calculating CRR'; | ||||
396 | 0 | 0 | return q{}; | ||||
397 | } | ||||||
398 | 0 | 0 | return $self->{'correct_rejections'} / $self->{'noise_trials'}; | ||||
399 | } | ||||||
400 | |||||||
401 | sub _init_rate { # Initialise hit and false-alarm rates: | ||||||
402 | 11 | 11 | 18 | my ( $count, $trials, $correction ) = @_; | |||
403 | 11 | 16 | my $rate; | ||||
404 | 11 | 100 | 31 | if ( !defined $correction ) { | |||
405 | 3 | 5 | $correction = 1; # default correction | ||||
406 | } | ||||||
407 | |||||||
408 | # Need (i) no. of hits and signal trials, or (ii) no. of false alarms and noise trials: | ||||||
409 | 11 | 50 | 33 | 37 | croak __PACKAGE__, | ||
410 | ' Number of hits/false-alarms and signal/noise trials needed to calculate rate' | ||||||
411 | if !defined $count || !defined $trials; | ||||||
412 | 11 | 50 | 33 | 26 | return if not is_int($trials) or $trials == 0; | ||
413 | |||||||
414 | 11 | 50 | 135 | if ( $correction > 1 ) { # loglinear correction, regardless of values: | |||
415 | 0 | 0 | $rate = _loglinear_correct( $count, $trials ); | ||||
416 | } | ||||||
417 | else | ||||||
418 | { # get rate first, applying corrections if needed (unless explicitly verboten): | ||||||
419 | 11 | 20 | $rate = $count / $trials; | ||||
420 | 11 | 50 | 40 | if ( $correction != 0 ) { | |||
421 | 11 | 29 | $rate = _n_correct( $rate, $trials ); | ||||
422 | } | ||||||
423 | } | ||||||
424 | 11 | 26 | return $rate; | ||||
425 | } | ||||||
426 | |||||||
427 | =head2 zrate | ||||||
428 | |||||||
429 | $z = $sdt->zrate('hr'); # or 'far', 'mr', 'crr' | ||||||
430 | |||||||
431 | Returns the I |
||||||
432 | |||||||
433 | =cut | ||||||
434 | |||||||
435 | sub zrate { | ||||||
436 | 0 | 0 | 1 | 0 | my ( $self, @args ) = @_; | ||
437 | 0 | 0 | return ndtri( $self->rate(@args) ); | ||||
438 | } | ||||||
439 | |||||||
440 | =head2 dc2hr | ||||||
441 | |||||||
442 | $sdt->dc2hr() # assume d' and c can be calculated from already inited param values | ||||||
443 | $sdt->dc2hr(d => FLOAT, c => FLOAT) | ||||||
444 | |||||||
445 | Returns the hit-rate estimated from given values of sensitivity I |
||||||
446 | |||||||
447 | =for html HR = φ(d’ / 2 – c) |
||||||
448 | |||||||
449 | =cut | ||||||
450 | |||||||
451 | sub dc2hr { | ||||||
452 | 1 | 1 | 1 | 1240 | my ( $self, %args ) = @_; | ||
453 | 1 | 4 | my ( $d, $c ) = _get_dc( $self, %args ); | ||||
454 | 2 | 2 | 9 | return ( all { hascontent($_) } ( $d, $c ) ) | |||
455 | 1 | 50 | 5 | ? _precisioned( $self->{'precision_s'}, ndtr( $d / 2 - $c ) ) | |||
456 | : q{}; | ||||||
457 | } | ||||||
458 | |||||||
459 | =head2 dc2far | ||||||
460 | |||||||
461 | $sdt->dc2far() # assume d' and c can be calculated from already inited param values | ||||||
462 | $sdt->dc2far(d => FLOAT, c => FLOAT) | ||||||
463 | |||||||
464 | Returns the false-alarm-rate estimated from given values of sensitivity I |
||||||
465 | |||||||
466 | =for html FAR = φ(–d’ / 2 – c) |
||||||
467 | |||||||
468 | =cut | ||||||
469 | |||||||
470 | sub dc2far { | ||||||
471 | 1 | 1 | 1 | 263 | my ( $self, %args ) = @_; | ||
472 | 1 | 4 | my ( $d, $c ) = _get_dc( $self, %args ); | ||||
473 | 2 | 2 | 18 | return ( all { hascontent($_) } ( $d, $c ) ) | |||
474 | 1 | 50 | 4 | ? _precisioned( $self->{'precision_s'}, ndtr( -1 * $d / 2 - $c ) ) | |||
475 | : q{}; | ||||||
476 | } | ||||||
477 | |||||||
478 | # -------------------- | ||||||
479 | # Sensitivity measures: | ||||||
480 | # -------------------- | ||||||
481 | |||||||
482 | =head2 sens | ||||||
483 | |||||||
484 | $s = $sdt->sens('dprime'); # or 'aprime', 'adprime' | ||||||
485 | $s = $sdt->sens('dprime', { signal_trials => POSINT }); # set args, optionally | ||||||
486 | $s = $sdt->sens('d_a', { stdev_n => POS_FLOAT, stdev_s => POS_FLOAT }); # required args | ||||||
487 | |||||||
488 | I |
||||||
489 | |||||||
490 | Returns one of the sensitivity measures, as indicated by the first argument string, optionally updating any of the measure variables and options with a subsequent hashref. The measures are as follows, accessed by giving the name (or at least its first two letters) as the first argument. | ||||||
491 | |||||||
492 | =over 4 | ||||||
493 | |||||||
494 | =item dprime | ||||||
495 | |||||||
496 | Returns the index of standard deviation units of sensitivity, or discrimination, I |
||||||
497 | |||||||
498 | =for html d’ = φ–1(HR) – φ–1(FAR) |
||||||
499 | |||||||
500 | Larger positive values indicate greater sensitivity. If both HR and FAR are either 0 or 1, then L |
||||||
501 | |||||||
502 | For estimating dprime for I |
||||||
503 | |||||||
504 | The present interface to these methods is limited in that they are given, for proportion-correct, the hit-rate as for the yes/no design: as the count of hits divided by number of signal trials. Rather than give these methods a value for B , the L |
||||||
505 | |||||||
506 | B>: satisfies "the 2% bound for all I |
||||||
507 | |||||||
508 | Smith's I |
||||||
509 | |||||||
510 | =for html d’ = Kln( [ (n – 1)HR ] / [ 1 – HR ] ) |
||||||
511 | |||||||
512 | where | ||||||
513 | |||||||
514 | =for html K = .86 – .085 * ln(n – 1). |
||||||
515 | |||||||
516 | Smith's I |
||||||
517 | |||||||
518 | =for html d’ = (A + B)φ–1(HR) |
||||||
519 | |||||||
520 | where | ||||||
521 | |||||||
522 | =for html A = (–4 + sqrt[16 + 25 * ln(n – 1)]) / 3 |
||||||
523 | |||||||
524 | and | ||||||
525 | |||||||
526 | =for html B = sqrt( [ln(n – 1) + 2] / [ln(n – 1) + 1] ) |
||||||
527 | |||||||
528 | The limits of the method can be noted in that, when I |
||||||
529 | |||||||
530 | B> (which never fails the latter elementary test): "gives values of I |
||||||
531 | |||||||
532 | =for html d’ = [ φ–1(HR) – φ–1(1/n) ] / An |
||||||
533 | |||||||
534 | where I |
||||||
535 | |||||||
536 | =for html An = 1 - 1 / (1.93 + 4.75 * log10(n) + .63[log10(n)]2) |
||||||
537 | |||||||
538 | =item d_a | ||||||
539 | |||||||
540 | Returns estimate of SDT sensitivity for without assuming equal variances, given values of B |
||||||
541 | |||||||
542 | =for html d’ = sqrt[ 2 / (1 + b2) ][φ–1(HR) – bφ–1(FAR)] |
||||||
543 | |||||||
544 | where | ||||||
545 | |||||||
546 | =for html b = σ(N) / σ(S) |
||||||
547 | |||||||
548 | =item aprime | ||||||
549 | |||||||
550 | Returns the nonparametric index of sensitivity, I, a.k.a. I |
||||||
551 | |||||||
552 | =for html a’ = [ .5 + d(1 + d) ] / 4j |
||||||
553 | |||||||
554 | where, if HR >= FAR, I |
||||||
555 | |||||||
556 | Ranges from 0 to 1. Values greater than 0.5 indicate positive discrimination (1 = perfect performance); a value of 0.5 indicates no sensitivity to the presence of the signal (it cannot be discriminated from noise); and values less than 0.5 indicate negative discrimination (perhaps given consistent response confusion or inhibition). | ||||||
557 | |||||||
558 | =item adprime | ||||||
559 | |||||||
560 | Returns I |
||||||
561 | |||||||
562 | =for html Ad’ = φ(d’ / sqrt(2)) |
||||||
563 | |||||||
564 | Ranges between 0 and 1, with a value of 0.5 reflecting no discriminative ability when comparing two stimuli. If both the hit-rate and false-alarm-rate are either 0 or 1, then the returned value of C |
||||||
565 | |||||||
566 | =back | ||||||
567 | |||||||
568 | =cut | ||||||
569 | |||||||
570 | sub sens { | ||||||
571 | 15 | 15 | 1 | 1557 | my ( $self, $meas, $args ) = @_; | ||
572 | 15 | 22 | local $_ = $meas; | ||||
573 | 15 | 16 | my $d; | ||||
574 | CASE: { | ||||||
575 | 15 | 100 | 16 | /^d|f/ixsm && do { $d = $self->_d_sensitivity( %{$args} ); }; | |||
15 | 62 | ||||||
13 | 15 | ||||||
13 | 50 | ||||||
576 | 15 | 100 | 42 | /^a[p\b]/ixsm && do { $d = $self->_a_sensitivity( %{$args} ) }; | |||
1 | 2 | ||||||
1 | 7 | ||||||
577 | 15 | 100 | 33 | /^ad/ixsm && do { $d = $self->_ad_sensitivity( %{$args} ); }; | |||
1 | 2 | ||||||
1 | 4 | ||||||
578 | |||||||
579 | #/^h/ixsm && do { $d = $self->_hthresh_sensitivity( %{$args} ); }; | ||||||
580 | #/^p/ixsm && do { $d = $self->_pcorrect( %{$args} ); }; | ||||||
581 | #/^lp/ixsm && do { $d = $self->_lpcorrect( %{$args} ); }; | ||||||
582 | } #end CASE | ||||||
583 | 15 | 43 | return _precisioned( $self->{'precision_s'}, $d ); | ||||
584 | } | ||||||
585 | *discriminability = \&sens; # Alias | ||||||
586 | *sensitivity = \&sens; | ||||||
587 | |||||||
588 | sub _d_sensitivity { | ||||||
589 | 13 | 13 | 26 | my ( $self, %args ) = @_; | |||
590 | 13 | 33 | my ( $h, $f, $m ) = $self->init(%args); | ||||
591 | |||||||
592 | #croak 'No hit-rate for calculating d-sensitivity' if ! defined $h; | ||||||
593 | 13 | 17 | my $d; | ||||
594 | |||||||
595 | # If there are more than 2 alternatives, use a forced-choice method: | ||||||
596 | 13 | 100 | 66 | 67 | if ( defined $m and $m >= 2 ) { | ||
50 | |||||||
597 | |||||||
598 | #$self->rate(hr => $h, alternatives => $m); | ||||||
599 | $d = | ||||||
600 | 8 | 100 | 18 | $self->{'method'} eq 'smith' | |||
601 | ? _fc_smith( $h, $m ) | ||||||
602 | : _fc_alexander( $h, $m ); | ||||||
603 | } | ||||||
604 | 5 | 5 | 13 | elsif ( all { defined $args{$_} } qw/stdev_n stdev_s/ ) { | |||
605 | $d = | ||||||
606 | 0 | 0 | 0 | ( all { hascontent($_) } ( $h, $f ) ) | |||
607 | 0 | 0 | 0 | ? _d_a( $h, $f, $args{'stdev_s'}, $args{'stdev_n'} ) | |||
608 | : q{}; | ||||||
609 | } | ||||||
610 | else { | ||||||
611 | 5 | 50 | 10 | 15 | $d = ( all { hascontent($_) } ( $h, $f ) ) ? _dprime( $h, $f ) : q{}; | ||
10 | 80 | ||||||
612 | } | ||||||
613 | 13 | 37 | return $d; | ||||
614 | } | ||||||
615 | |||||||
616 | sub _dprime { | ||||||
617 | 5 | 5 | 47 | my ( $hr, $far ) = @_; | |||
618 | 5 | 6 | my $d; | ||||
619 | |||||||
620 | # Assume d' = 0 if both rates = 0 or both = 1: | ||||||
621 | 5 | 50 | 33 | 49 | if ( ( !$hr && !$far ) || ( $hr == 1 && $far == 1 ) ) { | ||
33 | |||||||
33 | |||||||
622 | 0 | 0 | $d = 0; | ||||
623 | } | ||||||
624 | else { | ||||||
625 | 5 | 62 | $d = ndtri($hr) - ndtri($far); | ||||
626 | } | ||||||
627 | 5 | 10 | return $d; | ||||
628 | } | ||||||
629 | |||||||
630 | sub _d_a { | ||||||
631 | 0 | 0 | 0 | my ( $hr, $far, $stdev_s, $stdev_n ) = @_; | |||
632 | 0 | 0 | my $d; | ||||
633 | |||||||
634 | # Assume d' = 0 if both rates = 0 or both = 1: | ||||||
635 | 0 | 0 | 0 | 0 | if ( ( !$hr && !$far ) || ( $hr == 1 && $far == 1 ) ) { | ||
0 | |||||||
0 | |||||||
636 | 0 | 0 | $d = 0; | ||||
637 | } | ||||||
638 | else { | ||||||
639 | 0 | 0 | my $z_hr = ndtri($hr); | ||||
640 | 0 | 0 | my $z_far = ndtri($far); | ||||
641 | 0 | 0 | my $b = $stdev_n / $stdev_s; | ||||
642 | 0 | 0 | $d = sqrt( 2 / ( 1 + $b**2 ) ) * ( $z_hr - $b * $z_far ); | ||||
643 | } | ||||||
644 | 0 | 0 | return $d; | ||||
645 | } | ||||||
646 | |||||||
647 | # Smith (1982) method: | ||||||
648 | sub _fc_smith { | ||||||
649 | 2 | 2 | 4 | my ( $h, $m ) = @_; | |||
650 | 2 | 3 | my $d; | ||||
651 | 2 | 100 | 3 | if ( $m < 12 ) { | |||
652 | 1 | 5 | my $km = .86 - .085 * log( $m - 1 ); | ||||
653 | 1 | 4 | my $lm = ( ( $m - 1 ) * $h ) / ( 1 - $h ); | ||||
654 | 1 | 2 | $d = $km * log $lm; | ||||
655 | } | ||||||
656 | else { | ||||||
657 | 1 | 7 | my $a = ( -4 + sqrt( 16 + 25 * log( $m - 1 ) ) ) / 3; | ||||
658 | 1 | 5 | my $b = sqrt( ( log( $m - 1 ) + 2 ) / ( log( $m - 1 ) + 1 ) ); | ||||
659 | 1 | 3 | $d = $a + $b * ndtri($h); | ||||
660 | } | ||||||
661 | 2 | 4 | return $d; | ||||
662 | } | ||||||
663 | |||||||
664 | # Alexander (2006/1990) method: | ||||||
665 | sub _fc_alexander { | ||||||
666 | 6 | 6 | 9 | my ( $h, $m ) = @_; | |||
667 | 6 | 48 | my $an = 1 - ( 1 / ( 1.93 + 4.75 * log10($m) + .63 * ( log10($m)**2 ) ) ); | ||||
668 | 6 | 24 | return ( ndtri($h) - ndtri( 1 / $m ) ) / $an; | ||||
669 | } | ||||||
670 | |||||||
671 | sub _a_sensitivity { | ||||||
672 | 1 | 1 | 3 | my ( $self, @args ) = @_; | |||
673 | 1 | 3 | my ( $h, $f ) = $self->init(@args); | ||||
674 | 1 | 50 | 2 | 6 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
2 | 14 | ||||||
675 | 1 | 11 | my $d; | ||||
676 | 1 | 50 | 3 | if ( $h >= $f ) { | |||
677 | 1 | 4 | $d = | ||||
678 | ( .5 + ( ( $h - $f ) * ( 1 + $h - $f ) ) / ( 4 * $h * ( 1 - $f ) ) ); | ||||||
679 | } | ||||||
680 | else { | ||||||
681 | 0 | 0 | $d = | ||||
682 | ( .5 + ( ( $f - $h ) * ( 1 + $f - $h ) ) / ( 4 * $f * ( 1 - $h ) ) ); | ||||||
683 | } | ||||||
684 | 1 | 2 | return $d; | ||||
685 | } | ||||||
686 | |||||||
687 | sub _ad_sensitivity { | ||||||
688 | 1 | 1 | 2 | my ( $self, @args ) = @_; | |||
689 | 1 | 3 | my ( $h, $f ) = $self->init(@args); | ||||
690 | 1 | 50 | 2 | 5 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
2 | 16 | ||||||
691 | 1 | 11 | my $d; | ||||
692 | |||||||
693 | # Assume A(d') = 0.5 if both rates = 0 or both = 1: | ||||||
694 | 1 | 50 | 33 | 8 | if ( ( !$h && !$f ) || ( $h == 1 && $f == 1 ) ) { | ||
33 | |||||||
33 | |||||||
695 | 0 | 0 | $d = 0.5; | ||||
696 | } | ||||||
697 | else { | ||||||
698 | 1 | 4 | $self->rate( h => $h, f => $f ); | ||||
699 | 1 | 5 | $d = ndtr( $self->sensitivity('d') / sqrt 2 ); | ||||
700 | } | ||||||
701 | 1 | 3 | return $d; | ||||
702 | |||||||
703 | } | ||||||
704 | |||||||
705 | # -------------------- | ||||||
706 | # Bias measures: | ||||||
707 | # -------------------- | ||||||
708 | |||||||
709 | =head2 bias | ||||||
710 | |||||||
711 | $b = $sdt->bias('likelihood|loglikelihood|decision|griers') # based on values of the measure variables already inited or otherwise set | ||||||
712 | $b = $sdt->bias('likelihood' => { signal_trials => INT}) # pass to any of the measure variables | ||||||
713 | |||||||
714 | Returns an estimate of the SDT decision threshold/response-bias. The particular estimate is named by the first argument string (or at least its first two letters), as below. optionally updating any of the measure variables and options with a subsequent hashref (as given by example for L |
||||||
715 | |||||||
716 | With a I |
||||||
717 | |||||||
718 | =over 4 | ||||||
719 | |||||||
720 | =item beta, likelihood_bias | ||||||
721 | |||||||
722 | Returns the paramteric I |
||||||
723 | |||||||
724 | =for html β = exp( [φ–1(FAR)2 – φ–1(HR)2] / 2 ) |
||||||
725 | |||||||
726 | Values less than 1 indicate a bias toward the I |
||||||
727 | |||||||
728 | =item log_likelihood_bias | ||||||
729 | |||||||
730 | Returns the natural logarithm of the likelihood bias, I |
||||||
731 | |||||||
732 | =for html lnβ = [ φ–1(FAR)2 – φ–1(HR)2 ] / 2 |
||||||
733 | |||||||
734 | Ranges from -1 to +1, with values less than 0 indicating a bias toward the I |
||||||
735 | |||||||
736 | =item c, distance | ||||||
737 | |||||||
738 | Returns the I |
||||||
739 | |||||||
740 | =for html c = –[ φ–1(HR) + φ–1(FAR) ] / 2 |
||||||
741 | |||||||
742 | Ranges from -1 to +1, with deviations from zero, measured in standard deviation units. Values less than 0 indicate a bias toward the I |
||||||
743 | |||||||
744 | =item griers | ||||||
745 | |||||||
746 | Returns Griers I nonparametric measure of response bias. Defining I = HR(1 - HR) and I = FAR(1 - FAR) if HR >= FAR, otherwise I = FAR(1 - FAR) and I = HR(1 - HR), then I = ( I - I ) / ( I + I ); or, summarily: | ||||||
747 | |||||||
748 | =for html B” = sign(HR – FAR)[ HR(1 – HR) – FAR(1 – FAR) ] / [ HR(1 – HR) + FAR(1 – FAR) ] |
||||||
749 | |||||||
750 | Ranges from -1 to +1, with values less than 0 indicating a bias toward the I |
||||||
751 | |||||||
752 | =back | ||||||
753 | |||||||
754 | =cut | ||||||
755 | |||||||
756 | sub bias { | ||||||
757 | 7 | 7 | 1 | 1060 | my ( $self, $meas, $args ) = @_; | ||
758 | 7 | 10 | local $_ = $meas; | ||||
759 | 7 | 8 | my $v; | ||||
760 | CASE: { | ||||||
761 | 7 | 100 | 9 | /^b|li/ixsm && do { $v = $self->_likelihood_bias( %{$args} ); }; | |||
7 | 27 | ||||||
1 | 1 | ||||||
1 | 3 | ||||||
762 | 7 | 100 | 15 | /^lo/ixsm && do { $v = $self->_log_likelihood_bias( %{$args} ); }; | |||
1 | 2 | ||||||
1 | 4 | ||||||
763 | 7 | 100 | 20 | /^c|d/ixsm && do { $v = $self->_distance_bias( %{$args} ); }; | |||
4 | 6 | ||||||
4 | 11 | ||||||
764 | 7 | 100 | 24 | /^g/ixsm && do { $v = $self->_griers_bias( %{$args} ) }; | |||
1 | 2 | ||||||
1 | 4 | ||||||
765 | } #end CASE | ||||||
766 | 7 | 17 | return _precisioned( $self->{'precision_s'}, $v ); | ||||
767 | } | ||||||
768 | |||||||
769 | sub _likelihood_bias { # beta | ||||||
770 | 1 | 1 | 2 | my ( $self, @args ) = @_; | |||
771 | 1 | 3 | my ( $h, $f ) = $self->init(@args); | ||||
772 | 1 | 50 | 2 | 5 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
2 | 18 | ||||||
773 | 1 | 36 | my $diff = ( ndtri($f)**2 - ndtri($h)**2 ) / 2; | ||||
774 | 1 | 6 | return exp $diff; | ||||
775 | } | ||||||
776 | |||||||
777 | sub _log_likelihood_bias { # ln(beta) | ||||||
778 | 1 | 1 | 2 | my ( $self, @args ) = @_; | |||
779 | 1 | 3 | my ( $h, $f ) = $self->init(@args); | ||||
780 | 1 | 50 | 2 | 5 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
2 | 26 | ||||||
781 | 1 | 17 | return ( ndtri($f)**2 - ndtri($h)**2 ) / 2; | ||||
782 | } | ||||||
783 | |||||||
784 | sub _distance_bias { # c | ||||||
785 | 4 | 4 | 20 | my ( $self, @args ) = @_; | |||
786 | 4 | 9 | my ( $h, $f ) = $self->init(@args); | ||||
787 | 4 | 50 | 8 | 16 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
8 | 59 | ||||||
788 | 4 | 68 | return -1 * ( ( ndtri($h) + ndtri($f) ) / 2 ); | ||||
789 | } | ||||||
790 | |||||||
791 | sub _griers_bias { # B'' | ||||||
792 | 1 | 1 | 2 | my ( $self, @args ) = @_; | |||
793 | 1 | 6 | my ( $h, $f ) = $self->init(@args); | ||||
794 | 1 | 50 | 2 | 5 | return q{} if any { nocontent($_) } ( $h, $f ); | ||
2 | 14 | ||||||
795 | 1 | 11 | my $v1 = $h * ( 1 - $h ); | ||||
796 | 1 | 2 | my $v2 = $f * ( 1 - $f ); | ||||
797 | 1 | 3 | return _sign( $h - $f ) * ( ( $v1 - $v2 ) / ( $v1 + $v2 ) ); | ||||
798 | } | ||||||
799 | |||||||
800 | =head2 dc2logbeta | ||||||
801 | |||||||
802 | $sdt->dc2logbeta() # assume d' and c can be calculated from already inited param values | ||||||
803 | $sdt->dc2logbeta(d => FLOAT, c => FLOAT) | ||||||
804 | |||||||
805 | Returns the log-likelihood (beta) bias estimated from given values of sensitivity I |
||||||
806 | |||||||
807 | =for html lnβ = d’c |
||||||
808 | |||||||
809 | =cut | ||||||
810 | |||||||
811 | sub dc2logbeta { | ||||||
812 | 1 | 1 | 1 | 314 | my ( $self, %args ) = @_; | ||
813 | 1 | 4 | my ( $d, $c ) = _get_dc( $self, %args ); | ||||
814 | 1 | 50 | 2 | 5 | return q{} if any { nocontent($_) } ( $d, $c ); | ||
2 | 10 | ||||||
815 | 1 | 11 | return _precisioned( $self->{'precision_s'}, $d * $c ); | ||||
816 | } | ||||||
817 | |||||||
818 | =head2 criterion | ||||||
819 | |||||||
820 | $sdt->criterion() # from FAR or from d' and c from already inited param values | ||||||
821 | $sdt->criterion(far => PROPORTION) # from FAR or from d' and c from already inited param values | ||||||
822 | $sdt->criterion(d => FLOAT, c => FLOAT) | ||||||
823 | |||||||
824 | I |
||||||
825 | |||||||
826 | Returns the value of the decision criterion (critical output value of the input process) on the basis of either: | ||||||
827 | |||||||
828 | (1) the false-alarm-rate: | ||||||
829 | |||||||
830 | =for html xc = –φ–1(FAR) |
||||||
831 | |||||||
832 | or (2) both sensitivity I |
||||||
833 | |||||||
834 | =for html xc = d’ / 2 + c |
||||||
835 | |||||||
836 | The method firstly checks if FAR can be calculated from given data or specific argument B |
||||||
837 | |||||||
838 | =cut | ||||||
839 | |||||||
840 | sub criterion { | ||||||
841 | 2 | 2 | 1 | 424 | my ( $self, %args ) = @_; | ||
842 | 2 | 4 | my $xc; | ||||
843 | 2 | 50 | 5 | if ( is_float( $self->rate('far') ) ) { | |||
844 | 2 | 23 | $xc = -1 * ndtri( $self->rate('far') ); | ||||
845 | } | ||||||
846 | else { | ||||||
847 | 0 | 0 | my ( $d, $c ) = _get_dc( $self, %args ); | ||||
848 | 0 | 0 | 0 | 0 | if ( all { hascontent($_) } ( $d, $c ) ) { | ||
0 | 0 | ||||||
849 | 0 | 0 | $xc = $d / 2 + $c; | ||||
850 | } | ||||||
851 | } | ||||||
852 | 2 | 50 | 9 | return hascontent($xc) ? _precisioned( $self->{'precision_s'}, $xc ) : q{}; | |||
853 | } | ||||||
854 | *dc2k = \&criterion; # Alias | ||||||
855 | *crit = \&criterion; | ||||||
856 | |||||||
857 | sub _get_dc { | ||||||
858 | 3 | 3 | 5 | my ( $self, %params ) = @_; | |||
859 | 3 | 50 | 12 | my $d = defined $params{'d'} ? $params{'d'} : $self->sensitivity('d'); | |||
860 | 3 | 50 | 10 | my $c = defined $params{'c'} ? $params{'c'} : $self->bias('c'); | |||
861 | 3 | 9 | return ( $d, $c ); | ||||
862 | } | ||||||
863 | |||||||
864 | # give count of either hits & signal_trials; or false_alarms and noise_trials | ||||||
865 | sub _loglinear_correct { | ||||||
866 | 0 | 0 | 0 | my ( $count, $trials ) = @_; | |||
867 | 0 | 0 | return ( $count + .5 ) / ( $trials + 1 ); | ||||
868 | } | ||||||
869 | |||||||
870 | sub _n_correct { | ||||||
871 | 11 | 11 | 20 | my ( $rate, $trials ) = @_; | |||
872 | 11 | 15 | my $retval; | ||||
873 | 11 | 50 | 31 | if ( !$rate ) { | |||
100 | |||||||
874 | 0 | 0 | $retval = .5 / $trials; | ||||
875 | } | ||||||
876 | elsif ( $rate == 1 ) { | ||||||
877 | 5 | 10 | $retval = ( $trials - .5 ) / $trials; | ||||
878 | } | ||||||
879 | else { | ||||||
880 | 6 | 14 | $retval = $rate; | ||||
881 | } | ||||||
882 | 11 | 21 | return $retval; | ||||
883 | } | ||||||
884 | |||||||
885 | sub _precisioned { | ||||||
886 | 41 | 41 | 346 | my ( $lim, $val ) = @_; | |||
887 | 41 | 50 | 87 | return q{} if !is_float($val); | |||
888 | 41 | 100 | 660 | return $lim ? sprintf( q{%.} . $lim . q{f}, $val ) : $val; | |||
889 | } | ||||||
890 | |||||||
891 | sub _valid_p { | ||||||
892 | 6 | 6 | 509 | my $p = shift; | |||
893 | 6 | 100 | 66 | 70 | return ( $p !~ /^ 0 ? [.] \d+ $/msx ) || ( $p < 0 || $p > 1 ) ? 0 : 1; | ||
894 | } | ||||||
895 | |||||||
896 | sub _sign { | ||||||
897 | 4 | 4 | 697 | my $v = shift; | |||
898 | 4 | 100 | 15 | return $v >= 0 ? 1 : -1; | |||
899 | } | ||||||
900 | |||||||
901 | 1; | ||||||
902 | |||||||
903 | __END__ |