Tutorial: Lots of Plots

This tutorial assumes that you already know how to use Python to read LIGO data files. If you don't, you may want to take a look at the Introductory Tutorial before you go any further.

This tutorial will show you how to make a few common plots with time series data, including a Fourier domian representation, an amplitude spectral density, and a spectrogram.

All of the plots described in this tutorial can be seen in this figure. You can also download the entire script described in this tutorial.

Create a file called lotsofplots.py and start with the following code:

import numpy as np
import h5py
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import readligo as rl

Notice that ReadLIGO just refers to the python library described in step 4, which read data from a LIGO data file. Of course, you can also write your own code for this. We also added the mlab module from matplotlib, which includes a function to calculate a power spectral density (PSD).

To start, we'll read the strain data and data quality information from the LIGO data file that you downloaded in step 1 of the introductory tutorial:

fileName = 'fileName = 'H-H1_LOSC_4_V1-815411200-4096.hdf5'
strain, time, channel_dict = rl.loaddata(fileName)
ts = time[1] - time[0] #-- Time between samples
fs = int(1.0 / ts)          #-- Sampling frequency

To make some example plots, let's pick a segment of "good data", and then work with just the first 16 seconds of data in the segment:

segList = rl.dq_channel_to_seglist(channel_dict['DEFAULT'], fs)
length = 16  # seconds
strain_seg = strain[segList[0]][0:(length*fs)]
time_seg = time[segList[0]][0:(length*fs)]

In the example, we'll use pyplot.suplot so that all of the plots will be on one figure. You may prefer to use pyplot.figure() instead to make separate figures. To set up a grid of 6 figures, with a little extra space for axis labels:

fig = plt.figure(figsize=(10,10))
fig.subplots_adjust(wspace=0.3, hspace=0.3)
plt.subplot(321)

Plot a Time Series

You can plot the time series data like this:

plt.plot(time_seg - time_seg[0], strain_seg)
plt.xlabel('Time since GPS ' + str(time_seg[0]))
plt.ylabel('Strain')

If you don't see the plot, try the command plt.show().

Fourier Transform (FFT)

Next, let's try plotting the data in the Fourier domain, using a Fast Fourier Transform (FFT). Because GWOSC data has a great deal of excess noise at low frequencies, spectral leakage is often an issue. To mitigate this, we'll apply a Blackman Window before we take the FFT:

window = np.blackman(strain_seg.size)
windowed_strain = strain_seg*window
freq_domain = np.fft.rfft(windowed_strain) / fs
freq = np.fft.rfftfreq(len(windowed_strain))*fs

The vector returned in freq_domain is complex. Let's just plot the magnitude of this vector:

plt.subplot(322)
plt.loglog( freq, abs(freq_domain))
plt.axis([10, fs/2.0, 1e-24, 1e-18])
plt.grid('on')
plt.xlabel('Freq (Hz)')
plt.ylabel('Strain / Hz')

Power Spectral Density and Amplitude Spectral Density

Plotting in the Fourier domain gives us an idea of the frequency content of the data. Another way to visualize the frequency content of the data is to plot the power spectral density:

Pxx, freqs = mlab.psd(strain_seg, Fs = fs, NFFT=fs)
plt.subplot(323)
plt.loglog(freqs, Pxx)
plt.axis([10, 2000, 1e-46, 1e-36])
plt.grid('on')
plt.ylabel('PSD')
plt.xlabel('Freq (Hz)')

You can also plot an amplitude spectra density the same way, just by taking the square root, np.sqrt(), of Pxx.

Spectrograms

A spectrogram shows the power spectral density of a signal in a series of time bins. Pyplot has a convienient function for making spectrograms:

NFFT = 1024
window = np.blackman(NFFT)
plt.subplot(325)
spec_power, freqs, bins, im = plt.specgram(strain_seg, NFFT=NFFT, Fs=fs, 
                                    window=window)

Mainly, this spectrogram shows what we already knew from the PSD: there is a lot more power at very low frequencies than high frequencies. If we are looking for time variation in the data, it can be helpful to normalize each frequency bin by the typical power at that frequency. We can do this, and then re-plot the spectrogram, using the colormesh function:

med_power = np.zeros(freqs.shape)
norm_spec_power = np.zeros(spec_power.shape)
index = 0
for row in spec_power:
    med_power[index] = np.median(row)
    norm_spec_power[index] = row / med_power[index]
    index += 1

ax = plt.subplot(326)
ax.pcolormesh(bins, freqs, np.log10(norm_spec_power))
You can see all of these plots in this figure. You can also download the entire script described in this tutorial.