Почему pyplot.contour() требует, чтобы Z был 2D-массивом?
Функция matplotlib.pyplot.contour()
принимает 3 входных массива X
, Y
и Z
.
Массивы X
и Y
определяют x- и y-координаты точек, а Z
задает соответствующее значение интересующей функции, оцениваемой в точках.
Я понимаю, что np.meshgrid()
позволяет легко создавать массивы, которые служат аргументами для 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
Это прекрасно работает. И удобно, это отлично работает:
plt.contour(X, Y, Z_grid) # Works fine too
Однако почему вход Z
требуется как 2D-массив?
Почему что-то вроде следующего недопустимо, хотя он указывает все одинаковые данные, соответствующие соответствующим образом?
plt.contour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel()) # Disallowed
Кроме того, что такое семантика, когда указан только Z
(без соответствующих X
и Y
)?
Ответы
Ответ 1
Глядя на документацию по contour
можно обнаружить, что существует несколько способов вызова этой функции, например, contour(Z)
или contour(X,Y,Z)
. Таким образом, вы обнаружите, что для этого вообще не требуется никаких значений X
или Y
Однако, чтобы построить контур, базовая сетка должна быть известна функции. contour
Matplotlib основан на прямоугольной сетке. Но даже в этом случае разрешение contour(z)
, где z
является одномерным массивом, лишает возможности узнать, как должно быть построено поле. В случае contour(Z)
где Z
является двумерным массивом, его форма однозначно задает сетку для графика.
Как только эта сетка известна, становится неважно, сглажены ли необязательные массивы X
и Y
; что фактически говорит нам документация:
X и Y оба должны быть двумерными и иметь ту же форму, что и Z, или они оба должны быть одномерными, так что len (X) - это число столбцов в Z, а len (Y) - это количество строк в Z.
Также совершенно очевидно, что некоторые функции, такие как plt.contour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel())
не могут создать контурный график, потому что вся информация о форме сетки теряется и отсутствует Таким образом, функция контура может знать, как интерпретировать данные. Например, если len(Z_grid.ravel()) == 12
, основная форма сетки может быть любой из (1,12), (2,6), (3,4), (4,3), (6,2), (12,1)
.
Конечно, возможный выход может заключаться в том, чтобы разрешить одномерные массивы и ввести shape
аргумента, например, plt.contour(x,y,z, shape=(6,2))
. Это, однако, не так, поэтому вы должны учитывать тот факт, что Z
должен быть 2D.
Тем не менее, если вы ищете способ получения графического графика со сплюснутыми (выровненными) массивами, это возможно с помощью plt.tricontour()
.
plt.tricontour(X_grid.ravel(), Y_grid.ravel(), Z_grid.ravel())
Здесь треугольная сетка будет производиться внутри с использованием триангуляции Делоне. Поэтому даже полностью рандомизированные точки дают хороший результат, как видно на следующем рисунке, где это сравнивается с теми же случайными точками, заданными для contour
.
![enter image description here]()
(Вот код для создания этой картины)
Ответ 2
Фактический код алгоритма позади plt.contour
можно найти в _ countour.cpp. Это довольно сложный C-код, поэтому его трудно точно следовать, но если бы я пытался создать код, генерирующий контуры, я бы сделал это следующим образом. Выберите точку (x, y)
на границе и исправьте ее значение z
. Итерируйте по соседним точкам и выберите тот, для которого z-значение является самым близким к z-значению первой точки. Продолжайте итерацию для новой точки, выберите ближайшую точку с близким к z значением z (но убедитесь, что вы не вернетесь к точке, которую вы только что посетили, поэтому вам нужно идти в каком-то "направлении" ), и продолжайте, пока не получите цикл или достичь некоторой границы.
Кажется, что в _counter.cpp
реализовано что-то близкое (но немного более сложное).
Как видно из неофициального описания алгоритма, вы должны найти точку, которая находится "рядом" с текущей. Это легко сделать, если у вас прямоугольная сетка точек (требуется примерно 4 или 8 итераций: (x[i+1][j], y[i+1][j])
, (x[i][j+1], y[i][j+1])
, (x[i-1][j], y[i-1][j])
и т.д.). Но если у вас есть случайно выбранные точки (без какого-либо определенного порядка), эта проблема становится сложной: вам нужно перебирать все точки, которые вы должны найти рядом, и сделать следующий шаг. сложность такого шага O(n)
, где n
- количество точек (обычно это квадрат размера изображения). Таким образом, алгоритм становится намного медленнее, если у вас нет прямоугольной сетки.
Вот почему вам действительно нужны три 2d-массива, которые соответствуют x, y и z некоторых точек, расположенных над некоторой прямоугольной сеткой.
Как вы правильно отметили, x
и y
могут быть 1d-массивами. В этом случае соответствующие 2d-массивы реконструируются с помощью meshgrid
. Однако в этом случае вы должны иметь z
как 2d-массив.
Если указан только z
, x
и y
являются range
соответствующих длин.
ИЗМЕНИТЬ. Вы можете попытаться "подделать" двумерные массивы x
, y
и z
таким образом, чтобы x
и y
не формировали прямоугольную сетку, чтобы проверить правильность моих допущений.
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)
![Неверный результат]()
Как вы видите, изображение не похоже на что-либо близкое к правильному графу, если (x, y, z) являются лишь некоторыми случайными точками.
Теперь предположим, что x
сортируется как шаг предварительной обработки, как предлагает @dhrummel в комментариях. Обратите внимание, что мы не можем сортировать x
и y
одновременно, поскольку они не являются независимыми (мы хотим сохранить одни и те же точки).
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 отсортировано сейчас]()
Опять же, изображение неверно из-за того, что y
не сортируются (в каждом столбце), как они были, если бы мы имели прямоугольную сетку вместо некоторых случайных точек.
Ответ 3
Причиной X и Y для 2D является следующее.
Z соответствует каждой координате (x, y) в системе осей соответствующей "глубины" для создания 3D-графика с координатами x, y и z.
Теперь предположим, что мы хотим указать на произвольную точку в системе осей.
Мы можем это сделать, предоставив для этой точки координаты x и y (x, y). Например (0,0).
Теперь рассмотрим "строку" со значением x 1. На этой строке есть ряд значений n y, которые выглядят как-то вроде:
![введите описание изображения здесь]()
Если мы построим эти строки для всех значений x и значений y, мы получим что-л. как:
![введите описание изображения здесь]()
Как вы можете видеть, у нас есть 2D-аннотация, состоящая из массивов 2 2D, одна для значений x, которая имеет форму:
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, которые имеют форму:
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
Эти два вместе обеспечивают координаты (x, y) для каждой точки в системе координат. Теперь мы можем построить для каждой точки "глубина" значение Z (координата z).
Теперь также очевидно, почему переменная Z должна быть двумерной с формой (len (x), len (y)), поскольку в противном случае она не может обеспечить значение для всех точек.
Такое поведение может быть реализовано либо путем предоставления массивов 2D x, y и z функции OR: предоставить функции 1D x и y функции, а функция внутренне создает 2D-сетку из значений x и y с помощью smth. как X, Y = np.meshgrid(x, y), но тем не менее z должно быть двумерным.
Ответ 4
Позвольте мне объяснить это простым способом, так как я думал, что Z не должен быть также 2D. contourf()
нужны X и Y, чтобы построить свое собственное пространство, и отношение Z (X, Y), чтобы построить полное пространство, а не просто использовать несколько точек с 1D X, Y, Z информацией.
Ответ 5
Представьте, что вы хотите построить трехмерный график. У вас есть набор из x
точек и набор из y
точек. Цель состоит в том, чтобы получить значение z
для каждой пары x
и y
, или, другими словами, вам нужна функция f
, которая генерирует значение z
так что z = f(x, y)
.
Вот хороший пример (взят из MathWorks):
![surf]()
Координаты x
и y
находятся внизу справа и внизу слева соответственно. У вас будет функция f
такая, что для каждой пары x
и y
мы генерируем значение z
. Поэтому в предоставленном numpy.meshgrid
вызов numpy.meshgrid
сгенерирует два двумерных массива, так что для каждого уникального пространственного местоположения мы будем наблюдать значения x
и y
которые являются уникальными для этого местоположения.
Например, давайте использовать очень маленький пример:
In [1]: import numpy as np
In [2]: x, y = np.meshgrid(np.linspace(-1, 1, 3), np.linspace(-1, 1, 3))
In [3]: x
Out[3]:
array([[-1., 0., 1.],
[-1., 0., 1.],
[-1., 0., 1.]])
In [4]: y
Out[4]:
array([[-1., -1., -1.],
[ 0., 0., 0.],
[ 1., 1., 1.]])
Взгляните, например, на строку № 2 и колонку № 1 (я начинаю индексирование с 0 до 100). Это означает, что в этом пространственном местоположении мы будем иметь координаты x = 0.
и y = 1
. numpy.meshgrid
дает нам пару x
и y
которая требуется для генерации значения z
по этой конкретной координате. Для удобства он просто разделен на два 2D-массива.
Теперь, что, наконец, нужно поместить в вашу переменную z
это то, что она должна использовать функцию f
и обрабатывать то, что выводится для каждого значения в x
и его соответствующего y
.
В явном виде вам нужно будет сформулировать двумерный массив z
, чтобы:
z = [f(-1, -1) f(0, -1) f(1, -1)]
[f(-1, 0) f(0, 0) f(1, 0)]
[f(-1, 1) f(0, 1) f(1, 1)]
Посмотрите очень внимательно на пространственное расположение слагаемых x
и y
. Мы генерируем 9 уникальных значений для каждой пары значений x
и y
. Значения x
варьируются от -1 до 1 и одинаковы для y
. Как только вы сгенерируете этот 2D-массив для z
, вы можете использовать contourf
для рисования наборов уровней, так что каждая линия контура даст вам набор всех возможных значений x
и y
которые равны одному и тому же значению z
. Кроме того, между каждой соседней парой отдельных линий мы заполняем область между ними одним и тем же цветом.
Позвольте закончить это фактическим примером. Предположим, у нас есть функция f(x, y) = exp(-(x**2 + y**2)/10)
. Это двумерный гауссов со стандартным отклонением sqrt(5)
.
Поэтому, давайте создадим сетку из значений x
и y
, используйте это, чтобы сгенерировать значения z
и нарисовать contourf
график:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 101)
y = x
x, y = np.meshgrid(x, y)
z = np.exp(-(x**2 + y**2) / 10)
fig,ax2 = plt.subplots(1)
ax2.contourf(x,y,z)
plt.show()
Мы получаем:
![Contour]()