Як видалити рядки в графіку Matplotlib


84

Як я можу видалити рядок (або рядки) осей 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.

То чи є спосіб просто видалити один рядок з осей і повернути пам’ять? Це потенційне рішення також не працює.

Відповіді:


70

Я показую, що поєднання 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()

Я запустив ваш код і отримав: [8:37 вечора] @flattop: ~ / Desktop / sandbox> python delete_lines.py <slabref на 0x8dd348c; до 'Line2D' на 0x8dd43ec> <слабкий ref на 0x8dd348c; до 'Line2D' на 0x8dd43ec> <слабкий ref на 0x8dd348c; до 'Line2D' на 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

3
Я думаю, що це більше не потрібно в поточних версіях mpl. Художник має remove()функцію, яка очищає їх від mpl-речей, і тоді вам потрібно лише відстежувати свої посилання.
— tacaswell

2
Га, будь-яка ідея, в якій версії matplotlib ця зміна така ж?
— Вихровість

Це виявилось корисним при використанні групи сюжетів в анімації matplotlib. В іншому випадку ви отримуєте дуже великий обсяг використовуваної пам’яті. Тепер, щоб зробити це швидше.
— Денні Степл

14

Я спробував безліч різних відповідей на різних форумах. Я думаю, це залежить від машини, яку ви розробляєте. Але я використав заяву

ax.lines = []

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

Напр.

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

але я намагався видалити рядки багато разів. Також за допомогою бібліотеки слабореф перевіряв посилання на цей рядок під час видалення, але нічого у мене не працювало.

Сподіваюся, це працює для когось іншого = 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()

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.