22import json
33from typing import Any , Dict , Tuple , List , Optional , TextIO , Union
44from pathlib import Path
5+ import warnings
56
67import numpy as np
78from numpy .typing import NDArray
89
910from ._configuration import Configuration
1011from ._typing import EstimateType
12+ from ._utils import cast_np_scalar
1113from .tools import psychometric_with_eta
1214
1315class NumpyEncoder (json .JSONEncoder ):
@@ -115,14 +117,29 @@ def threshold(self, proportion_correct: np.ndarray, unscaled: bool = False, retu
115117 (thresholds, ci): stimulus values along with confidence intervals
116118
117119 """
120+ if return_ci :
121+ warnings .warn ("""The confidence intervals computed by this method are only upper bounds.
122+ To get a more accurate confidence interval at a level of proportion
123+ correct other than `thresh_PC`, you need to redefine the threshold, that is, redefine at
124+ which level the threshold parameter is set. You can do that by changing the argument
125+ 'thresh_PC', and call psignifit again. For an example, see documentation,
126+ page "Advanced options").
127+
128+ If instead you want to get the range of uncertainty for the psychometric
129+ function fit, then you need to sample from the posterior and visualize those
130+ samples. The function `plot_posterior_samples` in psigniplot does exactly that.
131+ You find an example of that visualization in the documentation, page Plotting.""" )
132+
118133 proportion_correct = np .asarray (proportion_correct )
119134 sigmoid = self .configuration .make_sigmoid ()
120135
121136 estimate = self .get_parameter_estimate (estimate_type )
122137 if unscaled : # set asymptotes to 0 for everything.
123138 lambd , gamma = 0 , 0
139+ proportion_correct_unscaled = proportion_correct
124140 else :
125141 lambd , gamma = estimate ['lambda' ], estimate ['gamma' ]
142+ proportion_correct_unscaled = (proportion_correct - gamma ) / (1 - lambd - gamma )
126143 new_threshold = sigmoid .inverse (proportion_correct , estimate ['threshold' ],
127144 estimate ['width' ], gamma , lambd )
128145 if not return_ci :
@@ -138,9 +155,24 @@ def threshold(self, proportion_correct: np.ndarray, unscaled: bool = False, retu
138155 else :
139156 gamma_ci = self .confidence_intervals ['gamma' ][coverage_key ]
140157 lambd_ci = self .confidence_intervals ['lambda' ][coverage_key ]
141- ci_min = sigmoid .inverse (proportion_correct , thres_ci [0 ], width_ci [0 ], gamma_ci [0 ], lambd_ci [0 ])
142- ci_max = sigmoid .inverse (proportion_correct , thres_ci [1 ], width_ci [1 ], gamma_ci [1 ], lambd_ci [1 ])
143- new_threshold_ci [coverage_key ] = [ci_min , ci_max ]
158+
159+ mask_above = proportion_correct_unscaled > self .configuration .thresh_PC
160+
161+ ci_min = np .zeros (proportion_correct .shape )
162+ ci_max = np .zeros (proportion_correct .shape )
163+
164+ ci_min [mask_above ] = sigmoid .inverse (proportion_correct [mask_above ],
165+ thres_ci [0 ], width_ci [0 ], gamma_ci [0 ], lambd_ci [0 ])
166+ ci_max [mask_above ] = sigmoid .inverse (proportion_correct [mask_above ],
167+ thres_ci [1 ], width_ci [1 ], gamma_ci [1 ], lambd_ci [1 ])
168+
169+ ci_min [~ mask_above ] = sigmoid .inverse (proportion_correct [~ mask_above ],
170+ thres_ci [0 ], width_ci [1 ], gamma_ci [0 ], lambd_ci [0 ])
171+ ci_max [~ mask_above ] = sigmoid .inverse (proportion_correct [~ mask_above ],
172+ thres_ci [1 ], width_ci [0 ], gamma_ci [1 ], lambd_ci [1 ])
173+
174+ new_threshold_ci [coverage_key ] = [cast_np_scalar (ci_min ),
175+ cast_np_scalar (ci_max )]
144176
145177 return new_threshold , new_threshold_ci
146178
0 commit comments