Wykres liniowy ma zbyt wiele linii, czy jest lepsze rozwiązanie?


30

Próbuję przedstawić liczbę działań użytkowników (w tym przypadku „polubień”) w czasie.

Mam więc „liczbę działań” jako moją oś y, moją oś x to czas (tygodnie), a każda linia reprezentuje jednego użytkownika.

Mój problem polega na tym, że chcę spojrzeć na te dane dla zestawu około 100 użytkowników. Wykres liniowy szybko staje się pomieszanym bałaganem ze 100 liniami. Czy istnieje lepszy rodzaj wykresu, którego można użyć do wyświetlenia tych informacji? Czy powinienem patrzeć na możliwość włączania / wyłączania poszczególnych linii?

Chciałbym zobaczyć wszystkie dane naraz, ale możliwość precyzyjnego rozpoznania liczby działań nie jest szczególnie ważna.

Dlaczego to robię

W przypadku podzbioru moich użytkowników (najlepszych użytkowników) chcę dowiedzieć się, którzy z nich nie polubili nowej wersji aplikacji, która została wdrożona w określonym dniu. Szukam znacznych spadków liczby działań poszczególnych użytkowników.


5
Czy zastanawiałeś się nad tym, aby linie były półprzezroczyste, zmieniając alfa używane do ich kreślenia?
Fomite,

1
@EpiGrad Rozsądna sugestia, ale tak naprawdę nie ułatwiłoby to zobaczenia, czego szukam.
regulatethis

1
@regulatethis Sugerowałbym podejście „małej wielokrotności” przy użyciu facet_wrapfunkcji ggplot2 w celu utworzenia bloku 4 x 5 wykresów (4 rzędy, 5 kolumn - dostosuj w zależności od pożądanego współczynnika proporcji) z ~ 5 użytkownikami na wykresie. To powinno być wystarczająco jasne i można skalować do około 10 użytkowników na wykresie, dając miejsce dla 200 z działką 4x5 lub 360 z działką 6x6.
SlowLearner,

Odpowiedzi:


31

Chciałbym zasugerować (standardową) wstępną analizę w celu usunięcia głównych skutków (a) zmienności wśród użytkowników, (b) typowej reakcji wszystkich użytkowników na zmianę oraz (c) typowej zmienności z jednego okresu na następny .

Prostym (ale nie najlepszym) sposobem na wykonanie tego jest wykonanie kilku iteracji „mediany dopracowania” na danych, aby zmieść mediany użytkownika i mediany okresu, a następnie wygładzić resztki w miarę upływu czasu. Zidentyfikuj gładkie, które bardzo się zmieniają: są to użytkownicy, których chcesz podkreślić w grafice.

Ponieważ są to dane zliczające, dobrze jest ponownie wyrazić je za pomocą pierwiastka kwadratowego.

Jako przykład tego, co może wyniknąć, oto symulowany 60-tygodniowy zestaw danych 240 użytkowników, którzy zazwyczaj podejmują od 10 do 20 działań tygodniowo. Zmiana u wszystkich użytkowników nastąpiła po 40 tygodniu. Trzech z nich „powiedziano”, by zareagowali negatywnie na zmianę. Lewy wykres pokazuje nieprzetworzone dane: liczby działań według użytkownika (użytkownicy wyróżniają się kolorem) w czasie. Jak stwierdzono w pytaniu, to bałagan. Właściwa fabuła pokazuje wyniki EDA - w tych samych kolorach, co poprzednio - z wyjątkowo reagującymi użytkownikami automatycznie identyfikowanymi i wyróżnianymi. Identyfikacja - choć jest nieco ad hoc - jest kompletna i poprawna (w tym przykładzie).

Rycina 1

Oto Rkod, który wygenerował te dane i przeprowadził analizę. Można to poprawić na kilka sposobów, w tym

  • Używanie pełnego środkowego połysku do znalezienia resztek, a nie tylko jednej iteracji.

  • Wygładzanie resztek osobno przed i po punkcie zmiany.

  • Być może przy użyciu bardziej zaawansowanego algorytmu wykrywania wartości odstających. Obecny oznacza jedynie wszystkich użytkowników, których zakres reszt jest ponad dwukrotnie większy niż mediana. Choć prosty, jest solidny i wydaje się działać dobrze. (Wartość ustawiana przez użytkownika threshold, może zostać dostosowana, aby uczynić tę identyfikację mniej lub bardziej rygorystyczną.)

Testy sugerują jednak, że to rozwiązanie działa dobrze w szerokim zakresie liczby użytkowników, od 12 do 240 lub więcej.

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")

3
threshold2.5n.users <- 500n.outliers <- 100threshold <- 2.5

16

Ogólnie stwierdzam, że więcej niż dwie lub trzy linie na jednym fragmencie fabuły stają się trudne do odczytania (chociaż ciągle to robię). Jest to więc interesujący przykład tego, co zrobić, gdy masz coś, co koncepcyjnie może być fabułą o 100 aspektach. Jednym z możliwych sposobów jest narysowanie wszystkich 100 aspektów, ale zamiast próbować umieścić je wszystkie na stronie na raz, oglądając je pojedynczo w animacji.

W mojej pracy wykorzystaliśmy tę technikę - pierwotnie stworzyliśmy animację pokazującą 60 różnych wykresów liniowych jako tło dla zdarzenia (uruchomienie nowej serii danych), a następnie stwierdziliśmy, że robiąc to, rzeczywiście wybraliśmy niektóre funkcje danych które nie były widoczne na wykresach fasetowanych z 15 lub 30 fasetami na stronę.

Oto więc alternatywny sposób prezentacji surowych danych, zanim zaczniesz usuwać użytkownika i typowe efekty czasowe zalecane przez @whuber. Jest to przedstawione jako dodatkowa alternatywa dla jego prezentacji surowych danych - w pełni zalecam, aby następnie przejść do analizy zgodnie z tymi, które on sugeruje.

Jednym ze sposobów obejścia tego problemu jest osobne utworzenie 100 (lub 240 w przykładzie @ Whubera) wykresów czasowych i połączenie ich w animację. Poniższy kod wygeneruje 240 oddzielnych obrazów tego rodzaju, a następnie możesz użyć bezpłatnego oprogramowania do tworzenia filmów, aby przekształcić je w film. Niestety, jedynym sposobem, w jaki mogłem to zrobić i zachować akceptowalną jakość, był plik 9 MB, ale jeśli nie musisz przesyłać go przez Internet, może to nie stanowić problemu, a poza tym jestem pewien, że można to zrobić z nieco większą ilością doświadczony animator. Pakiet animacji w R może być tutaj przydatny (pozwala to zrobić wszystko w rozmowie z R), ale dla tej ilustracji jest to proste.

Stworzyłem animację tak, że rysuje każdą linię na grubą czerń, a następnie pozostawia za sobą blady półprzezroczysty zielony cień, aby oko uzyskało stopniowy obraz gromadzących się danych. Są w tym zarówno zagrożenia, jak i szanse - kolejność dodawania wierszy pozostawi inne wrażenie, więc powinieneś rozważyć nadanie mu pewnego znaczenia.

Oto niektóre zdjęcia z filmu, który korzysta z tych samych danych, które wygenerował @whuber: wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

# ---------------------------- 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

+1, to fajny pomysł. Możesz także zainicjować nowe okno urządzenia za pomocą windows()lub quartz(), a następnie zagnieździć for()w nim pętlę. Uwaga: musisz umieścić Sys.sleep(1)na dole pętli, aby rzeczywiście zobaczyć iteracje. Oczywiście ta strategia nie zapisuje pliku filmowego - wystarczy go ponownie uruchomić za każdym razem, gdy chcesz go obejrzeć ponownie.
gung - Przywróć Monikę

+1 Bardzo fajny pomysł - spróbuję to przy następnej okazji. (Na przykład GTW, Mathematica robi krótkie prace nad tworzeniem i zapisywaniem takich animacji.)
whuber

Niesamowity pomysł - animacja wzdłuż tych linii (lub kodu i danych do wygenerowania) byłaby bardzo seksownym dodatkiem online do publikacji.
N Brouwer,

7

Jedną z najłatwiejszych rzeczy jest fabuła. Możesz natychmiast zobaczyć, w jaki sposób poruszają się twoje mediany próbek i które dni mają największe wartości odstające.

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)

wprowadź opis zdjęcia tutaj

Do analizy indywidualnej sugeruję pobranie małej przypadkowej próbki z danych i przeanalizowanie oddzielnych szeregów czasowych.


1
Ciekawe rozwiązanie, ale tak naprawdę chcę zobaczyć, jak wygląda „zmiana” dla poszczególnych użytkowników. Chcę zobaczyć wahania aktywności poszczególnych użytkowników. Dlatego początkowo wybrałem linię, ale wizualizacja jest teraz po prostu zbyt zagracona.
regulatethis

cóż, to naprawdę zależy od tego, jakie wzorce chcesz widzieć w swoich danych, być może gdybyś mógł nam powiedzieć, co próbujesz znaleźć, moglibyśmy znaleźć rozwiązanie.
jem77bfp,

W przypadku podzbioru moich użytkowników (najlepszych użytkowników) chcę dowiedzieć się, którzy z nich nie polubili nowej wersji aplikacji, która została wdrożona w określonym dniu. Szukam znacznych spadków liczby działań poszczególnych użytkowników.
reguluje ten

Witamy na stronie @ jem77bfp. powiedział, że chce zobaczyć wszystkie dane. Zgadzam się, ale byłoby miło mieć więcej szczegółów.
Peter Flom - Przywróć Monikę

+1 - zamiast wizualizacji wykresów pudełkowych, chociaż przydatne może być połączenie statystyk podsumowujących na wykresach liniowych. Zobacz moją odpowiedź na przykład i dyskusję poniżej.
Andy W

7

Pewnie. Najpierw posortuj według średniej liczby działań. Następnie utwórz (powiedzmy) 4 wykresy, każdy z 25 liniami, po jednym dla każdego kwartylu. Oznacza to, że możesz zmniejszyć osie y (ale wyczyść etykietę osi y). Za pomocą 25 linii możesz różnicować je według rodzaju i koloru linii, a być może także symbolu kreślenia i uzyskać większą przejrzystość

Następnie ułóż wykresy w pionie za pomocą pojedynczej osi czasu.

Byłoby to dość łatwe w R lub SAS (przynajmniej jeśli masz v. 9 SAS).


2
+1 - sugerowałbym jednak jeszcze mniej wierszy na małą wielokrotność! Zobacz mój powiązany post na blogu na ten temat i przykład. Sortowanie jest również świetnym pomysłem, a inne potencjalne mogą obejmować wartość początkową lub kontrolną lub miary zmiany (takie jak dodatnie lub ujemne nachylenie, zmiana procentowa itp.).
Andy W,

Miły! Co to jest blog społeczności? Jak można uzyskać do niego dostęp lub pisać?
Peter Flom - Przywróć Monikę

3
krępuj się zatrzymać przez różną dystrybucję czacie uzyskać szczegółowe informacje na temat przyłączenia się do bloga. Jesteśmy zawsze otwarci na większy wkład członków społeczności.
Andy W

0

Uważam, że gdy zabraknie Ci opcji dotyczących typu, jeśli wykres i ustawienia wykresu wprowadzenie czasu za pomocą animacji jest najlepszym sposobem wyświetlania, ponieważ daje dodatkowy wymiar do pracy i pozwala wyświetlać więcej informacji w łatwy sposób . Główny nacisk należy położyć na wrażenia użytkownika końcowego.


Czy miałeś na myśli coś, co różni się od rozwiązania, które opublikował tutaj Peter Ellis ? Jeśli tak, czy mógłbyś to rozwinąć?
whuber

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.