Search code examples
rkernel-density

How to find inflection points in a Kernel density plot in R?


I am trying to find the x-values of the inflection points in the curve of a Kernel density plot that I computed with the density() function.

I found the following answered question helpful in finding the turning points:

How to find all the turning points on a kernel density curve when window width varies.

So I would think there must be a way to fnd the x-values of the inflection points, too. Would be great if somene has a tipp.


Solution

  • By definition, an inflection point is the point where the second derivative of the function equals zero. In the practice, this means that an inflection point will be a point where the slope passes from increasing to decreasing, or v.v. Using this definition, I came with this approximate and non-automatic approach: Let's say that you have a dataframe, that I will call all, which contains the x-values in the first column, and the result of the density computation in the second one. From this dataframe, we can calculate the slope of two consecutive points like this :

    slopes <- vector()
    for(i in (2:nrow(all))){
      x1 <- all[i-1, 1]
      x2 <- all[i, 1]
      y1 <- all[i-1, 2]
      y2 <- all[i, 2]
      slope_i <- (y2-y1)/(x2-x1)
      slopes <- append(slopes, slope_i)
    }
    

    By the definition of inflection point, we can now calculate if, from one point to another, the slope gets larger or smaller:

    increment <- vector()
    for(j in 2:length(slopes)){
      increment_j <- slopes[j] - slopes[j-1]
      increment <- append(increment, increment_j)
    }
    

    The inflection points will be those points were this increment passes from positive to negative, or v.v.

    Now, let's separate these increments in positive and negative:

    pos <- which(increment>0)
    neg <- which(increment<0
    

    Now, whenever there is a jump in these pos or neg vectors, it means we have an inflection point. So, once again:

    steps_p <- vector()
    for(k in 2:length(pos)){
      steps_k <- pos[k] - pos[k-1]
      steps_p <- append(steps_p, steps_k)
    }
    steps_n <- vector()
    for(k in 2:length(neg)){
      steps_k <- neg[k] - neg[k-1]
      steps_n <- append(steps_n, steps_k)
    }
    

    Now, just ask R:

    which(steps_p>1)
    which(steps_n>1)
    

    This are the indices of your inflection points, now just go to your original dataframe and ask for the x value:

    all[pos[which(steps_p>1)],1]
    all[neg[which(steps_n>1)],1]
    

    Take in mind that the x value will be close to exact, but not quite, as during every loop we lose one index, but it will still be a very close solution.