¿Por qué pyplot.contour () requiere que Z sea una matriz 2D?

La función matplotlib.pyplot.contour() toma 3 matrices de entrada X , Y y Z
Las matrices X e Y especifican las coordenadas x e y de los puntos, mientras que Z especifica el valor correspondiente de la función de interés evaluada en los puntos.

Entiendo que np.meshgrid() facilita la producción de arreglos que sirven como argumentos para contour() :

 X = np.arange(0,5,0.01) Y = np.arange(0,3,0.01) X_grid, Y_grid = np.meshgrid(X,Y) Z_grid = X_grid**2 + Y_grid**2 plt.contour(X_grid, Y_grid, Z_grid) # Works fine 

Esto funciona bien. Y convenientemente, esto funciona bien también:

 plt.contour(X, Y, Z_grid) # Works fine too 

Sin embargo, ¿por qué se requiere que la entrada Z sea ​​una matriz 2D?

¿Por qué algo como lo siguiente no está permitido, a pesar de que especifica todos los mismos datos alineados adecuadamente?

 plt.contour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel()) # Disallowed 

Además, ¿cuáles son las semánticas cuando solo se especifica Z (sin las correspondientes X e Y )?

Al mirar la documentación de contour se encuentra que hay varias formas de llamar a esta función, por ejemplo, contour(Z) o contour(X,Y,Z) . Por lo tanto, encontrará que no requiere que ningún valor de X o Y esté presente en absoluto.

Sin embargo, para trazar un contorno, la cuadrícula subyacente debe ser conocida por la función. El contour de Matplotlib se basa en una cuadrícula rectangular. Pero aún así, permitir el contour(z) , siendo z una matriz 1D, haría imposible saber cómo se debe trazar el campo. En el caso del contour(Z) donde Z es una matriz 2D, su forma establece de forma inequívoca la cuadrícula para el trazado.

Una vez que se conoce esa cuadrícula, no es importante si las matrices opcionales X e Y se aplanan o no; que es en realidad lo que nos dice la documentación:

X e Y deben ser 2-D con la misma forma que Z, o ambos deben ser 1-D, de modo que len (X) es el número de columnas en Z y len (Y) es el número de filas en Z.

También es bastante obvio que algo como plt.contour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel()) no puede producir un gráfico de contorno, porque toda la información sobre la forma de la cuadrícula se pierde y no hay De esta forma la función de contorno podría saber interpretar los datos. Por ejemplo, si len(Z_grid.ravel()) == 12 , la forma de la cuadrícula subyacente podría ser cualquiera de (1,12), (2,6), (3,4), (4,3), (6,2), (12,1) .

Una posible salida podría ser, por supuesto, permitir matrices 1D e introducir una shape argumento, como plt.contour(x,y,z, shape=(6,2)) . Sin embargo, este no es el caso, por lo que tiene que vivir con el hecho de que Z debe ser 2D.

Sin embargo, si está buscando una manera de obtener un gráfico de contorno con matrices aplanadas (desaliñadas), esto es posible utilizando plt.tricontour() .

 plt.tricontour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel()) 

Aquí se producirá internamente una cuadrícula triangular utilizando una Triangualación de Delaunay. Por lo tanto, incluso los puntos completamente aleatorios producirán un buen resultado, como se puede ver en la siguiente imagen, donde se compara con los mismos puntos aleatorios dados al contour .

introduzca la descripción de la imagen aquí

(Aquí está el código para producir esta imagen )

El código real de un algoritmo detrás de plt.contour se puede encontrar en _countour.cpp . Es un código C bastante complicado, por lo que es difícil seguirlo con precisión, pero si estuviera intentando crear un código generador de contornos, lo haría de la siguiente manera. Seleccione un punto (x, y) en el borde y fije su valor z . Iterar sobre puntos cercanos y elegir aquel para el cual el valor z es el más cercano al valor z del primer punto. Continúe con la iteración para el nuevo punto, seleccione el punto cercano con el valor z más cercano al deseado (pero verifique que no regrese al punto que acaba de visitar, por lo que tiene que ir en alguna “dirección”), y continúe hasta que obtenga Un ciclo o llegar a alguna frontera.

Parece que algo cercano (pero un poco más complejo) se implementa en _counter.cpp .

Como se ve en la descripción informal del algoritmo, para continuar debe encontrar un punto que esté “cerca” del actual. Es fácil de hacer si tiene una cuadrícula rectangular de puntos (necesita aproximadamente 4 u 8 iteraciones como esta: (x[i+1][j], y[i+1][j]) , (x[i][j+1], y[i][j+1]) , (x[i-1][j], y[i-1][j]) y así sucesivamente). Pero si tiene algunos puntos seleccionados al azar (sin ningún orden en particular), este problema se vuelve difícil: debe recorrer todos los puntos que tiene para encontrar los cercanos y realizar el siguiente paso. La complejidad de este paso es O(n) , donde n es un número de puntos (normalmente, un cuadrado de un tamaño de una imagen). Por lo tanto, un algoritmo se vuelve mucho más lento si no tiene una cuadrícula rectangular.

Esta es la razón por la que realmente necesita tres matrices 2d que se correspondan con las respuestas de x, y yz de algunos puntos ubicados sobre una cuadrícula rectangular.

Como mencionas correctamente, las x y las s pueden ser matrices 1d. En este caso, las matrices 2d correspondientes se reconstruyen con meshgrid . Sin embargo, en este caso tienes que tener z como 2d-array de todos modos.

Si solo se especifica z , y son los range de longitudes apropiadas.

EDITAR. Puede intentar “falsificar” las matrices bidimensionales de x , y y z de tal manera que x e y no formen una cuadrícula rectangular para verificar si mis suposiciones son correctas.

 import matplotlib.pyplot as plt import numpy as np %matplotlib inline x = np.random.uniform(-3, 3, size=10000) y = np.random.uniform(-3, 3, size=10000) z = x**2 + y**2 X, Y, Z = (u.reshape(100, 100) for u in (x, y, z)) plt.contour(X, Y, Z) 

Resultado incorrecto

Como ve, la imagen no se parece en nada al gráfico correcto si (x, y, z) son solo algunos puntos aleatorios.

Ahora supongamos que x está ordenado como un paso de preprocesamiento como sugiere @dhrummel en los comentarios. Tenga en cuenta que no podemos clasificar y simultáneamente ya que no son independientes (queremos conservar los mismos puntos).

 x = np.random.uniform(-3, 3, size=10000) y = np.random.uniform(-3, 3, size=10000) z = x**2 + y**2 xyz = np.array([x, y, z]).T x, y, z = xyz[xyz[:, 0].argsort()].T assert (x == np.sort(x)).all() X, Y, Z = (u.reshape(100, 100) for u in (x, y, z)) plt.contour(X, Y, Z) 

x está ordenado ahora

Nuevamente, la imagen es incorrecta, debido al hecho de que las y no están ordenadas (en todas las columnas) como lo fueron si tuviéramos una cuadrícula rectangular en lugar de algunos puntos aleatorios.

La razón para que X e Y sean 2D es la siguiente. Z coincide con cada coordenada (x, y) en el sistema de ejes con una “profundidad” correspondiente para crear un gráfico 3D con las coordenadas x, y y z.

Ahora suponga que queremos apuntar a un punto arbitrario dentro del sistema de ejes. Podemos hacerlo proporcionando las coordenadas x e y (x, y) para este punto. Por ejemplo (0,0). Ahora considere la “línea” con el valor x 1. En esta línea hay un número de valores ny, que se ve algo así como:

introduzca la descripción de la imagen aquí

Si trazamos estas líneas para todos los valores de x y los valores de y obtendremos algo. me gusta:

introduzca la descripción de la imagen aquí

Como puede ver, tenemos una anotación 2D que consta de 2 matrices 2D , una para los valores x que tiene la forma:

 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 #--> Two dimensional x values array 

y uno para los valores de y que tiene la forma:

 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 ... 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 #--> Two dimensional y values array 

Esos dos juntos proporcionan las coordenadas (x, y) para cada punto dentro del sistema de coordenadas. Ahora podemos trazar para cada punto la “profundidad” significa el valor Z (coordenada z). Ahora también es obvio por qué la variable Z debe ser bidimensional con la forma (len (x), len (y)) porque de lo contrario no puede proporcionar un valor para todos los puntos.

Este comportamiento se puede realizar ya sea proporcionando matrices 2D x, y, y z a la función O: proporcione matrices 1D x e y a la función y la función crea internamente la malla 2D a partir de los valores x e y con smth. como X, Y = np.meshgrid (x, y) pero, sin embargo, z debe ser bidimensional.

Déjame explicarte de una manera simple, ya que pensé que Z no debería ser 2D también. contourf() necesita X e Y para construir su propio espacio, y la relación Z (X, Y) para construir un espacio completo, en lugar de simplemente usar varios puntos con 1D X, Y, Z información.