Skip to content

Commit

Permalink
Reintroduce _value, _slope, _inverse. Docstrings describe how to defi…
Browse files Browse the repository at this point in the history
…ne subclasses.
  • Loading branch information
pberkes committed Nov 27, 2024
1 parent 997d4be commit 2f18d17
Showing 1 changed file with 85 additions and 17 deletions.
102 changes: 85 additions & 17 deletions psignifit/sigmoids.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ class Sigmoid:
Handles negative output for the specific sigmoid implementations.
Sigmoid classes should derive from this class and implement
the methods `_scipy_distr` and `_standard_parameters`.
Sigmoid classes should derive from this class and implement the methods '_value', '_slope', '_threshold',
and `_standard_parameters`.
In many cases, a sigmoid corresponds to the CDF of a probability distribution. This is the case
for all the sigmoids included in the `psignifit`. If the probability distribution
is one of the distributions defined in `scipy.stats`, it is possible to define a `Sigmoid` subclass
by implementing only the methods `_scipy_distr` and `_standard_parameters`. The methods
'_value', '_slope', '_threshold' will be automatically defined in such a case.
The stimulus levels, threshold and width are parameters of method calls.
They correspond to the object attributes PC and alpha in the following way:
Expand Down Expand Up @@ -56,17 +62,14 @@ def __call__(self, stimulus_level: N, threshold: N, width: N) -> N:
""" Calculate the sigmoid value at specified stimulus levels.
Args:
prop_correct: Proportion correct at the threshold to calculate.
stimulus_level: Stimulus level value at which to calculate the slope
threshold: Parameter value for threshold at PC
width: Parameter value for width of the sigmoid
gamma: Parameter value for the lower offset of the sigmoid
lambd: Parameter value for the upper offset of the sigmoid
Returns:
Proportion correct at the stimulus values.
"""

loc, scale = self._standard_parameters(threshold=threshold, width=width)
value = self._cdf(stimulus_level, loc=loc, scale=scale)
value = self._value(stimulus_level, threshold, width)

if self.negative:
return 1 - value
Expand All @@ -77,7 +80,7 @@ def slope(self, stimulus_level: N, threshold: N, width: N, gamma: N = 0, lambd:
""" Calculate the slope at specified stimulus levels.
Args:
prop_correct: Proportion correct at the threshold to calculate.
stimulus_level: Stimulus level value at which to calculate the slope
threshold: Parameter value for threshold at PC
width: Parameter value for width of the sigmoid
gamma: Parameter value for the lower offset of the sigmoid
Expand All @@ -86,8 +89,7 @@ def slope(self, stimulus_level: N, threshold: N, width: N, gamma: N = 0, lambd:
Slope at the stimulus values.
"""

loc, scale = self._standard_parameters(threshold=threshold, width=width)
raw_slope = self._pdf(stimulus_level, loc=loc, scale=scale)
raw_slope = self._slope(stimulus_level, threshold, width)
slope = (1 - gamma - lambd) * raw_slope

if self.negative:
Expand Down Expand Up @@ -118,8 +120,7 @@ def inverse(self, prop_correct: N, threshold: N, width: N,
if self.negative:
prop_correct = 1 - prop_correct

loc, scale = self._standard_parameters(threshold=threshold, width=width)
result = self._ppf(prop_correct, loc=loc, scale=scale)
result = self._inverse(prop_correct, threshold, width)

return result

Expand All @@ -133,15 +134,85 @@ def standard_parameters(self, threshold: N, width: N) -> tuple:
For negative slope sigmoids, we return the same parameters as for the positive ones.
Args:
threshold: Parameter value for threshold at PC
width: Parameter value for width of the sigmoid
threshold: Threshold value at PC
width: Width of the sigmoid
Returns:
Standard parameters (loc, scale) for the sigmoid subclass.
"""
return self._standard_parameters(threshold, width)

# --- Private interface

def _value(self, stimulus_level: N, threshold: N, width: N) -> N:
""" Compute the sigmoid value at specified stimulus levels.
This method can be overwritten by subclasses to define new sigmoids. The base implementation uses the
CDF of a continuous distribution function defined in `scipy.stats` and returned by `_scipy_distr`.
Args:
stimulus_level: Stimulus level values at which to calculate the sigmoid value
threshold: Threshold value at PC
width: Width of the sigmoid
Returns:
Proportion correct at the stimulus values.
"""
loc, scale = self._standard_parameters(threshold=threshold, width=width)
value = self._cdf(stimulus_level, loc=loc, scale=scale)
return value

def _slope(self, stimulus_level: N, threshold: N, width: N) -> N:
""" Compute the slope of the sigmoid at a given stimulus level.
This method can be overwritten by subclasses to define new sigmoids. The base implementation uses the
PDF of a continuous distribution function defined in `scipy.stats` and returned by `_scipy_distr`.
Args:
stimulus_level: Stimulus level value at which to calculate the slope
threshold: Threshold value at PC
width: Width of the sigmoid
Returns:
Slope at the stimulus level value
"""
loc, scale = self._standard_parameters(threshold=threshold, width=width)
raw_slope = self._pdf(stimulus_level, loc=loc, scale=scale)
return raw_slope

def _inverse(self, prop_correct: N, threshold: N, width: N) -> N:
""" Compute the stimulus value at different proportion correct values.
This method can be overwritten by subclasses to define new sigmoids. The base implementation uses the
PPF of a continuous distribution function defined in `scipy.stats` and returned by `_scipy_distr`.
Args:
prop_correct: Proportion correct values at which to calculate the stimulus level values.
threshold: Threshold value at PC
width: Width of the sigmoid
Returns:
Stimulus values corresponding to the proportion correct values.
"""
loc, scale = self._standard_parameters(threshold=threshold, width=width)
result = self._ppf(prop_correct, loc=loc, scale=scale)
return result

def _standard_parameters(self, threshold: N, width: N) -> list:
""" Transforms parameters threshold and width to a standard parametrization.
This method must be overwritten by subclasses to define new sigmoids.
The interpretation of the standard parameters, location and scale, depends on the sigmoid class used.
For instance, for a Gaussian sigmoid, the location corresponds to the mean and the scale to the standard
deviation of the distribution.
For negative slope sigmoids, we return the same parameters as for the positive ones.
Args:
threshold: Threshold value at PC
width: Width of the sigmoid
Returns:
Standard parameters (loc, scale) for the sigmoid subclass.
"""
raise NotImplementedError("This should be overwritten by an implementation.")

def _scipy_distr(self):
""" Get the scipy.stats implementation of the underlying cdf.
Expand All @@ -163,9 +234,6 @@ def _cdf(self, x, loc=0, scale=1):
distr, distr_kwargs = self._scipy_distr()
return distr.cdf(x, loc=loc, scale=scale, **distr_kwargs)

def _standard_parameters(self, threshold: N, width: N) -> list:
raise NotImplementedError("This should be overwritten by an implementation.")


class Gaussian(Sigmoid):
""" Sigmoid based on the Gaussian distribution's CDF. """
Expand Down

0 comments on commit 2f18d17

Please sign in to comment.