Des méthodes statistiques pour tracer plus efficacement les données lorsque des millions de points sont présents?

31

Je trouve que R peut prendre beaucoup de temps pour générer des tracés lorsque des millions de points sont présents - ce qui n'est pas surprenant étant donné que les points sont tracés individuellement. De plus, ces parcelles sont souvent trop encombrées et denses pour être utiles. Beaucoup de points se chevauchent et forment une masse noire et beaucoup de temps est consacré au traçage de plus de points dans cette masse.

Existe-t-il des alternatives statistiques à la représentation de grandes données dans un nuage de points standard? J'ai envisagé un tracé de densité, mais quelles sont les autres alternatives?n

Alex Stoddard
la source
1
Pour certaines solutions avec des tracés linéaires, voir stats.stackexchange.com/questions/35220/… .
whuber

Réponses:

13

C'est une tâche difficile sans solutions prêtes (c'est bien sûr parce que le graphique de densité est si tentant que personne ne s'en soucie vraiment). Alors que peux-tu faire?

S'ils se chevauchent vraiment (c'est-à-dire qu'ils ont exactement les mêmes coordonnées X et Y) et que vous n'utilisez pas alpha, la meilleure idée serait simplement de réduire le chevauchement en utilisant unique(avec alpha, il peut être additionné sur de tels groupes).

Sinon, vous pouvez arrondir manuellement les coordonnées aux pixels les plus proches et utiliser la méthode précédente (mais c'est une mauvaise solution).

Enfin, vous pouvez créer un tracé de densité uniquement pour l'utiliser pour sous-échantillonner les points dans les zones les plus denses. En revanche, cela ne fera pas exactement le même tracé et peut introduire des artefacts s'il n'est pas réglé avec précision.


la source
5
La réduction du chevauchement avec uniqueou en arrondissant peut entraîner des tracés biaisés (trompeurs). Il est important d'indiquer en quelque sorte la quantité de chevauchement par le biais de certains moyens graphiques tels que la légèreté ou avec des parcelles de tournesol.
whuber
44

Regardez le paquet hexbin qui implémente paper / method par Dan Carr. La vignette pdf contient plus de détails que je cite ci-dessous:

1. Vue d'ensemble

Le regroupement d'hexagones est une forme d'histogramme bivarié utile pour visualiser la structure dans des ensembles de données avec un grand n. Le concept sous-jacent du binning hexagonal est extrêmement simple;

  1. le plan xy sur l'ensemble (plage (x), plage (y)) est tessellé par une grille régulière d'hexagones.
  2. le nombre de points tombant dans chaque hexagone est compté et stocké dans une structure de données
  3. les hexagones avec un compte> 0 sont tracés en utilisant une rampe de couleur ou en faisant varier le rayon de l'hexagone proportionnellement aux comptes. L'algorithme sous-jacent est extrêmement rapide et efficace pour afficher la structure des ensembles de données avecndix6

Si la taille de la grille et les coupes dans la rampe de couleur sont choisies de manière intelligente, la structure inhérente aux données devrait émerger dans les tracés groupés. Les mêmes mises en garde s'appliquent au binning hexagonal que pour les histogrammes et le choix des paramètres de binning doit être effectué avec soin.

Dirk Eddelbuettel
la source
4
Voilà une belle. Juste ce que le docteur à prescrit.
Roman Luštrik
13
(+1) Également intéressant, smoothScatter {RColorBrewer}et densCols {grDevices}. Je peux confirmer que cela fonctionne assez bien avec des milliers à des millions de points de données génétiques.
chl
2
Et si vous avez des données 3D? (trop pour scatterplot3d)
skan
Pour gagner du temps aux autres - j'ai trouvé smoothScatter, comme suggéré 2 commentaires, pour avoir de bien meilleurs paramètres par défaut / fonctionnement.
Charlie
16

Je dois admettre que je ne comprends pas bien votre dernier paragraphe:

"Je ne cherche pas un tracé de densité (bien que ceux-ci soient souvent utiles), je voudrais la même sortie qu'un simple appel de tracé mais beaucoup plus rapide que des millions de surplombs si possible."

On ne sait pas non plus quel type de parcelle (fonction) vous recherchez.

Étant donné que vous avez des variables métriques, vous pouvez trouver des parcelles en hexagone ou des tournesols utiles. Pour plus de références, voir

Bernd Weiss
la source
6

Another direct answer to the question is the rgl package, which can plot millions of points using OpenGL. Also, specify a point size (e.g. 3) and zoom out to see these centers of masses as monolithic blocks, or zoom in and see the structure of what used to be monolithic - the point sizes are constant but the distances among them on the screen depend on the zooming. Alpha levels can also be used.

Robi5
la source
5

Here's a file I call bigplotfix.R. If you source it, it will define a wrapper for plot.xy which "compresses" the plot data when it is very large. The wrapper does nothing if the input is small, but if the input is large then it breaks it into chunks and just plots the maximum and minimum x and y value for each chunk. Sourcing bigplotfix.R also rebinds graphics::plot.xy to point to the wrapper (sourcing multiple times is OK).

Note that plot.xy is the "workhorse" function for the standard plotting methods like plot(), lines(), and points(). Thus you can continue to use these functions in your code with no modification, and your large plots will be automatically compressed.

This is some example output. It's essentially plot(runif(1e5)), with points and lines, and with and without the "compression" implemented here. The "compressed points" plot misses the middle region due to the nature of the compression, but the "compressed lines" plot looks much closer to the uncompressed original. The times are for the png() device; for some reason points are much faster in the png device than in the X11 device, but the speed-ups in X11 are comparable (X11(type="cairo") was slower than X11(type="Xlib") in my experiments).

Sortie de test "bigplotfix.R"

La raison pour laquelle j'ai écrit ceci est parce que j'étais fatigué de courir plot()par accident sur un grand ensemble de données (par exemple un fichier WAV). Dans de tels cas, je devrais choisir entre attendre plusieurs minutes pour terminer le tracé et terminer ma session R avec un signal (perdant ainsi mon historique de commandes et mes variables récentes). Maintenant, si je me souviens de charger ce fichier avant chaque session, je peux réellement obtenir un tracé utile dans ces cas. Un petit message d'avertissement indique quand les données de tracé ont été "compressées".

# 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);
Métamorphique
la source
0

Peut-être que je serai évité pour ma méthode, les mauvais souvenirs d'un de mes professeurs de recherche criant aux gens pour avoir jeté de bonnes données en les traduisant en catégories (bien sûr, je suis d'accord maintenant un jour lol), je ne sais pas. Quoi qu'il en soit, si vous parlez d'un nuage de points, alors j'ai eu les mêmes problèmes. Maintenant, quand j'ai des données numériques, cela n'a pas beaucoup de sens pour moi de les catégoriser pour l'analyse. Mais visualiser est une autre histoire. Ce que j'ai trouvé qui fonctionne le mieux pour moi, c'est d'abord (1) de diviser votre variable indépendante en groupes en utilisant la fonction cut. Vous pouvez jouer avec le nombre de groupes, puis (2) simplement tracer le DV contre la version coupée de l'IV. R générera des diagrammes en boîte au lieu de ce diagramme de dispersion dégoûtant. Je recommande de supprimer les valeurs aberrantes du tracé (utilisez l'option contour = FAUX dans la commande de tracé). Encore une fois, je ne gaspillerais JAMAIS de bonnes données numériques en catégorisant puis en analysant. Trop de problèmes pour cela. Même si je sais que c'est un sujet délicat de débat. Mais faire cela spécifiquement dans le but de donner au moins un sens visuel aux données, pas beaucoup de mal que j'en ai vu. J'ai tracé des données aussi grandes que 10M et j'ai quand même réussi à donner un sens à cette méthode. J'espère que ça t'as aidé! Meilleures salutations! ai vu de lui. J'ai tracé des données aussi grandes que 10M et j'ai quand même réussi à donner un sens à cette méthode. J'espère que ça t'as aidé! Meilleures salutations! ai vu de lui. J'ai tracé des données aussi grandes que 10M et j'ai quand même réussi à donner un sens à cette méthode. J'espère que ça t'as aidé! Meilleures salutations!

Mgarvey
la source
0

Pour les grandes séries temporelles, j'ai appris à aimer smoothScatter (une partie de la base R pas moins). Je dois souvent inclure des données supplémentaires, et la préservation de l'API de tracé de base est vraiment utile, par exemple:

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)

Ce qui vous donne (si vous pardonnez le design):

Exemple avec scatterSmooth

Il est toujours disponible et fonctionne bien avec d'énormes ensembles de données, il est donc agréable de regarder au moins ce que vous avez.

Josh Rumbut
la source