| 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__ |