Графики и карты#

Самым популярным модулем для построения графиков в Python является matplotlib мы будем рассматривать именно её, но есть и другие, в частности plotnine и plotly.

Глобальный холст pyplot, объекты Figure и Axis#

Бекенды#

Модуль matplotlib предоставляет стандартное API для построение графиков и, так называемые, бекенды, которые отвечают за вывод результатов пользователю.

Они бывают как интерактивные (открываются в отдельном окне, позволяют заимодействовать с графиком), так и неинтерактивные, результатом работы которых является растровое или вектороное изображение. В средах использующих IPython (например Spyder и Jupyter) имеется дополнительный бекенд inline, для вывода графиков в среду. Неинтерактивные бекенды удобны для создания большого числа графиков.

Выбор бекенда осуществляется в настройках среды или в коде:

import matplotlib

Глобальный объект pyplot#

Модуль matplotlib содержит глобальный объект pyplot, который выступает в роли стандартного холста и в большинстве примеров импортируется как plt:

import numpy as np
import matplotlib.pyplot as plt

# рисуем
plt.plot([1,2,3],[5,3,7])

# отображаем или сохраняем
plt.show()
# plt.savefig('filename.png')
_images/3dea9e1d628cdf1ad768d265d8a84d9f39f307731ea9f44ce8d0e7a8835f575a.png

Для сохранение графиков целесообразно использовать векторные форматы, такие как: .svg, .eps или растровые без потерь: .png, .tiff.

При запуске по отдельным ячейкам среда (например Spyder или Jupyter) автоматически вызывает plt.show(), если в ячейке были вызовы функций рисования, но результат не был отображен или сохранен явно (через show() или savefig()).

После отображения или сохранения изображения в файл глобальный объект pyplot очищается.

Рисование с использованием объекта plt#

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0+0.01, 0.01)
v = 1 + np.sin(2*np.pi*t)

plt.plot(t, v, color="blue", label="v(t)")
plt.xlabel('Время, с')
plt.ylabel('Значение, мВ')
plt.title('Зависимость напряжения от времени')
plt.grid()
plt.legend()
plt.show()
_images/2cc3160d5ab4d892f7b2a09146b1e55f72387011db49534fdfb584553ad8c28a.png

Рисование с использованием объектов Axis и Figure#

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0+0.01, 0.01)
v = 1 + np.sin(2*np.pi*t)

fig = plt.figure()
ax = fig.add_subplot()
ax.plot(t, v, color="blue", label="v(t)")
ax.set_xlabel('Время, с')
ax.set_ylabel('Значение, мВ')
ax.set_title('Зависимость напряжения от времени')
ax.grid()
ax.legend();
_images/2cc3160d5ab4d892f7b2a09146b1e55f72387011db49534fdfb584553ad8c28a.png

Построение графиков X-Y#

Анатомия графика#

Анотомия графика

Популярные типы графиков для точек#

Метод

Назначение

plot(x, y)

Кривая

scatter(x, y)

Точки

errorbar(x, y, yerr, xerr)

Точки с усами ошибок

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0+0.01, 0.01)
v = 1 + np.sin(2*np.pi*t)

t2 = np.arange(0.0, 2.0+0.1, 0.1)
v2 = 1 + np.sin(2*np.pi*t2) + np.random.random(t2.shape)-0.5

fig = plt.figure()
ax = fig.add_subplot()
ax.plot(t, v, color="blue", label="v(t)")
ax.scatter(t2, v2, color="red", label="v[t]", zorder=2.5)
ax.errorbar(t2, v2, 0.5 + 0.5*(np.random.random(t2.shape)-0.5), linestyle="", color="red", capsize=2)
ax.set_xlabel('Время, с')
ax.set_ylabel('Значение, мВ')
ax.set_title('Зависимость напряжения от времени')
ax.grid()
ax.legend();
_images/9e596a729c398482dbaea7162f1a2364d4762af887416552452c64d90649b699.png

Некоторые аргументы plot и scatter#

Аргумент

Назначение

Примеры значений

color

Цвет линии

"black", "#f000f0"

label

Текст в легенде

"синус икс"

linestyle

Стиль линии

"", "-", "--", "dotted"

linewidth

Ширина линии

6.5

marker

Форма маркера

"", "+", "o", "4"

markerfacecolor

Цвет маркера

"black", "#f000f0"

markeredgecolor

Цвет обводки маркера

"black", "#f000f0"

markeredgewidth

Ширина обводки маркера

0.5

markersize

Размер маркера

2.5

Вспомогательные функции - надписи и подписи#

Функция

Назначение

Пример использования

ax.set_xlabel

Подпись оси X

plt.xlabel("Подпись Х")

ax.set_ylabel

Подпись оси Y

plt.ylabel("Подпись Y")

ax.set_title

Заголовок графика

plt.title("Заголовок")

ax.legend

Показать легенду

plt.legend(loc='upper left')

ax.annotate

Добавить аннотацию к точке xy

ax.annotate('Максимум', xy=(2, 1))

В тексте можно использовать команды на TeXе окружая их знаками долларов: $4\pi^2$.

Вспомогательные функции - оси#

Функция

Назначение

Пример использования

ax.set_xlim

Границы по оси X

plt.xlim(-4.0, 4.0)

ax.set_ylim

Границы по оси Y

plt.ylim(-3.0, 10.0)

ax.set_xticks

Отсчеты по оси X

plt.xticks([1, 2, 3, 4, 5])

ax.set_yticks

Отсчеты по оси Y

plt.yticks([1,2,3,], ['1a','2a','3a'])

ax.grid

Включить сетку

plt.grid(True)

ax.hlines

Горизонтальные линии

ax.hlines(3, -10, 10)

ax.vlines

Вертикальные линии

ax.vlines(3, -10, 10)

Линия тренда#

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot()

x = np.random.random(100)
y = 3*x + x*x*np.random.normal(1, 0.5, x.shape)
ax.scatter(x, y, alpha=0.5)

m, b = np.polyfit(x, y, deg=1)

ax.axline(xy1=(0, b), slope=m, color='r', label=f'$y = {m:.2f}x {b:+.2f}$')

a2, a1, a0 = np.polyfit(x, y, deg=2)

def foo(x):
    return a2*x**2 + a1*x + a0

q = np.linspace(0,1,20)

ax.plot(q,foo(q), color='b', label=f'$y = {a2:.2f}x^2 {a1:+.2f}x {a0:+.2f}$')

plt.legend()
plt.show()
_images/0a465bd4ad0e9af192312cbdd6f309d04792b0e8e526f961bab3c7ce194281d6.png

Построение теплокарт и контуров#

Метод

Назначение

Пример

plt.imshow

Рисование теплокарты

plt.imshow(data, extent=[-3, 3, -3, 3], vmin=-1, vmax=1)

plt.contour

Рисование контуров (линии)

cnt_data = plt.contour(data, levels = [1,2,3], colors = 'white')

plt.contourf

Рисование контуров (заливка)

cnt_data = plt.contour(data, levels = [1,2,3], colors = 'white')

Для построения теплокарты необходимо иметь двумерный массив с данными и его extent - привязку углов массива к координатам.

data = np.random.random((6,6))

data[0,0] = 2

fig = plt.figure()
ax = fig.add_subplot()

im = ax.imshow(data, extent=[-3, 3, -3, 3], vmin=-1, vmax=2, origin='lower', cmap='YlGnBu')
fig.colorbar(im);
_images/9670e378d6b5672dd77fafba0028ed89e534cfe258e7b995064f2416638b88c7.png

Для построения контуров необходимо иметь: два одномерных массива с равномерными интервалами между точками и двумерный массив с данными. Ключевые методы plt.contour и plt.contourf.

# Источник данных — функция двух переменных
def f(x, y):
    return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-x ** 2 -y ** 2)

# Создаем массивы для осей:
n = 256
x = np.linspace(-3, 3, n)
y = np.linspace(-3, 3, n)

# Делаем мешгрид
gridX, gridY = np.meshgrid(x, y)
data = f(gridX, gridY)

#Раскрашивание контуров цветами (палитра 'jet')
cntrf = plt.contourf(gridX, gridY, data, 8, alpha=.75, cmap='jet')

#Разграничение контуров линиями 
cntr = plt.contour(gridX, gridY, data, 8, colors='black')

#Добавляем подписи на контуры
plt.clabel(cntr, inline=1, fontsize=10)

#Добавляем цветовую легенду
plt.colorbar(cntrf);
_images/7eddbaa3465e7b5379105d75977636f76a66bbeabace332966f659ca8afba96f.png

Управление расположением цветовой шкалы#

from mpl_toolkits.axes_grid1 import make_axes_locatable

data = np.random.random((6,6))
data[0,0] = 2
fig = plt.figure()
ax = fig.add_subplot()
im = ax.imshow(data, extent=[-3, 3, -3, 3], vmin=-2, vmax=2, origin='lower', cmap='magma')

# находим нашу систему коордит в рамках Figure
divider = make_axes_locatable(ax)
# определяем новую область для рисования цветовой шкалы
color_ax = divider.append_axes("bottom", size="8%", pad="10%")
# добавляем к фигуре новую область для цветовой шкалы
fig.add_axes(color_ax)
#рисуем шкалу
fig.colorbar(im, cax=color_ax, orientation="horizontal");
_images/936943426623222af658a97a6c52e0817d708e25ed175a0a9a6956908df9fd63.png

Построение гистограмм и столбчатых диаграмм#

Метод

Назначение

Пример

plt.hist

Гистограмма

plt.hist(x, np.arange(0,3,1/5))

plt.bar

Столбчатая диаграмма

plt.bar(ind, values, width)

data = np.random.normal(0,1,300)

fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(1,2,1)

# Простая гистограмма
ax.hist(data, bins=10, color="red");

# Расчитанная гистограмма, hist - значение в столбце, bin_edges - границы столбцов
hist, bin_edges = np.histogram(data, bins=20)
ax.bar(bin_edges[:-1], hist, 0.25, color="blue")

import scipy

# Ядерная оценка плотности
kde = scipy.stats.gaussian_kde(data)
xtx = np.linspace(-3,3,100)

ax2 = fig.add_subplot(1,2,2)

ax2.plot(xtx, kde(xtx), color="purple", linewidth=3);
_images/c7068ffa464f0e61cf090bb100d35c7edfbbe6f87249965c8da17b9f582000fa.png
t = np.arange(0.0, 1.0+0.1, 0.1)
v = 1 + np.sin(2*np.pi*t)

fig = plt.figure()
ax = fig.add_subplot()

ax.bar(t,v,0.09);
_images/da01bdd59cfcf4df22466abd74b0382bdbe857cba0535ab085a8ab734d498b89.png

Специальные виды графиков#

Диаграммы потоков#

import numpy as np
from findiff import FinDiff, Gradient, Divergence, Laplacian
import matplotlib.pyplot as plt

borders = [-2*np.pi, 2*np.pi, -2*np.pi, 2*np.pi]

x = np.linspace(-2*np.pi, 2*np.pi, 30)
dx = x[1]-x[0]
y = np.linspace(-2*np.pi, 2*np.pi, 30)
dy = y[1]-y[0]

X, Y = np.meshgrid(x, y, indexing='ij')
f = Y*np.sin(1/2*X) + np.cos(Y/4)

grad = Gradient(h=[dx, dy])
grad_f = grad(f)

fig = plt.figure(figsize=(12,4))
ax = fig.add_subplot(1,2,1)

im = ax.imshow(f.T ,  origin='lower', extent=borders, cmap="magma_r")
fig.colorbar(im)
ax.quiver(X,Y,grad_f[0],grad_f[1], color="green")

d_dx = FinDiff(0, dx, 1)
d_dy = FinDiff(1, dy, 1)

ax2 = fig.add_subplot(1,2,2)
ax2.streamplot(x,y,grad_f[0].T,grad_f[1].T);
_images/1f0f5af1b6bd2b6f567f8f942481ce1a572ccd3f1c112d47b3b063094119336c.png

Логорифмические оси и полярные координаты#

Функция

Назначение

Пример использования

ax.set_xscale

Масштаб по оси X

ax.set_xscale("log")

ax.set_yscale

Масштаб по оси Y

ax.set_yscale("linear")

Создать систему полярных координат

fig.add_subplot(projection='polar')

t = np.linspace(0.0, 1.5, 50)
v = 1 + np.sin(2*np.pi*t)

fig = plt.figure(figsize=(9,3))
ax = fig.add_subplot(1,2,1)
ax.plot(t, v)
ax.grid(True, which='both');

ax = fig.add_subplot(1,2,2)
ax.plot(t, v)
ax.set_xscale('log')
ax.set_yscale('log')
ax.grid(True, which='both');
_images/4f4461e43222b3331f8119aabeee8c00169d25ae579635af6135eb42f5e2edda.png
import matplotlib.pyplot as plt
import numpy as np

r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r

fig = plt.figure(figsize=(9,3))

ax1 = fig.add_subplot(1,2,1)
ax1.plot(theta, r)
ax1.grid(True);

ax2 = fig.add_subplot(1,2,2, projection='polar')
ax2.plot(theta, r)
ax2.set_rmax(2)
ax2.set_rticks([0.5, 1, 1.5, 2])
ax2.grid(True);
_images/881967aa32428201d68c25147596d1689a448b951c68039daf94e37b4a5f01c5.png

Несколько систем координат на одном рисунке#

Для построения несолькоих систем координат будем использовать matplotlib.gridspec.

# Задаем сетку для деления 2 строки 3 столбца
grsp =  matplotlib.gridspec.GridSpec(2, 3)

fig = plt.figure()

# Получаем указатели на отельные оси с помощью слайсов
axis1 = fig.add_subplot(grsp[0, :]) # Вся верхняя строка
axis2 = fig.add_subplot(grsp[1, 0:2]) # Нижняя строка две левых клетки
axis3 = fig.add_subplot(grsp[1, 2]) #  Нижняя строка правый угол

t = np.arange(0.0, 1.0+0.1, 0.01)
v = 1 + np.sin(2*np.pi*t)

axis1.plot(t,v)
axis2.plot(t,v)
axis3.plot(t,v)
axis3.set_yticks([]);
_images/c0313a69376edf7c33bcf9e27415a55a99504f36e3c5631578fd3b9e3cd97aa5.png

Карты#

Раньше для построения карт использовался модуль basemap, но он устарел и более не поддерживается. На замену ему пришел модуль cartopy.

Для выбора нужной проекции используем документацию.

Рисование данных поверх карты выполняется обычными методами, например plot с добавлением параметра transform если координаты указаны, как долгота и широта в градусах.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy import crs as ccrs

fig = plt.figure()

ax = fig.add_subplot(projection=ccrs.PlateCarree())

ax.set_extent([-6, 3, 48, 58], crs=ccrs.PlateCarree())

gl = ax.gridlines(draw_labels=True)
gl.xlocator = mticker.FixedLocator(np.arange(-6.0,3.0,2.0))
gl.ylocator = mticker.FixedLocator(np.arange(48.0,58.0,2.0))

ax.scatter([-2],[52], transform=ccrs.PlateCarree())

ax.coastlines(resolution='50m');
_images/61d7eed5a6494c14af5645bcc2833e02ceeca07fa42c4fbe42c10e3e4d60d174.png

Картографические данные Natural Earth#

Картографические данные cartopy берет из проекта Natural Earth.

Какие именно картографические данные доступны см. [по ссылке] (https://github.com/nvkelso/natural-earth-vector) в наборах cultural и physical. Карты автоматически скачиваются по мере надобности, при этом в консоль выводится предупреждение DownloadWarning.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy import crs as ccrs
import cartopy.feature

fig = plt.figure()

ax = fig.add_subplot(projection=ccrs.PlateCarree())

ax.set_extent([-6, 3, 38, 48], crs=ccrs.PlateCarree())

gl = ax.gridlines(draw_labels=True)
gl.xlocator = mticker.FixedLocator(np.arange(-6.0,3.0,2.0))
gl.ylocator = mticker.FixedLocator(np.arange(38.0,48.0,2.0))

resol = '50m' # еще есть '110m' - самая грубая версия и '10m' - самая точная версия
bodr = cartopy.feature.NaturalEarthFeature(category='cultural', 
    name='admin_0_boundary_lines_land', scale=resol, facecolor='none', alpha=0.7)
land = cartopy.feature.NaturalEarthFeature('physical', 'land', \
    scale=resol, edgecolor='k', facecolor=cartopy.feature.COLORS['land'])
ocean = cartopy.feature.NaturalEarthFeature('physical', 'ocean', \
    scale=resol, edgecolor='none', facecolor=cartopy.feature.COLORS['water'])
lakes = cartopy.feature.NaturalEarthFeature('physical', 'lakes', \
    scale=resol, edgecolor='b', facecolor=cartopy.feature.COLORS['water'])
rivers = cartopy.feature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', \
    scale=resol, edgecolor='b', facecolor='none')

ax.add_feature(land)
ax.add_feature(ocean)
ax.add_feature(lakes)
ax.add_feature(rivers)
ax.add_feature(bodr, linestyle='--', alpha=1);
_images/0b26b98490b43d78617f5ab309638c9c6444fd3c70e0d0d7eb266ff63a17fc0a.png

Использование Tile-серверов в cartopy#

import matplotlib.pyplot as plt
from cartopy import crs as ccrs
from cartopy.io.img_tiles import GoogleWTS, OSM, GoogleTiles


def make_map_from_url(url):
    class URLMAP(GoogleWTS):
        def _image_url(self, tile):
            x, y, z = tile
            return url.format(x=x,y=y,z=z)
    return URLMAP()

#Спутниковые снимки

#Спутник ESRI
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}')

#Спутник Yandex
#tiles_provider = make_map_from_url('https://core-sat.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}')

#Карты

#OpenStreetMap
tiles_provider = OSM()

#OpenStreetMap CycloSM
#tiles_provider = make_map_from_url('https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png')

#Карта Yandex
#tiles_provider = make_map_from_url('https://core-renderer-tiles.maps.yandex.net/tiles?l=map&x={x}&y={y}&z={z}&scale=2&lang=ru-RU')

#Карта Google
#tiles_provider = GoogleTiles()

#Карта ESRI Street
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}')

#Карта ESRI Topo
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}')

#TopoMapper
#tiles_provider = make_map_from_url('https://proxy.nakarte.me/http/88.99.52.155/cgi-bin/tapp/tilecache.py/1.0.0/topomapper_v2/{z}/{x}/{y}.jpg')

#Без глобального охвата

#Генштаб 10km
#tiles_provider = make_map_from_url('https://c.tiles.nakarte.me/topo001m/{z}/{x}/{y}')

#Генштаб 1km
#tiles_provider = make_map_from_url('https://b.tiles.nakarte.me/topo001m/{z}/{x}/{y}')

#Прочее от ESRI

#Рельеф с тенями
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}')

#Суша с тенями
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}')

#Физическая карта
#tiles_provider = make_map_from_url('https://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}')

plt.figure(figsize=(10, 10))

ax = plt.axes(projection=tiles_provider.crs)

ax.set_extent([-6, 3, 38, 48], ccrs.PlateCarree())

ax.add_image(tiles_provider, 6);
_images/c116f929f5de26e163581263895f30921953205739684009069347c6b0c6a118.png

Некоторые приемы работы с cartopy#

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy import crs as ccrs

fig = plt.figure()

ax = fig.add_subplot(projection=ccrs.PlateCarree())

ax.set_extent([-6, 3, 48, 58], crs=ccrs.PlateCarree())

# Скрытие части подписей по осям:
gridlines = ax.gridlines(draw_labels=True)
gridlines.top_labels = False
gridlines.right_labels = False

# Добавление теплокарты:
data = np.random.random((6,6))
im = ax.imshow(data,  origin='upper', extent =[-6, 3, 48, 58], transform=ccrs.PlateCarree()) 
fig.colorbar(im, ax=ax)
ax.coastlines(resolution='50m')

#Добавление точки с подписью:
chords = np.array([[0.1],[51.5]])

ax.scatter(chords[0],chords[1], color="red", transform=ccrs.PlateCarree())

mpl_trans = ccrs.PlateCarree()._as_mpl_transform(ax)
ax.annotate('LONDON',(chords[0,0]+0.2,chords[1,0]+0.2), color="red", xycoords=mpl_trans);
_images/6f3dfc73d42ad5ee6599efd3c768e044f6ee36738d141d600bc1f0805f95168f.png

Построение карт из NetCDF файлов#