Search code examples
pythonpandasrollover

Handling wrapping of values in pandas


I am working with data coming from a position sensor that reports in angular coordinates (pitch, roll, and yaw). The sensors readout is such that the maximum reading is 180 degrees. Going past that, the readings jump to negative degrees. For example, a slightly oscillating series of readings might go something like:

 178.8
 178.9
-178.8
-178.0
-179.0
 180.0
 179.0

I'm presently handling this by checking all the values sequentially in a loop. I get the delta between two sequential readings and keep a running total to get my current angular position. If I cross from the 3rd quadrant (90 <= previous value <= 180) into the fourth (-180 <= present value <= -90), I add 360 to the present value before taking the delta. If I'm going the fourth quadrant into the 3rd, I subtract 360 before taking the delta. For example:

    Yaw = inData['Yaw']
    cum_sum = Yaw[0]
    for i in range(1, Yaw)):
        x_prev = Yaw[i-1]
        x = Yaw[i]
        if 90 <= x_prev and x_prev <= 180 and -180 <= x and x <= -90:
            x = x + 360
        elif -180 <= x_prev and x_prev <= -90 and 90 <= x and x <= 180:
            x = x - 360
        else:
            pass
        delta = x - x_prev
        cum_sum += delta

Here, inData is a dataframe containing angular positions from multiple axes, and 'Yaw' is a series in that dataframe.

If I could do a consistent correction to the angle values, I could generate a series of deltas and do a cumulative sum to get position. However, adding 360 to all readings or working with the angles as modulo 360 only moves the roll-over point from crossing between +/-180 to crossing between 0 and 360.

Given that I already have these readings in a DataFrame/Series, I was wondering if there is a more Pythonic (and faster) way to do this.


Solution

  • All vectorized, so it should be fast.

    s = pd.Series([-178.8, 178.9, -178., -179., 180., 179.])
    
    delta = (s.diff() + 
             (((s.shift() >= 90) & (s <= -90)).astype(int) - 
              ((s.shift() <= -90) & (s >= 90)).astype(int)) * 360)
    
    >>> delta.cumsum()
    0    NaN
    1   -2.3
    2    0.8
    3   -0.2
    4   -1.2
    5   -2.2
    dtype: float64
    

    Regarding the logic, ((s.shift() >= 90) & (s <= -90)).astype(int) will evaluate to 1 if the measurement is in the upper left quadrant and zero otherwise. -((s.shift() <= -90) & (s >= 90)).astype(int) will evaluate to minus one if the measurement is in the bottom right quadrant and zero otherwise. This is a vectorized way to say that you should add 360 to the difference if you are in the upper left quadrant and subtract 360 from the difference if you are in the bottom right quadrant, otherwise just take the difference.