почему построение графика с помощью Matplotlib такое медленное?

102

В настоящее время я оцениваю различные библиотеки построения графиков Python. Прямо сейчас я пробую matplotlib и очень разочарован производительностью. Следующий пример модифицирован из примеров SciPy и дает мне только ~ 8 кадров в секунду!

Есть ли способы ускорить это или мне следует выбрать другую библиотеку для построения графиков?

from pylab import *
import time

ion()
fig = figure()
ax1 = fig.add_subplot(611)
ax2 = fig.add_subplot(612)
ax3 = fig.add_subplot(613)
ax4 = fig.add_subplot(614)
ax5 = fig.add_subplot(615)
ax6 = fig.add_subplot(616)

x = arange(0,2*pi,0.01)
y = sin(x)
line1, = ax1.plot(x, y, 'r-')
line2, = ax2.plot(x, y, 'g-')
line3, = ax3.plot(x, y, 'y-')
line4, = ax4.plot(x, y, 'm-')
line5, = ax5.plot(x, y, 'k-')
line6, = ax6.plot(x, y, 'p-')

# turn off interactive plotting - speeds things up by 1 Frame / second
plt.ioff()


tstart = time.time()               # for profiling
for i in arange(1, 200):
    line1.set_ydata(sin(x+i/10.0))  # update the data
    line2.set_ydata(sin(2*x+i/10.0))
    line3.set_ydata(sin(3*x+i/10.0))
    line4.set_ydata(sin(4*x+i/10.0))
    line5.set_ydata(sin(5*x+i/10.0))
    line6.set_ydata(sin(6*x+i/10.0))
    draw()                         # redraw the canvas

print 'FPS:' , 200/(time.time()-tstart)
Мне самому
источник
Следующее может иметь отношение: stackoverflow.com/questions/5003094/…
NPE
2
@aix - Glumpy помог только в этом примере, потому что он имел дело с быстрым отображением данных изображения. В этом случае это не поможет.
Джо Кингтон
1
Попробуйте сменить бэкэнд. См. Мой ответ: stackoverflow.com/a/30655528/2066079 . или этот FAQ о бэкэндах
dberm22,

Ответы:

115

Во-первых, (хотя это никак не повлияет на производительность) подумайте о том, чтобы очистить свой код, примерно так:

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.01)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)
styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
lines = [ax.plot(x, y, style)[0] for ax, style in zip(axes, styles)]

fig.show()

tstart = time.time()
for i in xrange(1, 20):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    fig.canvas.draw()

print 'FPS:' , 20/(time.time()-tstart)

В приведенном выше примере я получаю около 10 кадров в секунду.

Небольшое примечание: в зависимости от конкретного случая использования matplotlib может быть не лучшим выбором. Он ориентирован на показатели качества публикации, а не на отображение в реальном времени.

Однако есть много вещей, которые вы можете сделать, чтобы ускорить этот пример.

Есть две основные причины, по которым это происходит так медленно.

1) Вызов всеfig.canvas.draw() перерисовывает . Это ваше узкое место. В вашем случае вам не нужно перерисовывать такие вещи, как границы осей, метки и т. Д.

2) В вашем случае есть много подзаголовков с множеством меток. На их рисование уходит много времени.

И то, и другое можно исправить с помощью блиттинга.

Чтобы выполнять блиттинг эффективно, вам придется использовать код, специфичный для серверной части. На практике, если вы действительно беспокоитесь о плавной анимации, вы в любом случае обычно встраиваете графики matplotlib в какой-то набор инструментов gui, так что это не большая проблема.

Однако, не зная больше о том, что вы делаете, я не могу вам помочь.

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

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]

tstart = time.time()
for i in xrange(1, 2000):
    items = enumerate(zip(lines, axes, backgrounds), start=1)
    for j, (line, ax, background) in items:
        fig.canvas.restore_region(background)
        line.set_ydata(np.sin(j*x + i/10.0))
        ax.draw_artist(line)
        fig.canvas.blit(ax.bbox)

print 'FPS:' , 2000/(time.time()-tstart)

Это дает мне ~ 200 кадров в секунду.

Чтобы сделать это немного более удобным, animationsв последних версиях matplotlib есть модуль.

Например:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

def animate(i):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    return lines

# We'd normally specify a reasonable "interval" here...
ani = animation.FuncAnimation(fig, animate, xrange(1, 200), 
                              interval=0, blit=True)
plt.show()
Джо Кингтон
источник
ваш код действительно очень быстрый, но я получаю 2000 строк на ось! каким-то образом "line.set_ydata" создает новую строку вместо ее обновления - или фон просто не очищается? Кроме того, почему ваша версия намного быстрее? только потому, что вы отказались от «draw ()» и заменили его на «ax.draw_artist»?
Memyself
В каком примере? (Я тестировал их, но, возможно, скопировал неправильную версию в ответ.) Кроме того, какую версию matplotlib вы используете?
Джо Кингтон
4
вот ссылка на получившееся изображение i.imgur.com/aBRFz.png, может быть, это артефакт, вызванный моей видеокартой?
Memyself
7
Я видел то же самое, что и я, в i.imgur.com/aBRFz.png, пока я не переместил фоновый снимок ниже fig.show ().
Майкл Браун
4
Хорошо, но, animationкажется, обновляет график по intervalпериоду времени, что, если я просто хочу обновить его, когда будут готовы новые данные?
Alcott
29

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

Люк
источник
1
Мне очень нравится pyqtgraph.org/documentation для потоковых данных в реальном времени. отличная работа,
luke
11

Для начала ответ Джо Кингтона дает очень хороший совет с использованием подхода, нейтрального к графическому интерфейсу, и вы обязательно должны принять его совет (особенно в отношении Blitting) и применить его на практике. Подробнее об этом подходе читайте в Поваренной книге Matplotlib.

Однако подход, не связанный с графическим интерфейсом пользователя (ориентированный на графический интерфейс?), Является ключом к ускорению построения графика. Другими словами, серверная часть чрезвычайно важна для скорости построения.

Поместите эти две строки, прежде чем импортировать что-либо еще из matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

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

dberm22
источник
Это работает только в Windows, знаете ли вы, как заставить его работать на Mac. Причина, по которой он специфичен для Windows, заключается в том, что pygtk специфичен для Windows
user308827
2
pygtk не зависит от Windows. На самом деле, заставить его работать под Windows - огромная боль (если это вообще возможно, я сдался)
Джозеф Редферн
7

Для первого решения, предложенного Джо Кингтоном (.copy_from_bbox & .draw_artist & canvas.blit), мне пришлось снимать фон после строки fig.canvas.draw (), иначе фон не имел никакого эффекта, и я получил тот же результат, что и ты упомянул. Если вы поместите его после fig.show (), он все равно не будет работать, как было предложено Майклом Брауном.

Так что просто поместите фоновую строку после canvas.draw ():

[...]
fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]
Себастьян
источник
4
вам следует просто отредактировать его ответ, а не публиковать его как отдельный
endolith
1

Это может не относиться ко многим из вас, но я обычно работаю на своих компьютерах под Linux, поэтому по умолчанию я сохраняю свои графики matplotlib как PNG и SVG. Это отлично работает под Linux, но невыносимо медленно на моих установках с Windows 7 [MiKTeX под Python (x, y) или Anaconda], поэтому я решил добавить этот код, и там снова все работает нормально:

import platform     # Don't save as SVG if running under Windows.
#
# Plot code goes here.
#
fig.savefig('figure_name.png', dpi = 200)
if platform.system() != 'Windows':
    # In my installations of Windows 7, it takes an inordinate amount of time to save
    # graphs as .svg files, so on that platform I've disabled the call that does so.
    # The first run of a script is still a little slow while everything is loaded in,
    # but execution times of subsequent runs are improved immensely.
    fig.savefig('figure_name.svg')
Марисано
источник