abcd
abcd

Reputation: 10781

Smooth circular data

I have an array of data Y such that Y is a function of an independent variable X (another array).

The values in X vary from 0 to 360, with wraparound.

The values in Y vary from -180 to 180, also with wraparound.

(That is, these values are angles in degrees around a circle.)

Does anyone know of any function in Python (in numpy, scipy, etc.) capable of low-pass filtering my Y values as a function of X?

In case this is at all confusing, here's a plot of example data:

enter image description here

Upvotes: 2

Views: 2087

Answers (3)

Claas Bontus
Claas Bontus

Reputation: 2018

You can use convolve2D from scipy.signal. Here is a function, which applies smoothing to a numpy array a. If a has more than one dimension smoothing is applied to the innermost (fastest) dimension.

import numpy as np
from scipy import signal

def cyclic_moving_av( a, n= 3, win_type= 'boxcar' ):
  window= signal.get_window( win_type, n, fftbins=False ).reshape( (1,n) )
  shp_a= a.shape
  b= signal.convolve2d( a.reshape( ( np.prod( shp_a[:-1], dtype=int ), shp_a[-1] ) ), 
                        window, boundary='wrap', mode='same' )
  return ( b / np.sum( window ) ).reshape( shp_a )

For instance it can be used like

import matplotlib.pyplot as plt

x = np.linspace(0, 360, 360)
y1 = 5 * np.sin(x / 90. * 3.14) + 0.5 * np.random.randn(360)
y2 = 5 * np.cos(0.8 * x / 90. * 3.14) + 0.5 * np.random.randn(360)

y_av=  cyclic_moving_av( np.stack((y1,y2)), n=10 )  #1

plt.plot(x, y1, '+')
plt.plot(x, y2, '+')
plt.plot(x, y_av[0])
plt.plot(x, y_av[1])
plt.show()

This results in Cyclic smoothed data

Line #1 is equivalent to

y_av[0]=  cyclic_moving_av( y1, n=10 )
y_av[1]=  cyclic_moving_av( y2, n=10 )

win_type= 'boxcar' results in averaging over neighbors with equal weights. See signal.get_window for other options.

Upvotes: 1

Ami Tavory
Ami Tavory

Reputation: 76386

Say you start with

import numpy as np

x = np.linspace(0, 360, 360)
y = 5 * np.sin(x / 90. * 3.14) + np.random.randn(360)

plot(x, y, '+');

enter image description here

To perform a circular convolution, you can do the following:

yy = np.concatenate((y, y))
smoothed = np.convolve(np.array([1] * 5), yy)[5: len(x) + 5]

This uses, at each point, the cyclic average with the previous 5 points (inclusive). Of course, there are other ways of doing so.

>>> plot(x, smoothed)

enter image description here

Upvotes: 1

RootTwo
RootTwo

Reputation: 4418

Here is a solution using pandas to do a moving average. First unwrap the data (need to convert to radians and back), so there are no discontinuities (e.g., jump from 180 to -179). Then compute the moving average and finally convert back to wrapped data if desired. Also, check out this numpy cookbook recipe using np.convolve().

import numpy as np
import pandas as pd

# generate random data
X = pd.Series([(x  + 5*np.random.random())%360       for x in range(-100, 600, 15)])
Y = pd.Series([(y  + 5*np.random.random())%360 - 180 for y in range(-200, 500, 15)])

# 'unwrap' the angles so there is no wrap around
X1 = pd.Series(np.rad2deg(np.unwrap(np.deg2rad(Y))))
Y1 = pd.Series(np.rad2deg(np.unwrap(np.deg2rad(Y))))

# smooth the data with a moving average
# note: this is pandas 17.1, the api changed for version 18
X2 = pd.rolling_mean(X1, window=3)
Y2 = pd.rolling_mean(Y1, window=3)

# convert back to wrapped data if desired
X3 = X2 % 360
Y3 = (Y2 + 180)%360 - 180

Upvotes: 1

Related Questions