124 votes

Créer un numéro de groupe pour chaque séquence consécutive

J'ai le data.frame ci-dessous. Je veux ajouter une colonne 'g' qui classe mes données en fonction des séquences consécutives dans la colonne. h_no . C'est-à-dire que la première séquence de h_no 1, 2, 3, 4 est le groupe 1, la deuxième série de h_no (1 à 7) est le groupe 2, et ainsi de suite, comme indiqué dans la dernière colonne "g".

h_no   h_freq    h_freqsq g
1     0.09091 0.008264628 1
2     0.00000 0.000000000 1
3     0.04545 0.002065702 1
4     0.00000 0.000000000 1  
1     0.13636 0.018594050 2
2     0.00000 0.000000000 2
3     0.00000 0.000000000 2
4     0.04545 0.002065702 2
5     0.31818 0.101238512 2
6     0.00000 0.000000000 2
7     0.50000 0.250000000 2 
1     0.13636 0.018594050 3 
2     0.09091 0.008264628 3
3     0.40909 0.167354628 3
4     0.04545 0.002065702 3

158voto

Roman Luštrik Points 19295

Vous pouvez ajouter une colonne à vos données en utilisant différentes techniques. Les citations ci-dessous proviennent de la section "Détails" du texte d'aide correspondant, [[.data.frame .

Les trames de données peuvent être indexées selon plusieurs modes. Lorsque [ et [[ sont utilisés avec un seul indice vectoriel ( x[i] ou x[[i]] ), ils indexent le cadre de données comme s'il s'agissait d'une liste.

my.dataframe["new.col"] <- a.vector
my.dataframe[["new.col"]] <- a.vector

La méthode data.frame pour $ , des friandises x en tant que liste

my.dataframe$new.col <- a.vector

Lorsque [ et [[ sont utilisés avec deux indices ( x[i, j] et x[[i, j]] ), ils agissent comme l'indexation d'une matrice

my.dataframe[ , "new.col"] <- a.vector

Puisque la méthode de data.frame part du principe que si vous ne précisez pas si vous travaillez avec des colonnes ou des lignes, il supposera que vous voulez dire des colonnes.


Pour votre exemple, cela devrait fonctionner :

# make some fake data
your.df <- data.frame(no = c(1:4, 1:7, 1:5), h_freq = runif(16), h_freqsq = runif(16))

# find where one appears and 
from <- which(your.df$no == 1)
to <- c((from-1)[-1], nrow(your.df)) # up to which point the sequence runs

# generate a sequence (len) and based on its length, repeat a consecutive number len times
get.seq <- mapply(from, to, 1:length(from), FUN = function(x, y, z) {
            len <- length(seq(from = x[1], to = y[1]))
            return(rep(z, times = len))
         })

# when we unlist, we get a vector
your.df$group <- unlist(get.seq)
# and append it to your original data.frame. since this is
# designating a group, it makes sense to make it a factor
your.df$group <- as.factor(your.df$group)

   no     h_freq   h_freqsq group
1   1 0.40998238 0.06463876     1
2   2 0.98086928 0.33093795     1
3   3 0.28908651 0.74077119     1
4   4 0.10476768 0.56784786     1
5   1 0.75478995 0.60479945     2
6   2 0.26974011 0.95231761     2
7   3 0.53676266 0.74370154     2
8   4 0.99784066 0.37499294     2
9   5 0.89771767 0.83467805     2
10  6 0.05363139 0.32066178     2
11  7 0.71741529 0.84572717     2
12  1 0.10654430 0.32917711     3
13  2 0.41971959 0.87155514     3
14  3 0.32432646 0.65789294     3
15  4 0.77896780 0.27599187     3
16  5 0.06100008 0.55399326     3

0 votes

Quelle est la différence entre les deux dernières méthodes d'ajout d'une colonne ?

2 votes

@huon-dbaupp la méthode avec une virgule est explicite et fonctionnera aussi sur les matrices, tandis que la dernière ne fonctionne que sur les data.frames. Si aucune virgule n'est fournie, R suppose que vous voulez dire colonnes.

12voto

user1333396 Points 37

Facilement : Votre cadre de données est A

b <- A[,1]
b <- b==1
b <- cumsum(b)

Vous obtenez alors la colonne b.

0 votes

Sympa et court. Je changerais juste le dernier élément pour qu'au lieu d'être cumsum(b) -> b le résultat serait directement ajouté comme une colonne au cadre de données original, quelque chose comme A$groups <- cumsum(b) .

0 votes

cumsum(b) vous donnera un vecteur de longueur 3, ou j'ai raté quelque chose ?

0 votes

@RomanLuštrik, voir La solution de dbaupp qui explique comment le cumsum fonctionnerait dans ce cas.

7voto

dbaupp Points 20762

Si je comprends bien la question, vous voulez détecter quand la h_no n'augmente pas, puis incrémente le class . (Je vais vous expliquer comment j'ai résolu ce problème, il y a une fonction autonome à la fin).

Travail

Nous ne nous préoccupons que de la h_no pour le moment, nous pouvons donc l'extraire du cadre de données :

> h_no <- data$h_no

Nous voulons détecter quand h_no n'augmente pas, ce que nous pouvons faire en déterminant quand la différence entre des éléments successifs est négative ou nulle. R fournit le diff qui nous donne le vecteur des différences :

> d.h_no <- diff(h_no)
> d.h_no
 [1]  1  1  1 -3  1  1  1  1  1  1 -6  1  1  1

Une fois que nous avons cela, il est facile de trouver ceux qui ne sont pas positifs :

> nonpos <- d.h_no <= 0
> nonpos
 [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
[13] FALSE FALSE

En R, TRUE et FALSE sont fondamentalement les mêmes que 1 et 0 donc si nous obtenons la somme cumulée de nonpos il augmentera de 1 dans (presque) les endroits appropriés. Le site cumsum (qui est fondamentalement l'opposé de la fonction diff ) peut le faire.

> cumsum(nonpos)
 [1] 0 0 0 1 1 1 1 1 1 1 2 2 2 2

Mais il y a deux problèmes : les chiffres sont un peu trop faibles et il manque le premier élément (il devrait y en avoir quatre dans la première classe).

Le premier problème est simplement résolu : 1+cumsum(nonpos) . Et la seconde nécessite simplement d'ajouter un 1 au début du vecteur, puisque le premier élément est toujours dans la classe 1 :

 > classes <- c(1, 1 + cumsum(nonpos))
 > classes
  [1] 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3

Maintenant, nous pouvons le rattacher à notre cadre de données avec cbind (en utilisant le class= nous pouvons donner à la colonne la syntaxe class tête) :

 > data_w_classes <- cbind(data, class=classes)

Et data_w_classes contient maintenant le résultat.

Résultat final

Nous pouvons comprimer les lignes ensemble et les regrouper dans une fonction pour en faciliter l'utilisation :

classify <- function(data) {
   cbind(data, class=c(1, 1 + cumsum(diff(data$h_no) <= 0)))
}

Ou, puisque c'est logique pour le class pour être un facteur :

classify <- function(data) {
   cbind(data, class=factor(c(1, 1 + cumsum(diff(data$h_no) <= 0))))
}

Vous utilisez l'une ou l'autre fonction comme :

> classified <- classify(data) # doesn't overwrite data
> data <- classify(data) # data now has the "class" column

(Cette méthode de résolution de ce problème est bonne car elle évite l'itération explicite, qui est généralement recommandée pour R, et évite de générer beaucoup de vecteurs intermédiaires et de listes, etc. Et aussi, c'est assez chouette de pouvoir l'écrire en une seule ligne :) )

2voto

Paul Hiemstra Points 28390

En plus de la réponse de Roman, quelque chose comme ceci pourrait être encore plus simple. Notez que je ne l'ai pas testé car je n'ai pas accès à R pour le moment.

# Note that I use a global variable here
# normally not advisable, but I liked the
# use here to make the code shorter
index <<- 0
new_column = sapply(df$h_no, function(x) {
  if(x == 1) index = index + 1
  return(index)
})

La fonction itère sur les valeurs dans n_ho et renvoie toujours la catégorie à laquelle appartient la valeur actuelle. Si une valeur de 1 est détecté, nous augmentons la variable globale index et continuer.

0 votes

J'aime le hack avec la variable globale. Alors Cish. :P

-1voto

user2759975 Points 15
Data.frame[,'h_new_column'] <- as.integer(Data.frame[,'h_no'], breaks=c(1, 4, 7))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X