Verwenden Sie gganimate, um eine Histogrammbeobachtung durch Beobachtung zu erstellen? Muss für größere Datensätze arbeiten (~ n = 5000)

11

Ich möchte Punkte aus einer Normalverteilung abtasten und dann nacheinander mit dem gganimatePaket ein Punktdiagramm erstellen, bis der letzte Frame das vollständige Punktdiagramm zeigt.

Eine Lösung, die für größere Datensätze mit 5.000 bis 20.000 Punkten geeignet ist, ist unerlässlich.

Hier ist der Code, den ich bisher habe:

library(gganimate)
library(tidyverse)

# Generate 100 normal data points, along an index for each sample 
samples <- rnorm(100)
index <- seq(1:length(samples))

# Put data into a data frame
df <- tibble(value=samples, index=index)

Der df sieht so aus:

> head(df)
# A tibble: 6 x 2
    value index
    <dbl> <int>
1  0.0818     1
2 -0.311      2
3 -0.966      3
4 -0.615      4
5  0.388      5
6 -1.66       6

Das statische Diagramm zeigt das richtige Punktdiagramm:

# Create static version
plot <- ggplot(data=df, mapping=aes(x=value))+
          geom_dotplot()

Die gganimateVersion jedoch nicht (siehe unten). Es werden nur die Punkte auf der x-Achse platziert und nicht gestapelt.

plot+
  transition_reveal(along=index)

Statische Darstellung

Geben Sie hier die Bildbeschreibung ein

Ähnliches wäre ideal: Bildnachweis: https://gist.github.com/thomasp85/88d6e7883883315314f341d2207122a1 Geben Sie hier die Bildbeschreibung ein

max
quelle
Heya. Darf ich zur besseren Durchsuchbarkeit einen anderen Titel vorschlagen? Ich fing wirklich an, dieses animierte Histogramm zu mögen, und ich denke, es ist eine großartige Visualisierung ... Etwas wie "Animiertes Punkthistogramm, Beobachtung durch Beobachtung aufgebaut" wäre vielleicht relevanter?
Tjebo vor

Antworten:

10

Eine andere Möglichkeit besteht darin, die Punkte mit einem anderen Geom zu zeichnen. Sie müssen zuerst einige Zählungen an Ihren Daten vornehmen (und diese zusammenfassen), aber es ist nicht erforderlich, Ihre Daten länger zu machen.

Zum Beispiel können Sie verwenden geom_point, aber die Herausforderung besteht darin, die Abmessungen Ihrer Punkte richtig zu machen, damit sie sich berühren / nicht berühren. Dies hängt vom Ansichtsfenster / der Dateigröße ab.

Du kannst aber auch einfach ggforce::geom_ellipsedeine Punkte zeichnen :)

geom_point (Versuch und Irrtum mit Ansichtsfenstergröße)

library(tidyverse)
library(gganimate)

set.seed(42)
samples <- rnorm(100)
index <- seq(1:length(samples))
df <- tibble(value = samples, index = index)

bin_width <- 0.25

count_data <- # some minor data transformation
  df %>%
  mutate(x = plyr::round_any(value, bin_width)) %>%
  group_by(x) %>%
  mutate(y = seq_along(x))

plot <-
  ggplot(count_data, aes(group = index, x, y)) + # group by index is important
  geom_point(size = 5)

p_anim <- 
  plot +
  transition_reveal(index)

animate(p_anim, width = 550, height = 230, res = 96)

geom_ellipse (Volle Kontrolle über die Punktgröße )

library(ggforce)
plot2 <- 
  ggplot(count_data) +
  geom_ellipse(aes(group = index, x0 = x, y0 = y, a = bin_width/2, b = 0.5, angle = 0), fill = 'black') +
  coord_equal(bin_width) # to make the dots look nice and round

p_anim2 <- 
  plot2 +
  transition_reveal(index) 

animate(p_anim2) 

Wenn Sie den Link aktualisieren, den Sie zu Thomas 'erstaunlichem Beispiel bereitstellen, können Sie sehen, dass er einen ähnlichen Ansatz verwendet - er verwendet geom_circle anstelle von geom_ellipse, das ich aufgrund der besseren Kontrolle sowohl des vertikalen als auch des horizontalen Radius gewählt habe.

Um den "fallenden Tropfen" -Effekt zu erzielen, benötigen Sie transition_stateseine lange Dauer und viele Bilder pro Sekunde.

p_anim2 <- 
  plot2 +
  transition_states(states = index, transition_length = 100, state_length = 1) +
  shadow_mark() +
  enter_fly(y_loc = 12) 

animate(p_anim2, fps = 40, duration = 20) 

Erstellt am 2020-04-29 durch das reprex-Paket (v0.3.0)

Einige Inspirationen von: ggplot dotplot: Was ist die richtige Verwendung von geom_dotplot?

Tjebo
quelle
Ich suche nach Punkten, die nacheinander und nicht in Zeilen nach Y-Wert kommen.
Max
2
@max siehe Update - ersetzen Sie einfach y durch Index.
Tjebo
3

Versuche dies. Die Grundidee besteht darin, die Obs in Frames zu gruppieren, dh nach Index aufzuteilen und dann die Samples in Frames zu akkumulieren, dh in Frame 1 wird nur die erste Obs angezeigt, in Frame 2 Obs 1 und 2, ..... Vielleicht dort ist ein eleganterer Weg, um dies zu erreichen, aber es funktioniert:

library(ggplot2)
library(gganimate)
library(dplyr)
library(purrr)

set.seed(42)

# example data
samples <- rnorm(100)
index <- seq(1:length(samples))

# Put data into a data frame
df <- tibble(value=samples, index=index)

# inflated df. Group obs together into frames
df_ani <- df %>% 
  split(.$index) %>% 
  accumulate(~ bind_rows(.x, .y)) %>% 
  bind_rows(.id = "frame") %>% 
  mutate(frame = as.integer(frame))
head(df_ani)
#> # A tibble: 6 x 3
#>   frame  value index
#>   <int>  <dbl> <int>
#> 1     1  1.37      1
#> 2     2  1.37      1
#> 3     2 -0.565     2
#> 4     3  1.37      1
#> 5     3 -0.565     2
#> 6     3  0.363     3

p_gg <- ggplot(data=df, mapping=aes(x=value))+
  geom_dotplot()
p_gg
#> `stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.

p_anim <- ggplot(data=df_ani, mapping=aes(x=value))+
  geom_dotplot()

anim <- p_anim + 
  transition_manual(frame) +
  ease_aes("linear") +
  enter_fade() +
  exit_fade()
anim
#> `stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.

Erstellt am 2020-04-27 durch das reprex-Paket (v0.3.0)

stefan
quelle
Dies funktioniert, wird jedoch für größere Datenmengen schnell nicht mehr durchführbar, da die Tabelle viele Zeilen doppelter Daten enthält.
Max
Um beispielsweise 5000 Punkte zu zeichnen, hat der Datenrahmen 12 Millionen Zeilen :(
max.
Entschuldigung für die späte Antwort. Momentan etwas beschäftigt. Ja. Ich weiß, worauf du hinauswillst. Ich bin mir ziemlich sicher, dass es eine bessere und geradlinigere Lösung für diese Art von Problem geben muss. Ich bin jedoch immer noch ein Neuling in Sachen Organisation und bis jetzt hatte ich keine Zeit, alle Möglichkeiten und Funktionen zu prüfen. Ich fürchte, ich kann im Moment keine bessere Lösung finden.
Stefan
3

Ich denke, der Schlüssel hier ist, sich vorzustellen, wie Sie diese Animation manuell erstellen würden, das heißt, Sie würden dem resultierenden Punktdiagramm jeweils eine Beobachtung hinzufügen. In diesem Sinne bestand der Ansatz, den ich hier verwendete, darin, ein ggplotObjekt zu erstellen , das aus Plotebenen = Anzahl der Beobachtungen bestand, und dann Schicht für Schicht über Schritt für Schritt durchzugehen transition_layer.

# create the ggplot object
df <- data.frame(id=1:100, y=rnorm(100))

p <- ggplot(df, aes(y))

for (i in df$id) {
  p <- p + geom_dotplot(data=df[1:i,])
}

# animation
anim <- p + transition_layers(keep_layers = FALSE) +
    labs(title='Number of dots: {frame}')
animate(anim, end_pause = 20, nframes=120, fps=20)

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass ich festgelegt habe keep_layers=FALSE, um ein Überzeichnen zu vermeiden. Wenn Sie das ursprüngliche ggplotObjekt zeichnen , werden Sie sehen, was ich meine, da die erste Beobachtung 100 Mal, die zweite 99 Mal ... usw. aufgezeichnet wird.

Was ist mit der Skalierung für größere Datensätze?

Da Anzahl der Frames = Anzahl der Beobachtungen, müssen Sie die Skalierbarkeit anpassen. Hier halten Sie einfach die # Frames konstant, was bedeutet, dass Sie den Code die Frames in Segmente gruppieren lassen müssen, was ich über die seq()Funktion mache und spezifiziere length.out=100. Beachten Sie auch im neuen Beispiel, dass der Datensatz enthält n=5000. Um das Punktdiagramm im Rahmen zu halten, müssen Sie die Größe der Punkte wirklich klein machen. Ich habe die Punkte hier wahrscheinlich etwas zu klein gemacht, aber Sie haben die Idee. Nun ist die Anzahl der Frames = Anzahl der Beobachtungsgruppen.

df <- data.frame(id=1:5000, y=rnorm(5000))

p <- ggplot(df, aes(y))

for (i in seq(0,length(df$id), length.out=100)) {
  p <- p + geom_dotplot(data=df[1:i,], dotsize=0.08)
}

anim <- p + transition_layers(keep_layers=FALSE) +
  labs(title='Frame: {frame}')

animate(anim, end_pause=20, nframes=120, fps=20)

Geben Sie hier die Bildbeschreibung ein

chemdork123
quelle
Dies funktioniert gut für kleine Datensätze, lässt sich jedoch nicht gut auf mäßig große Datenmengen skalieren (n = 5000).
Max
Hier ist der Fehler Berichte für n = 5000: Fehler: C-Stack-Nutzung 7969904 ist zu nahe am Limit
max
Ja, hier hat das Beispiel Frame = Anzahl der Beobachtungen. Ich habe die Antwort für die Skalierbarkeit bearbeitet, in dem Sie die # Frames konstant bei 100 halten und dann so , dass Frames Skalierung = Anzahl von Beobachtungsgruppen
chemdork123