Utiliser gganimate pour construire une observation d'histogramme par observation? Doit fonctionner pour des ensembles de données plus importants (~ n = 5000)

12

Je voudrais échantillonner des points à partir d'une distribution normale, puis créer un pointplot un par un en utilisant le gganimatepackage jusqu'à ce que la trame finale montre le pointplot complet.

Une solution qui fonctionne pour des ensembles de données plus importants ~ 5 000 - 20 000 points est essentielle.

Voici le code que j'ai jusqu'à présent:

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)

Le df ressemble à ceci:

> 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

Le tracé statique montre le pointplot correct:

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

Cependant, la gganimateversion ne fonctionne pas (voir ci-dessous). Il place uniquement les points sur l'axe des x et ne les empile pas.

plot+
  transition_reveal(along=index)

Tracé statique

entrez la description de l'image ici

Quelque chose de similaire serait idéal: Crédit: https://gist.github.com/thomasp85/88d6e7883883315314f341d2207122a1 entrez la description de l'image ici

max
la source
Heya. Puis-je suggérer un titre différent pour une meilleure recherche? J'ai vraiment commencé à aimer cet histogramme animé, et je pense que c'est une excellente visualisation ... Sth comme "Histogramme de points animé, construit observation par observation" serait peut-être plus pertinent?
Tjebo

Réponses:

11

Une autre option consiste à dessiner les points avec un autre géom. vous devrez d'abord compter sur vos données (et les regrouper), mais cela ne nécessite pas de prolonger la durée de vos données.

Par exemple, vous pouvez utiliser geom_point, mais le défi sera d'obtenir les bonnes dimensions de vos points, afin qu'ils touchent / ne touchent pas. Cela dépend de la taille du périphérique / fichier.

Mais vous pouvez aussi simplement utiliser ggforce::geom_ellipsepour dessiner vos points :)

geom_point (essai et erreur avec les dimensions de l'appareil)

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 (Contrôle total de la taille du point)

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) 

mise à jour dans le lien que vous fournissez à l'exemple étonnant de thomas, vous pouvez voir qu'il utilise une approche similaire - il utilise geom_circle au lieu de geom_ellipse, que j'ai choisi en raison d'un meilleur contrôle pour le rayon vertical et horizontal.

Pour obtenir l'effet "gouttes tombantes", vous aurez besoin d' transition_statesune longue durée et de plusieurs images par seconde.

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) 

Créé le 2020-04-29 par le package reprex (v0.3.0)

une inspiration de: ggplot dotplot: Quelle est la bonne utilisation de geom_dotplot?

Tjebo
la source
Je cherche les points à venir un par un, pas en rangées selon la valeur Y.
max
2
@max voir la mise à jour - remplacez simplement y par index.
Tjebo
3

Essaye ça. L'idée de base est de regrouper les obs dans les trames, c'est-à-dire de les diviser par index, puis d'accumuler les échantillons dans les trames, c'est-à-dire que dans la trame 1, seul le premier obs est affiché, dans la trame 2 obs 1 et 2, ..... est un moyen plus élégant d'y parvenir, mais cela fonctionne:

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`.

Créé le 2020-04-27 par le package reprex (v0.3.0)

Stefan
la source
cela fonctionne, mais devient rapidement impossible pour des ensembles de données plus volumineux car la table contient de nombreuses lignes de données dupliquées.
max
par exemple, pour tracer 5000 points, la trame de données a 12 millions de lignes :(
max
Désolé pour la réponse tardive. Un peu occupé en ce moment. Oui. Je vois ce que tu veux dire. Je suis tout à fait sûr qu'il doit y avoir une solution meilleure et plus directe à ce type de problème. Cependant, je suis toujours un débutant dans le gganimate et jusqu'à présent, je n'ai pas eu le temps de vérifier toutes ses possibilités et fonctionnalités. Donc, je crains de ne pas pouvoir trouver une meilleure solution pour le moment.
Stefan
3

Je pense que la clé ici est d'imaginer comment vous créeriez cette animation manuellement, c'est-à-dire que vous ajouteriez des points une observation à la fois au pointplot résultant. Dans cet esprit, l'approche que j'ai utilisée ici était de créer un ggplotobjet composé de couches de tracé = nombre d'observations, puis de passer en revue couche par couche via 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)

entrez la description de l'image ici

Notez que j'ai défini keep_layers=FALSEpour éviter le surplotage. Si vous tracez l' ggplotobjet initial , vous verrez ce que je veux dire, puisque la première observation est tracée 100 fois, la seconde 99 fois ... etc.

Qu'en est-il de la mise à l'échelle pour des ensembles de données plus volumineux?

Étant donné que le nombre d'images = nombre d'observations, vous devez ajuster pour l'évolutivité. Ici, gardez les # frames constants, ce qui signifie que vous devez laisser le code grouper les frames en segments, ce que je fais via la seq()fonction, en spécifiant length.out=100. Notez également dans le nouvel exemple, l'ensemble de données contient n=5000. Afin de garder le pointplot dans le cadre, vous devez rendre les tailles des points vraiment minuscules. J'ai probablement fait les points un peu trop petits ici, mais vous avez l'idée. Maintenant, les # frames = nombre de groupes d'observations.

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)

entrez la description de l'image ici

chemdork123
la source
Cela fonctionne bien pour les petits ensembles de données, mais ne s'adapte pas bien aux données même moyennement grandes (n = 5000).
max
Voici l'erreur signalée pour n = 5000: Erreur: l'utilisation de la pile C 7969904 est trop proche de la limite
max
Oui, ici l'exemple a frame = nombre d'observations. J'ai édité la réponse pour l'évolutivité, où vous maintenez le nombre d'images # constant à 100, puis la mise à l'échelle de sorte que les images = nombre de groupes d'
chemdork123