3D-графика с питоном

Я пытаюсь построить поверхность в python. У меня есть таблица значений N на N. Я создал два вектора X и Y каждого из N элементов. Когда я пытаюсь построить это, я получаю сообщение об ошибке:

ValueError: total size of new array must be unchanged

Я проверил примеры и вижу, что для N элементов из Z существует N элементов для X и Y.

Это не имеет для меня никакого смысла. Почему мне нужны N элементов, а не N на N?

Вот пример кода:

импорт случайных import math

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

bignum = 100

mat = []
X = []
Y = []

for x in range(0,bignum):
    mat.append([])
    X.append(x);
    for y in range (0,bignum):
        mat[x].append(random.random())
        Y.append(y)

fig = plt.figure(figsize=plt.figaspect(2.))
ax = fig.add_subplot(1,1,1, projection='3d')
surf = ax.plot_surface(X,Y,mat)

Ответ 1

Прежде всего, никогда не делайте такие вещи:

mat = []
X = []
Y = []

for x in range(0,bignum):
    mat.append([])
    X.append(x);
    for y in range (0,bignum):
        mat[x].append(random.random())
        Y.append(y)

Это эквивалентно:

mat = np.random.random((bignum, bignum))
X, Y = np.mgrid[:bignum, :bignum]

... но он на порядок быстрее и использует часть памяти, которая использует списки, а затем преобразовывается в массивы.

Однако ваш пример отлично работает.

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

bignum = 100
mat = np.random.random((bignum, bignum))
X, Y = np.mgrid[:bignum, :bignum]

fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
surf = ax.plot_surface(X,Y,mat)
plt.show()

enter image description here

Если вы прочитали документацию для plot_surface, в ней ясно сказано, что X, Y и Z должны быть двумерными массивами.

Это так, что вы можете строить более сложные поверхности (например, сферы), по сути определяя связность между точками. (Например, см. Этот пример в галерее matplotlib: http://matplotlib.sourceforge.net/examples/mplot3d/surface3d_demo2.html)

Если у вас есть массивы 1D X и Y и требуется простая поверхность из 2D-сетки, используйте numpy.meshgrid или numpy.mgrid для создания соответствующих массивов X и Y 2D.

Edit: Чтобы объяснить, что делают mgrid и meshgrid, взгляните на их вывод:

print np.mgrid[:5, :5]

дает:

array([[[0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1],
        [2, 2, 2, 2, 2],
        [3, 3, 3, 3, 3],
        [4, 4, 4, 4, 4]],

       [[0, 1, 2, 3, 4],
        [0, 1, 2, 3, 4],
        [0, 1, 2, 3, 4],
        [0, 1, 2, 3, 4],
        [0, 1, 2, 3, 4]]])

Итак, он возвращает одиночный трехмерный массив с формой 2x5x5, но его легче думать об этом как о двух 2D-массивах. Один представляет координаты i для любой точки сетки 5x5, а другой представляет координаты j.

Из-за того, как работает распаковка python, мы можем просто написать:

xx, yy = np.mgrid[:5, :5]

Python не заботится о том, что возвращает mgrid, он просто попытается распаковать его на два элемента. Поскольку массивы numpy перебирают срезы их первой оси, мы получим массивы размером 2, 5x5, если мы распакуем массив с формой (2x5x5). Точно так же мы можем делать такие вещи, как:

xx, yy, zz = np.mgrid[:5, :5, :5]

... и получить 3, 3D 5x5x5 массивы указателей. Кроме того, если мы срезаем с другим диапазоном (например, xx, yy = np.mgrid[10:15, 3:8], он будет делиться цифрами от 10 до 14 (включительно) и от 3 до 7 (включительно).

Здесь немного больше, чем mgrid (он может принимать сложные аргументы шага для имитации linspace, например xx, yy = np.mgrid[0:1:10j, 0:5:5j] будет возвращать 2 массива 10x5 с увеличением числа между 0-1 и 0-5 соответственно), но пропустите переход на meshgrid на секунду.

meshgrid принимает два массива и разбивает их аналогично mgrid. В качестве примера:

x = np.arange(5)
y = np.arange(5)
xx, yy = np.meshgrid(x, y)
print xx, yy

дает:

(array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]]), 

array([[0, 0, 0, 0, 0],
       [1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3],
       [4, 4, 4, 4, 4]]))

meshgrid на самом деле происходит возврат кортежа 2, 5x5 2D-массивов, но это различие не имеет значения. Основное различие заключается в том, что индикаторы не должны увеличиваться в определенном направлении. Он просто разбивает массивы, которые он дал. В качестве примера:

x = [0.1, 2.4, -5, 19]
y = [-4.3, 2, -1, 18.4]
xx, yy = np.meshgrid(x, y)

дает:

(array([[  0.1,   2.4,  -5. ,  19. ],
       [  0.1,   2.4,  -5. ,  19. ],
       [  0.1,   2.4,  -5. ,  19. ],
       [  0.1,   2.4,  -5. ,  19. ]]),
 array([[ -4.3,  -4.3,  -4.3,  -4.3],
       [  2. ,   2. ,   2. ,   2. ],
       [ -1. ,  -1. ,  -1. ,  -1. ],
       [ 18.4,  18.4,  18.4,  18.4]]))

Как вы заметили, он просто выложил значения, которые мы ему дали.

В основном вы используете их, когда вам нужно работать с указателями в той же форме, что и ваша входная сетка. Это в основном полезно, когда вы хотите оценить функцию по значениям сетки.

например.

import numpy as np
import matplotlib.pyplot as plt

x, y = np.mgrid[-10:10, -10:10]
dist = np.hypot(x, y) # Linear distance from point 0, 0
z = np.cos(2 * dist / np.pi)

plt.title(r'$\cos(\frac{2*\sqrt{x^2 + y^2}}{\pi})$', size=20)
plt.imshow(z, origin='lower', interpolation='bicubic',
          extent=(x.min(), x.max(), y.min(), y.max()))
plt.colorbar()
plt.show()

enter image description here