почему построение графика с помощью 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, но, согласно упомянутой ранее поваренной книге, это был самый быстрый. См. Ссылку о бэкэндах для получения дополнительных опций.


Это работает только в 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')
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.