Ajouter une colonne à un data.frame

115

J'ai le data.frame ci-dessous. Je veux ajouter une colonne qui classe mes données selon la colonne 1 ( h_no) de cette manière que la première série de h_no 1,2,3,4 est de classe 1, la deuxième série de h_no(1 à 7) est de classe 2 etc. comme indiqué dans la dernière colonne.

h_no  h_freq  h_freqsq
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
Susanne Dreisigacker
la source

Réponses:

155

Vous pouvez ajouter une colonne à vos données à l'aide de diverses techniques. Les citations ci - dessous proviennent de la section « Détails » du texte d'aide pertinente [[.data.frame.

Les trames de données peuvent être indexées dans plusieurs modes. Lorsque [et [[sont utilisés avec un seul index vectoriel ( x[i]ou x[[i]]), ils indexent la trame 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 $, traite xcomme une liste

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

Quand [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 pour data.framesuppose que si vous ne spécifiez pas si vous travaillez avec des colonnes ou des lignes, elle 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
Roman Luštrik
la source
Quelle est la différence entre les deux dernières méthodes d'ajout d'une colonne?
huon
2
@ huon-dbaupp la méthode avec une virgule est explicite et fonctionnera également sur les matrices, tandis que la dernière ne fonctionne que sur data.frames. Si aucune virgule n'est fournie, R suppose que vous voulez dire des colonnes.
Roman Luštrik
12

Facilement: votre bloc de données est A

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

Ensuite, vous obtenez la colonne b.

user1333396
la source
Sympa et court. Je changerais simplement le dernier élément afin qu'au lieu d'être cumsum(b) -> ble résultat soit directement ajouté en tant que colonne au bloc de données d'origine, quelque chose comme A$groups <- cumsum(b).
A5C1D2H2I1M1N2O1R2T1
cumsum(b)vous donnera un vecteur de longueur 3, ou est-ce que je manque quelque chose?
Roman Luštrik
@ RomanLuštrik, voir la solution de dbaupp qui explique comment cumsum fonctionnerait dans ce cas.
A5C1D2H2I1M1N2O1R2T1
2
@ RomanLuštrik, Cette solution peut être réécrite très bien en une seule ligne. En utilisant vos your.dfdonnées, vous pouvez simplement faire your.df$group = cumsum(your.df[, 1]==1)pour obtenir votre nouvelle colonne de groupe.
A5C1D2H2I1M1N2O1R2T1
7

Si je comprends bien la question, vous voulez détecter quand le h_non'augmente pas, puis incrémenter 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 soucions que de la h_nocolonne pour le moment, nous pouvons donc l'extraire de la trame de données:

> h_no <- data$h_no

Nous voulons détecter quand h_none monte pas, ce que nous pouvons faire en déterminant quand la différence entre les éléments successifs est soit négative, soit nulle. R fournit la difffonction 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 simple 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

Dans R, TRUEet FALSEsont fondamentalement les mêmes que 1et 0, donc si nous obtenons la somme cumulée de nonpos, elle augmentera de 1 dans (presque) les endroits appropriés. La cumsumfonction (qui est fondamentalement l'opposé de 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 nombres sont trop petits; et, il nous manque le premier élément (il devrait y en avoir quatre dans la première classe).

Le premier problème est résolu simplement: 1+cumsum(nonpos). Et le second nécessite simplement d'ajouter un 1à l'avant du vecteur, puisque le premier élément est toujours en 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 bloc de données avec cbind(en utilisant la class=syntaxe, nous pouvons donner à la colonne l'en- classtête):

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

Et data_w_classescontient maintenant le résultat.

Résultat final

Nous pouvons compresser les lignes ensemble et tout envelopper dans une fonction pour le rendre plus facile à utiliser:

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

Ou, puisqu'il est logique que le classsoit 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 les itérations explicites, ce qui est généralement recommandé pour R, et évite de générer beaucoup de vecteurs intermédiaires et de listes, etc. Et aussi c'est plutôt chouette comment cela peut être écrit sur une seule ligne :))

huon
la source
2

En plus de la réponse de Roman, quelque chose comme ça 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_hoet renvoie toujours la catégorie à laquelle appartient la valeur actuelle. Si une valeur de 1est détectée, nous augmentons la variable globale indexet continuons.

Paul Hiemstra
la source
J'aime le hack avec la variable globale. Alors Cish. : P
Roman Luštrik
2

Je crois que l'utilisation de "cbind" est le moyen le plus simple d'ajouter une colonne à un bloc de données dans R. Ci-dessous un exemple:

    myDf = data.frame(index=seq(1,10,1), Val=seq(1,10,1))
    newCol= seq(2,20,2)
    myDf = cbind(myDf,newCol)
Emanuele Catane
la source
1
Data.frame[,'h_new_column'] <- as.integer(Data.frame[,'h_no'], breaks=c(1, 4, 7))
user2759975
la source
0

Approche basée sur l'identification du nombre de groupes ( xpo mapply) et de sa longueur ( ypo mapply)

mytb<-read.table(text="h_no  h_freq  h_freqsq group
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", header=T, stringsAsFactors=F)
mytb$group<-NULL

positionsof1s<-grep(1,mytb$h_no)

mytb$newgroup<-unlist(mapply(function(x,y) 
  rep(x,y),                      # repeat x number y times
  x= 1:length(positionsof1s),    # x is 1 to number of nth group = g1:g3
  y= c( diff(positionsof1s),     # y is number of repeats of groups g1 to penultimate (g2) = 4, 7
        nrow(mytb)-              # this line and the following gives number of repeat for last group (g3)
          (positionsof1s[length(positionsof1s )]-1 )  # number of rows - position of penultimate group (g2) 
      ) ) )
mytb
Ferroao
la source