Intro To Audification and Sonification

(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.


Background

Audification

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.

Sonification

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...


Let's Dive In

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.

Financial Data Query


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]:
<matplotlib.axes._subplots.AxesSubplot at 0x10a997bd0>

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]:
<matplotlib.axes._subplots.AxesSubplot at 0x10b547490>

In [5]:
type(returns)


Out[5]:
pandas.core.frame.DataFrame


Audification

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.

Adj_Close Audification


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]:
array([   16.66,    16.85,    16.93, ...,  2099.68,  2097.45,  2110.3 ])

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]:
array([ 0.00789461,  0.00798465,  0.00802256, ...,  0.99496754,
        0.99391082,  1.        ])

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]:
[<matplotlib.lines.Line2D at 0x10b8825d0>]

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]:


Returns Audification


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]:
array([ 0.        ,  0.01134002,  0.00473654, ..., -0.00031428,
       -0.00106263,  0.0061078 ])

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]:
array([ 0.        ,  0.04952034,  0.02068383, ..., -0.00137244,
       -0.00464037,  0.02667192])

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]:
[<matplotlib.lines.Line2D at 0x10baffad0>]

In [13]:
from IPython.display import Audio

audio_returns = Audio(data=returns_numpy, rate=len(returns_numpy)/4)
audio_returns


Out[13]:


Analysis

You can pick up on the large fluctuations in return values and it would be interesting to line them up with the timeline of the market prices to better understand how they fit together.

  • need to add time values so I can plot both on same axis

In [39]:
plt.plot(adj_close_numpy)
plt.hold(True)
plt.plot(returns_numpy-0.25)


Out[39]:
[<matplotlib.lines.Line2D at 0x11c094250>]

In [40]:
audio_adj_close


Out[40]:

In [41]:
audio_returns


Out[41]:

In [14]:


Sonification

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]:
array([  250.        ,   250.06806328,   250.0967215 , ...,   996.19562102,
         995.39677308,  1000.        ])

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]:
[<matplotlib.lines.Line2D at 0x10bb5c210>]

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]:
220500

In [20]:
plt.plot(adj_close_numpy_resampled)


Out[20]:
[<matplotlib.lines.Line2D at 0x10edb68d0>]

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]:
array([ 0.        ,  0.01549135,  0.02722632, ..., -0.46874109,
        0.2138184 ,  0.41408512])

In [22]:
len(y)


Out[22]:
220500

In [23]:
plt.plot(y)


Out[23]:
[<matplotlib.lines.Line2D at 0x10c2b0790>]

In [24]:
audio = Audio(data=y, rate=framerate)
audio


Out[24]:

In [25]:
specgram(y, Fs=framerate)


Out[25]:
(array([[  3.58155784e-05,   3.31116061e-06,   2.99757329e-05, ...,
           3.10737153e-06,   1.04484736e-07,   2.59803411e-08],
        [  3.41662482e-04,   3.78973524e-04,   3.48288495e-04, ...,
           2.49267841e-06,   1.72029618e-06,   9.23199357e-07],
        [  3.26635545e-04,   3.14523224e-04,   3.23633542e-04, ...,
           1.37019065e-06,   2.39359530e-07,   9.95686263e-07],
        ..., 
        [  6.00481369e-20,   8.98586324e-20,   9.44127473e-20, ...,
           2.25843717e-06,   7.80245097e-06,   4.03299621e-06],
        [  5.59715733e-20,   8.13755493e-20,   5.64943647e-20, ...,
           6.71710463e-06,   8.96766306e-06,   5.46088103e-06],
        [  2.73077329e-20,   3.93044041e-20,   2.19417669e-20, ...,
           1.54106122e-07,   2.36057044e-07,   1.52802030e-06]]),
 array([     0.      ,    172.265625,    344.53125 ,    516.796875,
           689.0625  ,    861.328125,   1033.59375 ,   1205.859375,
          1378.125   ,   1550.390625,   1722.65625 ,   1894.921875,
          2067.1875  ,   2239.453125,   2411.71875 ,   2583.984375,
          2756.25    ,   2928.515625,   3100.78125 ,   3273.046875,
          3445.3125  ,   3617.578125,   3789.84375 ,   3962.109375,
          4134.375   ,   4306.640625,   4478.90625 ,   4651.171875,
          4823.4375  ,   4995.703125,   5167.96875 ,   5340.234375,
          5512.5     ,   5684.765625,   5857.03125 ,   6029.296875,
          6201.5625  ,   6373.828125,   6546.09375 ,   6718.359375,
          6890.625   ,   7062.890625,   7235.15625 ,   7407.421875,
          7579.6875  ,   7751.953125,   7924.21875 ,   8096.484375,
          8268.75    ,   8441.015625,   8613.28125 ,   8785.546875,
          8957.8125  ,   9130.078125,   9302.34375 ,   9474.609375,
          9646.875   ,   9819.140625,   9991.40625 ,  10163.671875,
         10335.9375  ,  10508.203125,  10680.46875 ,  10852.734375,
         11025.      ,  11197.265625,  11369.53125 ,  11541.796875,
         11714.0625  ,  11886.328125,  12058.59375 ,  12230.859375,
         12403.125   ,  12575.390625,  12747.65625 ,  12919.921875,
         13092.1875  ,  13264.453125,  13436.71875 ,  13608.984375,
         13781.25    ,  13953.515625,  14125.78125 ,  14298.046875,
         14470.3125  ,  14642.578125,  14814.84375 ,  14987.109375,
         15159.375   ,  15331.640625,  15503.90625 ,  15676.171875,
         15848.4375  ,  16020.703125,  16192.96875 ,  16365.234375,
         16537.5     ,  16709.765625,  16882.03125 ,  17054.296875,
         17226.5625  ,  17398.828125,  17571.09375 ,  17743.359375,
         17915.625   ,  18087.890625,  18260.15625 ,  18432.421875,
         18604.6875  ,  18776.953125,  18949.21875 ,  19121.484375,
         19293.75    ,  19466.015625,  19638.28125 ,  19810.546875,
         19982.8125  ,  20155.078125,  20327.34375 ,  20499.609375,
         20671.875   ,  20844.140625,  21016.40625 ,  21188.671875,
         21360.9375  ,  21533.203125,  21705.46875 ,  21877.734375,  22050.      ]),
 array([  2.90249433e-03,   5.80498866e-03,   8.70748299e-03, ...,
          4.98938776e+00,   4.99229025e+00,   4.99519274e+00]),
 <matplotlib.image.AxesImage at 0x10a897490>)

In [25]: