Как строить трехмерные графики в Matplotlib по неравномерным данным

В статье Как рисовать трехмерные графики говорилось о том, как строить трехмерные поверхности с помощью метода plot_surface() из класса Axes3d, когда имеются данные в узлах прямоугольной равномерной сетки.

Скрипт для построения такой поверхности выглядит следующим образом:

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import numpy as np


def makeData():
    x = np.linspace(-10, 10, 40)
    y = np.linspace(-10, 10, 40)
    xgrid, ygrid = np.meshgrid(x, y)

    z = np.sin(xgrid * 0.3) * np.cos(ygrid * 0.75)
    return xgrid, ygrid, z


if __name__ == '__main__':
    x, y, z = makeData()

    fig = plt.figure()
    axes = fig.add_subplot(projection='3d')
    axes.plot_surface(x, y, z, rstride=1, cstride=1,
                      linewidth=0.5, edgecolors='k')
    plt.show()

Здесь функция makeData() создает данные, которые будут использоваться Matplotlib для построения поверхности. Если вы не знакомы с используемой здесь функцией meshgrid() из библиотеки numpy, то она описана в вышеупомянутой статье про рисование трехмерных графиков в разделе Первый трехмерный график. В примерах к этой статье для наглядности у трехмерных поверхностей будут использоваться черные линии сетки, установленные с помощью параметров linewidth=0.5 и edgecolors='k' . В результате будет показана следующая трехмерная поверхность.

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

Первый случай, когда сетка остается прямоугольной, но отсчеты по осям расставлены случайным образом. Такую сетку будем называть прямоугольной неравномерной.

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

Рассмотрим способы для построения графиков в обоих случаях.

Случай прямоугольной неравномерной сетки

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

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import numpy as np


def makeData():
    # Координаты задаются случайным образом
    x = np.random.rand(100) * 20.0 - 10.0
    y = np.random.rand(100) * 20.0 - 10.0
    xgrid, ygrid = np.meshgrid(x, y)

    z = np.sin(xgrid * 0.3) * np.cos(ygrid * 0.75)
    return xgrid, ygrid, z


if __name__ == '__main__':
    x, y, z = makeData()

    fig = plt.figure()
    axes = fig.add_subplot(projection='3d')
    axes.plot_surface(x, y, z, rstride=1, cstride=1,
                      linewidth=0.5, edgecolors='k')
    plt.show()

Мы изменили только способ создания координат для задания узлов сетки. Результат этого скрипта будет следующим.

Если вы не поклонник художников-кубистов, то это вряд ли то, что вы хотели бы видеть, хотя очертания правильного графика прослеживаются. Проблема в том, что matplotlib отображает грани поверхности последовательно в том порядке, как они заданы на сетке. Если в первом примере для каждой оси мы задали отсчеты (массивы x и y, по которым затем будут созданы матрицы xgrid, ygrid для сетки) в виде последовательности чисел [-10.0, -9.9, -9.8, ...], то в новом примере отсчеты расположены в хаотичном порядке. Именно в этом порядке matplotlib и соединяет узлы. Таким образом, чтобы правильно отобразить график, достаточно отсортировать исходные массивы x и y, как показано в следующем примере:

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import numpy as np


def makeData():
    # Координаты задаются случайным образом
    x = np.random.rand(100) * 20.0 - 10.0
    y = np.random.rand(100) * 20.0 - 10.0

    x.sort()
    y.sort()

    xgrid, ygrid = np.meshgrid(x, y)

    z = np.sin(xgrid * 0.3) * np.cos(ygrid * 0.75)
    return xgrid, ygrid, z


if __name__ == '__main__':
    x, y, z = makeData()

    fig = plt.figure()
    axes = fig.add_subplot(projection='3d')
    axes.plot_surface(x, y, z, rstride=1, cstride=1,
                      linewidth=0.5, edgecolors='k')
    plt.show()

Этот скрипт отобразит график в следующем виде:

В данном примере взято по 100 отсчетов по каждой оси (т.е. всего 10000 узлов сетки), и на графике видно неравномерное расположение линий сетки.

Случай непрямоугольной сетки

В случае непрямоугольной сетки мы уже не можем воспользоваться функцией meshgrid, которая создает двумерные матрицы сетки по одномерным массивам с отсчетами по осям. Также нам не подойдет метод plot_surface() для рисования поверхности.

Чтобы стало окончательно понятно, что мы подразумеваем под непрямоугольной случайной сеткой, изменим исходный скрипт таким образом, чтобы создавались точки со случайными координатами, и отобразим их в объеме с помощью метода scatter.

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import numpy as np


def makeData():
    x = np.random.rand(500) * 20.0 - 10.0
    y = np.random.rand(len(x)) * 20.0 - 10.0

    z = np.sin(x * 0.3) * np.cos(y * 0.75)
    return x, y, z


if __name__ == '__main__':
    x, y, z = makeData()

    fig = plt.figure()
    axes = fig.add_subplot(projection='3d')
    axes.scatter(x, y, z)

    axes.set_xlim(-10, 10)
    axes.set_ylim(-10, 10)

    plt.show()

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

Нам нужно провести поверхность через такой набор точек. С помощью matplotlib это делается очень легко, достаточно всего лишь использовать метод plot_trisurf(), который производит триангуляцию переданных ему точек и строит поверхность из полученных треугольников.

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import numpy as np


def makeData():
    x = np.random.rand(500) * 20.0 - 10.0
    y = np.random.rand(len(x)) * 20.0 - 10.0

    z = np.sin(x * 0.3) * np.cos(y * 0.75)
    return x, y, z


if __name__ == '__main__':
    x, y, z = makeData()

    fig = plt.figure()
    axes = fig.add_subplot(projection='3d')

    axes.plot_trisurf(x, y, z, linewidth=0.5, edgecolors='k')

    axes.set_xlim(-10, 10)
    axes.set_ylim(-10, 10)

    plt.show()

Результат будет выглядеть следующим образом:

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

Если увеличить количество точек, то график будет более гладким:

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

Другие статьи про Matplotlib

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
4.5 stars

Рейтинг 4.3/5. Всего 7 голос(а, ов)




Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 276