views:

43

answers:

1

If I wanted to make a combined image like the one shown below (original source here), could you point me to the matplotlib objects do I need to assemble? I've been trying to work with AxesImage objects and I've also downloaded SciKits Timeseries - but do I need this, or can is it as easy to use strptime, mktime, and strftime from the time module and roll my own custom axes? Thanks ~

alt text

+2  A: 

You shouldn't need any custom axes. The Timeseries Scikit is great, but you don't need it at all to work with dates in matplotlib...

You'll probably want to use the various functions in matplotlib.dates, plot_date to plot your values, imshow (and/or pcolor in some cases) to plot your specgrams of various sorts, and matplotlib.mlab.specgram to compute them.

For your subplots, you'll want to use the sharex kwarg when creating them, so that they all share the same x-axis. To disable the x-axis labels on some axes when using sharing an x-axis between the plots, you'll need to use something like matplotlib.pyplot.setp(ax1.get_xticklabels(), visible=False). (It's a slightly crude hack, but it's the only way to only display x-axis labels on the bottom subplot when sharing the same x-axis between all subplots) To adjust the spacing between subplots, see subplots_adjust.

Hope all that makes some sense... I'll add a quick example of using all of that when I have time later today...

Edit: So here's a rough example of the idea. Some things (e.g. multicolored axis labels) shown in your example are rather difficult to do in matplotlib. (Not impossible, but I've skipped them here...)

import datetime

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

from matplotlib import mlab

from mpl_toolkits.axes_grid1 import make_axes_locatable

def main():
    #-- Make a series of dates
    start = datetime.datetime(2010,9,15,8,0)
    end = datetime.datetime(2010,9,15,18,0)
    delta = datetime.timedelta(seconds=1)

    # Note: "time" is now an array of floats, where 1.0 corresponds
    # to one day, and 0.0 corresponds to 1900 (I think...)
    # It's _not_ an array of datetime objects!
    time = mpl.dates.drange(start, end, delta)

    num = time.size

    #-- Generate some data
    x = brownian_noise(num) 
    y = brownian_noise(num)
    z = brownian_noise(num)

    plot(x, y, z, time)
    plt.show()

def plot(x, y, z, time):
    fig = plt.figure()

    #-- Panel 1
    ax1 = fig.add_subplot(311)
    im, cbar = specgram(x, time, ax1, fig)
    ax1.set_ylabel('X Freq. (Hz)')
    ax1.set_title('Fake Analysis of Something')

    #-- Panel 2
    ax2 = fig.add_subplot(312, sharex=ax1)
    im, cbar = specgram(y, time, ax2, fig)
    ax2.set_ylabel('Y Freq. (Hz)')

    #-- Panel 3
    ax3 = fig.add_subplot(313, sharex=ax1)
    # Plot the 3 source datasets
    xline = ax3.plot_date(time, x, 'r-')
    yline = ax3.plot_date(time, y, 'b-')
    zline = ax3.plot_date(time, z, 'g-')
    ax3.set_ylabel(r'Units $(\mu \phi)$')

    # Make an invisible spacer...
    cax = make_legend_axes(ax3)
    plt.setp(cax, visible=False)

    # Make a legend
    ax3.legend((xline, yline, zline), ('X', 'Y', 'Z'), loc='center left', 
            bbox_to_anchor=(1.0, 0.5), frameon=False)

    # Set the labels to be rotated at 20 deg and aligned left to use less space
    plt.setp(ax3.get_xticklabels(), rotation=-20, horizontalalignment='left')

    # Remove space between subplots
    plt.subplots_adjust(hspace=0.0)

def specgram(x, time, ax, fig):
    """Make and plot a log-scaled spectrogram"""
    dt = np.diff(time)[0] # In days...
    fs = dt * (3600 * 24) # Samples per second

    spec_img, freq, _ = mlab.specgram(x, Fs=fs, noverlap=200)
    t = np.linspace(time.min(), time.max(), spec_img.shape[1])

    # Log scaling for amplitude values
    spec_img = np.log10(spec_img)

    # Log scaling for frequency values (y-axis)
    ax.set_yscale('log')

    # Plot amplitudes
    im = ax.pcolormesh(t, freq, spec_img)

    # Add the colorbar in a seperate axis
    cax = make_legend_axes(ax)
    cbar = fig.colorbar(im, cax=cax, format=r'$10^{%0.1f}$')
    cbar.set_label('Amplitude', rotation=-90)

    ax.set_ylim([freq[1], freq.max()])

    # Hide x-axis tick labels
    plt.setp(ax.get_xticklabels(), visible=False)

    return im, cbar

def make_legend_axes(ax):
    divider = make_axes_locatable(ax)
    legend_ax = divider.append_axes('right', 0.4, pad=0.2)
    return legend_ax

def brownian_noise(num):
    x = np.random.random(num) - 0.5
    x = np.cumsum(x)
    return x


if __name__ == '__main__':
    main()

alt text

Joe Kington
Thanks very helpful! I'll have to start looking this evening also. If example code would not be too much to ask, that would also be great, but lots of help already!
Stephen
OMG this is so awesome! Much, much appreciated - I am sorry that I can only upvote you once!
Stephen
It's really interesting how different the code structure is from matlab - handle graphics are object-oriented but not quite in this way.
Stephen
@Stephen - Yeah, they're similar, but different. Matplotlib's pyplot interface is actually one of the less "pythonic" python libraries because of this... It can be a bit of an odd hybrid at times! Regardless, once you get used to it, I find it much easier to write reusable plotting functions in python with matplotlib than in Matlab. Good luck!
Joe Kington