Altrove in questo thread ho proposto un semplice ma in qualche modo ad hoc per sottocampionare i punti. È veloce, ma richiede un po 'di sperimentazione per produrre grandi trame. La soluzione che sta per essere descritta è un ordine di grandezza più lento (impiega fino a 10 secondi per 1,2 milioni di punti) ma è adattivo e automatico. Per set di dati di grandi dimensioni, dovrebbe dare buoni risultati la prima volta e farlo ragionevolmente rapidamente.
Dn , la massima deviazione verticale da una linea adattata. Di conseguenza, l'algoritmo è questo:
( x , y)ty , sostituisci il grafico con questa linea. Altrimenti, suddividere i dati in quelli che precedono il punto di massima deviazione verticale e in quelli successivi e applicare ricorsivamente l'algoritmo ai due pezzi.
Ci sono alcuni dettagli di cui occuparsi, in particolare per far fronte a set di dati di diversa lunghezza. Lo faccio sostituendo quello più corto con i quantili corrispondenti a quello più lungo: in effetti, al posto dei valori dei dati effettivi viene utilizzata un'approssimazione lineare a tratti dell'EDF di quello più corto. ("Più breve" e "più lungo" possono essere invertiti impostando use.shortest=TRUE.)
Ecco Run'implementazione.
qq <- function(x0, y0, t.y=0.0005, use.shortest=FALSE) {
qq.int <- function(x,y, i.min,i.max) {
# x, y are sorted and of equal length
n <-length(y)
if (n==1) return(c(x=x, y=y, i=i.max))
if (n==2) return(cbind(x=x, y=y, i=c(i.min,i.max)))
beta <- ifelse( x[1]==x[n], 0, (y[n] - y[1]) / (x[n] - x[1]))
alpha <- y[1] - beta*x[1]
fit <- alpha + x * beta
i <- median(c(2, n-1, which.max(abs(y-fit))))
if (abs(y[i]-fit[i]) > thresh) {
assemble(qq.int(x[1:i], y[1:i], i.min, i.min+i-1),
qq.int(x[i:n], y[i:n], i.min+i-1, i.max))
} else {
cbind(x=c(x[1],x[n]), y=c(y[1], y[n]), i=c(i.min, i.max))
}
}
assemble <- function(xy1, xy2) {
rbind(xy1, xy2[-1,])
}
#
# Pre-process the input so that sorting is done once
# and the most detail is extracted from the data.
#
is.reversed <- length(y0) < length(x0)
if (use.shortest) is.reversed <- !is.reversed
if (is.reversed) {
y <- sort(x0)
n <- length(y)
x <- quantile(y0, prob=(1:n-1)/(n-1))
} else {
y <- sort(y0)
n <- length(y)
x <- quantile(x0, prob=(1:n-1)/(n-1))
}
#
# Convert the relative threshold t.y into an absolute.
#
thresh <- t.y * diff(range(y))
#
# Recursively obtain points on the QQ plot.
#
xy <- qq.int(x, y, 1, n)
if (is.reversed) cbind(x=xy[,2], y=xy[,1], i=xy[,3]) else xy
}
Ad esempio, utilizzo i dati simulati come nella mia precedente risposta (con un outlier estremamente elevato lanciato ye un po 'più di contaminazione in xquesto momento):
set.seed(17)
n.x <- 1.21 * 10^6
n.y <- 1.20 * 10^6
k <- floor(0.01*n.x)
x <- c(rnorm(n.x-k), rnorm(k, mean=2, sd=2))
x <- x[x <= -3 | x >= -2.5]
y <- c(rbeta(n.y, 10,13), 1)
Tracciamo diverse versioni, usando valori sempre più piccoli della soglia. Con un valore di .0005 e la visualizzazione su un monitor di 1000 pixel di altezza, garantiremmo un errore non superiore alla metà di un pixel verticale ovunque sulla trama. Questo è mostrato in grigio (solo 522 punti, uniti da segmenti di linea); le approssimazioni più grossolane sono tracciate sopra di essa: prima in nero, poi in rosso (i punti rossi saranno un sottoinsieme di quelli neri e li sovrapporranno), quindi in blu (che di nuovo sono un sottoinsieme e un overplot). I tempi vanno da 6,5 (blu) a 10 secondi (grigio). Dato che si ridimensionano così bene, si potrebbe anche usare circa mezzo pixel come valore predefinito universale per la soglia ( ad esempio , 1/2000 per un monitor alto 1000 pixel) e farlo con esso.
qq.1 <- qq(x,y)
plot(qq.1, type="l", lwd=1, col="Gray",
xlab="x", ylab="y", main="Adaptive QQ Plot")
points(qq.1, pch=".", cex=6, col="Gray")
points(qq(x,y, .01), pch=23, col="Black")
points(qq(x,y, .03), pch=22, col="Red")
points(qq(x,y, .1), pch=19, col="Blue")

modificare
Ho modificato il codice originale per qqrestituire una terza colonna di indici nel più lungo (o più breve, come specificato) dei due array originali xe y, corrispondente ai punti selezionati. Questi indici indicano valori "interessanti" dei dati e quindi potrebbero essere utili per ulteriori analisi.
Ho anche rimosso un bug che si verificava con valori ripetuti di x(che risultava betaessere indefinito).
approx()funzione entra in gioco nellaqqplot()funzione.