The general form of the logistic function is:
\[f(x)=\frac{L}{1+e^{-k(x-x_{0})}}\]
where \(x_{0}\) is the midpoint (or inflection point at which the rate of change switches from positive to negative) of the logistic curve, \(L\) is the maximum value at which the curve levels out (or its asymptote), and \(k\) is the steepness of the curve (or its logistic growth rate). Let’s make a few quick substitutions to turn this into something more recognizable in the context of visibility cost. We’ll substitute rescaled visibility cost (which we’ll call \(v_{r}\)) for \(f(x)\) and original visibility cost (\(v\)) for \(x\), giving us:
\[v_{r}=\frac{L}{1+e^{-k(v-v_{0})}}\]
So, let’s see how this looks in reality. If we start with some simple constants, where \(L=1\), \(k=1\), and \(v_{0}=0.5\), we get this curve:
# define logistic function
log.fun <- function(v,v0,L,k){
vr <- L / (1 + exp(-k * (v - v0)))
return(vr)
}
# define vector of visibility costs
v <- seq(0,1,0.01)
# get logistic-rescaled visibility costs
vr <- log.fun(v,0.5,1,1)
# plot it out
par(mar = c(5,5,1,1), las = 1)
plot(vr ~ v, type = "l", lwd = 2)
With these parameters, we’re basically looking at a linear transformation. In the interest of maintaining the original ranges of values [0,1], let’s use the following equation to coerce the rescaled values to 0-1:
\[x_{[0,1]}=\frac{x-\min(x)}{\max(x)-\min(x)}\]
where \(x_{[0,1]}\) is the resulting values of some variable \(x\) after being coerced to a range of [0,1]. I’ll now repeat what I just did, but adding a line to the logistic function that coerces the data to 0-1:
# define logistic function
log.fun <- function(v,v0,L,k){
vr <- L / (1 + exp(-k * (v - v0)))
vr <- (vr-min(vr)) / (max(vr) - min(vr))
return(vr)
}
# define vector of visibility costs
v <- seq(0,1,0.01)
# get logistic-rescaled visibility costs
vr <- log.fun(v,0.5,1,1)
# plot it out
par(mar = c(5,5,1,1), las = 1)
plot(vr ~ v, type = "l", lwd = 2)
Cool. But still not very interesting. To make it look more like a classic logistic growth curve, we’ll need to change the steepness of the center of the curve (\(k\)). Let’s run a range of \(k\) values to see the impact:
# load the viridis library
library(viridis)
## Loading required package: viridisLite
# plot it out
par(mar = c(5,5,1,1), las = 1)
cols <- viridis(10)
plot(x = c(0,1), y = c(0,1), type = "n", xlab = "v", ylab = "vr")
i <- 0
for (k in seq(1,20,2)){
i <- i + 1
vr <- log.fun(v,0.5,1,k)
lines(vr ~ v, lwd = 2, col = cols[i])
}
legend("topleft", legend = paste0("k = ", seq(1,20,2)), lwd = 2, col = cols)
OK, so clearly higher \(k\) values yield more sigmoidal (s-like) curves. But the interesting thing about sigmoids is the the first half is exponential and the second half is logarithmic. So, in theory, by manipulating \(v_{0}\), the term that defines the center point of the curve, we can make it more expontial-ish or more logarithmic-ish. Let’s settle on \(k=10\) for now and run some more curves, varying the centering term \(k_{0}\):
# plot it out
par(mar = c(5,5,1,1), las = 1)
cols <- viridis(11)
plot(x = c(0,1), y = c(0,1), type = "n", xlab = "v", ylab = "vr")
i <- 0
for (v0 in seq(0,1,0.1)){
i <- i + 1
vr <- log.fun(v,v0,1,10)
lines(vr ~ v, lwd = 2, col = cols[i])
}
legend("topleft", legend = paste0("v0 = ", seq(0,1,0.1)), lwd = 2, col = cols)
Cool! So, if \(v_{0}=0\), then it’s basically a log curve, and if \(v_{0}=1\) then it’s basically an exponential curve. I think that might be a nice continuous basis upon which to test different visibility cost rescaling functions.
Perhaps if we wanted to get really fancy, we could simultaneously explore the two key terms (\(k\) and \(v_{0}\) simultaneously). In the example below, I’ll plot out 25 unique combinations of these two parameters:
# plot it out
par(mfrow = c(5,5), mar = rep(0,4), oma = c(5,5,2,4), las = 1)
for (k in c(1,2,5,10,20)){
for (v0 in seq(0,1,0.25)){
vr <- log.fun(v,v0,1,k)
plot(vr ~ v, lwd = 2, type = "l",
xaxt = "n", xlab = NA, yaxt = "n", ylab = NA)
grid()
if (k == 1){
mtext(paste0("v0 = ", v0), line = 0.5)
}
if (v0 == 1){
mtext(paste0("k = ", k), 4, line = 0.5, las = 2)
}
if (k == 20){
axis(1)
mtext("v", 1, 2.5, outer = T)
}
if (v0 == 0){
axis(2)
mtext("vr", 2, 2.5, outer = T)
}
}
}
Anyway, just some food for thought…