Как удалить строки в графике Matplotlib

85

Как я могу удалить строку (или строки) осей matplotlib таким образом, чтобы она фактически собирала мусор и освобождала память обратно? Приведенный ниже код, кажется, удаляет строку, но никогда не освобождает память (даже при явных вызовах gc.collect())

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e7))
# large so you can easily see the memory footprint on the system monitor.
fig = pyplot.Figure()
ax  = pyplot.add_subplot(1, 1, 1)
lines = ax.plot(a) # this uses up an additional 230 Mb of memory.
# can I get the memory back?
l = lines[0]
l.remove()
del l
del lines
# not releasing memory
ax.cla() # this does release the memory, but also wipes out all other lines.

Так есть ли способ просто удалить одну строку из осей и вернуть память? Это потенциальное решение тоже не работает.

Дэвид Мортон
источник

Ответы:

71

Я показываю, что комбинация lines.pop(0) l.remove()и del lделает свое дело.

from matplotlib import pyplot
import numpy, weakref
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

l = lines.pop(0)
wl = weakref.ref(l)  # create a weak reference to see if references still exist
#                      to this object
print wl  # not dead
l.remove()
print wl  # not dead
del l
print wl  # dead  (remove either of the steps above and this is still live)

Я проверил ваш большой набор данных, и освобождение памяти также подтверждается на системном мониторе.

Конечно, более простой способ (если не устранять неполадки) - вытащить его из списка и вызвать removeобъект строки без создания на него жесткой ссылки:

lines.pop(0).remove()
Павел
источник
Я запустил ваш код и получил: [20:37] @flattop: ~ / Desktop / sandbox> python delete_lines.py <weakref at 0x8dd348c; в 'Line2D' по адресу 0x8dd43ec> <weakref по адресу 0x8dd348c; в 'Line2D' по адресу 0x8dd43ec> <weakref по адресу 0x8dd348c; to 'Line2D' at 0x8dd43ec> Я использую matplotlib версии 0.99.1.1 в ubuntu 10.04
Дэвид Мортон
1
@David Morton Я только что понизил версию до 0.99.1 и теперь воспроизвожу вашу проблему. Думаю, я могу рекомендовать только обновление до 1.0.1. С момента выхода 0.99.x было исправлено множество ошибок
Пол
1
Проблема здесь, скорее всего, в том, что ссылки торчат, когда их не должно быть. Могу поспорить, что OP использовал IPython для проверки. Смотрите мой ответ.
Vorticity
67

Это очень длинное объяснение, которое я напечатал для своего коллеги. Думаю, здесь тоже было бы полезно. Но будьте терпеливы. Я подхожу к реальной проблеме, с которой вы столкнулись ближе к концу. Как тизер, это проблема наличия дополнительных ссылок на ваши Line2Dобъекты.

ПРЕДУПРЕЖДЕНИЕ. Еще одно замечание, прежде чем мы углубимся. Если вы используете IPython для проверки этого, IPython сохраняет собственные ссылки, и не все из них являются слабыми ссылками. Итак, тестирование сборки мусора в IPython не работает. Это просто сбивает с толку.

Хорошо, поехали. Каждый matplotlibобъект ( Figure, Axesи т. Д.) Предоставляет доступ к своим дочерним художникам через различные атрибуты. Следующий пример становится довольно длинным, но должен проливать свет.

Мы начинаем с создания Figureобъекта, а затем добавляем Axesобъект к этой фигуре. Обратите внимание, что axи fig.axes[0]являются одним и тем же объектом (одинаковым id()).

>>> #Create a figure
>>> fig = plt.figure()
>>> fig.axes
[]

>>> #Add an axes object
>>> ax = fig.add_subplot(1,1,1)

>>> #The object in ax is the same as the object in fig.axes[0], which is 
>>> #   a list of axes objects attached to fig 
>>> print ax
Axes(0.125,0.1;0.775x0.8)
>>> print fig.axes[0]
Axes(0.125,0.1;0.775x0.8)  #Same as "print ax"
>>> id(ax), id(fig.axes[0])
(212603664, 212603664) #Same ids => same objects

Это также распространяется на строки в объекте оси:

>>> #Add a line to ax
>>> lines = ax.plot(np.arange(1000))

>>> #Lines and ax.lines contain the same line2D instances 
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]
>>> print ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]

>>> print lines[0]
Line2D(_line0)
>>> print ax.lines[0]
Line2D(_line0)

>>> #Same ID => same object
>>> id(lines[0]), id(ax.lines[0])
(216550352, 216550352)

Если бы вы вызывали, plt.show()используя то, что было сделано выше, вы бы увидели фигуру, содержащую набор осей и одну строку:

Фигура, содержащая набор осей и одну линию

Теперь, хотя мы увидели, что содержимое linesи ax.linesявляется одинаковым, очень важно отметить, что объект, на который ссылается linesпеременная, не совпадает с объектом, на который обращается внимание, ax.linesчто можно увидеть из следующего:

>>> id(lines), id(ax.lines)
(212754584, 211335288)

Как следствие, удаление элемента из linesтекущего графика ничего не делает, но удаление элемента из ax.linesудаляет эту строку из текущего графика. Так:

>>> #THIS DOES NOTHING:
>>> lines.pop(0)

>>> #THIS REMOVES THE FIRST LINE:
>>> ax.lines.pop(0)

Итак, если вы запустите вторую строку кода, вы удалите Line2Dобъект, содержащийся в ax.lines[0]текущем графике, и он исчезнет. Обратите внимание, что это также можно сделать, ax.lines.remove()имея в виду, что вы можете сохранить Line2Dэкземпляр в переменной, а затем передать его, ax.lines.remove()чтобы удалить эту строку, например:

>>> #Create a new line
>>> lines.append(ax.plot(np.arange(1000)/2.0))
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

Фигура, содержащая набор осей и две линии

>>> #Remove that new line
>>> ax.lines.remove(lines[0])
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84dx3>]

Фигура, содержащая набор осей и только вторую линию

Все вышеперечисленное работает fig.axesтак же хорошо, как и дляax.lines

Итак, настоящая проблема здесь. Если мы сохраняем ссылку , содержащуюся в ax.lines[0]в weakref.refобъект, а затем попытаться удалить его, мы заметим , что он не получает мусора:

>>> #Create weak reference to Line2D object
>>> from weakref import ref
>>> wr = ref(ax.lines[0])
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

>>> #Delete the line from the axes
>>> ax.lines.remove(wr())
>>> ax.lines
[]

>>> #Test weakref again
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

Ссылка все еще жива! Почему? Это потому, что есть еще одна ссылка на Line2Dобъект, на который wrуказывает ссылка . Помните, как linesне было такого же идентификатора, как, ax.linesно содержали те же элементы? Ну вот и проблема.

>>> #Print out lines
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

To fix this problem, we simply need to delete `lines`, empty it, or let it go out of scope.

>>> #Reinitialize lines to empty list
>>> lines = []
>>> print lines
[]
>>> print wr
<weakref at 0xb758af8; dead>

Итак, мораль этой истории такова: убирайте за собой. Если вы ожидаете, что что-то будет собрано сборщиком мусора, но это не так, вы, скорее всего, оставите где-то висеть ссылку.

Завихренность
источник
2
Именно то, что мне нужно. Я рисую тысячи карт, каждая с диаграммой рассеяния поверх проекции карты мира. Они занимали по 3 секунды! Повторно используя фигуру с уже нарисованной картой и извлекая полученную коллекцию из ax.collections, я сократил время до 1/3 секунды. Благодаря!
GaryBishop
4
Думаю, в текущих версиях mpl в этом больше нет необходимости. У художника есть remove()функция, которая очистит их от mpl-стороны вещей, и тогда вам нужно будет только отслеживать ваши ссылки.
tacaswell
2
Да, есть идеи, в какой версии matplotlib это изменение изменилось?
Vorticity 02
Это оказалось полезным при использовании набора графиков в анимации matplotlib. В противном случае вы получите очень большой объем используемой памяти. Теперь, чтобы сделать это быстрее.
Дэнни Стейпл
14

Я перепробовал много разных ответов на разных форумах. Я думаю, это зависит от машины, которую вы разрабатываете. Но я использовал заявление

ax.lines = []

и работает отлично. Я не использую, cla()потому что он удаляет все определения, которые я сделал для сюжета

Ex.

pylab.setp(_self.ax.get_yticklabels(), fontsize=8)

но я много раз пытался удалить строки. Также с помощью библиотеки weakref проверять ссылку на эту строку, пока я удалял, но у меня ничего не работало.

Надеюсь, это сработает для кого-то другого = D

Джеронимо Шрейер
источник
Проблема здесь, скорее всего, в том, что ссылки торчат, когда их не должно быть. Могу поспорить, что OP использовал IPython для проверки. Смотрите мой ответ.
Vorticity
5

(используя тот же пример, что и парень выше)

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

for i, line in enumerate(ax.lines):
    ax.lines.pop(i)
    line.remove()
Jeronimo
источник
1

Надеюсь, это поможет другим: в приведенных выше примерах используется ax.lines. В более поздних версиях mpl (3.3.1) есть файлы ax.get_lines(). Это избавляет от необходимости звонитьax.lines=[]

for line in ax.get_lines(): # ax.lines:
    line.remove()
# ax.lines=[] # needed to complete removal when using ax.lines
Brobr
источник