(Work In Progress)
Welcome! This is my first introduction to a variety of tools and ideas. I first learned about the concepts of Audification and Sonification from a great friend's work while I was in college and the ideas have been inspiring me recently so I decided to dive in and do a bit of exploring. Below will be an example of what I have found and more information about some of the background for why all of this may be worthwhile.
The examples I give will be basic and the field has been around for a number of years, but is not nearly as developed as visual aids for data analysis. The reasons to consider using audification and sonification is that you have an immensely powerful auditory system capable of great feats in pattern recognition. This introductory post won't go into much of how it all works or even great examples of where your auditory system may identify components that a simple visual aid does not easily distinguish, but look forward to potential future posts on those topics.
From Wikipedia: Audification is an auditory display technique for representing a sequence of data values as sound. An audification does this by interpreting the data sequence, usually a time series, as an audio waveform: input data is mapped to sound pressure levels.
In short, it is just a way to listen directly to the data without applying any further processing or modification. It works particularly well with large data sets that may translate well to sime series information.
From Wikipedia: Sonification is the use of non-speech audio to convey information or perceptualize data.[1] Auditory perception has advantages in temporal, spatial, amplitude, and frequency resolution that open possibilities as an alternative or complement to visualization techniques.
Potentially less clear when reading this definition, but involves mapping the data to a sonic parameter. In our case, we will map data to the frequency of a sine wave, but it could be mapped to amplitude or the frequency of a filter, etc...
We will dive into a basic example taking the S&P500 index of the past 115 years and listening to it in this variety of formats.
In [29]:
from pylab import *
from pandas.io.data import *
from pandas import *
from pandas.tools.plotting import *
import matplotlib.pyplot as plt
%matplotlib inline
# 1. Data Gathering
# - stock symbols to download
symbols = ['^GSPC']
# Download data from Yahoo as pandas panel object
stock_data = get_data_yahoo(symbols, start='1/1/1900')
In [28]:
# 2. Extract and plot data
# Extract and Plot Adjusting Closing Prices
# - pull out adjusted close prices as pandas DataFrame object
adj_close = stock_data['Adj Close']
adj_close.plot(title='Daily Adjusted Closing Prices')
Out[28]:
In [4]:
# Extract and Plot Daily Returns
# - calculate continuously compounded returns
returns = log(adj_close/adj_close.shift(1))
returns.plot(title='Daily Returns')
Out[4]:
In [5]:
type(returns)
Out[5]:
This is the process of listening to the data directly, the only processing we do will be conversion between different formats (structure and scale). The end result will still be a isomorphic representation of the data we started with.
In order to convert this audio to a wave file, we first need to convert it from a pandas dataframe to a numpy array in the format that the IPython.display.Audio class will expect.
In [6]:
# Transpose and convert to numpy array
adj_close_numpy = adj_close.T.as_matrix()
# slice array
adj_close_numpy = adj_close_numpy[0,:]
adj_close_numpy = np.nan_to_num(adj_close_numpy)
adj_close_numpy
Out[6]:
In [7]:
# Normalize array
# - convert the scale of the data to range from [-1,1]. To do so,
# we will divide the array by the max value of the array.
# find max
myMax = np.max(adj_close_numpy)
# find min
myMin = np.min(adj_close_numpy)
# if negative, convert to positive for comparison
if myMin < 0:
myMin = myMin * -1
# save larger of abs(max and min)
if myMax >= myMin:
absMax = myMax
else:
absMax = myMin
# divide numpy array by max value
adj_close_numpy = adj_close_numpy / absMax
adj_close_numpy
Out[7]:
In [8]:
# Let us plot it to be sure that it is accurate to what we saw
# above and we have modified the y-axis scale to a max value of 1.
plt.plot(adj_close_numpy)
Out[8]:
In [9]:
from IPython.display import Audio
audio_adj_close = Audio(data=adj_close_numpy, rate=len(adj_close_numpy)/2)
audio_adj_close
Out[9]:
In [10]:
# Transpose and convert to numpy array
returns_numpy = returns.T.as_matrix()
# slice array
returns_numpy = returns_numpy[0,:]
returns_numpy = np.nan_to_num(returns_numpy)
returns_numpy
Out[10]:
In [11]:
# Normalize array
# - convert the scale of the data to range from [-1,1]. To do so,
# we will divide the array by the max value of the array.
# find max
myMax = np.max(returns_numpy)
# find min
myMin = np.min(returns_numpy)
# if negative, convert to positive for comparison
if myMin < 0:
myMin = myMin * -1
# save larger of abs(max and min)
if myMax >= myMin:
absMax = myMax
else:
absMax = myMin
# divide numpy array by max value
returns_numpy = returns_numpy / absMax
returns_numpy
Out[11]:
In [12]:
# Let us plot it to be sure that it is accurate to what we saw
# above and we have modified the y-axis scale to a max value of 1.
plt.plot(returns_numpy)
Out[12]:
In [13]:
from IPython.display import Audio
audio_returns = Audio(data=returns_numpy, rate=len(returns_numpy)/4)
audio_returns
Out[13]:
In [39]:
plt.plot(adj_close_numpy)
plt.hold(True)
plt.plot(returns_numpy-0.25)
Out[39]:
In [40]:
audio_adj_close
Out[40]:
In [41]:
audio_returns
Out[41]:
In [14]:
This is the process of listening to the data after it has been mapped to a parameter of an audio generator or audio filter. We will utilize the files we had already processed from DataFrames into normalized NumPy arrays to begin.
Our goal is to convert these two arrays to the frequency of a sine wave.
In [15]:
def smooth(x,window_len=250,window='flat'):
"""smooth the data using a window with requested size.
This method is based on the convolution of a scaled window with the signal.
The signal is prepared by introducing reflected copies of the signal
(with the window size) in both ends so that transient parts are minimized
in the begining and end part of the output signal.
input:
x: the input signal
window_len: the dimension of the smoothing window; should be an odd integer
window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
flat window will produce a moving average smoothing.
output:
the smoothed signal
example:
t=linspace(-2,2,0.1)
x=sin(t)+randn(len(t))*0.1
y=smooth(x)
see also:
numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
scipy.signal.lfilter
TODO: the window parameter could be the window itself if an array instead of a string
NOTE: length(output) != length(input), to correct this: return y[(window_len/2-1):-(window_len/2)] instead of just y.
"""
if x.ndim != 1:
raise ValueError, "smooth only accepts 1 dimension arrays."
if x.size < window_len:
raise ValueError, "Input vector needs to be bigger than window size."
if window_len<3:
return x
if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
raise ValueError, "Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'"
s=np.r_[x[window_len-1:0:-1],x,x[-1:-window_len:-1]]
#print(len(s))
if window == 'flat': #moving average
w=np.ones(window_len,'d')
else:
w=eval('numpy.'+window+'(window_len)')
y=np.convolve(w/w.sum(),s,mode='valid')
return y
In [16]:
# Rescale data to fall between desired auditory range
# define current and new range
cur_max = np.max(adj_close_numpy)
cur_min = np.min(adj_close_numpy)
n_max = 1000
n_min = 250
# define scaled array over length of data
adj_close_numpy_scaled = np.zeros(len(adj_close_numpy))
# modify all values
for i in range(0, len(adj_close_numpy)):
adj_close_numpy_scaled[i] = (n_max - n_min) / (cur_max - cur_min) * (adj_close_numpy[i] - cur_min) + n_min
adj_close_numpy_scaled
Out[16]:
In [17]:
# Let us plot it to be sure that it is accurate to what we saw
# above and we have modified the y-axis scale to a range [250, 1000].
plt.plot(adj_close_numpy_scaled)
Out[17]:
In [18]:
# set duration of desired playback (seconds)
duration = 5
# set framerate based on number of points
framerate = 44100
# create numpy array for all time values
t = np.linspace(0, duration, framerate*duration)
In [19]:
adj_close_numpy_smoothed = smooth(adj_close_numpy_scaled)
from scipy import signal
adj_close_numpy_resampled = signal.resample(adj_close_numpy_smoothed, framerate*duration)
len(adj_close_numpy_resampled)
Out[19]:
In [20]:
plt.plot(adj_close_numpy_resampled)
Out[20]:
In [21]:
# define output array
y = np.zeros(len(adj_close_numpy_resampled))
y = 0.5*np.sin(2*np.pi*adj_close_numpy_resampled*t)
y
Out[21]:
In [22]:
len(y)
Out[22]:
In [23]:
plt.plot(y)
Out[23]:
In [24]:
audio = Audio(data=y, rate=framerate)
audio
Out[24]:
In [25]:
specgram(y, Fs=framerate)
Out[25]:
In [25]: