Инфраструктура Python. Визуализация

Рабочей лошадкой для графиков является библиотека matplotlib (входит в Anaconda), воспроизводящая графические возможности Matlab. Галерея примеров http://matplotlib.org/gallery.html

Библиотека seaborn (тоже входит в Anaconda) содержит несколько полезных видов графиков http://seaborn.pydata.org/examples/index.html и больше заточена под статистические применения. До версии 0.8.0 она при импорте заменяла дефолтный стиль matplotlib на несколько более эстетичный, но затем matplotlib огламурили и теперь для того же эффекта нужен явный вызов seaborn.set().

Функции plot и scatter принимают иксы и игреки отображаемых точек и дополнительные аргументы стиля отображения. При этом plot рисует линию между ними в том же порядке, в котором они идут в массиве. Прочее на графике (оси, подписи, надписи на графике, риски на осях и подписи к ним, логарифмический или линейный масштаб) настраивается дополнительными вызовами.

In [3]:
import IPython, pandas, seaborn
%pylab inline
Populating the interactive namespace from numpy and matplotlib

Магия axes().set_aspect('equal') устанавливает равный масштаб по осям.

In [8]:
axes().set_aspect('equal')
plot([1,2,3,5], [0,1,0,0.5]);

Заголовок графика title. Подписи осей xlabel и ylabel, риски на осях xticks и yticks. Текст на графике text. Метки графиков параметр label=, подпись с именами меток legend.

In [316]:
xx = linspace(0, 10)

title(u'Синус $\sin{x}$ и косинус $\cos{x}$')

xticks(range(0, 11), ['*%d*' % i for i in range(0, 11)])
yticks(arange(-1, 1.01, 1./3))
xlabel('x')
ylabel('y')

plot(xx, sin(xx), label='sin');
text(6, 0, '$y = \sin{x}$', fontsize=20, rotation=73)

plot(xx, cos(xx), '*m--', label='cos')

legend(frameon=False);

Магия '*m--' задает тип маркера marker *, цвет color m (magenta) и тип линии linestyle --. Полный список https://matplotlib.org/api/pyplot_api#matplotlib.pyplot.plot

Логарифмический масштаб по осям xscale и yscale. Пределы графика xlim и ylim. Сетка параллельно осям grid.

In [4]:
xx = linspace(0, 10)
yscale('log')
xlim([0,10])
grid(True, axis='y')
plot(xx, xx ** 3);

Параметрические графики не требуют никаких новых знаний. Фигура Лиссажу

In [6]:
tt = linspace(0, 100, 10000)
plot(sin(tt * 4), cos(tt * 3));

График в полярных координатах — параметр projection='polar' у subplot. Полярная роза

In [5]:
theta = arange(0, 2 * pi, 0.01)
r = cos(5 * theta)

subplot(111, projection='polar')
title("Polar axis", fontsize=18)
plot(theta, r)
grid(True)

Для выделения некоторой координаты служит axvline и axhline, интервала координат axvspan и axhspan, направления или точки — arrow и annotate.

In [7]:
xlim([0,2])
ylim([0,2])
arrow(1.0, 0.5, 0.3, 1, length_includes_head=True, width=0.02, color='k')
axvspan(1.2, 1.4, color='yellow', alpha=0.5)
axvline(x=1, color='#808080', linestyle='--', lw=1)
axhline(y=0.5, color=(0.5,0.5,0.5), linestyle='--');
In [7]:
xx = linspace(0, 10)
yy = sin(xx)

annotate('end of sin period', xy=(2*pi, 0), xytext=(7, -0.5), arrowprops={'facecolor': 'black'})
plot(xx, yy);

Для выделения области служат fill_between и fill_betweenx.

In [22]:
title('fill_between')
xlim([0,1.2])
xx = linspace(0, 1.2)
plot(xx, xx ** 2);
plot(xx, xx ** 3);
fill_between(xx, xx ** 2, xx ** 3, alpha=0.3);

Визуализировать точки на плоскости можно с помощью scatter или hexbin.

In [15]:
seed(0)
xx = r_[randn(1000), randn(1000) * 0.4 + 2]
yy = r_[randn(1000), randn(1000) * 0.8 + 0.5]
col = r_[zeros(1000), ones(1000)]

title('scatter')
axes().set_aspect('equal')
scatter(xx, yy, alpha=0.4, c=col, cmap=cm.RdYlGn_r);
In [9]:
seed(0)
xs = [randint(100) for i in range(100)]
ys = [randint(100) for i in range(100)]
sizes = [randint(200) for i in range(100)]
colors = [randint(10) for i in range(100)]
scatter(xs, ys, s=sizes, c=colors, alpha=0.9, cmap='autumn')
axis('scaled');
In [13]:
title('hexbin')
xlim(min(xx), max(xx))
ylim(min(yy), max(yy))
axes().set_aspect('equal')
hexbin(xx, yy, gridsize=20, cmap='inferno')
colorbar();

Это пример «анатомия графика» из документации к matplotlib, описывающий его компоненты

In [38]:
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator, MultipleLocator, FuncFormatter

np.random.seed(19680801)

X = np.linspace(0.5, 3.5, 100)
Y1 = 3+np.cos(X)
Y2 = 1+np.cos(1+X/0.75)/2
Y3 = np.random.uniform(Y1, Y2, len(X))

fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(1, 1, 1, aspect=1)


def minor_tick(x, pos):
    if not x % 1.0:
        return ""
    return "%.2f" % x

ax.xaxis.set_major_locator(MultipleLocator(1.000))
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
ax.yaxis.set_major_locator(MultipleLocator(1.000))
ax.yaxis.set_minor_locator(AutoMinorLocator(4))
ax.xaxis.set_minor_formatter(FuncFormatter(minor_tick))

ax.set_xlim(0, 4)
ax.set_ylim(0, 4)

ax.tick_params(which='major', width=1.0)
ax.tick_params(which='major', length=10)
ax.tick_params(which='minor', width=1.0, labelsize=10)
ax.tick_params(which='minor', length=5, labelsize=10, labelcolor='0.25')

ax.grid(linestyle="--", linewidth=0.5, color='.25', zorder=-10)

ax.plot(X, Y1, c=(0.25, 0.25, 1.00), lw=2, label="Blue signal", zorder=10)
ax.plot(X, Y2, c=(1.00, 0.25, 0.25), lw=2, label="Red signal")
ax.plot(X, Y3, linewidth=0,
        marker='o', markerfacecolor='w', markeredgecolor='k')

ax.set_title("Anatomy of a figure", fontsize=20, verticalalignment='bottom')
ax.set_xlabel("X axis label")
ax.set_ylabel("Y axis label")

ax.legend()


def draw_circle(x, y, radius=0.15):
    from matplotlib.patches import Circle
    from matplotlib.patheffects import withStroke
    circle = Circle((x, y), radius, clip_on=False, zorder=10, linewidth=1,
                    edgecolor='black', facecolor=(0, 0, 0, .0125),
                    path_effects=[withStroke(linewidth=5, foreground='w')])
    ax.add_artist(circle)


def draw_text(x, y, text):
    ax.text(x, y, text, backgroundcolor="white",
            ha='center', va='top', weight='bold', color='blue')


# Minor tick
draw_circle(0.50, -0.10)
draw_text(0.50, -0.32, "Minor tick label")

# Major tick
draw_circle(-0.03, 4.00)
draw_text(0.03, 3.80, "Major tick")

# Minor tick
draw_circle(0.00, 3.50)
draw_text(0.00, 3.30, "Minor tick")

# Major tick label
draw_circle(-0.15, 3.00)
draw_text(-0.15, 2.80, "Major tick label")

# X Label
draw_circle(1.80, -0.27)
draw_text(1.80, -0.45, "X axis label")

# Y Label
draw_circle(-0.27, 1.80)
draw_text(-0.27, 1.6, "Y axis label")

# Title
draw_circle(1.60, 4.13)
draw_text(1.60, 3.93, "Title")

# Blue plot
draw_circle(1.75, 2.80)
draw_text(1.75, 2.60, "Line\n(line plot)")

# Red plot
draw_circle(1.20, 0.60)
draw_text(1.20, 0.40, "Line\n(line plot)")

# Scatter plot
draw_circle(3.20, 1.75)
draw_text(3.20, 1.55, "Markers\n(scatter plot)")

# Grid
draw_circle(3.00, 3.00)
draw_text(3.00, 2.80, "Grid")

# Legend
draw_circle(3.70, 3.80)
draw_text(3.70, 3.60, "Legend")

# Axes
draw_circle(0.5, 0.5)
draw_text(0.5, 0.3, "Axes")

# Figure
draw_circle(-0.3, 0.65)
draw_text(-0.3, 0.45, "Figure")

color = 'blue'
ax.annotate('Spines', xy=(4.0, 0.35), xycoords='data',
            xytext=(3.3, 0.5), textcoords='data',
            weight='bold', color=color,
            arrowprops=dict(arrowstyle='->',
                            connectionstyle="arc3",
                            color=color))

ax.annotate('', xy=(3.15, 0.0), xycoords='data',
            xytext=(3.45, 0.45), textcoords='data',
            weight='bold', color=color,
            arrowprops=dict(arrowstyle='->',
                            connectionstyle="arc3",
                            color=color))

ax.text(4.0, -0.4, "Made with http://matplotlib.org",
        fontsize=10, ha="right", color='.5')

plt.show()

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

In [16]:
figure(figsize=(16,4))
subplot(1, 2, 1) # или subplot(121)
grid(True, axis='x')
plot([[1,2,3], [2,3,4], [4,7,8], [8,1,7]])

subplot(1, 2, 2)
xxx = linspace(-1.0, 1.0)
plot(xxx, 1.0 / xxx);
In [55]:
fig = figure()

subplot2grid((3, 3), (0, 0), rowspan=2, colspan=2)

magic1 = lambda x: abs(abs(x+1) % 2.0 - 1)
magic2 = lambda x: 3 * (magic1((x - pi/2)/pi)-1./2)
magic3 = lambda x: x**3-x**2
xs = linspace(-pi, pi, num=200)
r = 6 + magic3(magic2(xs))

gca().set_aspect('equal')
plot(r * cos(xs), r * sin(xs), 'r')

xs = linspace(-10, 10)
subplot2grid((3, 3), (0, 2))
plot(xs, cos(xs));

subplot2grid((3, 3), (2, 0), colspan=3)
plot(xs, xs ** 2);

subplot2grid((3, 3), (1, 2))
plot(xs, xs ** 3);

fig.tight_layout()

Добавить ось с другой стороны twinx, twiny. При этом меняется текущая настраиваемая ось.

In [82]:
xlabel(u'Время, секунды')
xlim([0,10])
twiny()
xlim([0,10])
xticks(range(0, 10 + 1))
xlabel(u'Время, британские имперские секунды', color='b')
xx = linspace(0, 10)
plot(xx, sin(xx));

Оси можно скрывать и переносить

In [665]:
axes().spines['right'].set_color('none')
axes().spines['top'].set_color('none')
axes().xaxis.set_ticks_position('bottom')
axes().spines['bottom'].set_position(('data',0)) # set position of x spine to x=0
axes().yaxis.set_ticks_position('left')
axes().spines['left'].set_position(('data',0))   # set position of y spine to y=0

xx = linspace(-2, 2)
plot(xx, (xx-1)*xx*(xx+1));

Для откладывания по осям меток, связанных с датами, matplotlib поддерживает локаторы и форматтеры в модуле matplotlib.dates.

In [123]:
import dateutil, requests
from io import BytesIO

startdate = datetime.datetime(2013, 1, 1)
enddate = datetime.datetime.now()
url = 'http://export.rbc.ru/free/cb.0/free.fcgi'
params = {
    'period': 'DAILY', 'tickers': 'EUR', 'separator': 'TAB', 'data_format': 'BROWSER', 'header': 1,
    'd1': startdate.day, 'm1': startdate.month, 'y1': startdate.year,
    'd2': enddate.day, 'm2': enddate.month, 'y2': enddate.year
}
r = requests.get(url, params)
df = pandas.read_csv(BytesIO(r.content), sep='\t')
df.head()
Out[123]:
TICKER DATE OPEN HIGH LOW CLOSE VOL WAPRICE NOMINAL
0 EUR 2013-01-10 NaN NaN NaN 39.8096 NaN NaN 1.0
1 EUR 2013-01-11 NaN NaN NaN 39.6385 NaN NaN 1.0
2 EUR 2013-01-12 NaN NaN NaN 40.1104 NaN NaN 1.0
3 EUR 2013-01-15 NaN NaN NaN 40.5009 NaN NaN 1.0
4 EUR 2013-01-16 NaN NaN NaN 40.4003 NaN NaN 1.0
In [134]:
figure(figsize=(16,5))
ax = subplot()
grid(True, which='both')
xlim(map(matplotlib.dates.date2num, [startdate, enddate]))
locator_y = matplotlib.dates.YearLocator()
locator_m = matplotlib.dates.MonthLocator(bymonth=range(3, 11+1, 2))
formatter_y = matplotlib.dates.DateFormatter('%Y')
formatter_m = matplotlib.dates.DateFormatter('%m')
ax.xaxis.set_major_locator(locator_y)
ax.xaxis.set_major_formatter(formatter_y)
ax.xaxis.set_minor_locator(locator_m)
ax.xaxis.set_minor_formatter(formatter_m)
plot(matplotlib.dates.date2num(map(dateutil.parser.parse, df['DATE'])), df['CLOSE']);

Двумерную функцию можно табулировать (рассчитать в каждой точке сетки) и отобразить imshow.

In [201]:
alpha = 0.7
phi_ext = 2 * pi * 0.5

def flux_qubit_potential(phi_m, phi_p):
    return 2 + alpha - 2 * cos(phi_p) * cos(phi_m) - alpha * cos(phi_ext - 2*phi_p)

phi_m = linspace(0, 2*pi, 100)
phi_p = linspace(0, 2*pi, 100)
X,Y = meshgrid(phi_p, phi_m)
Z = flux_qubit_potential(X, Y).T

title('imshow')
imshow(Z, cmap='RdBu', vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1], interpolation='bilinear')
colorbar();

Схожий функционал реализует pcolormesh.

In [14]:
xx, yy = meshgrid(arange(-20, 20, 0.2), arange(-20, 20, 0.2))
zz = sin(xx**2 + yy**2)

figure(figsize=(7, 7))
title('pcolormesh')
axes().set_aspect('equal')
pcolormesh(xx, yy, zz, cmap='autumn');

Для отображения только изолиний подходит contour.

In [13]:
xx, yy = meshgrid(linspace(-5, 5), linspace(-5, 5))
zz = xx ** 2 + yy ** 2

title('contour')
axes().set_aspect('equal')
cs = contour(xx, yy, zz)
clabel(cs);

Визуализация векторного поля streamplot.

In [235]:
X, Y = meshgrid(linspace(-3, 3), linspace(-3, 3))
U = -1 - X**2 + Y
V = 1 + X - Y**2
speed = sqrt(U*U + V*V)
lw = 5*speed / speed.max()

title('streamplot')
streamplot(X, Y, U, V, color=U, linewidth=lw, cmap=cm.autumn)
colorbar();

Столбцы произвольной природы можно отобразить bar и barh.

In [211]:
import scipy.special

n = 10
xx = arange(0, n + 1)

figure(figsize=(16,4))
subplot(121)
title('bar')
p = 0.3
yy = scipy.special.binom(n, xx) * p ** xx * (1 - p) ** (n - xx)
bar(xx, yy);

subplot(122)
title('barh')
p = 0.6
yy = scipy.special.binom(n, xx) * p ** xx * (1 - p) ** (n - xx)
barh(xx, yy);

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

Для просмотра плотности распределения можно использовать hist, step, seaborn.distplot, seaborn.kdeplot.

In [129]:
seed(0)
xlim(-1, 2)
hist(random.random((500,1)), bins=30, range=(-1, 2))
hist(random.random((200,1)), bins=30, range=(-1, 2), color='r');
In [191]:
seed(0)
rnd_dist = randn(100)

figure(figsize=(16,4))

subplot(1, 4, 1)
title('hist')
hist(rnd_dist)

subplot(1, 4, 2)
title('step')
data, _ = histogram(rnd_dist, bins=20)
step(range(len(data)), data)

subplot(1, 4, 3)
title('kdeplot')
seaborn.kdeplot(rnd_dist)

subplot(1, 4, 4)
title('distplot')
seaborn.distplot(rnd_dist, rug=True);

Функция kdeplot умеет рисовать и плотности двухмерных выборок.

In [236]:
seed(0)
X, Y = randn(2, 300)
title('kdeplot')
seaborn.kdeplot(X, Y, cmap='inferno', shade=True, cbar=True)
gca().set_aspect('equal');

Посмотреть на двухмерную выборку можно и через seaborn.jointplot. Он создает свою figure, так что их нельзя так просто взять и сунуть в subplot.

In [203]:
seaborn.jointplot(X, Y);
seaborn.jointplot(X, Y, kind='kde');