O gráfico de linhas tem muitas linhas. Existe uma solução melhor?

31

Estou tentando representar graficamente o número de ações dos usuários (neste caso, "curtidas") ao longo do tempo.

Portanto, tenho "Número de ações" como meu eixo y, meu eixo x é o tempo (semanas) e cada linha representa um usuário.

Meu problema é que quero analisar esses dados para um conjunto de cerca de 100 usuários. Um gráfico de linhas rapidamente se torna uma bagunça confusa com 100 linhas. Existe um tipo melhor de gráfico que eu possa usar para exibir essas informações? Ou devo considerar ser capaz de ativar / desativar linhas individuais?

Gostaria de ver todos os dados de uma só vez, mas ser capaz de discernir o número de ações com alta precisão não é muito importante.

Por que estou fazendo isso

Para um subconjunto de meus usuários (usuários principais), quero descobrir quais podem não ter gostado de uma nova versão do aplicativo que foi lançada em uma determinada data. Estou procurando quedas significativas no número de ações de usuários individuais.

regular
fonte
5
Você já pensou em tornar as linhas semi-transparentes alterando o alfa que está sendo usado para plotá-las?
fomite
11
@EpiGrad Sugestão razoável, mas isso não tornaria mais fácil ver o que estou procurando.
regulatethis
11
@regulatethis sugeriria uma abordagem de "pequenos múltiplos" usando a facet_wrapfunção ggplot2 para criar um bloco de gráficos 4 x 5 (4 linhas, 5 colunas - ajuste dependendo da proporção desejada) com ~ 5 usuários por gráfico. Isso deve ficar claro o suficiente e você pode escalá-lo para cerca de 10 usuários por gráfico, dando espaço para 200 com um gráfico 4x5 ou 360 com um gráfico 6x6.
SlowLearner

Respostas:

31

Eu gostaria de sugerir uma análise preliminar (padrão) para remover os principais efeitos de (a) variação entre usuários, (b) resposta típica entre todos os usuários à mudança e (c) variação típica de um período para o próximo .

Uma maneira simples (mas de nenhuma maneira a melhor) de fazer isso é executar algumas iterações de "polimento mediano" nos dados para varrer as medianas do usuário e medianas do período e suavizar os resíduos ao longo do tempo. Identifique os suaves que mudam muito: eles são os usuários que você deseja enfatizar no gráfico.

Como esses são dados de contagem, é uma boa ideia expressá-los novamente usando uma raiz quadrada.

Como exemplo do que pode resultar, aqui está um conjunto de dados simulado de 60 semanas de 240 usuários que normalmente realizam de 10 a 20 ações por semana. Uma mudança em todos os usuários ocorreu após a semana 40. Três deles foram "instruídos" a responder negativamente à mudança. O gráfico à esquerda mostra os dados brutos: contagem de ações do usuário (com usuários diferenciados por cor) ao longo do tempo. Como afirmado na pergunta, é uma bagunça. A plotagem correta mostra os resultados dessa EDA - nas mesmas cores de antes - com os usuários incomumente responsivos identificados e destacados automaticamente . A identificação - embora seja um pouco ad hoc - está completa e correta (neste exemplo).

figura 1

Aqui está o Rcódigo que produziu esses dados e realizou a análise. Poderia ser melhorado de várias maneiras, incluindo

  • Usando um polonês mediano completo para encontrar os resíduos, em vez de apenas uma iteração.

  • Suavização dos resíduos separadamente antes e depois do ponto de mudança.

  • Talvez usando um algoritmo de detecção de outlier mais sofisticado. O atual apenas sinaliza todos os usuários cujo intervalo de resíduos é mais do que o dobro do intervalo médio. Embora simples, é robusto e parece funcionar bem. (Um valor configurável pelo usuário,, thresholdpode ser ajustado para tornar essa identificação mais ou menos rigorosa.)

No entanto, os testes sugerem que esta solução funciona bem para uma ampla variedade de contagens de usuários, 12 - 240 ou mais.

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
fonte
3
threshold2.5n.users <- 500n.outliers <- 100threshold <- 2.5
16

Geralmente, acho que mais de duas ou três linhas em uma única faceta de um enredo começam a ser difíceis de ler (embora eu ainda o faça o tempo todo). Portanto, este é um exemplo interessante do que fazer quando você tem algo que conceitualmente pode ser um gráfico de 100 facetas. Uma maneira possível é desenhar todas as 100 facetas, mas em vez de tentar colocá-las na página de uma só vez, olhando uma de cada vez em uma animação.

Na verdade, usamos essa técnica no meu trabalho - originalmente fizemos a animação mostrando 60 gráficos de linhas diferentes como pano de fundo para um evento (o lançamento de uma nova série de dados) e descobrimos que, ao fazê-lo, captamos alguns recursos dos dados. que não eram visíveis em gráficos facetados com 15 ou 30 facetas por página.

Então, aqui está uma maneira alternativa de apresentar os dados brutos, antes de começar a remover o usuário e os efeitos de tempo típicos, conforme recomendado pelo @whuber. Isso é apresentado apenas como uma alternativa adicional à sua apresentação dos dados brutos - eu recomendo totalmente que você prossiga com a análise em linhas como as que ele sugere.

Uma maneira de contornar esse problema é produzir as séries cronológicas de 100 (ou 240 no exemplo do whuber) separadamente e uni-las em uma animação. O código abaixo produzirá 240 imagens separadas desse tipo e você poderá usar o software gratuito para criação de filmes para transformá-lo em filme. Infelizmente, a única maneira de fazer isso e manter a qualidade aceitável era um arquivo de 9 MB, mas se você não precisar enviá-lo pela Internet, isso pode não ser um problema. animação experiente. O pacote de animação em R pode ser útil aqui (permite fazer tudo em uma chamada de R), mas mantive isso simples para esta ilustração.

Fiz a animação de tal forma que desenha cada linha em preto pesado e deixa uma sombra verde semi-transparente pálida para trás, para que o olho obtenha uma imagem gradual dos dados acumulados. Existem riscos e oportunidades nisso - a ordem em que as linhas são adicionadas deixará uma impressão diferente; portanto, considere torná-la significativa de alguma forma.

Aqui estão algumas das fotos do filme, que usa os mesmos dados que o @whuber gerou: insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

# ---------------------------- 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
fonte
+1, é uma boa ideia. Você também pode iniciar uma nova janela do dispositivo usando windows()ou quartz()e aninhar seu for()loop dentro dela. NB, você precisará colocar um Sys.sleep(1)na parte inferior do seu loop para poder ver as iterações. Obviamente, essa estratégia não salva um arquivo de filme - você só precisa executá-lo novamente sempre que quiser vê-lo novamente.
gung - Restabelece Monica
+1 Ótima idéia - tentarei isso na próxima chance que tiver. (GTW, Mathematica , por exemplo, faz um pequeno trabalho de criação e salvamento de tais animações.)
whuber
Idéia incrível - uma animação nesse sentido (ou o código e os dados a serem gerados) seria um apêndice online muito sexy para uma publicação.
N Brouwer
7

Uma das coisas mais fáceis de fazer é um boxplot. Você pode ver imediatamente como as medianas da amostra se movem e quais dias têm mais discrepâncias.

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)

insira a descrição da imagem aqui

Para análise individual, sugiro coletar uma pequena amostra aleatória de seus dados e analisar séries temporais separadas.

jem77bfp
fonte
11
Solução interessante, mas o que realmente quero ver como é a "mudança" por usuário. Quero ver as flutuações na atividade de usuários individuais. Foi por isso que escolhi uma linha inicialmente, mas a visualização está muito confusa agora.
regulatethis
bem, depende realmente de quais padrões você deseja ver em seus dados; talvez, se você pudesse nos dizer o que está tentando descobrir, poderíamos encontrar uma solução.
jem77bfp
Para um subconjunto de meus usuários (usuários principais), quero descobrir quais podem não ter gostado de uma nova versão do aplicativo que foi lançada em uma determinada data. Estou procurando quedas significativas no número de ações de usuários individuais.
regulatethis
Bem-vindo ao site @ jem77bfp. ele disse que queria ver todos os dados. Mas seria bom ter mais detalhes, eu concordo.
Peter Flom - Restabelece Monica
+1 - em vez de visualizar os gráficos das caixas, pode ser útil conectar as estatísticas resumidas nos gráficos de linhas. Veja esta resposta minha para um exemplo e discussão abaixo.
21712 Andy W
7

Certo. Primeiro, classifique pelo número médio de ações. Então faça (digamos) 4 gráficos, cada um com 25 linhas, uma para cada quartil. Isso significa que você pode reduzir os eixos y (mas deixar o rótulo do eixo y claro). E com 25 linhas, você pode variar de acordo com o tipo e a cor da linha e, talvez, com o símbolo de plotagem e obter alguma clareza

Empilhe os gráficos verticalmente com um único eixo de tempo.

Isso seria bem fácil no R ou SAS (pelo menos se você tiver o v. 9 do SAS).

Peter Flom - Restabelece Monica
fonte
2
+1 - eu sugeriria ainda menos linhas por múltiplo pequeno! Veja meu post relacionado ao assunto e um exemplo. A classificação também é uma ótima ideia, e outras possíveis podem incluir valor na linha de base ou no acompanhamento ou medidas de mudança (como inclinação positiva ou negativa, porcentagem de mudança etc.).
21712 Andy
Agradável! Qual é o blog da comunidade? Como alguém acessa ou escreve para ele?
Peter Flom - Restabelece Monica
3
fique à vontade para parar na sala de bate-papo do Skewed Distribution para obter detalhes sobre como ingressar no blog. Estamos sempre abertos a mais contribuições dos membros da comunidade.
21712 Andy W
0

Acho que quando você está se esgotando, se há opções relacionadas ao tipo, se o gráfico e as configurações do gráfico, a introdução do tempo através da animação é a melhor maneira de exibir, porque fornece uma dimensão extra para trabalhar e permite exibir mais informações de uma maneira fácil de seguir . Seu foco principal deve estar na experiência do usuário final.

DataDancer
fonte
Você tinha algo em mente que difere da solução que Peter Ellis postou aqui ? Em caso afirmativo, você poderia elaborar sobre isso?
whuber
0

Se você está mais interessado na mudança para usuários individuais, talvez seja uma boa situação para uma coleção de Sparklines (como este exemplo do The Pudding ):

Exemplo de sparklines de pudding.cool

Estes são bastante detalhados, mas você pode mostrar muito mais gráficos ao mesmo tempo removendo as etiquetas e as unidades do eixo.

Muitas ferramentas de dados os incorporam (o Microsoft Excel possui linhas de flutuação ), mas acho que você desejaria extrair um pacote para construí-los em R.

bryanbraun
fonte