Як видалити рядки в графіку 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.