Liniendiagramm hat zu viele Linien, gibt es eine bessere Lösung?


30

Ich versuche, die Anzahl der Aktionen von Benutzern (in diesem Fall "Gefällt mir") im Laufe der Zeit grafisch darzustellen.

Ich habe also "Anzahl der Aktionen" als meine y-Achse, meine x-Achse ist die Zeit (Wochen) und jede Zeile repräsentiert einen Benutzer.

Mein Problem ist, dass ich diese Daten für einen Satz von ungefähr 100 Benutzern betrachten möchte. Aus einem Liniendiagramm wird schnell ein Durcheinander mit 100 Linien. Gibt es einen besseren Diagrammtyp, mit dem ich diese Informationen anzeigen kann? Oder sollte ich mir überlegen, ob ich einzelne Leitungen ein- und ausschalten kann?

Ich würde gerne alle Daten auf einmal sehen, aber es ist nicht besonders wichtig, die Anzahl der Aktionen mit hoher Genauigkeit zu erkennen.

Warum mache ich das?

Für eine Untergruppe meiner Benutzer (Top-Benutzer) möchte ich herausfinden, welchen Benutzern eine neue Version der Anwendung, die an einem bestimmten Datum eingeführt wurde, möglicherweise nicht gefallen hat. Ich suche nach signifikanten Einbußen bei der Anzahl der Aktionen einzelner Benutzer.


5
Haben Sie darüber nachgedacht, die Linien durch Ändern des Alphas, mit dem sie geplottet werden, halbtransparent zu machen?
Fomite

1
@EpiGrad Vernünftiger Vorschlag, aber das würde es nicht wirklich einfacher machen zu sehen, wonach ich suche.
regulatethis

1
@regulatethis Ich würde einen "Small Multiple" -Ansatz unter Verwendung der facet_wrapFunktion von ggplot2 vorschlagen, um einen Block mit 4 x 5 Diagrammen (4 Zeilen, 5 Spalten - je nach gewünschtem Seitenverhältnis anpassen) mit ~ 5 Benutzern pro Diagramm zu erstellen. Das sollte klar genug sein und Sie können es auf ungefähr 10 Benutzer pro Diagramm skalieren, was Platz für 200 bei einem 4 × 5-Diagramm oder 360 bei einem 6 × 6-Diagramm bietet.
SlowLearner

Antworten:


31

Ich möchte eine (Standard-) vorläufige Analyse vorschlagen, um die Haupteffekte von (a) Abweichungen unter den Benutzern, (b) der typischen Reaktion aller Benutzer auf die Änderung und (c) der typischen Abweichung von einem Zeitraum zum nächsten zu beseitigen .

Ein einfacher (aber keinesfalls der beste) Weg, dies zu tun, besteht darin, einige Iterationen des "Median-Polierens" an den Daten durchzuführen, um Benutzer-Mediane und Zeit-Mediane herauszusuchen und dann die Residuen über die Zeit hinweg zu glätten. Identifizieren Sie die Glättungen, die sich stark ändern: Sie sind die Benutzer, die Sie in der Grafik hervorheben möchten.

Da es sich um Zählungsdaten handelt, ist es eine gute Idee, sie mit einer Quadratwurzel erneut auszudrücken.

Als Beispiel für das, was daraus resultieren kann, sehen Sie hier einen simulierten 60-Wochen-Datensatz von 240 Benutzern, die normalerweise 10 bis 20 Aktionen pro Woche ausführen. Eine Änderung bei allen Benutzern trat nach der 40. Woche auf. Drei von ihnen wurden "angewiesen", negativ auf die Änderung zu reagieren. Das linke Diagramm zeigt die Rohdaten: Anzahl der Aktionen nach Benutzer (wobei Benutzer nach Farbe unterschieden sind) über die Zeit. Wie in der Frage behauptet, ist es ein Durcheinander. Das rechte Diagramm zeigt die Ergebnisse dieser EDA - in denselben Farben wie zuvor -, wobei die ungewöhnlich ansprechenden Benutzer automatisch identifiziert und hervorgehoben werden. Die Identifikation ist - obwohl etwas ad hoc - vollständig und korrekt (in diesem Beispiel).

Abbildung 1

Hier ist der RCode, der diese Daten erzeugt und die Analyse durchgeführt hat. Es könnte auf verschiedene Arten verbessert werden, einschließlich

  • Verwenden Sie einen vollständigen Mittelwert, um die Residuen zu finden, anstatt nur eine Iteration.

  • Glätten Sie die Reste separat vor und nach dem Änderungspunkt.

  • Vielleicht mit einem ausgeklügelten Ausreißererkennungsalgorithmus. Die aktuelle Option kennzeichnet lediglich alle Benutzer, deren Residuenbereich mehr als das Doppelte des Medianbereichs beträgt. Obwohl einfach, ist es robust und scheint gut zu funktionieren. (Ein vom Benutzer einstellbarer Wert thresholdkann angepasst werden, um diese Identifikation mehr oder weniger streng zu machen.)

Tests haben jedoch ergeben, dass diese Lösung für eine Vielzahl von Benutzergruppen von 12 bis 240 oder mehr geeignet ist.

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
Bei mehr als 100-200 Usern würde ich mich thresholdauf ca. erhöhen2.5n.users <- 500n.outliers <- 100threshold <- 2.5

16

Im Allgemeinen finde ich, dass mehr als zwei oder drei Zeilen auf einer Facette eines Plots schwer zu lesen sind (obwohl ich es immer noch mache). Das ist also ein interessantes Beispiel dafür, was zu tun ist, wenn Sie etwas haben, das konzeptionell ein 100-Facetten-Plot sein könnte. Eine Möglichkeit besteht darin, alle 100 Facetten zu zeichnen, anstatt zu versuchen, alle auf einmal auf die Seite zu bringen und sie einzeln in einer Animation zu betrachten.

Wir haben diese Technik tatsächlich bei meiner Arbeit verwendet - wir haben ursprünglich die Animation erstellt, die 60 verschiedene Liniendiagramme als Hintergrund für ein Ereignis zeigt (der Start einer neuen Datenreihe), und dabei festgestellt, dass wir tatsächlich einige Merkmale der Daten aufgegriffen haben das war in facettierten Plots mit 15 oder 30 Facetten pro Seite nicht sichtbar.

Hier finden Sie eine alternative Methode zur Darstellung der Rohdaten, bevor Sie den Benutzer und die typischen Zeiteffekte entfernen, wie von @whuber empfohlen. Dies ist nur eine zusätzliche Alternative zu seiner Darstellung der Rohdaten. Ich empfehle Ihnen nachdrücklich, mit der Analyse nach den von ihm vorgeschlagenen Grundsätzen fortzufahren.

Eine Möglichkeit, dieses Problem zu umgehen, besteht darin, die 100 (oder 240 in @ whubers Beispiel) Zeitreihendiagramme separat zu erstellen und zu einer Animation zusammenzufügen. Mit dem folgenden Code werden 240 separate Bilder dieser Art erstellt. Anschließend können Sie mit der kostenlosen Filmerstellungssoftware einen Film daraus machen. Leider war die einzige Möglichkeit, dies zu tun und akzeptable Qualität beizubehalten, eine 9-MB-Datei, aber wenn Sie sie nicht über das Internet senden müssen, ist dies möglicherweise kein Problem, und trotzdem bin ich mir sicher, dass ein bisschen mehr Abhilfe schafft Animation versiert. Das Animationspaket in R könnte hier nützlich sein (Sie können alles in einem Aufruf von R aus erledigen), aber ich habe es für diese Illustration einfach gehalten.

Ich habe die Animation so erstellt, dass sie jede Linie in schwerem Schwarz zeichnet und dann einen blassen, halbtransparenten grünen Schatten hinterlässt, damit das Auge ein allmähliches Bild der akkumulierten Daten erhält. Dies birgt sowohl Risiken als auch Chancen - die Reihenfolge, in der die Zeilen hinzugefügt werden, hinterlässt einen anderen Eindruck. Daher sollten Sie in Betracht ziehen, dies in irgendeiner Weise aussagekräftig zu machen.

Hier sind einige der Standbilder aus dem Film, die dieselben Daten verwenden, die @whuber generiert hat: Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

# ---------------------------- 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, das ist eine nette Idee. Sie können auch mit windows()oder ein neues Gerätefenster initiieren quartz()und dann Ihre for()Schleife darin verschachteln . NB, Sie müssen ein Sys.sleep(1)am Ende Ihrer Schleife einfügen, damit Sie die Iterationen tatsächlich sehen können. Natürlich speichert diese Strategie eine Filmdatei nicht wirklich - Sie müssen sie nur jedes Mal neu starten, wenn Sie sie erneut ansehen möchten.
gung - Wiedereinsetzung von Monica

+1 Sehr nette Idee - ich werde es bei der nächsten Gelegenheit versuchen. (GTW, Mathematica , zum Beispiel, macht kurze Arbeit mit dem Erstellen und Speichern solcher Animationen.)
whuber

Erstaunliche Idee - eine Animation in dieser Richtung (oder der zu generierende Code und die zu generierenden Daten) würde einen sehr sexy Online-Anhang zu einer Publikation ergeben.
N Brouwer

7

Eines der einfachsten Dinge ist ein Boxplot. Sie können sofort sehen, wie sich Ihre Stichprobenmediane bewegen und an welchen Tagen die meisten Ausreißer auftreten.

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)

Bildbeschreibung hier eingeben

Für die individuelle Analyse empfehle ich, eine kleine Zufallsstichprobe aus Ihren Daten zu ziehen und separate Zeitreihen zu analysieren.


1
Interessante Lösung, aber was ich wirklich sehen möchte, ist die "Änderung" auf Benutzerbasis. Ich möchte die Schwankungen in der Aktivität für einzelne Benutzer sehen. Aus diesem Grund habe ich zunächst eine Zeile ausgewählt, aber die Visualisierung ist jetzt einfach zu unübersichtlich.
regulatethis

Nun, es hängt wirklich davon ab, welche Muster Sie in Ihren Daten sehen möchten. Wenn Sie uns vielleicht sagen könnten, was Sie herausfinden möchten, könnten wir eine Lösung finden.
jem77bfp

Für eine Untergruppe meiner Benutzer (Top-Benutzer) möchte ich herausfinden, welchen Benutzern eine neue Version der Anwendung, die an einem bestimmten Datum eingeführt wurde, möglicherweise nicht gefallen hat. Ich suche nach signifikanten Einbußen bei der Anzahl der Aktionen einzelner Benutzer.
regulatethis

Willkommen auf der Site @ jem77bfp. Er sagte, er wolle alle Daten sehen. Aber es wäre schön, mehr Details zu haben, da stimme ich zu.
Peter Flom - Wiedereinsetzung von Monica

+1 - Anstatt die Box-Plots zu visualisieren, kann es nützlich sein, die Zusammenfassungsstatistik in Liniendiagrammen zu verknüpfen. Sehen Sie diese Antwort von mir für ein Beispiel und eine Diskussion unten.
Andy W

7

Sicher. Sortieren Sie zunächst nach der durchschnittlichen Anzahl von Aktionen. Dann machen Sie (sagen wir) 4 Graphen mit jeweils 25 Linien, eine für jedes Quartil. Das heißt, Sie können die y-Achsen verkleinern (aber die Beschriftung der y-Achse deutlich machen). Und mit 25 Linien können Sie sie nach Linientyp und Farbe variieren und möglicherweise das Symbol zeichnen, um eine gewisse Klarheit zu erzielen

Dann stapeln Sie die Grafiken vertikal mit einer einzigen Zeitachse.

Dies wäre in R oder SAS ziemlich einfach (zumindest wenn Sie Version 9 von SAS haben).


2
+1 - Ich würde jedoch noch weniger Zeilen pro kleines Vielfaches vorschlagen! Siehe meinen Blog-Beitrag zum Thema und ein Beispiel. Sortieren ist auch eine großartige Idee, und andere potenzielle Beispiele können Werte zu Beginn oder im Nachhinein oder Kennzahlen für Änderungen (wie positive oder negative Steigung, prozentuale Änderung usw.) sein.
Andy W

Nett! Was ist der Community-Blog? Wie greift man darauf zu oder schreibt es?
Peter Flom - Wiedereinsetzung von Monica

3
Besuchen Sie den Chatroom von Skewed Distribution, um zu erfahren, wie Sie dem Blog beitreten können. Wir sind immer offen für weitere Beiträge von Community-Mitgliedern.
Andy W

0

Ich finde, wenn Ihnen die Zeit ausgeht, sind die Optionen für die Anzeige von Grafiken und Grafikeinstellungen die beste Möglichkeit, die Zeit durch Animation einzuführen, da sie Ihnen eine zusätzliche Dimension für die Arbeit bieten und es Ihnen ermöglichen, mehr Informationen auf einfach zu befolgende Weise anzuzeigen . Ihr Hauptaugenmerk muss auf der Endbenutzererfahrung liegen.


Hatten Sie etwas im Sinn, das sich von der Lösung unterscheidet, die Peter Ellis hier gepostet hat ? Wenn ja, könnten Sie dies bitte näher erläutern?
whuber

0

Wenn Sie am meisten an der Änderung für einzelne Benutzer interessiert sind, ist dies möglicherweise eine gute Situation für eine Sammlung von Sparklines (wie dieses Beispiel aus The Pudding ):

Beispiel für Sparklines aus pudding.cool

Diese sind ziemlich detailliert, aber Sie können viel mehr Diagramme gleichzeitig anzeigen, indem Sie Achsenbeschriftungen und Einheiten entfernen.

In vielen Datentools sind sie integriert ( Microsoft Excel verfügt über Sparklines ), aber ich nehme an, Sie möchten ein Paket einbinden, um sie in R zu erstellen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.