2010-10-12 36 views
22

Actualmente estoy usando matplotlib para trazar una medición contra 2 o 3 otras medidas (a veces categóricas) en el eje x. Actualmente, estoy agrupando los datos en el eje x en tuplas y ordenándolos antes de trazar ... el resultado se parece a la imagen de la izquierda a continuación. Lo que me gustaría hacer es trazar los datos con múltiples ejes x como puede ver en la imagen de la derecha. La agrupación de las etiquetas del "eje x de tratamiento" sería la guinda del pastel.¿Cómo grafico múltiples ejes X o Y en matplotlib?

alt text

Respuesta

19

En primer lugar, pregunta cool! Definitivamente es posible con matplotlib> = 1.0.0. (La nueva funcionalidad de espinas lo permite)

Se requiere un poco de vudú, aunque ... Mi ejemplo es lejos de ser perfecto, pero espero que tiene cierto sentido:

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

def main(): 
    #-- Generate some data ---------------------------------------------------- 
    nx = 10 
    x = np.linspace(0, 2*np.pi, 10) 
    y = 2 * np.sin(x) 

    groups = [('GroupA', (x[0], x[nx//3])), 
       ('GroupB', (x[-2*nx//3], x[2*nx//3])), 
       ('GroupC', (x[-nx//3], x[-1]))] 

    #-- Plot the results ------------------------------------------------------ 
    fig = plt.figure() 
    ax = fig.add_subplot(111) 

    # Give ourselves a bit more room at the bottom 
    plt.subplots_adjust(bottom=0.2) 

    ax.plot(x,y, 'k^') 

    # Drop the bottom spine by 40 pts 
    ax.spines['bottom'].set_position(('outward', 40)) 

    # Make a second bottom spine in the position of the original bottom spine 
    make_second_bottom_spine(label='Treatment') 

    # Annotate the groups 
    for name, xspan in groups: 
     annotate_group(name, xspan) 

    plt.xlabel('Dose') 
    plt.ylabel('Response') 
    plt.title('Experimental Data') 

    plt.show() 


def annotate_group(name, xspan, ax=None): 
    """Annotates a span of the x-axis""" 
    def annotate(ax, name, left, right, y, pad): 
     arrow = ax.annotate(name, 
       xy=(left, y), xycoords='data', 
       xytext=(right, y-pad), textcoords='data', 
       annotation_clip=False, verticalalignment='top', 
       horizontalalignment='center', linespacing=2.0, 
       arrowprops=dict(arrowstyle='-', shrinkA=0, shrinkB=0, 
         connectionstyle='angle,angleB=90,angleA=0,rad=5') 
       ) 
     return arrow 
    if ax is None: 
     ax = plt.gca() 
    ymin = ax.get_ylim()[0] 
    ypad = 0.01 * np.ptp(ax.get_ylim()) 
    xcenter = np.mean(xspan) 
    left_arrow = annotate(ax, name, xspan[0], xcenter, ymin, ypad) 
    right_arrow = annotate(ax, name, xspan[1], xcenter, ymin, ypad) 
    return left_arrow, right_arrow 

def make_second_bottom_spine(ax=None, label=None, offset=0, labeloffset=20): 
    """Makes a second bottom spine""" 
    if ax is None: 
     ax = plt.gca() 
    second_bottom = mpl.spines.Spine(ax, 'bottom', ax.spines['bottom']._path) 
    second_bottom.set_position(('outward', offset)) 
    ax.spines['second_bottom'] = second_bottom 

    if label is not None: 
     # Make a new xlabel 
     ax.annotate(label, 
       xy=(0.5, 0), xycoords='axes fraction', 
       xytext=(0, -labeloffset), textcoords='offset points', 
       verticalalignment='top', horizontalalignment='center') 

if __name__ == '__main__': 
    main() 

Two bottom spines in a matplotlib plot

+0

estoy familiarizado con este vudú - cuidado para mostrar cómo generalizar esto a ejes más categóricas? Pensé que crear un tercer lomo inferior con algo de compensación lo haría visible, pero eso no funciona para mí, todavía está apilado justo encima del segundo. (Puedo abrir una nueva pregunta si eso es posible) – Thomas

+0

nm Lo tengo ahora; aunque también me gustaría ver su implementación (más limpia). – Thomas

9

El ejemplo de Joe es bueno. También arrojaré el mío. Estuve trabajando en ello hace unas horas, pero luego tuve que salir corriendo a una reunión. Roba de here.

import matplotlib.pyplot as plt 
import matplotlib.ticker as ticker 

## the following two functions override the default behavior or twiny() 
def make_patch_spines_invisible(ax): 
    ax.set_frame_on(True) 
    ax.patch.set_visible(False) 
    for sp in ax.spines.itervalues(): 
     sp.set_visible(False) 

def make_spine_invisible(ax, direction): 
    if direction in ["right", "left"]: 
     ax.yaxis.set_ticks_position(direction) 
     ax.yaxis.set_label_position(direction) 
    elif direction in ["top", "bottom"]: 
     ax.xaxis.set_ticks_position(direction) 
     ax.xaxis.set_label_position(direction) 
    else: 
     raise ValueError("Unknown Direction : %s" % (direction,)) 

    ax.spines[direction].set_visible(True) 

data = (('A',0.01),('A',0.02),('B',0.10),('B',0.20)) # fake data 

fig = plt.figure(1) 
sb = fig.add_subplot(111) 
sb.xaxis.set_major_locator(ticker.FixedLocator([0,1,2,3])) 

sb.plot([i[1] for i in data],"*",markersize=10) 
sb.set_xlabel("dose") 

plt.subplots_adjust(bottom=0.17) # make room on bottom 

par2 = sb.twiny() # create a second axes 
par2.spines["bottom"].set_position(("axes", -.1)) # move it down 

## override the default behavior for a twiny axis 
make_patch_spines_invisible(par2) 
make_spine_invisible(par2, "bottom") 
par2.set_xlabel("treatment") 

par2.plot([i[1] for i in data],"*",markersize=10) #redraw to put twiny on same scale 
par2.xaxis.set_major_locator(ticker.FixedLocator([0,1,2,3])) 
par2.xaxis.set_ticklabels([i[0] for i in data]) 

plt.show() 

Produce:

alt text

Cuestiones relacionadas