Metodi statistici per tracciare i dati in modo più efficiente quando sono presenti milioni di punti?


31

Trovo che R possa richiedere molto tempo per generare grafici quando sono presenti milioni di punti, il che non sorprende dato che i punti vengono tracciati singolarmente. Inoltre, tali trame sono spesso troppo ingombra e densi per essere utili. Molti dei punti si sovrappongono e formano una massa nera e molto tempo viene impiegato per tracciare più punti in quella massa.

Esistono alternative statistiche alla rappresentazione di dati di grandi dimensioni in un diagramma a dispersione standard? Ho considerato un diagramma di densità, ma quali altre alternative ci sono?n


1
Per alcune soluzioni con grafici lineari, consultare stats.stackexchange.com/questions/35220/… .
whuber

Risposte:


13

Questo è un compito difficile senza soluzioni pronte (questo ovviamente perché la trama della densità è un fallback così allettante di quanto a nessuno importi davvero). Che cosa si può fare?

Se si sovrappongono davvero (cioè hanno esattamente le stesse coordinate X e Y) e non si utilizza l'alfa, l'idea migliore sarebbe quella di ridurre la sovrapposizione utilizzando unique(con l'alfa, potrebbe essere sommata su tali gruppi).

In caso contrario, puoi arrotondare manualmente le coordinate ai pixel più vicini e utilizzare il metodo precedente (ma questa è una soluzione sporca).

Infine, puoi creare un diagramma di densità solo per usarlo per sottocampionare i punti nelle aree più dense. Questo d'altra parte non farà esattamente la stessa trama e potrebbe introdurre artefatti se non sintonizzati con precisione.


5
Riducendo la sovrapposizione con uniqueo arrotondando si possono ottenere grafici distorti (ingannevoli). È importante in qualche modo indicare la quantità di sovrapposizione attraverso alcuni mezzi grafici come la leggerezza o con trame di girasole.
whuber

44

Guarda il pacchetto hexbin che implementa paper / method di Dan Carr. La vignetta pdf ha più dettagli che cito di seguito:

1. Panoramica

Il binning esagonale è una forma di istogramma bivariato utile per visualizzare la struttura in set di dati con n grande. Il concetto alla base del binning esagonale è estremamente semplice;

  1. il piano xy sul set (range (x), range (y)) è tassellato da una griglia regolare di esagoni.
  2. il numero di punti che cadono in ciascun esagono viene contato e memorizzato in una struttura di dati
  3. n106

Se la dimensione della griglia e i tagli nella scala dei colori sono scelti in modo intelligente, la struttura inerente ai dati dovrebbe emergere nei grafici concatenati. Le stesse avvertenze si applicano al binning esagonale come si applicano agli istogrammi e occorre prestare attenzione nella scelta dei parametri di binning


4
È bello. Proprio quello che il medico ha ordinato.
Roman Luštrik,

13
(+1) Anche di interesse, smoothScatter {RColorBrewer}e densCols {grDevices}. Posso confermare che funziona abbastanza bene con migliaia o milioni di punti dai dati genetici.
chl

2
Cosa succede se i dati 3D? (troppi per scatterplot3d)
skan

Per risparmiare un po 'di tempo, ho trovato smoothScatter, come suggerito da 2 commenti, per avere impostazioni / funzionamento molto migliori.
Charlie

16

Devo ammettere che non capisco pienamente il tuo ultimo paragrafo:

"Non sto cercando un diagramma di densità (anche se spesso sono utili), vorrei lo stesso output di una semplice chiamata di trama, ma molto più veloce di milioni di overplot, se possibile."

Non è anche chiaro quale tipo di trama (funzione) stai cercando.

Dato che hai variabili metriche, potresti trovare utili diagrammi con esagoni o girasole. Per ulteriori riferimenti, vedere


6

Un'altra risposta diretta alla domanda è il pacchetto rgl, che può tracciare milioni di punti usando OpenGL. Inoltre, specifica una dimensione in punti (ad es. 3) e rimpicciolisci per vedere questi centri di masse come blocchi monolitici, oppure ingrandisci e vedi la struttura di quello che era un tempo monolitico - le dimensioni dei punti sono costanti ma le distanze tra loro sullo schermo dipende dallo zoom. È possibile utilizzare anche i livelli alfa.


5

Ecco un file che chiamo bigplotfix.R. Se la fonte, definirà un wrapper per il plot.xyquale "comprime" i dati della trama quando è molto grande. Il wrapper non fa nulla se l'input è piccolo, ma se l'input è grande, lo suddivide in blocchi e traccia semplicemente il valore massimo e minimo xey per ogni blocco. Anche il sourcing bigplotfix.Rsi ricollega graphics::plot.xyal punto sul wrapper (l'approvvigionamento più volte è OK).

Si noti che plot.xyè la funzione di "cavallo di battaglia" per i metodi di plottaggio standard come plot(), lines()e points(). Quindi puoi continuare a usare queste funzioni nel tuo codice senza alcuna modifica e i tuoi grafici di grandi dimensioni verranno automaticamente compressi.

Questo è un esempio di output. È essenzialmente plot(runif(1e5)), con punti e linee, e con e senza la "compressione" implementata qui. La trama dei "punti compressi" manca la regione centrale a causa della natura della compressione, ma la trama delle "linee compresse" sembra molto più vicina all'originale non compresso. I tempi sono per il png()dispositivo; per alcuni motivi i punti sono molto più veloci nel pngdispositivo che nel X11dispositivo, ma le accelerazioni X11sono comparabili ( X11(type="cairo")era più lenta che X11(type="Xlib")nei miei esperimenti).

Uscita di test "bigplotfix.R"

Il motivo per cui ho scritto questo è perché ero stanco di correre plot()per sbaglio su un set di dati di grandi dimensioni (ad esempio un file WAV). In questi casi dovrei scegliere tra attendere diversi minuti per il completamento della trama e terminare la mia sessione R con un segnale (perdendo così la mia cronologia dei comandi e le variabili recenti). Ora, se ricordo di aver caricato questo file prima di ogni sessione, in questi casi posso effettivamente ottenere una trama utile. Un piccolo messaggio di avviso indica quando i dati della trama sono stati "compressi".

# bigplotfix.R
# 28 Nov 2016

# This file defines a wrapper for plot.xy which checks if the input
# data is longer than a certain maximum limit. If it is, it is
# downsampled before plotting. For 3 million input points, I got
# speed-ups of 10-100x. Note that if you want the output to look the
# same as the "uncompressed" version, you should be drawing lines,
# because the compression involves taking maximum and minimum values
# of blocks of points (try running test_bigplotfix() for a visual
# explanation). Also, no sorting is done on the input points, so
# things could get weird if they are out of order.
test_bigplotfix = function() {
  oldpar=par();
  par(mfrow=c(2,2))
  n=1e5;
  r=runif(n)
  bigplotfix_verbose<<-T
  mytitle=function(t,m) { title(main=sprintf("%s; elapsed=%0.4f s",m,t["elapsed"])) }
  mytime=function(m,e) { t=system.time(e); mytitle(t,m); }

  oldbigplotfix_maxlen = bigplotfix_maxlen
  bigplotfix_maxlen <<- 1e3;

  mytime("Compressed, points",plot(r));
  mytime("Compressed, lines",plot(r,type="l"));
  bigplotfix_maxlen <<- n
  mytime("Uncompressed, points",plot(r));
  mytime("Uncompressed, lines",plot(r,type="l"));
  par(oldpar);
  bigplotfix_maxlen <<- oldbigplotfix_maxlen
  bigplotfix_verbose <<- F
}

bigplotfix_verbose=F

downsample_xy = function(xy, n, xlog=F) {
  msg=if(bigplotfix_verbose) { message } else { function(...) { NULL } }
  msg("Finding range");
  r=range(xy$x);
  msg("Finding breaks");
  if(xlog) {
    breaks=exp(seq(from=log(r[1]),to=log(r[2]),length.out=n))
  } else {
    breaks=seq(from=r[1],to=r[2],length.out=n)
  }
  msg("Calling findInterval");
  ## cuts=cut(xy$x,breaks);
  # findInterval is much faster than cuts!
  cuts = findInterval(xy$x,breaks);
  if(0) {
    msg("In aggregate 1");
    dmax = aggregate(list(x=xy$x, y=xy$y), by=list(cuts=cuts), max)
    dmax$cuts = NULL;
    msg("In aggregate 2");
    dmin = aggregate(list(x=xy$x, y=xy$y), by=list(cuts=cuts), min)
    dmin$cuts = NULL;
  } else { # use data.table for MUCH faster aggregates
    # (see http://stackoverflow.com/questions/7722493/how-does-one-aggregate-and-summarize-data-quickly)
    suppressMessages(library(data.table))
    msg("In data.table");
    dt = data.table(x=xy$x,y=xy$y,cuts=cuts)
    msg("In data.table aggregate 1");
    dmax = dt[,list(x=max(x),y=max(y)),keyby="cuts"]
    dmax$cuts=NULL;
    msg("In data.table aggregate 2");
    dmin = dt[,list(x=min(x),y=min(y)),keyby="cuts"]
    dmin$cuts=NULL;
    #  ans = data_t[,list(A = sum(count), B = mean(count)), by = 'PID,Time,Site']
  }
  msg("In rep, rbind");
  # interleave rows (copied from a SO answer)
  s <- rep(1:n, each = 2) + (0:1) * n
  xy = rbind(dmin,dmax)[s,];
  xy
}

library(graphics);
# make sure we don't create infinite recursion if someone sources
# this file twice
if(!exists("old_plot.xy")) {
  old_plot.xy = graphics::plot.xy
}

bigplotfix_maxlen = 1e4

# formals copied from graphics::plot.xy
my_plot.xy = function(xy, type, pch = par("pch"), lty = par("lty"),
  col = par("col"), bg = NA, cex = 1, lwd = par("lwd"),
  ...) {

  if(bigplotfix_verbose) {
    message("In bigplotfix's plot.xy\n");
  }

  mycall=match.call();
  len=length(xy$x)
  if(len>bigplotfix_maxlen) {
    warning("bigplotfix.R (plot.xy): too many points (",len,"), compressing to ",bigplotfix_maxlen,"\n");
    xy = downsample_xy(xy, bigplotfix_maxlen, xlog=par("xlog"));
    mycall$xy=xy
  }
  mycall[[1]]=as.symbol("old_plot.xy");

  eval(mycall,envir=parent.frame());
}

# new binding solution adapted from Henrik Bengtsson
# https://stat.ethz.ch/pipermail/r-help/2008-August/171217.html
rebindPackageVar = function(pkg, name, new) {
  # assignInNamespace() no longer works here, thanks nannies
  ns=asNamespace(pkg)
  unlockBinding(name,ns)
  assign(name,new,envir=asNamespace(pkg),inherits=F)
  assign(name,new,envir=globalenv())
  lockBinding(name,ns)
}
rebindPackageVar("graphics", "plot.xy", my_plot.xy);

0

Forse mi eviterò per il mio metodo, i brutti ricordi di uno dei miei professori di ricerca che urlavano alle persone per aver buttato via i buoni dati traducendoli in categorie (ovviamente, sono d'accordo ora un giorno lol), non lo so. Comunque, se stai parlando di un diagramma a dispersione, allora ho avuto gli stessi problemi. Ora, quando ho i dati numerici, non ha molto senso classificarli per l'analisi. Ma visualizzare è una storia diversa. Quello che ho scoperto che funziona meglio per me è prima di tutto (1) suddividere la variabile indipendente in gruppi usando la funzione di taglio. Puoi giocare con il numero di gruppi e poi (2) semplicemente tramare il DV contro la versione tagliata dell'IV. R genererà grafici a scatole invece di quel disgustoso diagramma a dispersione. Consiglio di rimuovere gli outlier dalla trama (usare l'opzione outline = FALSE nel comando plot). Ancora una volta, non sprecherei MAI dati numerici perfettamente buoni classificandoli e quindi analizzandoli. Troppi problemi nel farlo. Anche se so che è un argomento delicato di dibattito. Ma farlo appositamente per l'obiettivo di almeno ricavare un po 'di senso visivo dai dati, non molto danno che ho visto da esso. Ho tracciato dati grandi quanto 10M e sono comunque riuscito a dare un senso a questo metodo. Spero che sia d'aiuto! I migliori saluti! ne ho visto. Ho tracciato dati grandi quanto 10M e sono comunque riuscito a dare un senso a questo metodo. Spero che sia d'aiuto! I migliori saluti! ne ho visto. Ho tracciato dati grandi quanto 10M e sono comunque riuscito a dare un senso a questo metodo. Spero che sia d'aiuto! I migliori saluti!


0

Per le serie storiche di grandi dimensioni, ho imparato ad amare smoothScatter (parte della base R non meno). Spesso devo includere alcuni dati aggiuntivi e preservare l'API della trama di base è davvero utile, ad esempio:

set.seed(1)
ra <- rnorm(n = 100000, sd = 1, mean = 0)
smoothScatter(ra)
abline(v=25000, col=2)
text(25000, 0, "Event 1", col=2)

Che ti dà (se perdoni il design):

scatterSmooth esempio

È sempre disponibile e funziona bene con enormi set di dati, quindi è bello almeno dare un'occhiata a quello che hai.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.