12

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) http://www.guillermoluijk.com/misc/contrast.gif

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!

Praveen
  • 1,563
  • 1
  • 12
  • 23
pure_bordem
  • 173
  • 1
  • 1
  • 7
  • 1
    I'm not sure if it has the analytic properties that you want, but the CDF of a beta distribution is pretty versatile and meets your explicit requirements. – Micah Aug 05 '13 at 04:26

12 Answers12

8

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. https://www.desmos.com/calculator/tswgrnoosy

Dino Dini
  • 96
  • 1
  • 1
5

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

William LeGate
  • 151
  • 1
  • 2
  • 1
    This is by far the best answer. It's elegant and easily scalable on x-axis. – mertyildiran Mar 23 '17 at 20:25
  • Oops, I changed my mind because it's repetitive not like sigmoid or any kind of a soft step function. – mertyildiran Mar 23 '17 at 21:23
  • How is this solution adjustable? – leetNightshade May 10 '17 at 18:43
  • @leetNightshade It's not adjustable in the form provided, but this is! ... https://www.desmos.com/calculator/ynelze3uqa Please note: this is not a sigmoid curve, which is what the question requested. However, a sine wave was perfectly usable in my case. – musicin3d Apr 20 '21 at 04:10
  • And here's another that also supports X coordinates of both points... https://www.desmos.com/calculator/xpodzo2xqq It could be simplified a little more, but how it works is a little more obvious this way. – musicin3d Apr 21 '21 at 04:22
5

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.

litturt
  • 103
  • 2
pure_bordem
  • 173
  • 1
  • 1
  • 7
  • Thx for the links. Denominator typo. in first formula: `(2kx - k -1)` instead of `(2k - k - 1)` – arun Jan 31 '14 at 01:04
  • Thanks for posting and I'm looking for answer like this. But I have some different questions about I want to make the point of inflection into above Adjustable Sigmoid Curve. for example, I want have two more options about the point of inflection like a: x-axis and b: y-axis. So If I choice a= 0.5 and b=0.1 (0 What am I supposed to do this? Please Help me. thanks. – Carter Jan 03 '15 at 12:39
  • ACtually I want to draw from (0,0) to (1,1) not (-1,-1) to (1,1). How do I edit your equation? Also I want to move the point of inflection in this equation, How I can make this? – Carter Jan 03 '15 at 12:56
  • This looks really good, thanks. I graphed it out to verify: https://www.desmos.com/calculator/eijfplyf1l – leetNightshade May 10 '17 at 02:55
  • Dino shared a full tunable sigmoid that's X from -1 to 1 where k is -1 to 1. It's beautiful: twitter.com/dndn1011/status/841113222673489920 – leetNightshade May 10 '17 at 23:56
2

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()
Lenar Hoyt
  • 932
  • 10
  • 18
1
  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$.

Bjorn Roche
  • 171
  • 6
  • Thank you for the insight. I have tried doing it piecewise before and could construct a static S curve but alas the adjustable component was alluding me. I attempted to plot the above function in Wolfram but seem to get odd results. Perhaps I am mistaking something? Ex: a = 0.5 [link](http://www.wolframalpha.com/input/?i=plot+%280.5*%285sin%2810x%2Fpi%29%29%29%2B%28%281-0.5%29*x%29%2C+from+x+%3D+0+to+1) Ex: a = 0.1 [link](http://www.wolframalpha.com/input/?i=plot+%280.1*%285sin%2810x%2Fpi%29%29%29%2B%28%281-0.1%29*x%29%2C+from+x+%3D+0+to+1) – pure_bordem Aug 05 '13 at 04:06
  • Both plots you link use .5*5sin(10x/pi), for g(x), which is not correct even for x<.5 – Bjorn Roche Aug 05 '13 at 14:20
1

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$

Vladhagen
  • 4,678
  • 6
  • 30
  • 41
Henry
  • 11
  • 1
  • Glad it was of help! I don't have time to run your equation through Wolfram, but anything to make it more general is always good by me! – pure_bordem Nov 02 '14 at 00:56
0

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.

Lenar Hoyt
  • 932
  • 10
  • 18
0

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)$.

ZyTelevan
  • 746
  • 6
  • 19
0

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()    
MikeTeX
  • 1,621
  • 7
  • 16
0

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: https://www.desmos.com/calculator/uzf18tbyij

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

311411
  • 2,820
  • 6
  • 15
  • 34
Taylor Vance
  • 101
  • 2
0

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. $$

https://www.desmos.com/calculator/ym0phzdvll

Ry S.
  • 1
-1

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;
}
Andrew
  • 1
  • I know the comment will be deleted but at least moderator will read it. Just want to point out that those instant down-vote really made me sad. I just wanted to share good solution and its the only single reason I registered there. Don't see any adequate points why my answer is THAT BAD. Now I will definitely not share my formulas again. You may delete the answer as well if you want – Andrew Dec 04 '19 at 16:27