Как с помощью Matplotlib рисовать линии уровня

Немного рекламы

Линия уровня функции - это кривая, в каждой точке которой функция принимает одно и то же фиксированное значение.

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

  1. В виде трех двумерных матриц, описывающих двумерную сетку и значения функции в узлах этой сетки. В этом случае используются функции contour и contourf.
  2. В виде трех одномерных массивов, описывающих произвольно расположенные точки на плоскости и значения функции в этих точках. В этом случае используются функции tricontour и tricontourf.

Сначала рассмотрим случай, когда данные заданы в виде двумерных матриц. В этой статье мы будем рисовать линии уровня функции, заданной следующей формулой:

Сначала построим трехмерный график этой функции, чтобы представить себе, как выглядит данная функция:

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

import pylab
from mpl_toolkits.mplot3d import Axes3D
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


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

    fig = pylab.figure()
    axes = Axes3D(fig)

    axes.plot_surface(x, y, z)

    pylab.show()

В трехмерном виде эта функция выглядит следующим образом:

Рисование линий уровня

Чтобы построить линии уровня по данным, которые заданы в виде трех двумерных матриц (по данным, расположенным в узлах прямоугольной сетки), можно воспользоваться функциями countour или contourf из модуля pylab. Отличие этих двух функций состоит в том, что contourf, в отличие от contour, закрашивает области между линиями уровня однотонным цветом. Эти функции имеют некоторое количество параметров для настройки внешнего вида графика, но для начала ими можно пренебречь и передать в функцию только данные, по которым нужно построить линии уровня. Например:

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


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

    pylab.show()

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

Если заменить функцию contour на contourf, то результат будет следующий:

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


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

    pylab.show()

Добавление подписей к линиям уровня

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

Функции contour / contourf возвращают объект типа QuadContourSet (это производный класс от более общего класса ContourSet), который описывает наш график. Чтобы добавить на график подписи для линий уровня, нужно вызвать функцию clabel из модуля pylab. Эта Функция в качестве первого параметра ожидает экземпляр класса ContourSet. Таким образом, функции clabel в качестве первого параметра можно передать экземпляр класса, который был возвращен из функций contour / contourf.

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z)
    pylab.clabel(cs)

    pylab.show()

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

Такого же результата можно добиться, если использовать не функцию clabel из pylab, а одноименный метод из класса QuadContourSet, то есть в предыдущем примере заменить строку pylab.clabel(cs) на cs.clabel().

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

Полный список параметров функции clabel вы можете найти в документации, некоторые из них мы рассмотрим чуть позже в этой статье.

Задание значений отображаемых линий уровня

В предыдущих примерах мы не задавали явно значения, которые мы хотели бы отметить с помощью линий уровня, а понадеялись на то, что библиотека Matplotlib выберет за нас эти значения таким образом, чтобы адекватно представить отображаемую функцию. Однако в реальности скорее всего мы захотим явно задавать значения, по которым должны проходить линии уровня. Для этого функции contour / contourf нужно задать дополнительный параметр, который может быть либо целым числом, задающим количество отображаемых линий уровня (тогда разность значений между соседними линиями уровня будут равные), либо массив, явно задающий значения линий уровня, которые мы хотим отобразить. В последнем случае массив обязательно должен быть отсортирован по возрастанию значений.

В следующем примере мы задаем количество линий уровня, равное 20, а учитывая, что значения нашей функции лежит в интервале от -1 до 1, то расстояние между линиями уровня будет равно 0.1.

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z, 20)
    pylab.clabel(cs)

    pylab.show()

Но мы можем явно задать интересующие нас уровни:

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    levels = [-0.99, -0.7, -0.5, -0.3, -0.1, 0.0, 0.1, 0.3, 0.5, 0.7, 0.99]
    cs = pylab.contour(x, y, z, levels)
    pylab.clabel(cs)

    pylab.show()

Задание цветов для линий уровня

По умолчанию цвета линий уровня выбираются, исходя из установленной в данный момент цветовой карты (Colormap, cmap в терминах Matplotlib), но мы можем явно указать, каким цветом отображать линии. Для этого в функцию contour / contourf можно передать именованный параметр colors, который может принимать значение None, что является значением по умолчанию, строку, описывающую цвет (в этом случае все линии уровня будут рисоваться этим цветом) или кортеж элементов, описывающих цвета (скорее всего это будут строки, хотя Matplotlib поддерживает и другие форматы задания цвета), в этом случае цвета будут последовательно перебираться среди элементов этого кортежа.

Давайте сделаем, чтобы наш график стал черно-белым, т.е. линии уровня рисовались только черным цветом.

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z, colors='black')
    pylab.clabel(cs)

    pylab.show()

Если на графике мы используем только один цвет, то отрицательные значения будут отображаться штриховыми линиями. Если вы хотите, чтобы и отрицательные значения отображались сплошной линией, то для этого нужно в словаре настроек pylab.rcParams (или, что то же самое, matplotlib.rcParams) по ключу "contour.negative_linestyle" установить значение "solid":

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    pylab.rcParams['contour.negative_linestyle'] = 'solid'
    cs = pylab.contour(x, y, z, colors='black')
    pylab.clabel(cs)

    pylab.show()

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

Оформление надписей на линиях уровня

Перейдем теперь к настройке внешнего вида подписей, создаваемых с помощью функции clabel.

Пожалуй, наиболее полезный из дополнительных параметров функции clabel - это именованный параметр colors, который задает цвета для подписей линий уровня. Этот параметр работает аналогично параметру colors для функций contour / contourf, который был описан в предыдущем разделе. Параметр colors может быть равен None (значение по умолчанию), в этом случае метки линий уровня будут рисоваться теми же цветами, что и соответствующая линия. Параметр colors может быть равен строке, задающий цвет, в этом случае все метки будут отображаться одинаковым цветом. Также параметр colors может быть равен кортежу аргументов, описывающих цвета, в этом случае метки будут рисоваться цветами из кортежа, а цвета будут сменять друг друга по очереди.

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

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z)
    pylab.clabel(cs, colors="#000000")

    pylab.show()

Того же эффекта мы бы добились, если передали параметру colors значения black или просто k.

Если вам не нравится, что метки прерывают собой линии уровня, то в функцию clabel можно передать именованный параметр inline, равный False как показано в следующем примере:

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z)
    pylab.clabel(cs, colors="black", inline=False)

    pylab.show()

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

Еще один полезный параметр функции clabel - это именованный параметр fmt, который задает формат чисел на графике. В простейшем случае параметр fmt может быть строкой, задающий формат вывода чисел. По умолчанию установлен вывод числа с тремя знаками после запятой, что соответствует строке "%1.3f".

Зададим параметр fmt, таким образом чтобы перед значениями было написано "x=", а числа выводились с двумя знаками после запятой.

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z)
    pylab.clabel(cs, colors="black", fmt='x=%.2f')

    pylab.show()

Теперь наш график выглядит следующим образом:

Именованный параметр fmt в качестве значения может принимать словарь, в котом ключ - это величина функции для линии уровня, а значение - строка, которая будет выведена для данного значения линии уровня. При этом обязательно словарь должен включать все значения для линий уровня, которые есть на графике, иначе вы получите исключение KeyError при попытке Matplotlib обратиться к несуществующему элементу словаря.

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

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

import pylab
import numpy


def makeData():
    x = numpy.arange(-10, 10, 0.05)
    y = numpy.arange(-10, 10, 0.05)
    xgrid, ygrid = numpy.meshgrid(x, y)

    zgrid = (numpy.sin(xgrid * 0.3) * numpy.cos(ygrid * 0.75) /
             (1 + numpy.abs(xgrid * ygrid) * 0.05))
    return xgrid, ygrid, zgrid


if __name__ == '__main__':
    x, y, z = makeData()
    cs = pylab.contour(x, y, z)

    fmt_dict = {0.0: '',
                -0.25: '-0.25',
                0.25: '0.25',
                -0.5: '-0.5',
                0.5: '0.5',
                -0.75: '-0.75 (!)',
                0.75: '0.75 (!)',
                }
    pylab.clabel(cs, colors="black", fmt=fmt_dict)

    pylab.show()

Кроме того, в качестве значения параметра fmt может выступать экземпляр класса matplotlib.ticker.Formatter, про использование этого класса подробно написано в статье Использование библиотеки Matplotlib. Как изменять формат меток на осях.

Рисование линий уровня по произвольно расположенным точкам

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

def makeData():
    x = numpy.random.rand(2000) * 20.0 - 10.0
    y = numpy.random.rand(len(x)) * 20.0 - 10.0
    z = (numpy.sin(x * 0.3) * numpy.cos(y * 0.75) /
         (1 + numpy.abs(x * y) * 0.05))
    return x, y, z

В этом случае мы уже не сможем воспользоваться функцией contour, нам понадобится другая функция - tricontour. Самое главное, что функция tricontour возвращает класс matplotlib.tri.tricontour.TriContourSet, который тоже является производным от класса ContourSet, а это значит, что для оформления меток мы можем использовать уже рассмотренную выше функцию clabel, а оформление самих линий уровня подобно оформлению линий уровня для функции contour. Работа с функцией tricontour с настройками внешнего вида показана в следующем примере:

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

import pylab
import numpy


def makeData():
    x = numpy.random.rand(2000) * 20.0 - 10.0
    y = numpy.random.rand(len(x)) * 20.0 - 10.0
    z = (numpy.sin(x * 0.3) * numpy.cos(y * 0.75) /
         (1 + numpy.abs(x * y) * 0.05))
    return x, y, z


if __name__ == '__main__':
    x, y, z = makeData()
    pylab.rcParams['contour.negative_linestyle'] = 'solid'
    cs = pylab.tricontour(x, y, z, colors='black')

    fmt_dict = {0.0: '',
                -0.25: '-0.25',
                0.25: '0.25',
                -0.5: '-0.5',
                0.5: '0.5',
                -0.75: '-0.75 (!)',
                0.75: '0.75 (!)',
                }
    pylab.clabel(cs, fmt=fmt_dict)

    pylab.show()

Рисунок выглядит несколько искаженным из-за небольшого количества точек. Если увеличить количество точек до 5000, то результат будет более сглаженным:

Заключение

Мы рассмотрели основные функции для рисования линий уровня с помощью библиотеки Matplotlib, а также научились настраивать внешний вид полученного графика. В этой статье мы рассмотрели лишь наиболее часто используемые параметры, но кроме них функции contour, contourf, tricontour и clabel имеют и другие параметры, о которых вы можете прочитать в справке.

На этом все, жду ваших комментариев.

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

Немного рекламы

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

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




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