¿Cómo puedo establecer la relación de aspecto en matplotlib?

108

Estoy tratando de hacer un diagrama cuadrado (usando imshow), es decir, una relación de aspecto de 1: 1, pero no puedo. Ninguno de estos funciona:

import matplotlib.pyplot as plt

ax = fig.add_subplot(111,aspect='equal')
ax = fig.add_subplot(111,aspect=1.0)
ax.set_aspect('equal')
plt.axes().set_aspect('equal')

Parece que las llamadas simplemente se están ignorando (un problema que a menudo parece tener con matplotlib).

jtlz2
fuente
6
¿Lo intentaste ax.axis('equal')por casualidad? Como todos han dicho, lo que hiciste debería funcionar, pero ax.axispodría ser otra ruta para intentar una solución alternativa.
Joe Kington

Respuestas:

77

La tercera es la vencida. Supongo que esto es un error y la respuesta de Zhenya sugiere que está corregido en la última versión. Tengo la versión 0.99.1.1 y he creado la siguiente solución:

import matplotlib.pyplot as plt
import numpy as np

def forceAspect(ax,aspect=1):
    im = ax.get_images()
    extent =  im[0].get_extent()
    ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_xlabel('xlabel')
ax.set_aspect(2)
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')
forceAspect(ax,aspect=1)
fig.savefig('force.png')

Esto es 'force.png': ingrese la descripción de la imagen aquí

A continuación se muestran mis intentos infructuosos, pero con suerte informativos.

Segunda respuesta:

Mi 'respuesta original' a continuación es excesiva, ya que hace algo similar a axes.set_aspect(). Creo que quieres usar axes.set_aspect('auto'). No entiendo por qué este es el caso, pero produce una gráfica de imagen cuadrada para mí, por ejemplo, este script:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_aspect('equal')
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')

Produce un gráfico de imagen con una relación de aspecto 'igual': ingrese la descripción de la imagen aquí y uno con una relación de aspecto 'automática': ingrese la descripción de la imagen aquí

El código proporcionado a continuación en la 'respuesta original' proporciona un punto de partida para una relación de aspecto controlada explícitamente, pero parece que se ignora una vez que se llama a un imshow.

Respuesta original:

Aquí hay un ejemplo de una rutina que ajustará los parámetros de la subparcela para que obtenga la relación de aspecto deseada:

import matplotlib.pyplot as plt

def adjustFigAspect(fig,aspect=1):
    '''
    Adjust the subplot parameters so that the figure has the correct
    aspect ratio.
    '''
    xsize,ysize = fig.get_size_inches()
    minsize = min(xsize,ysize)
    xlim = .4*minsize/xsize
    ylim = .4*minsize/ysize
    if aspect < 1:
        xlim *= aspect
    else:
        ylim /= aspect
    fig.subplots_adjust(left=.5-xlim,
                        right=.5+xlim,
                        bottom=.5-ylim,
                        top=.5+ylim)

fig = plt.figure()
adjustFigAspect(fig,aspect=.5)
ax = fig.add_subplot(111)
ax.plot(range(10),range(10))

fig.savefig('axAspect.png')

Esto produce una figura como esta: ingrese la descripción de la imagen aquí

Me imagino que si tiene varias subparcelas dentro de la figura, querrá incluir el número de subtramas yyx como parámetros de palabras clave (por defecto en 1 cada una) en la rutina proporcionada. Luego, usando esos números y las palabras clave hspacey wspace, puede hacer que todas las subtramas tengan la relación de aspecto correcta.

Yann
fuente
1
Para los casos en los que get_imageshay una lista vacía (como sucedería con ax.plot([0,1],[0,2]), puede usar get_xlimyget_ylim
Joel
Me parece que esto no funcionará si se hace con escala logs. Agregué una respuesta que prueba eso y lo maneja. Siéntete libre de incorporar eso en tu respuesta y luego quitaré la mía.
Joel
1
La razón por la que el aspecto parece desigual es porque el mismo aspecto significa que la distancia visual en x será la misma que y. Si la imagen es cuadrada, pero el gráfico dx y dy son diferentes, entonces esa no es una relación de aspecto de 1: 1. En ese caso, la relación de aspecto será dy / dx.
bart cubrich
22

¿Cuál es la matplotlibversión que está ejecutando? Recientemente tuve que actualizarme y 1.1.0, con él, add_subplot(111,aspect='equal')funciona para mí.

ev-br
fuente
1
Funciona bien para mí en la matplotlibversión 2.0.2. jupyter notebookversión 5.0.0. Gracias.
Sathish
5

Después de muchos años de éxito con las respuestas anteriores, he descubierto que esto no funciona de nuevo, pero encontré una solución de trabajo para las subtramas en

https://jdhao.github.io/2017/06/03/change-aspect-ratio-in-mpl

Con todo el crédito, por supuesto, para el autor anterior (que quizás prefiera publicar aquí), las líneas relevantes son:

ratio = 1.0
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright-xleft)/(ybottom-ytop))*ratio)

El enlace también tiene una explicación muy clara de los diferentes sistemas de coordenadas utilizados por matplotlib.

Gracias por todas las excelentes respuestas recibidas, especialmente @ Yann, que seguirá siendo el ganador.

jtlz2
fuente
3

deberías probar con figaspect. Esto funciona para mi. De los documentos:

Crea una figura con una relación de aspecto especificada. Si arg es un número, use esa relación de aspecto. > Si arg es una matriz, figaspect determinará el ancho y la altura de una figura que se ajuste a la matriz conservando la relación de aspecto. Se devuelven el ancho y la altura de la figura en pulgadas. Asegúrese de crear ejes con la misma altura y, por ejemplo,

Uso de ejemplo:

  # make a figure twice as tall as it is wide
  w, h = figaspect(2.)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

  # make a figure with the proper aspect for an array
  A = rand(5,3)
  w, h = figaspect(A)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

Editar: no estoy seguro de lo que estás buscando. El código anterior cambia el lienzo (el tamaño de la parcela). Si desea cambiar el tamaño de la ventana de matplotlib, de la figura, utilice:

In [68]: f = figure(figsize=(5,1))

esto produce una ventana de 5x1 (ancho x alto).

Joaquín
fuente
Gracias por esto, tiene algún efecto, al cambiar la relación de aspecto del lienzo: Para ser más específico, necesito cambiar la relación de aspecto de la figura en sí, lo que no hace lo siguiente (formato de apols ...): fig = plt.figure (figsize = (plt.figaspect (2.0)))
jtlz2
3

Esta respuesta se basa en la respuesta de Yann. Establecerá la relación de aspecto para gráficos lineales o log-log. He utilizado información adicional de https://stackoverflow.com/a/16290035/2966723 para probar si los ejes son de escala logarítmica.

def forceAspect(ax,aspect=1):
    #aspect is width/height
    scale_str = ax.get_yaxis().get_scale()
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    if scale_str=='linear':
        asp = abs((xmax-xmin)/(ymax-ymin))/aspect
    elif scale_str=='log':
        asp = abs((scipy.log(xmax)-scipy.log(xmin))/(scipy.log(ymax)-scipy.log(ymin)))/aspect
    ax.set_aspect(asp)

Obviamente puedes usar cualquier versión de logtu que quieras, he usado scipy, pero numpyo mathdebería estar bien.

Joel
fuente