Source code for EQcorrscan_plotting

#!/usr/bin/python
"""
Utility code for most of the plots used as part of the EQcorrscan package.

Copyright 2015 Calum Chamberlain

This file is part of EQcorrscan.

    EQcorrscan is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    EQcorrscan is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with EQcorrscan.  If not, see <http://www.gnu.org/licenses/>.

"""
import numpy as np
import matplotlib.pylab as plt
[docs]def triple_plot(cccsum, trace, threshold, save=False, savefile=''): """ Main function to make a triple plot with a day-long seismogram, day-long correlation sum trace and histogram of the correlation sum to show normality :type cccsum: numpy.array :param cccsum: Array of the cross-channel cross-correlation sum :type trace: obspy.Trace :param trace: A sample trace from the same time as cccsum :type threshold: float :param threshold: Detection threshold within cccsum :type save: Bool, optional :param save: If True will svae and not plot to screen, vice-versa if False :type savefile: String, optional :param savefile: Path to save figure to, only required if save=True """ if len(cccsum) != len(trace.data): print 'cccsum is: '+str(len(cccsum))+' trace is: '+str(len(trace.data)) raise ValueError('cccsum and trace must have the same number of data points') df = trace.stats.sampling_rate npts = trace.stats.npts t = np.arange(npts, dtype=np.float32) / (df*3600) # Generate the subplot for the seismic data ax1 = plt.subplot2grid((2,5), (0,0), colspan=4) ax1.plot(t, trace.data, 'k') ax1.axis('tight') ax1.set_ylim([-15*np.mean(np.abs(trace.data)),15*np.mean(np.abs(trace.data))]) # Generate the subplot for the correlation sum data ax2 = plt.subplot2grid((2,5), (1,0), colspan=4, sharex=ax1) # Plot the threshold values ax2.plot([min(t), max(t)], [threshold, threshold], color='r', lw=1, label="Threshold") ax2.plot([min(t), max(t)], [-threshold,-threshold], color='r', lw=1) ax2.plot(t, cccsum, 'k') ax2.axis('tight') ax2.set_ylim([-1.7*threshold, 1.7*threshold]) ax2.set_xlabel("Time after %s [hr]" % trace.stats.starttime.isoformat()) # ax2.legend() # Generate a small subplot for the histogram of the cccsum data ax3 = plt.subplot2grid((2,5), (1,4), sharey=ax2) ax3.hist(cccsum, 200, normed=1, histtype='stepfilled', \ orientation='horizontal', color='black') ax3.set_ylim([-5, 5]) fig=plt.gcf() fig.suptitle(trace.id) fig.canvas.draw() if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def peaks_plot(data, starttime, samp_rate, save=False, peaks=[(0,0)], \ savefile=''): """ Simple utility code to plot the correlation peaks to check that the peak finding routine is running correctly, used in debugging for the EQcorrscan module. :type data: numpy.array :param data: Numpy array of the data within which peaks have been found :type starttime: obspy.UTCDateTime :param starttime: Start time for the data :type samp_rate: float :param samp_rate: Sampling rate of data in Hz :type save: Boolean, optional :param save: Save figure or plot to screen (False) :type peaks: List of Tuple, optional :param peaks: List of peak locations and amplitudes (loc, amp) :type savefile: String, optional :param savefile: Path to save to, only used if save=True """ npts=len(data) t = np.arange(npts, dtype=np.float32) / (samp_rate*3600) fig=plt.figure() ax1=fig.add_subplot(111) ax1.plot(t, data, 'k') ax1.scatter(peaks[0][1]/(samp_rate*3600),abs(peaks[0][0]),color='r', label='Peaks') for peak in peaks: ax1.scatter(peak[1]/(samp_rate*3600),abs(peak[0]),color='r') ax1.legend() ax1.set_xlabel("Time after %s [hr]" % starttime.isoformat()) ax1.axis('tight') fig.suptitle('Peaks') if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def cumulative_detections(dates, template_names, save=False, savefile=''): """ Simple plotting function to take a list of datetime objects and plot a cumulative detections list. Can take dates as a list of lists and will plot each list seperately, e.g. if you have dates from more than one template it will overlay them in different colours. :type dates: list of lists of datetime.datetime :param dates: Must be a list of lists of datetime.datetime objects :type template_names: list of strings :param template_names: List of the template names in order of the dates :type save: Boolean, optional :param save: Save figure or show to screen :type savefile: String, optional :param savefile: String to save to. """ # Set up a default series of parameters for lines colors=['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', \ 'firebrick', 'purple', 'darkgoldenrod', 'gray'] linestyles=['-','-.', '--', ':'] # Check that dates is a list of lists if type(dates[0]) != list: dates=[dates] i=0 j=0 k=0 plothandles=[] for template_dates in dates: template_dates.sort() counts=np.arange(0,len(template_dates)) print str(i)+' '+str(j)+' '+str(k) filename,=plt.plot(template_dates,counts, linestyles[j], \ color=colors[i], label=template_names[k],\ linewidth=3.0) k+=1 plothandles.append(filename) if i < len(colors)-1: i+=1 else: i=0 if j < len(linestyles)-1: j+=1 else: j=0 plt.xlabel('Date') plt.ylabel('Cumulative detections') plt.title('Cumulative detections for all templates') plt.legend(loc=2,prop={'size':8},ncol=2)#handles=plothandles) if save: plt.savefig(savefile) plt.close() else: plt.show() return
[docs]def threeD_gridplot(nodes, save=False, savefile=''): """ Function to plot in 3D a series of grid points. :type nodes: List of tuples :param nodes: List of tuples of the form (lat, long, depth) :type save: bool :param save: if True will save without plotting to screen, if False\ (default) will plot to screen but not save :type savefile: str :param savefile: required if save=True, path to save figure to. """ lats=[] longs=[] depths=[] for node in nodes: lats.append(float(node[0])) longs.append(float(node[1])) depths.append(float(node[2])) from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(lats, longs, depths) ax.set_ylabel("Latitude (deg)") ax.set_xlabel("Longitude (deg)") ax.set_zlabel("Depth(km)") ax.get_xaxis().get_major_formatter().set_scientific(False) ax.get_yaxis().get_major_formatter().set_scientific(False) if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def multi_event_singlechan(streams, picks, clip=10.0, pre_pick=2.0,\ freqmin=False, freqmax=False, realign=False, \ cut=(-3.0,5.0), PWS=False, title=False): """ Function to plot data from a single channel at a single station for multiple events - data will be alligned by their pick-time given in the picks :type streams: List of :class:obspy.stream :param streams: List of the streams to use, can contain more traces than\ you plan on plotting :type picks: List of :class:PICK :param picks: List of picks, one for each stream :type clip: float :param clip: Length in seconds to plot, defaults to 10.0 :type pre_pick: Float :param pre_pick: Length in seconds to extract and plot before the pick,\ defaults to 2.0 :type freqmin: float :param freqmin: Low cut for bandpass in Hz :type freqmax: float :param freqmax: High cut for bandpass in Hz :type realign: Bool :param realign: To compute best alignement based on correlation or not. :type cut: tuple: :param cut: tuple of start and end times for cut in seconds from the pick :type PWS: bool :param PWS: compute Phase Weighted Stack, if False, will compute linear stack :type title: str :param title: Plot title. :returns: Alligned and cut traces, and new picks """ import stacking, copy from core.match_filter import normxcorr2 from obspy import Stream fig, axes = plt.subplots(len(picks)+1, 1, sharex=True, figsize=(7, 12)) axes = axes.ravel() traces=[] al_traces=[] # Keep input safe plist=copy.deepcopy(picks) st_list=copy.deepcopy(streams) for i in xrange(len(plist)): if st_list[i].select(station=plist[i].station, \ channel='*'+plist[i].channel[-1]): tr=st_list[i].select(station=plist[i].station, \ channel='*'+plist[i].channel[-1])[0] else: print 'No data for '+plist[i].station+'.'+plist[i].channel continue tr.detrend('linear') if freqmin: tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax) if realign: tr_cut=tr.copy() tr_cut.trim(plist[i].time+cut[0], plist[i].time+cut[1],\ nearest_sample=False) if len(tr_cut.data)<=0.5*(cut[1]-cut[0])*tr_cut.stats.sampling_rate: print 'Not enough in the trace for '+plist[i].station+'.'+plist[i].channel print 'Suggest removing pick from sfile at time '+str(plist[i].time) else: al_traces.append(tr_cut) else: tr.trim(plist[i].time-pre_pick, plist[i].time+clip-pre_pick,\ nearest_sample=False) if len(tr.data)==0: print 'No data in the trace for '+plist[i].station+'.'+plist[i].channel print 'Suggest removing pick from sfile at time '+str(plist[i].time) continue traces.append(tr) if realign: shift_len=int(0.25*(cut[1]-cut[0])*al_traces[0].stats.sampling_rate) shifts=stacking.align_traces(al_traces, shift_len) for i in xrange(len(shifts)): print 'Shifting by '+str(shifts[i])+' seconds' plist[i].time-=shifts[i] traces[i].trim(plist[i].time-pre_pick, plist[i].time+clip-pre_pick,\ nearest_sample=False) # We now have a list of traces traces=[(trace, trace.stats.starttime.datetime) for trace in traces] traces.sort(key=lambda tup:tup[1]) traces=[trace[0] for trace in traces] # Plot the traces for i in xrange(len(traces)): tr=traces[i] y = tr.data x = np.arange(len(y)) x = x/tr.stats.sampling_rate # convert to seconds axes[i+1].plot(x, y, 'k', linewidth=1.1) # axes[i+1].set_ylabel(tr.stats.starttime.datetime.strftime('%Y/%m/%d %H:%M'),\ # rotation=0) axes[i+1].yaxis.set_ticks([]) i+=1 traces=[Stream(trace) for trace in traces] if PWS: linstack=stacking.PWS_stack(traces) else: linstack=stacking.linstack(traces) tr=linstack.select(station=picks[0].station, \ channel='*'+picks[0].channel[-1])[0] y = tr.data x = np.arange(len(y)) x = x/tr.stats.sampling_rate axes[0].plot(x, y, 'r', linewidth=2.0) axes[0].set_ylabel('Stack', rotation=0) axes[0].yaxis.set_ticks([]) for i in xrange(len(traces)): cc=normxcorr2(tr.data, traces[i][0].data) axes[i+1].set_ylabel('cc='+str(round(np.max(cc),2)), rotation=0) axes[i+1].text(0.9, 0.15, str(round(np.max(traces[i][0].data))), \ bbox=dict(facecolor='white', alpha=0.95),\ transform=axes[i+1].transAxes) axes[i+1].text(0.7, 0.85, traces[i][0].stats.starttime.datetime.strftime('%Y/%m/%d %H:%M:%S'), \ bbox=dict(facecolor='white', alpha=0.95),\ transform=axes[i+1].transAxes) axes[-1].set_xlabel('Time (s)') if title: axes[0].set_title(title) plt.subplots_adjust(hspace=0) plt.show() return traces, plist
[docs]def detection_multiplot(stream, template, times, streamcolour='k',\ templatecolour='r'): """ Function to plot the stream of data that has been detected in, with the template on top of it timed according to a list of given times, just a pretty way to show a detection! :type stream: obspy.Stream :param stream: Stream of data to be plotted as the base (black) :type template: obspy.Stream :param template: Template to be plotted on top of the base stream (red) :type times: List of datetime.datetime :param times: list of times of detections in the order of the channels in template. :type streamcolour: str :param streamcolour: String of matplotlib colour types for the stream :type templatecolour: str :param templatecolour: Colour to plot the template in. """ import datetime as dt fig, axes = plt.subplots(len(template), 1, sharex=True) axes = axes.ravel() print 'Template has '+str(len(template))+' channels' for i in xrange(len(template)): template_tr=template[i] print 'Working on: '+template_tr.stats.station+' '+\ template_tr.stats.channel image=stream.select(station=template_tr.stats.station,\ channel='*'+template_tr.stats.channel[-1]) if not image: print 'No data for '+template_tr.stats.station+' '+\ template_tr.stats.channel continue image=image.merge()[0] t_start=(times[i]-image.stats.starttime.datetime) # Gives a timedelta print t_start # Downsample if needed if image.stats.sampling_rate > 20: image.decimate(int(image.stats.sampling_rate/20)) if template_tr.stats.sampling_rate > 20: template_tr.decimate(int(template_tr.stats.sampling_rate/20)) image_times=[image.stats.starttime.datetime+dt.timedelta((j*image.stats.delta)/86400)\ for j in xrange(len(image.data))] # Give list of datetime objects t_start=t_start+image.stats.starttime.datetime template_times=[t_start+dt.timedelta((j*template_tr.stats.delta)/86400)\ for j in xrange(len(template_tr.data))] print image_times[0] print template_times[0] axes[i].plot(image_times, image.data,'k') axes[i].plot(template_times, template_tr.data,'r') axes[i].set_ylabel(template_tr.stats.station+'.'+template_tr.stats.channel) axes[len(axes)-1].set_xlabel('Time') plt.show() return
[docs]def interev_mag_sfiles(sfiles): """ Function to plot interevent-time versus magnitude for series of events. :type sfiles: List :param sfiles: List of sfiles to read from """ import Sfile_util times=[Sfile_util.readheader(sfile).time for sfile in sfiles] mags=[Sfile_util.readheader(sfile).Mag_1 for sfile in sfiles] interev_mag(times, mags)
[docs]def interev_mag(times, mags): """ Function to plot interevent times against magnitude for given times and magnitudes. :type times: list of datetime :param times: list of the detection times, must be sorted the same as mags :type mags: list of float :param mags: list of magnitudes """ l = [(times[i], mags[i]) for i in xrange(len(times))] l.sort(key=lambda tup:tup[0]) times=[x[0] for x in l] mags=[x[1] for x in l] # Make two subplots next to each other of time before and time after fig, axes = plt.subplots(1,2, sharey=True) axes = axes.ravel() pre_times=[] post_times=[] for i in xrange(len(times)): if i > 0: pre_times.append((times[i]-times[i-1])/60) if i < len(times)-1: post_times.append((times[i+1]-times[i])/60) axes[0].scatter(pre_times, mags[1:]) axes[0].set_title('Pre-event times') axes[0].set_ylabel('Magnitude') axes[0].set_xlabel('Time (Minutes)') # axes[0].set_xlim([0, max(pre_times)+(0.1*(max(pre_times)-min(pre_times)))]) plt.setp(axes[0].xaxis.get_majorticklabels(), rotation=30 ) axes[1].scatter(pre_times, mags[:-1]) axes[1].set_title('Post-event times') axes[1].set_xlabel('Time (Minutes)') # axes[1].set_xlim([0, max(post_times)+(0.1*(max(post_times)-min(post_times)))]) plt.setp(axes[1].xaxis.get_majorticklabels(), rotation=30 ) plt.show()
[docs]def threeD_seismplot(stations, nodes): """ Function to plot seismicity and stations in a 3D, movable, zoomable space using matplotlibs Axes3D package. :type stations: list of tuple :param stations: list of one tuple per station of (lat, long, elevation), with up positive :type nodes: list of tuple :param nodes: list of one tuple per event of (lat, long, depth) with down positive """ stalats=[] stalongs=[] staelevs=[] evlats=[] evlongs=[] evdepths=[] for station in stations: stalats+=[station[0]] stalongs+=[station[1]] staelevs+=[station[2]] for node in nodes: evlats+=[node[0]] evlongs+=[node[1]] evdepths+=[-1*node[2]] from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(evlats, evlongs, evdepths, marker="x", c="k") ax.scatter(stalats, stalongs, staelevs, marker="v", c="r") ax.set_ylabel("Latitude (deg)") ax.set_xlabel("Longitude (deg)") ax.set_zlabel("Depth(km)") ax.get_xaxis().get_major_formatter().set_scientific(False) ax.get_yaxis().get_major_formatter().set_scientific(False) plt.show() return
[docs]def Noise_plotting(station, channel, PAZ, datasource): """ Function to make use of obspy's PPSD functionality to read in data from a single station and the poles-and-zeros for that station before plotting the PPSD for this station. See McNamara(2004) for more details. :type station: String :param station: Station name as it is in the filenames in the database :type channel: String :param channel: Channel name as it is in the filenames in the database :type PAZ: Dict :param PAZ: Must contain, Poles, Zeros, Sensitivity, Gain :type Poles: List of Complex :type Zeros: List of Complex :type Sensitivity: Float :type Gain: Float :type datasource: String :param datasource: The directory in which data can be found, can contain wildcards. :returns: PPSD object """ from obspy.signal import PPSD from obspy import read as obsread import glob stafiles=glob.glob(datasource+'/*'+station+'*'+channel+'*') stafiles.sort() # Initialize PPSD st=obsread(stafiles[0]) ppsd = PPSD(st[0].stats, PAZ) for stafile in stafiles[1:]: print 'Adding waveform from: '+stafile st=obsread(stafile) # Add after read to conserve memory ppsd.add(st) # Plot the PPSD ppsd.plot() return ppsd
[docs]def pretty_template_plot(template, size=(18.5, 10.5), save=False, title=False,\ background=False): """ Function to make a pretty plot of a single template, designed to work better than the default obspy plotting routine for short data lengths. :type template: :class: obspy.Stream :param template: Template stream to plot :type size: tuple :param size: tuple of plot size :type save: Boolean :param save: if False will plot to screen, if True will save :type title: Boolean :param title: String if set will be the plot title :type backrgound: :class: obspy.stream :param background: Stream to plot the template within. """ fig, axes = plt.subplots(len(template), 1, sharex=True, figsize=size) if len(template) > 1: axes = axes.ravel() else: return if not background: mintime=template.sort(['starttime'])[0].stats.starttime else: mintime=background.sort(['starttime'])[0].stats.starttime i=0 template.sort(['network','station', 'starttime']) for tr in template: delay=tr.stats.starttime-mintime delay*=tr.stats.sampling_rate y=tr.data x=np.arange(len(y)) x+=delay x=x/tr.stats.sampling_rate # x=np.arange(delay, (len(y)*tr.stats.sampling_rate)+delay,\ # tr.stats.sampling_rate) if background: btr=background.select(station=tr.stats.station, \ channel=tr.stats.channel)[0] bdelay=btr.stats.starttime-mintime bdelay*=btr.stats.sampling_rate by=btr.data bx=np.arange(len(by)) bx+=bdelay bx=bx/btr.stats.sampling_rate axes[i].plot(bx,by,'k',linewidth=1) axes[i].plot(x, y, 'r', linewidth=1.1) else: axes[i].plot(x, y, 'k', linewidth=1.1) print tr.stats.station+' '+str(len(x))+' '+str(len(y)) axes[i].set_ylabel(tr.stats.station+'.'+tr.stats.channel, rotation=0) axes[i].yaxis.set_ticks([]) i+=1 axes[i-1].set_xlabel('Time (s) from start of template') plt.subplots_adjust(hspace=0) if title: axes[0].set_title(title) if not save: plt.show() plt.close() else: plt.savefig(save)
[docs]def NR_plot(stream, NR_stream, detections, false_detections=False,\ size=(18.5,10), save=False, title=False): """ Function to plot the Network response alongside the streams used - highlights detection times in the network response :type stream: :class: obspy.Stream :param stream: Stream to plot :type NR_stream: :class: obspy.Stream :param NR_stream: Stream for the network response :type detections: List of datetime objects :param detections: List of the detections :type false_detections: List of datetime :param false_detections: Either False (default) or list of false detection\ times :type size: tuple :param size: Size of figure, default is (18.5,10) :type save: bool :param save: Save figure or plot to screen, if not False, must be string of\ save path :type title: str :param title: String for the title of the plot, set to False """ import datetime as dt import matplotlib.dates as mdates fig, axes = plt.subplots(len(stream)+1, 1, sharex=True, figsize=size) if len(stream) > 1: axes = axes.ravel() else: return mintime=stream.sort(['starttime'])[0].stats.starttime i=0 stream.sort(['network','station', 'starttime']) for tr in stream: delay=tr.stats.starttime-mintime delay*=tr.stats.sampling_rate y=tr.data x=[tr.stats.starttime + dt.timedelta(seconds=s/tr.stats.sampling_rate)\ for s in xrange(len(y))] x=mdates.date2num(x) axes[i].plot(x, y, 'k', linewidth=1.1) axes[i].set_ylabel(tr.stats.station+'.'+tr.stats.channel, rotation=0) axes[i].yaxis.set_ticks([]) axes[i].set_xlim(x[0], x[-1]) i+=1 # Plot the network response tr=NR_stream[0] delay=tr.stats.starttime-mintime delay*=tr.stats.sampling_rate y=tr.data x=[tr.stats.starttime + dt.timedelta(seconds=s/tr.stats.sampling_rate)\ for s in xrange(len(y))] x=mdates.date2num(x) axes[i].plot(x, y, 'k', linewidth=1.1) axes[i].set_ylabel(tr.stats.station+'.'+tr.stats.channel, rotation=0) axes[i].yaxis.set_ticks([]) axes[-1].set_xlabel('Time') axes[-1].set_xlim(x[0], x[-1]) # Plot the detections! ymin, ymax = axes[-1].get_ylim() if false_detections: for detection in false_detections: xd=mdates.date2num(detection) axes[-1].plot((xd, xd), (ymin, ymax), 'k--', linewidth=0.5, alpha=0.5) for detection in detections: xd=mdates.date2num(detection) axes[-1].plot((xd, xd), (ymin, ymax), 'r--', linewidth=0.75) # Set formatters for x-labels mins=mdates.MinuteLocator() if (tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() >= 10800\ and (tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() <= 25200: hours=mdates.MinuteLocator(byminute=[0,15,30,45]) elif(tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() <= 1200: hours=mdates.MinuteLocator(byminute=range(0,60,2)) elif (tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() > 25200\ and (tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() <= 172800: hours=mdates.HourLocator(byhour=range(0,24,3)) elif (tr.stats.endtime.datetime-tr.stats.starttime.datetime).total_seconds() > 172800: hours=mdates.DayLocator() else: hours=mdates.MinuteLocator(byminute=range(0,60,5)) hrFMT=mdates.DateFormatter('%Y/%m/%d %H:%M:%S') axes[-1].xaxis.set_major_locator(hours) axes[-1].xaxis.set_major_formatter(hrFMT) axes[-1].xaxis.set_minor_locator(mins) plt.gcf().autofmt_xdate() axes[-1].fmt_xdata=mdates.DateFormatter('%Y/%m/%d %H:%M:%S') plt.subplots_adjust(hspace=0) if title: axes[0].set_title(title) if not save: plt.show() plt.close() else: plt.savefig(save) return
[docs]def SVD_plot(SVStreams, SValues, stachans, title=False): """ Function to plot the singular vectors from the clustering routines, one plot for each stachan :type SVStreams: List of :class:Obspy.Stream :param SVStreams: See clustering.SVD_2_Stream - will assume these are\ ordered by power, e.g. first singular vector in the first stream :type SValues: List of float :param SValues: List of the singular values corresponding to the SVStreams :type stachans: List :param stachans: List of station.channel """ for stachan in stachans: print stachan plot_traces=[SVStream.select(station=stachan.split('.')[0],\ channel=stachan.split('.')[1])[0]\ for SVStream in SVStreams] fig, axes = plt.subplots(len(plot_traces), 1, sharex=True) axes = axes.ravel() i=0 for tr in plot_traces: y = tr.data x = np.arange(len(y)) x = x*tr.stats.delta axes[i].plot(x,y,'k', linewidth=1.1) axes[i].set_ylabel('SV '+str(i+1)+'='+\ str(round(SValues[i]/len(SValues),2)), rotation=0) axes[i].yaxis.set_ticks([]) print i i+=1 axes[-1].set_xlabel('Time (s)') plt.subplots_adjust(hspace=0) if title: axes[0].set_title(title) else: axes[0].set_title(stachan) plt.show() return