Skip to content

Tracciare un gran numero di punti con matplotlib e esaurire la memoria

Non hai più bisogno di indagare di più su Internet poiché sei nello spazio necessario, abbiamo la soluzione che stai cercando ma senza complicarla.

Soluzione:

Il suggerimento di @tcaswell di sovrascrivere il parametro Axes.draw è sicuramente il modo più flessibile di affrontare la questione.

Tuttavia, si può usare/abusare il blitting per fare questo senza sottoclassi Axes. Basta usare draw_artist ogni volta senza ripristinare il canvas.

C'è un ulteriore trucco: Dobbiamo avere uno speciale elemento save perché tutti gli altri disegnano la tela prima di salvarla, cancellando tutto ciò che vi è stato disegnato in precedenza.

Inoltre, come nota tcaswell, chiamando il metodo draw_artist per ogni elemento è piuttosto lenta, quindi per un gran numero di punti, è meglio raggruppare i dati di input. Il chunking darà una significativa accelerazione, ma questo metodo sarà sempre più lento rispetto al disegno di un singolo elemento PathCollection.

In ogni caso, una di queste risposte dovrebbe alleviare i problemi di memoria. Ecco un esempio semplicistico.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import _png
from itertools import izip

def main():
    # We'll be saving the figure's background, so let's make it transparent.
    fig, ax = plt.subplots(facecolor='none')

    # You'll have to know the extent of the input beforehand with this method.
    ax.axis([0, 10, 0, 10])

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

    # This won't actually ever be drawn. We just need an artist to update.
    col = ax.scatter([5], [5], color=[0.1, 0.1, 0.1], alpha=0.3)

    for xy, color in datastream(int(1e6), chunksize=int(1e4)):
        col.set_offsets(xy)
        col.set_color(color)
        ax.draw_artist(col)

    save(fig, 'test.png')

def datastream(n, chunksize=1):
    """Returns a generator over "n" random xy positions and rgb colors."""
    for _ in xrange(n//chunksize):
        xy = 10 * np.random.random((chunksize, 2))
        color = np.random.random((chunksize, 3))
        yield xy, color

def save(fig, filename):
    """We have to work around `fig.canvas.print_png`, etc calling `draw`."""
    renderer = fig.canvas.renderer
    with open(filename, 'w') as outfile:
        _png.write_png(renderer._renderer.buffer_rgba(),
                       renderer.width, renderer.height,
                       outfile, fig.dpi)

main()

enter image description here

Inoltre, potreste notare che le spine dorsali superiore e sinistra vengono disegnate sopra. Si può ovviare a questo problema ridisegnando le due spine (ax.draw_artist(ax.spines['top']), ecc.) prima di salvare.

Qualcosa del genere (scusate la lunghezza del codice, la maggior parte è copiata dallo standard axes.Axes.draw):

from operator import itemgetter
class generator_scatter_axes(matplotlib.axes.Axes):
    def __init__(self, *args, **kwargs):
        matplotlib.axes.Axes.__init__(self, *args, **kwargs)
        self._big_data = None
    def draw(self, renderer=None, inframe=None):
        # copied from original draw (so you can still add normal artists ect)
        if renderer is None:
            renderer = self._cachedRenderer

        if renderer is None:
            raise RuntimeError('No renderer defined')
        if not self.get_visible():
            return
        renderer.open_group('axes')

        locator = self.get_axes_locator()
        if locator:
            pos = locator(self, renderer)
            self.apply_aspect(pos)
        else:
            self.apply_aspect()

        artists = []

        artists.extend(self.collections)
        artists.extend(self.patches)
        artists.extend(self.lines)
        artists.extend(self.texts)
        artists.extend(self.artists)
        if self.axison and not inframe:
            if self._axisbelow:
                self.xaxis.set_zorder(0.5)
                self.yaxis.set_zorder(0.5)
            else:
                self.xaxis.set_zorder(2.5)
                self.yaxis.set_zorder(2.5)
            artists.extend([self.xaxis, self.yaxis])
        if not inframe:
            artists.append(self.title)
            artists.append(self._left_title)
            artists.append(self._right_title)
        artists.extend(self.tables)
        if self.legend_ is not None:
            artists.append(self.legend_)

        # the frame draws the edges around the axes patch -- we
        # decouple these so the patch can be in the background and the
        # frame in the foreground.
        if self.axison and self._frameon:
            artists.extend(self.spines.itervalues())

        if self.figure.canvas.is_saving():
            dsu = [(a.zorder, a) for a in artists]
        else:
            dsu = [(a.zorder, a) for a in artists
                   if not a.get_animated()]

        # add images to dsu if the backend support compositing.
        # otherwise, does the manaul compositing  without adding images to dsu.
        if len(self.images) <= 1 or renderer.option_image_nocomposite():
            dsu.extend([(im.zorder, im) for im in self.images])
            _do_composite = False
        else:
            _do_composite = True

        dsu.sort(key=itemgetter(0))

        # rasterize artists with negative zorder
        # if the minimum zorder is negative, start rasterization
        rasterization_zorder = self._rasterization_zorder
        if (rasterization_zorder is not None and
            len(dsu) > 0 and dsu[0][0] < rasterization_zorder):
            renderer.start_rasterizing()
            dsu_rasterized = [l for l in dsu if l[0] < rasterization_zorder]
            dsu = [l for l in dsu if l[0] >= rasterization_zorder]
        else:
            dsu_rasterized = []

        # the patch draws the background rectangle -- the frame below
        # will draw the edges
        if self.axison and self._frameon:
            self.patch.draw(renderer)

        if _do_composite:
            # make a composite image blending alpha
            # list of (mimage.Image, ox, oy)

            zorder_images = [(im.zorder, im) for im in self.images
                             if im.get_visible()]
            zorder_images.sort(key=lambda x: x[0])

            mag = renderer.get_image_magnification()
            ims = [(im.make_image(mag), 0, 0, im.get_alpha()) for z, im in zorder_images]

            l, b, r, t = self.bbox.extents
            width = mag * ((round(r) + 0.5) - (round(l) - 0.5))
            height = mag * ((round(t) + 0.5) - (round(b) - 0.5))
            im = mimage.from_images(height,
                                    width,
                                    ims)

            im.is_grayscale = False
            l, b, w, h = self.bbox.bounds
            # composite images need special args so they will not
            # respect z-order for now

            gc = renderer.new_gc()
            gc.set_clip_rectangle(self.bbox)
            gc.set_clip_path(mtransforms.TransformedPath(
                    self.patch.get_path(),
                    self.patch.get_transform()))

            renderer.draw_image(gc, round(l), round(b), im)
            gc.restore()

        if dsu_rasterized:
            for zorder, a in dsu_rasterized:
                a.draw(renderer)
            renderer.stop_rasterizing()

        for zorder, a in dsu:
            a.draw(renderer)
        ############################    
        # new bits
        ############################
        if self._big_data is not None:

            for x, y, z in self._big_data:
                # add the (single point) to the axes
                a = self.scatter(x, y, color='r',
                            alpha=1, s=10, marker='s', linewidth=0)
                # add the point, in Agg this will render + composite
                a.draw(renderer)
                # remove the artist from the axes, shouldn't let the render know
                a.remove()
                # delete the artist for good measure
                del a
        #######################
        # end new bits
        #######################    
        # again, from original to clean up
        renderer.close_group('axes')
        self._cachedRenderer = renderer

utilizzarlo in questo modo:

In [42]: fig = figure()

In [43]: ax = generator_scatter_axes(fig, [.1, .1, .8, .8])

In [44]: fig.add_axes(ax)
Out[44]: <__main__.generator_scatter_axes at 0x56fe090>

In [45]: ax._big_data = rand(500, 3)

In [46]: draw()

Ho modificato la funzione di dispersione in modo che le forme siano visibili in piccoli numeri. Questo sarà molto lento, dato che si sta impostando una funzione scatter oggetto ogni tempo. Prenderei dei pezzi sensibili dei vostri dati e li traccerei, oppure sostituirei la chiamata a scatter agli oggetti artista sottostanti, oppure seguirei il suggerimento di Joe e aggiornerei solo un singolo artista.

Potete assecondare la nostra analisi esponendo un commento e lasciando una valutazione, vi ringraziamo.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.