Combining Sigmoid Curves

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Combining Sigmoid Curves

Rich Shepard
   When I last visited I was given excellent advice about Gaussian and other
bell-shaped curves. Upon further reflection I realized that the Gaussian
curves will not do; the curve does need to have y=0.0 at each end.

   I tried to apply a Beta distribution, but I cannot correlate the alpha and
beta parameters with the curve characteristics that I have.

   What will work (I call it a pi curve) is a matched pair of sigmoid curves,
the ascending curve on the left and the descending curve on the right. Using
the Boltzmann function for these I can calculate and plot each individually,
but I'm having difficulty in appending the x and y values across the entire
range. This is where I would appreciate your assistance.

   The curve parameters passed to the function are the left end, right end,
and midpoint. The inflection point (where y = 0.5) is half-way between the
ends and the midpoint.

   What I have in the function is this:

  def piCurve(ll, lr, mid):
   flexL = (mid - ll)/2.0
   flexR = (lr - mid)/2.0
   tau = (mid - ll)/10.0

   x = []
   y = []

   xL = nx.arange(ll,mid,0.1)
   for i in xL:
     x.append(xL[i])
   yL = 1.0 / (1.0 + nx.exp(-(xL-flexL)/tau))
   for j in yL:
     y.append(yL[j])

   xR = nx.arange(mid,lr,0.1)
   for i in xR:
     x.append(xR[i])
   yR = 1 - (1.0 / (1.0 + nx.exp(-(xR-flexR)/tau)))
   for j in yR:
     y.append(yR[j])

   appData.plotX = x
   appData.plotY = y

Python complains about adding to the list:

     yL = 1.0 / (1.0 + nx.exp(-(x-flexL)/tau))
TypeError: unsupported operand type(s) for -: 'list' and 'float'

   What is the appropriate way to generate two sigmoid curves so that the x
values range from the left end to the right end and the y values rise from
0.0 to 1.0 at the midpoint, then lower to 0.0 again?

Rich

_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Angus McMorland-2
2008/5/2 Rich Shepard <[hidden email]>:

>    When I last visited I was given excellent advice about Gaussian and other
>  bell-shaped curves. Upon further reflection I realized that the Gaussian
>  curves will not do; the curve does need to have y=0.0 at each end.
>
>    I tried to apply a Beta distribution, but I cannot correlate the alpha and
>  beta parameters with the curve characteristics that I have.
>
>    What will work (I call it a pi curve) is a matched pair of sigmoid curves,
>  the ascending curve on the left and the descending curve on the right. Using
>  the Boltzmann function for these I can calculate and plot each individually,
>  but I'm having difficulty in appending the x and y values across the entire
>  range. This is where I would appreciate your assistance.
>
>    The curve parameters passed to the function are the left end, right end,
>  and midpoint. The inflection point (where y = 0.5) is half-way between the
>  ends and the midpoint.
>
>    What I have in the function is this:
>
>         def piCurve(ll, lr, mid):
>           flexL = (mid - ll)/2.0
>           flexR = (lr - mid)/2.0
>           tau = (mid - ll)/10.0
>
>           x = []
>           y = []
>
>           xL = nx.arange(ll,mid,0.1)
>           for i in xL:
>             x.append(xL[i])
>           yL = 1.0 / (1.0 + nx.exp(-(xL-flexL)/tau))
>           for j in yL:
>             y.append(yL[j])
>
>           xR = nx.arange(mid,lr,0.1)
>           for i in xR:
>             x.append(xR[i])
>           yR = 1 - (1.0 / (1.0 + nx.exp(-(xR-flexR)/tau)))
>           for j in yR:
>             y.append(yR[j])
>
>           appData.plotX = x
>           appData.plotY = y
>
>  Python complains about adding to the list:
>
>      yL = 1.0 / (1.0 + nx.exp(-(x-flexL)/tau))
>  TypeError: unsupported operand type(s) for -: 'list' and 'float'
>
>    What is the appropriate way to generate two sigmoid curves so that the x
>  values range from the left end to the right end and the y values rise from
>  0.0 to 1.0 at the midpoint, then lower to 0.0 again?

How about multiplying two Boltzmann terms together, ala:

f(x) = 1/(1+exp(-(x-flex1)/tau1)) * 1/(1+exp((x-flex2)/tau2))

You'll find if your two flexion points get too close together, the
peak will drop below the maximum for each individual curve, but the
transition will be continuous.

Angus.
--
AJC McMorland, PhD candidate
Physiology, University of Auckland

(Nearly) post-doctoral research fellow
Neurobiology, University of Pittsburgh
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Rich Shepard
On Fri, 2 May 2008, Angus McMorland wrote:

> How about multiplying two Boltzmann terms together, ala:
>
> f(x) = 1/(1+exp(-(x-flex1)/tau1)) * 1/(1+exp((x-flex2)/tau2))

> You'll find if your two flexion points get too close together, the peak
> will drop below the maximum for each individual curve, but the transition
> will be continuous.

Angus,

   With an x range from 0.0-100.0 (and the flexion points at 25.0 and 75.0),
the above formula provides a nice bell-shaped curve from x=0.0 to x=50.0,
and a maximum y of only 0.25 rather than 2.0.

   Modifying the above so the second term is subtracted from 1 before the
multiplication, or by negating the exponent in the second term, yields only
the first half: the ascending 'S' curve from 0-50.

Thanks,

Rich

--
Richard B. Shepard, Ph.D.               |  Integrity            Credibility
Applied Ecosystem Services, Inc.        |            Innovation
<http://www.appl-ecosys.com>     Voice: 503-667-4517      Fax: 503-667-8863
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Chris Barker - NOAA Federal
In reply to this post by Rich Shepard
Rich,

this could use some serious vectorization/numpyification! Poke around
the scipy Wiki and whatever other tutorials you can find -- you'll be
glad you did. A hint:

When you are writing a loop like:
>    for i in xL:
>      x.append(xL[i])

You should be doing array operations!

Specifics:
   xL = nx.arange(ll,mid,0.1)

# you've now created an array of your X values
   for i in xL:
      x.append(xL[i])
#this dumps them into a list - why not keep them an array?
# if you really need an array (you don't here), then you can use:
xL.tolist()

That's why you get this error:
TypeError: unsupported operand type(s) for -: 'list' and 'float'

you're trying to do array operations on a list.

Also, you probably want np.linspace, rather than arange, it's a better
option for floats.

Here is my version:
import numpy as np

ll, lr, mid = (-10, 10, 0)

flexL = (mid - ll)/2.0
flexR = (lr - mid)/2.0
tau = (mid - ll)/10.0

xL = np.linspace(ll, mid, 5)
yL = 1.0 / (1.0 + np.exp(-(xL-flexL)/tau))

print xL
print yL

xR = np.linspace(mid, lr, 5)
yR = 1 - (1.0 / (1.0 + np.exp(-(xR-flexR)/tau)))

print xR
print yR

# now put them together:
x = np.hstack((xL, xR[1:])) # don't want to duplicate the midpoint
y = np.hstack((yL, yR[1:]))

print x
print y

Though it doesn't look like the numbers are right.

Also, you don't need to create the separate left and right arraysand put
them together, slicing gives a view, so you could do:

numpoints = 11 # should be an odd number to get the midpoint
x = linspace(ll,lr, numpoints)
y = zeros_like(x)

xL = x[:numpoints/2]
yL = y[:numpoints/2]

yL[:] = 1.0 / (1.0 + np.exp(-(xL-flexL)/tau))

you could also use something like:

np.where(x<0, .....)

left as an exercise for the reader...

-Chris


--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

[hidden email]
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Rich Shepard
On Fri, 2 May 2008, Christopher Barker wrote:

> this could use some serious vectorization/numpyification! Poke around the
> scipy Wiki and whatever other tutorials you can find -- you'll be glad you
> did. A hint:
>
> When you are writing a loop like:
>>    for i in xL:
>>      x.append(xL[i])
>
> You should be doing array operations!

Chris,

   Good suggestions. I need to append the two numpy arrays so I return all x
values in a single list and all y values in another list. I'll read the
numpy book again.

> That's why you get this error:
> TypeError: unsupported operand type(s) for -: 'list' and 'float'
>
> you're trying to do array operations on a list.

   Ah, so!

> Also, you probably want np.linspace, rather than arange, it's a better
> option for floats.

   arange() has worked just fine in other functions.

> # now put them together:
> x = np.hstack((xL, xR[1:])) # don't want to duplicate the midpoint
> y = np.hstack((yL, yR[1:]))

> Also, you don't need to create the separate left and right arraysand put
> them together, slicing gives a view, so you could do:

   I assume that there's a way to mate the two curves in a single equation.
I've not been able to find what that is.

Thanks,

Rich
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Anne Archibald
In reply to this post by Rich Shepard
2008/5/2 Rich Shepard <[hidden email]>:

>    What will work (I call it a pi curve) is a matched pair of sigmoid curves,
>  the ascending curve on the left and the descending curve on the right. Using
>  the Boltzmann function for these I can calculate and plot each individually,
>  but I'm having difficulty in appending the x and y values across the entire
>  range. This is where I would appreciate your assistance.

It's better not to work point-by-point, appending things, when working
with numpy. Ideally you could find a formula which just produced the
right curve, and then you'd apply it to the input vector and get the
output vector all at once. For example for a Gaussian (which you're
not using, I know), you'd write a function something like

def f(x):
    return np.exp(-x**2)

and then call it like:

f(np.linspace(-1,1,1000)).

This is efficient and clear. It does not seem to me that the logistic
function, 1/(1+np.exp(x)) does quite what you want. How about using
the cosine?

def f(left, right, x):
    scaled_x = (x-(right+left)/2)/((right-left)/2)
    return (1+np.cos((np.pi/2) * scaled_x))/2

exactly zero at both endpoints, exactly one at the midpoint,
inflection points midway between, where the value is 1/2. If you want
to normalize it so that the area underneath is one, that's easy to do.

More generally, the trick of producing a scaled_x as above lets you
move any function anywhere you like.

If you want the peak not to be at the midpoint of the interval, you
will need to do something a litle more clever, perhaps choosing a
different function, scaling the x values with a quadratic polynomial
so that (left, middle, right) becomes (-1,0,1), or using a piecewise
function.

Good luck,
Anne
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Rich Shepard
On Fri, 2 May 2008, Anne Archibald wrote:

> It's better not to work point-by-point, appending things, when working
> with numpy. Ideally you could find a formula which just produced the right
> curve, and then you'd apply it to the input vector and get the output
> vector all at once.

Anne,

   That's been my goal. :-)

> How about using the cosine?
>
> def f(left, right, x):
>    scaled_x = (x-(right+left)/2)/((right-left)/2)
>    return (1+np.cos((np.pi/2) * scaled_x))/2
>
> exactly zero at both endpoints, exactly one at the midpoint,
> inflection points midway between, where the value is 1/2. If you want
> to normalize it so that the area underneath is one, that's easy to do.

> More generally, the trick of producing a scaled_x as above lets you
> move any function anywhere you like.

   This looks like a pragmatic solution. When I print scaled_x (using left =
0.0 and right = 100.0), the values range from -1.0 to +0.998. So, I need to
figure out the scale_x that sets the end points at 0 and 100.

Thanks very much,

Rich
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Anne Archibald
2008/5/2 Rich Shepard <[hidden email]>:

> On Fri, 2 May 2008, Anne Archibald wrote:
>
>  > It's better not to work point-by-point, appending things, when working
>  > with numpy. Ideally you could find a formula which just produced the right
>  > curve, and then you'd apply it to the input vector and get the output
>  > vector all at once.
>
>  Anne,
>
>    That's been my goal. :-)
>
>
>  > How about using the cosine?
>  >
>  > def f(left, right, x):
>  >    scaled_x = (x-(right+left)/2)/((right-left)/2)
>  >    return (1+np.cos((np.pi/2) * scaled_x))/2
>  >
>  > exactly zero at both endpoints, exactly one at the midpoint,
>  > inflection points midway between, where the value is 1/2. If you want
>  > to normalize it so that the area underneath is one, that's easy to do.
>
>  > More generally, the trick of producing a scaled_x as above lets you
>  > move any function anywhere you like.
>
>    This looks like a pragmatic solution. When I print scaled_x (using left =
>  0.0 and right = 100.0), the values range from -1.0 to +0.998. So, I need to
>  figure out the scale_x that sets the end points at 0 and 100.

No, no. You *want* scaled_x to range from -1 to 1. (The 0.998 is
because you didn't include the endpoint, 100.) The function I gave,
(1+np.cos((np.pi/2) * scaled_x))/2, takes [-1, 1] to a nice
bump-shaped function.  If you feed in numbers from 0 to 100 as x, they
get transformed to scaled_x, and you feed them to the function,
getting a result that goes from 0 at x=0 to 1 at x=50 to 0 at x=100.

Anne
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Chris Barker - NOAA Federal
Anne Archibald wrote:
> 2008/5/2 Rich Shepard <[hidden email]>:
> No, no. You *want* scaled_x to range from -1 to 1.

Why not just scale to -pi to pi right there?

(The 0.998 is because you didn't include the endpoint, 100.)

Which is why you want linspace, rather than arange. Really, trust me on
this!

If the cosine curve isn't right for you, a little create use of np.where
would let you do what you want in maybe another line or two of code.
Something like:

y = np.where( x < 0 , Leftfun(x), Rightfun(x) )

or just compute one side and then flip it to make the other side:

y = np.zeros_like(x)
y[center:] = fun(x[center:])
y[:center] = y[center+1:][::-1] # don't want the center point twice

if it's symetric anyway.

-Chris


--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

[hidden email]
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion
Reply | Threaded
Open this post in threaded view
|

Re: Combining Sigmoid Curves

Rich Shepard
On Fri, 2 May 2008, Christopher Barker wrote:

> Why not just scale to -pi to pi right there?

   Dunno, Chris. As I wrote to Anne (including a couple of files and the
resulting plot), it's been almost three decades since I dealt with the math
underlying distribution functions.

> Which is why you want linspace, rather than arange. Really, trust me on
> this!

   I see the difference in the book. Didn't know about linspace(), but adding
True brings the end point to 100.0

> If the cosine curve isn't right for you, a little create use of np.where
> would let you do what you want in maybe another line or two of code.
> Something like:
>
> y = np.where( x < 0 , Leftfun(x), Rightfun(x) )
>
> or just compute one side and then flip it to make the other side:
>
> y = np.zeros_like(x)
> y[center:] = fun(x[center:])
> y[:center] = y[center+1:][::-1] # don't want the center point twice
>
> if it's symetric anyway.

   I'll look into this over the weekend (after upgrading my machines to
Slackware-12.1). I can get nice sigmoid curves with the Boltzmann function.
This thread started when I asked how to combine the two into a single curve
with one set of x,y points.

Much appreciated,

Rich

--
Richard B. Shepard, Ph.D.               |  Integrity            Credibility
Applied Ecosystem Services, Inc.        |            Innovation
<http://www.appl-ecosys.com>     Voice: 503-667-4517      Fax: 503-667-8863
_______________________________________________
Numpy-discussion mailing list
[hidden email]
http://projects.scipy.org/mailman/listinfo/numpy-discussion