Adjustable Sigmoid Curve (S-Curve) from $(0,0)$ to $ (1,1)$

$\begingroup$

I feel like this is such a simple question but I am at such a loss. I currently have a set of values that I would like to weigh by an S Curve. My data ranges from $0$ to $1$ and never leaves those bounds, but nearly every version of a Sigmoid I see assumes $x = 0$ is the point of inflection. Furthermore many of the Sigmoids reach y=0 at approximately $x = -6$, and $y=1$ at approx $x= 6$. However in my case I need to reach $y=0$ at $x=0$ and $y=1$ at $x=1$, and the point to of inflection to be at $x = 0.5$.

To summarize I need a S Curve that meets the following requirements..

  • $x$ from $0$ to $1$
  • $y$ from $0$ to $1$
  • $0$ remains $0$ and $1$ remains $1$
  • Adjustable shape for different Curves
  • Bonus If I can adjust the point of inflection while maintaining the above

Here is an example of the above (except for a movable inflection)

I have looked at Sigmoids, Logistics, tan and sing approximations, contrast and AI equations, and nothing quite gets me what I need. Any explanation to go along with an answer would be much appreciated!

$\endgroup$ 1

12 Answers

$\begingroup$

I put three in a chain: I use the positive domain of the NTS to 'skew' the input, the second is the basic sigmoid, and then I use the positive domain to skew the output. The result is an extremely flexible curve which I tend to use everywhere now. Here is a Desmos sheet, I think it is what you were looking for, although the other answers seem good too.

$\endgroup$ 1 $\begingroup$

I really hate to answer my own question but I believe I found the answer. I tripped across an equation by Game Dev Dino Dini which creates a half normal tunable sigmoid that ranges from $(0,0)$ to $(1,1)$. However the $K$ value is rather wonky in this case. Dino however created another version which he posted via Twitter leading to a half normal tunable sigmoid with a K range from -1 to 1. This equation is...

$$\large \frac{kx-x}{2kx-k-1}=f(x)$$

$$ 0\leq x\leq 1,-1\leq k\leq 1$$

To achieve a S curve in the same range I simply had to scale down the equation and make it piecewise. The lower half then becomes...

$$\large \frac{k*2x-2x}{2k*2x-k-1}*0.5$$ $$or$$ $$\large f(2x)*0.5$$ $$for$$ $$x\leq0.5, -1\leq k \leq 1$$

And then the upper half of the curve is...

$$\large 0.5*\frac{(-k*2(x-0.5))-(2(x-0.5)))}{2*-k*2(x-0.5)-(-k)-1}+0.5$$ $$or$$ $$\large 0.5*f(2(x-0.5))+0.5$$ $$for$$ $$ x>0.5, -1\leq -k \leq 1$$

note the $k$ is negative for the upper half of the curve. Here is an example of the lower half and the upper half. The only issue that remains with this equation is that technically $k = -1,1$ returns $y = 0$, however $k = -0.9...,0.9...$ is close enough. Unfortunately it would appear I cannot adjust my point of inflection to be anything other than $y=x=0.5$ but that was not necessary for the core operation.

$\endgroup$ 5 $\begingroup$

3 years late, but I was looking for an answer to the same question & all the responses on here seem overly complicated...

I ended up working it out on my own and got the solution below which is mathematically perfect:

y = 0.5 * (1 + sin((x*pi)-(pi/2)))

Check it out on Wolfram Alpha

$\endgroup$ 5 $\begingroup$

Following Micah's comment above I've tried out the cumulative distribution function of the beta distribution, which is available in scipy.special.betainc. It produces nice curves for $a=b\geq 1$. For $a=b<1$ there is a kink, probably due to numerical issues. It might not be the fastest function to compute, but it is readily available.

Plot of the Beta cdf

%matplotlib inline
from scipy.special import betainc
import numpy as np
import math
import matplotlib.pyplot as plt
x = np.linspace(0., 1., 100)
N = 7
for i in range(N): k = 0.5 * np.exp(i) / N plt.plot(x, betainc(k, k, x))
plt.gca().set_xlim(0,1)
plt.gca().set_ylim(0,1)
plt.show()
$\endgroup$ 1 $\begingroup$
  1. Select one extreme "setting" of your curve (eg, the magenta curve in your link).Define your criteria for this shape a bit more carefully, eg: $f(0) = 0$, $f(.5) = .5$, $f(1) = 1$, and function is both continuous and differentiable everywhere. Furthermore, $f(x) > x$ for $0 < x < .5$ and $f(x) > x$ for $.5 < x < 1$.

  2. Divide it in two at what you call the "inflection point" ($x=.5$).

  3. Construct a piecewise smooth function in two pieces (one below $.5$ and one above) with the properties selected in step 1. I recommend using trig functions, but exponential and log functions can be made to work as well. I will call this function $g(x)$. One possibility is given by: $$ .5 sin( 10x/pi ), x\leq.5 \\ -.5 sin( 10x/pi )+1, x>.5 $$ Other options exist, of course.

  4. To achieve other, less extreme, curves in the same family, interpolate linearly, between $g(x)$ and $x$, eg: $f(x) = a * g(x) + (1-a) * x$, where $0 < a < 1$. To other curves in the same family but in the other direction, you will have to extrapolate, but this is simple: $f(x) = a * g(x) + (1-a) * x$, where $-1 < a < 0$. Thus, the complete family is given simply by $f(x) = a * g(x) + (1-a) * x$, where $-1 < a < 1$.

$\endgroup$ 2 $\begingroup$

I like the answer. Thanks for posting. And I would like to make it more general: Point reflection across (0,0) will make it full S-shape: $y=f(x,k)=Sign(x) * (k * Abs(x) - Abs(x)) / (2 * k * Abs(x) - k - 1)$, where -1<x<1 and -1<y<1. Scaling and translation will make it fit into a rectangle: $g(x)=y_{scale} * f(x / x_{scale} - x0, k) + y0$

$\endgroup$ 1 $\begingroup$

In computer graphics one often needs something like this for smooth interpolations, also called Smoothstep functions. What is great is that these functions have zero derivatives at the end-points and they are chosen for fast computation, however, they are not so easily adjustable.

$\endgroup$ $\begingroup$

I've been looking around for such function and came up with two:
1) $$ f(x,z) = \left\{\begin{aligned} &0.5 \cdot (2x)^z &&: 0 \le x \le 0.5 \\ &1 - 0.5 \cdot (2 - 2x)^z &&: 0.5 < x \le 1 \end{aligned} \right.$$ This is two pieces of a polynomial, 'glued' in the middle.

2) $$f(x,z) = 0.5 + 0.5\cdot \text{sign}(x - 0.5) \cdot |2 (x - 0.5)|^z$$ This is a polynomial that looks like it has an odd degree, regardless of its actual degree.

The first one is probably more applicable in most cases. In both cases you can change the shape by changing $z$ between $0$ and $\infty$. Additionally you could also use $\tan(\frac{\pi}{2}\cdot z)$ for the exponent and let $z \in (-1, 1)$, this is probably more convenient than $(0, \infty)$.

$\endgroup$ $\begingroup$

Here is a fully configurable sigmoid function. I post the full code, try the plotting option. I add furthermore two other functions derivated from the sigmoid, just in case:

def sigmosym(x, Lambda = 0.5, mirror = False, plotting = False): # Symetric sigmoid function f from [0 1] to [0 1], with the # property that f(0) = 0, f(1) = 1, f'(0) = f'(1) = 0, and f is # symetric about the point (1/2, 1/2). # If mirror is set to True, then sigmosym returns 1 - f. # # INPUT: # * x: a scalar # * Lambda (default 1): a real number > 0; if lambda <=1, then # the sigmoid is convex at the first half of the curve, and # concave at the second half, that is, it has only one # inflexion point; if Lambda >1, it will be concave between 0 and # 1/2 and convex between 1/2 and 1, with 4 inflexion points, # extending to the horizontal around the center. # * mirror (default False): if set to True, then the function returns # 1-f, where f is the function returned if mirror = False # * plotting (default False): if set to True, the function is plotted, # and no value is returned. if plotting: import matplotlib.pyplot as plt x = list(np.arange(0, 1, 0.01)) y = [sigmosym(t, Lambda, mirror = mirror, plotting = False) for t in x] fig1 = plt.figure() ax = fig1.add_subplot(1,1,1) ax.grid(True) ax.plot(x, y) return None f = 2 * np.abs(x) - 1; f = (np.sign(f) * (np.abs(f)**Lambda) + 1) / 2 f = np.sin(np.pi * f/2)**2 if mirror: f = 1 - f return f
def sigmocurve(x, stiffness = 0.5, inflexion = 1, mirror = False, plotting = False): # A positive sigmoidal function f extending from 0 to +infty, with the # property that f(0) = f'(0) = 0 = f'(+inf), f(+inf) = 1, # f is convex between 0 and a point x_infl very near to "inflexion", f is # concave between x_infl and +inf. # If mirror is set to True, then sigmocurve returns 1 - f. # # INPUT: # * x: a scalar # * stiffness (default 0.5): a real number between 0 and 1 (>0, <= 1): # the more "stiffness" is close to 0, the more abrubtly the function # climbs from x_infl to 1. # * inflexion: the approximative inflexion point of the curve, that # is, the point where the curve climbs to upper_bound; "inflexion" # must be > 0. # * mirror (default False): if set to True, then the function returns # 1-f, where f is the function returned if mirror = False # * plotting (default False): if set to True, the function is plotted, # and no value is returned. # if plotting: import matplotlib.pyplot as plt intvl = 60 x = list(np.arange(0, intvl, 0.01)) y = [sigmocurve(t, stiffness = stiffness, inflexion = inflexion, mirror = mirror, plotting = False) for t in x] fig2 = plt.figure() ax = fig2.add_subplot(1,1,1) ax.plot(x, y) ax.grid(True) return None a_infl = 1/inflexion t = a_infl * x / (1 + a_infl * x) f = sigmosym(t, stiffness, mirror = False) if mirror: f = 1 - f return f
def sigmobell(x, mu = 0, sigma = 1, Lambda = 1, plotting = False): # A bell shaped sygmoid based function f with center mu, and extent sigma # (it is 0 outside of [mu-sigma, mu+ sigma]). f is symmetrical about mu, # and reach its maximum f = 1 at x = mu. The two halves of the bell are # built using the sygmosym function. # # INPUT: # * x : a scalar # * mu : the center of the bell (default 0) # * sigma: a number > 0 the radius of the bell (default 1) # * Lambda: a number between 0 and 1, that controls the shape of # the bell (default 1): the more Lambda is close to 0, the more # the bell resembles to a rectangle, and the more it is close # to 1, the more it resembles a well shaped bell. # * plotting (default False): if set to True, the function is plotted, # and no value is returned. # if plotting: import matplotlib.pyplot as plt x = list(np.arange(mu - sigma - 1, mu + sigma + 1, 0.01)) y = [sigmobell(t, mu = mu, sigma = sigma, Lambda = Lambda, plotting = False) for t in x] fig3 = plt.figure() ax = fig3.add_subplot(1,1,1) ax.grid(True) ax.plot(x, y) return None Delta = (x - mu) / sigma if np.abs(Delta) > 1: Delta = 1 f = 1 - sigmosym(Delta, Lambda = Lambda, mirror = False) return f
if __name__ == '__main__': # PLOT THE FUNCTIONS FOR SOME PARAMETERS sigmosym(None, Lambda = 0.5, mirror = False, plotting = True) sigmocurve(None, stiffness = 0.2, inflexion = 12, mirror = False, plotting = True) sigmobell(None, mu = 0, sigma = 2, Lambda = 1, plotting = True) plt.show() 
$\endgroup$ $\begingroup$

Taking the best of all of these comments and Dino's updated formula:

$$\frac{(k-1)(2x-1)}{2(4k|x-0.5|-k-1)}+\frac12,\quad\quad-1\leq k\leq 1.$$

Animation:

It meets all of your criteria (except inflection option), and best of all it's not piecewise.

$\endgroup$ $\begingroup$

The equation provided by Taylor Vance can also be modified to have a tunable inflection point, using an additional parameter "b":

$$ y = \frac{\left(\frac{b\left(k-1\right)\left(\frac{x}{b}-1\right)}{\left(4k\left|x-b\right|-k-1\right)}+\frac{b\left(k-1\right)}{4bk-k-1}\right)}{\left(\frac{b\left(k-1\right)\left(\frac{1}{b}-1\right)}{\left(4k\left|1-b\right|-k-1\right)}+\frac{b\left(k-1\right)}{4bk-k-1}\right)}, \;\;\;\;\;\;\;\;\;\; 0 < b < 1;\;\;\;\;\;\; −1 ≤ k ≤ 1. $$

$\endgroup$ $\begingroup$

Here is a variant of sigmoid described in question I have made through arctan() function

$$y=\left(\frac{-1}{2\cdot\arctan(-\frac{a}{2\pi})}\right)\cdot\arctan\left(\frac{a\cdot(x-0.5)}{\pi}\right)+0.5$$

Those big left multiplier depends only on parameter and can be calculated separately so basically it is:$$K=\frac{-1}{2\cdot\arctan(-\frac{a}{2\pi})}$$$$y=K\cdot\arctan\left(\frac{a\cdot(x-0.5)}{\pi}\right)+0.5$$

arctan parametric sigmoid picture

Pseudocode:

a = 100.0;
K = -1.0 / (2.0 * atan(-a * 0.5 / pi));
for(x = 0.0; x <= 1.0; x += 0.01)
{ y = K * atan(a * (x - 0.5) / pi) + 0.5;
}
$\endgroup$ 1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like