El gráfico de líneas tiene demasiadas líneas, ¿hay una mejor solución?

31

Estoy tratando de graficar el número de acciones de los usuarios (en este caso, "me gusta") a lo largo del tiempo.

Así que tengo "Número de acciones" como mi eje y, mi eje x es el tiempo (semanas) y cada línea representa un usuario.

Mi problema es que quiero ver estos datos para un conjunto de aproximadamente 100 usuarios. Un gráfico lineal se convierte rápidamente en un desorden desordenado con 100 líneas. ¿Existe un mejor tipo de gráfico que pueda usar para mostrar esta información? ¿O debería considerar poder activar / desactivar líneas individuales?

Me gustaría ver todos los datos a la vez, pero poder discernir la cantidad de acciones con alta precisión no es muy importante.

¿Por qué estoy haciendo esto?

Para un subconjunto de mis usuarios (usuarios principales), quiero averiguar cuáles pueden no haberles gustado una nueva versión de la aplicación que se lanzó en una fecha determinada. Estoy buscando caídas significativas en el número de acciones de usuarios individuales.

regula esto
fuente
55
¿Has considerado hacer las líneas semitransparentes cambiando el alfa que se usa para trazarlas?
Fomite
1
@EpiGrad Sugerencia razonable, pero eso realmente no haría más fácil ver lo que estoy buscando.
regula este
1
@regulatethis Sugeriría un enfoque de "pequeño múltiplo" utilizando la facet_wrapfunción de ggplot2 para crear un bloque de 4 x 5 gráficos (4 filas, 5 columnas, ajuste según la relación de aspecto deseada) con ~ 5 usuarios por gráfico. Eso debería ser lo suficientemente claro y podría escalarlo hasta unos 10 usuarios por gráfico, dando espacio para 200 con un gráfico de 4x5 o 360 con un gráfico de 6x6.
SlowLearner

Respuestas:

31

Me gustaría sugerir un análisis preliminar (estándar) para eliminar los efectos principales de (a) la variación entre los usuarios, (b) la respuesta típica de todos los usuarios al cambio, y (c) la variación típica de un período de tiempo al siguiente .

Una manera simple (pero de ninguna manera la mejor) de hacer esto es realizar algunas iteraciones de "pulido medio" en los datos para barrer las medianas del usuario y las medianas del período de tiempo, luego suavizar los residuos con el tiempo. Identifique los suavizados que cambian mucho: son los usuarios que desea enfatizar en el gráfico.

Debido a que estos son datos de conteo, es una buena idea volver a expresarlos usando una raíz cuadrada.

Como ejemplo de lo que puede resultar, aquí hay un conjunto de datos simulados de 60 semanas de 240 usuarios que generalmente realizan entre 10 y 20 acciones por semana. Se produjo un cambio en todos los usuarios después de la semana 40. A tres de ellos se les "dijo" que respondieran negativamente al cambio. El gráfico de la izquierda muestra los datos sin procesar: recuentos de acciones por usuario (con usuarios distinguidos por color) a lo largo del tiempo. Como se afirma en la pregunta, es un desastre. El gráfico de la derecha muestra los resultados de este EDA, en los mismos colores que antes, con los usuarios inusualmente sensibles identificados y resaltados automáticamente . La identificación, aunque es algo ad hoc, es completa y correcta (en este ejemplo).

Figura 1

Aquí está el Rcódigo que produjo estos datos y llevó a cabo el análisis. Se podría mejorar de varias maneras, incluyendo

  • Usando un pulido mediano completo para encontrar los residuos, en lugar de solo una iteración.

  • Alisar los residuos por separado antes y después del punto de cambio.

  • Quizás utilizando un algoritmo de detección de valores atípicos más sofisticado. El actual simplemente marca a todos los usuarios cuyo rango de residuos es más del doble del rango medio. Aunque simple, es robusto y parece funcionar bien. (Un valor configurable por el usuario threshold, se puede ajustar para que esta identificación sea más o menos estricta).

Sin embargo, las pruebas sugieren que esta solución funciona bien para una amplia gama de usuarios, de 12 a 240 o más.

n.users <- 240        # Number of users (here limited to 657, the number of colors)
n.periods <- 60       # Number of time periods
i.break <- 40         # Period after which change occurs
n.outliers <- 3       # Number of greatly changed users
window <- 1/5         # Temporal smoothing window, fraction of total period
response.all <- 1.1   # Overall response to the change
threshold <- 2        # Outlier detection threshold

# Create a simulated dataset
set.seed(17)
base <- exp(rnorm(n.users, log(10), 1/2))
response <- c(rbeta(n.users - n.outliers, 9, 1),
              rbeta(n.outliers, 5, 45)) * response.all
actual <- cbind(base %o% rep(1, i.break), 
                base * response %o% rep(response.all, n.periods-i.break))
observed <- matrix(rpois(n.users * n.periods, actual), nrow=n.users)

# ---------------------------- The analysis begins here ----------------------------#
# Plot the raw data as lines
set.seed(17)
colors = sample(colors(), n.users) # (Use a different method when n.users > 657)
par(mfrow=c(1,2))
plot(c(1,n.periods), c(min(observed), max(observed)), type="n",
     xlab="Time period", ylab="Number of actions", main="Raw data")
i <- 0
apply(observed, 1, function(a) {i <<- i+1; lines(a, col=colors[i])})
abline(v = i.break, col="Gray")  # Mark the last period before a change

# Analyze the data by time period and user by sweeping out medians and smoothing
x <- sqrt(observed + 1/6)                        # Re-express the counts
mean.per.period <- apply(x, 2, median)
residuals <- sweep(x, 2, mean.per.period)
mean.per.user <- apply(residuals, 1, median)
residuals <- sweep(residuals, 1, mean.per.user)

smooth <- apply(residuals, 1, lowess, f=window)  # Smooth the residuals
smooth.y <- sapply(smooth, function(s) s$y)      # Extract the smoothed values
ends <- ceiling(window * n.periods / 4)          # Prepare to drop near-end values
range <- apply(smooth.y[-(1:ends), ], 2, function(x) max(x) - min(x))

# Mark the apparent outlying users
thick <- rep(1, n.users)
thick[outliers <- which(range >= threshold * median(range))] <- 3
type <- ifelse(thick==1, 3, 1)

cat(outliers) # Print the outlier identifiers (ideally, the last `n.outliers`)

# Plot the residuals
plot(c(1,n.periods), c(min(smooth.y), max(smooth.y)), type="n",
     xlab="Time period", ylab="Smoothed residual root", main="Residuals")
i <- 0
tmp <- lapply(smooth, 
       function(a) {i <<- i+1; lines(a, lwd=thick[i], lty=type[i], col=colors[i])})
abline(v = i.break, col="Gray")
whuber
fuente
3
Para más de 100-200 usuarios, aumentaría thresholda aproximadamente para evitar falsos positivos. Por ejemplo , tratar con el código , (que es una gran parte!), Y . 2.5n.users <- 500n.outliers <- 100threshold <- 2.5
whuber
16

En general, encuentro que más de dos o tres líneas en una sola faceta de una trama comienzan a ser difíciles de leer (aunque todavía lo hago todo el tiempo). Así que este es un ejemplo interesante de qué hacer cuando tienes algo que conceptualmente podría ser un diagrama de 100 facetas. Una forma posible es dibujar las 100 facetas, pero en lugar de tratar de ponerlas todas en la página a la vez, mirarlas una a la vez en una animación.

En realidad, utilizamos esta técnica en mi trabajo: originalmente hicimos la animación que mostraba 60 trazados de línea diferentes como fondo para un evento (el lanzamiento de una nueva serie de datos), y luego descubrimos que al hacerlo, realmente recogimos algunas características de los datos que no había sido visible en parcelas facetadas con 15 o 30 facetas por página.

Así que aquí hay una forma alternativa de presentar los datos sin procesar, antes de comenzar a eliminar al usuario y los efectos de tiempo típicos recomendados por @whuber. Esto se presenta solo como una alternativa adicional a su presentación de los datos en bruto: le recomiendo que continúe con el análisis siguiendo líneas como las que él sugiere.

Una forma de solucionar este problema es producir las 100 series de tiempo (o 240 en el ejemplo de @ whuber) por separado y unirlas en una animación. El siguiente código producirá 240 imágenes separadas de este tipo y luego puede usar un software gratuito para hacer películas para convertir eso en una película. Desafortunadamente, la única forma en que pude hacer esto y mantener una calidad aceptable fue un archivo de 9 MB, pero si no necesita enviarlo a través de Internet, eso puede no ser un problema y de todos modos estoy seguro de que hay maneras de evitarlo con un poco más experto en animación. El paquete de animación en R podría ser útil aquí (le permite hacerlo todo en una llamada desde R), pero lo he mantenido simple para esta ilustración.

Hice la animación de manera que dibuje cada línea en un negro intenso y luego deje una sombra verde pálida semitransparente detrás para que el ojo obtenga una imagen gradual de los datos acumulados. Hay riesgos y oportunidades en esto: el orden en que se agregan las líneas dejará una impresión diferente, por lo que debe considerar hacer que sea significativo de alguna manera.

Estas son algunas de las imágenes de la película, que usa los mismos datos que generó @whuber: ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

# ---------------------------- Data generation - by @whuber ----------------------------#

n.users <- 240        # Number of users (here limited to 657, the number of colors)
n.periods <- 60       # Number of time periods
i.break <- 40         # Period after which change occurs
n.outliers <- 3       # Number of greatly changed users
window <- 1/5         # Temporal smoothing window, fraction of total period
response.all <- 1.1   # Overall response to the change
threshold <- 2        # Outlier detection threshold

# Create a simulated dataset
set.seed(17)
base <- exp(rnorm(n.users, log(10), 1/2))
response <- c(rbeta(n.users - n.outliers, 9, 1),
              rbeta(n.outliers, 5, 45)) * response.all
actual <- cbind(base %o% rep(1, i.break), 
                base * response %o% rep(response.all, n.periods-i.break))
observed <- matrix(rpois(n.users * n.periods, actual), nrow=n.users)

# ---------------------------- The analysis begins here ----------------------------#

# Alternative presentation of original data 
# 
setwd("eg animation")

for (i in 1:n.users){
    png(paste("line plot", i, ".png"),600,600,res=60)
    plot(c(1,n.periods), c(min(observed), max(observed)), 
        xlab="Time period", ylab="Number of actions", 
        main="Raw data", bty="l", type="n")
    if(i>1){apply(observed[1:i,], 1, function(a) {lines(a, col=rgb(0,100,0,50,maxColorValue=255))})}
    lines(observed[i,], col="black", lwd=2)
    abline(v = i.break, col="Gray")  # Mark the last period before a change
    text(1,60,i)
    dev.off()
}

##
# Then proceed to further analysis eg as set out by @whuber
Peter Ellis
fuente
+1, esta es una buena idea. También puede iniciar una nueva ventana de dispositivo usando windows()o quartz(), y luego anidar su for()ciclo dentro de ella. NB, necesitará poner una Sys.sleep(1)en la parte inferior de su ciclo para que realmente pueda ver las iteraciones. Por supuesto, esta estrategia en realidad no guarda un archivo de película, solo tiene que volver a ejecutarlo cada vez que quiera verlo nuevamente.
gung - Restablece a Monica
+1 Muy buena idea. Intentaré esto la próxima vez que tenga. (GTW, Mathematica , por ejemplo, reduce el trabajo de crear y guardar dichas animaciones.)
whuber
Idea increíble: una animación en este sentido (o el código y los datos que se generarán) sería un apéndice en línea muy atractivo para una publicación.
N Brouwer
7

Una de las cosas más fáciles es un diagrama de caja. Puede ver de inmediato cómo se mueven sus medianas de muestra y qué días tienen los valores más atípicos.

day <- rep(1:10, 100)
likes <- rpois(1000, 10)
d <- data.frame(day, likes)
library(ggplot2)
qplot(x=day, y=likes, data=d, geom="boxplot", group=day)

ingrese la descripción de la imagen aquí

Para el análisis individual, sugiero tomar una pequeña muestra aleatoria de sus datos y analizar series de tiempo separadas.

jem77bfp
fuente
1
Solución interesante, pero lo que realmente quiero poder ver es el "cambio" por usuario. Quiero ver las fluctuaciones en la actividad de los usuarios individuales. Es por eso que elegí una línea inicialmente, pero la visualización es demasiado abarrotada ahora.
regula este
bueno, realmente depende de qué patrones quieras poder ver en tus datos, quizás si pudieras decirnos qué estás tratando de descubrir, podríamos encontrar una solución.
jem77bfp
Para un subconjunto de mis usuarios (usuarios principales), quiero averiguar cuáles pueden no haberles gustado una nueva versión de la aplicación que se lanzó en una fecha determinada. Estoy buscando caídas significativas en el número de acciones de usuarios individuales.
regula este
Bienvenido al sitio @ jem77bfp. él dijo que quería ver todos los datos. Pero estaría bien tener más detalles, estoy de acuerdo.
Peter Flom - Restablece a Monica
+1: en lugar de visualizar los diagramas de caja, puede ser útil conectar las estadísticas de resumen en gráficos de líneas. Vea esta respuesta mía para un ejemplo y discusión a continuación.
Andy W
7

Seguro. Primero, ordenar por número promedio de acciones. Luego haga (digamos) 4 gráficas, cada una con 25 líneas, una para cada cuartil. Eso significa que puede reducir los ejes y (pero aclarar la etiqueta del eje y). Y con 25 líneas, puede variarlas por tipo de línea y color y tal vez trazar un símbolo y obtener algo de claridad

Luego apile los gráficos verticalmente con un solo eje de tiempo.

Esto sería bastante fácil en R o SAS (al menos si tiene v. 9 de SAS).

Peter Flom - Restablece a Monica
fuente
2
+1 - ¡Sugeriría incluso menos líneas por múltiplo pequeño! Vea mi publicación de blog relacionada sobre el tema y un ejemplo. La clasificación también es una gran idea, y otras posibles podrían incluir el valor al inicio o el seguimiento, o medidas de cambio (como pendiente positiva o negativa, cambio porcentual, etc.).
Andy W
¡Agradable! ¿Qué es el blog de la comunidad? ¿Cómo se accede o escribe para ello?
Peter Flom - Restablece a Monica
3
no dude en pasar por la sala de chat Skewed Distribution para obtener detalles sobre cómo unirse al blog. Estamos siempre abiertos para más contribuciones de los miembros de la comunidad.
Andy W
0

Creo que cuando te estás quedando sin opciones con respecto al tipo si el gráfico y la configuración del gráfico, la introducción del tiempo a través de la animación es la mejor manera de mostrar porque te brinda una dimensión adicional para trabajar y te permite mostrar más información de una manera fácil de seguir . Su enfoque principal debe estar en la experiencia del usuario final.

DataDancer
fuente
¿Tenía algo en mente que difiere de la solución que Peter Ellis ha publicado aquí ? Si es así, ¿podría explicarlo más?
whuber
0

Si está más interesado en el cambio para usuarios individuales, tal vez esta sea una buena situación para una colección de Sparklines (como este ejemplo de The Pudding ):

Ejemplo de minigráficos de pudding.cool

Estos son bastante detallados, pero podría mostrar muchos más gráficos a la vez eliminando las etiquetas y unidades del eje.

Muchas herramientas de datos las tienen integradas ( Microsoft Excel tiene minigráficos ), pero supongo que querrá incluir un paquete para compilarlas en R.

Bryanbraun
fuente