2011-11-20 6 views
27

Come posso selezionare la prima e l'ultima riga per ciascun id univoco nel seguente dataframe?Come selezionare la prima e l'ultima riga all'interno di una variabile di raggruppamento in un frame di dati?

tmp <- structure(list(id = c(15L, 15L, 15L, 15L, 21L, 21L, 22L, 22L, 
22L, 23L, 23L, 23L, 24L, 24L, 24L, 24L), d = c(1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), gr = c(2L, 1L, 
1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 2L), mm = c(3.4, 
4.9, 4.4, 5.5, 4, 3.8, 4, 4.9, 4.6, 2.7, 4, 3, 3, 2, 4, 2), area = c(1L, 
2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 2L, 3L)), .Names = c("id", 
"d", "gr", "mm", "area"), class = "data.frame", row.names = c(NA, 
-16L)) 
tmp 
#> id d gr mm area 
#> 1 15 1 2 3.4 1 
#> 2 15 1 1 4.9 2 
#> 3 15 1 1 4.4 1 
#> 4 15 1 1 5.5 2 
#> 5 21 1 1 4.0 2 
#> 6 21 1 2 3.8 2 
#> 7 22 1 1 4.0 2 
#> 8 22 1 1 4.9 2 
#> 9 22 1 2 4.6 2 
#> 10 23 1 1 2.7 2 
#> 11 23 1 1 4.0 2 
#> 12 23 1 2 3.0 2 
#> 13 24 1 1 3.0 2 
#> 14 24 1 1 2.0 3 
#> 15 24 1 1 4.0 2 
#> 16 24 1 2 2.0 3 

risposta

22

Una soluzione plyr (tmp è la cornice di dati):

library("plyr") 
ddply(tmp, .(id), function(x) x[c(1, nrow(x)), ]) 
# id d gr mm area 
# 1 15 1 2 3.4 1 
# 2 15 1 1 5.5 2 
# 3 21 1 1 4.0 2 
# 4 21 1 2 3.8 2 
# 5 22 1 1 4.0 2 
# 6 22 1 2 4.6 2 
# 7 23 1 1 2.7 2 
# 8 23 1 2 3.0 2 
# 9 24 1 1 3.0 2 
# 10 24 1 2 2.0 3 

O con dplyr (vedi anche here):

library("dplyr") 
tmp %>% 
group_by(id) %>% 
slice(c(1, n())) %>% 
ungroup() 
# # A tibble: 10 × 5 
#  id  d gr mm area 
# <int> <int> <int> <dbl> <int> 
# 1  15  1  2 3.4  1 
# 2  15  1  1 5.5  2 
# 3  21  1  1 4.0  2 
# 4  21  1  2 3.8  2 
# 5  22  1  1 4.0  2 
# 6  22  1  2 4.6  2 
# 7  23  1  1 2.7  2 
# 8  23  1  2 3.0  2 
# 9  24  1  1 3.0  2 
# 10 24  1  2 2.0  3 
+0

Grazie mille per questa soluzione molto utile. Molto apprezzato!! – Francesco

70

una soluzione veloce e breve data.table:

tmp[, .SD[c(1,.N)], by=id] 

dove .SD rappresenta ciascuno (S) ubset di (D) ata, .N è il numero di righe in ciascun gruppo e tmp è un data.table; per esempio. come previsto da fread() per impostazione predefinita o convertendo un data.frame utilizzando setDT().

Si noti che se un gruppo contiene solo una riga, quella riga verrà visualizzata due volte nell'output perché quella riga è sia la prima che l'ultima riga di quel gruppo. Per evitare il ripetersi in questo caso, grazie a @Thell:

tmp[, .SD[unique(c(1,.N))], by=id] 

In alternativa, il seguente rende la logica esplicita per il caso .N==1 speciale:

tmp[, if (.N==1) .SD else .SD[c(1,.N)], by=id] 

Non è necessario .SD[1] nel primo parte del if perché in tal caso .N è 1 quindi .SD deve essere solo una riga comunque.

È possibile avvolgere j in {} e avere un'intera pagina di codice all'interno di {} se lo si desidera. Fintanto che l'ultima espressione all'interno di {} restituisce un oggetto simile a list da impilare (ad esempio un semplice list, data.table o data.frame).

tmp[, { ...; if (.N==1) .SD else .SD[c(1,.N)] } , by=id] 
+7

'.SD [unique (c (1, .N))]' per quando un gruppo ha un singolo membro. – Thell

+2

prendendo a prestito dal campo dati 'setkey (tmp, id)' e 'tmp [, .SD [c (1, .N)], .EACHI]' – Kerry

+0

@Kerry Devo ammettere che non conoscevo 'setkey (tmp, id); tmp [, .SD [c (1, .N)], .EACHI] 'funzionerebbe senza alcun presente. Dove si trova esattamente su DataCamp? Grazie. –

4

Ecco una soluzione a base di R. Se ci sono più gruppi con lo stesso id questo codice restituisce la prima e l'ultima riga per ciascuno di quei singoli gruppi.

EDIT: 12 gennaio 2017

Questa soluzione potrebbe essere un po 'più intuitivo che la mia altra risposta più in basso:

lmy.df = read.table(text = ' 
    id d gr  mm area 
    15 1  2 3.40  1 
    15 1  1 4.90  2 
    15 1  1 4.40  1 
    15 1  1 5.50  2 
    21 1  1 4.00  2 
    21 1  2 3.80  2 
    22 1  1 4.00  2 
    23 1  1 2.70  2 
    23 1  1 4.00  2 
    23 1  2 3.00  2 
    24 1  1 3.00  2 
    24 1  1 2.00  3 
    24 1  1 4.00  2 
    24 1  2 2.00  3 
', header = TRUE) 

head <- aggregate(lmy.df, by=list(lmy.df$id), FUN = function(x) { first = head(x,1) }) 
tail <- aggregate(lmy.df, by=list(lmy.df$id), FUN = function(x) { last = tail(x,1) }) 
head$order = 'first' 
tail$order = 'last' 

my.output <- rbind(head, tail) 
my.output 
# Group.1 id d gr mm area order 
#1  15 15 1 2 3.4 1 first 
#2  21 21 1 1 4.0 2 first 
#3  22 22 1 1 4.0 2 first 
#4  23 23 1 1 2.7 2 first 
#5  24 24 1 1 3.0 2 first 
#6  15 15 1 1 5.5 2 last 
#7  21 21 1 2 3.8 2 last 
#8  22 22 1 1 4.0 2 last 
#9  23 23 1 2 3.0 2 last 
#10  24 24 1 2 2.0 3 last 

EDIT: 18 giu 2016

Da quando ho postato la mia risposta originale, ho imparato che è meglio usare lapply rispetto a apply. Questo perché apply non funziona se ogni gruppo ha lo stesso numero di righe.Vedi qui: Error when numbering rows by group

lmy.df = read.table(text = ' 
    id d gr  mm area 
    15 1  2 3.40  1 
    15 1  1 4.90  2 
    15 1  1 4.40  1 
    15 1  1 5.50  2 
    21 1  1 4.00  2 
    21 1  2 3.80  2 
    22 1  1 4.00  2 
    23 1  1 2.70  2 
    23 1  1 4.00  2 
    23 1  2 3.00  2 
    24 1  1 3.00  2 
    24 1  1 2.00  3 
    24 1  1 4.00  2 
    24 1  2 2.00  3 
', header = TRUE) 


lmy.seq <- rle(lmy.df$id)$lengths 
lmy.df$first <- unlist(lapply(lmy.seq, function(x) seq(1,x))) 
lmy.df$last <- unlist(lapply(lmy.seq, function(x) seq(x,1,-1))) 
lmy.df 

lmy.df2 <- lmy.df[lmy.df$first==1 | lmy.df$last == 1,] 
lmy.df2 

# id d gr mm area first last 
#1 15 1 2 3.4 1  1 4 
#4 15 1 1 5.5 2  4 1 
#5 21 1 1 4.0 2  1 2 
#6 21 1 2 3.8 2  2 1 
#7 22 1 1 4.0 2  1 1 
#8 23 1 1 2.7 2  1 3 
#10 23 1 2 3.0 2  3 1 
#11 24 1 1 3.0 2  1 4 
#14 24 1 2 2.0 3  4 1 

Ecco un esempio in cui ogni gruppo ha due file:

lmy.df = read.table(text = ' 
    id d gr  mm area 
    15 1  2 3.40  1 
    15 1  1 4.90  2 
    21 1  1 4.00  2 
    21 1  2 3.80  2 
    22 1  1 4.00  2 
    22 1  1 6.00  2 
    23 1  1 2.70  2 
    23 1  2 3.00  2 
    24 1  1 3.00  2 
    24 1  2 2.00  3 
', header = TRUE) 

lmy.seq <- rle(lmy.df$id)$lengths 

lmy.df$first <- unlist(lapply(lmy.seq, function(x) seq(1,x))) 
lmy.df$last <- unlist(lapply(lmy.seq, function(x) seq(x,1,-1))) 
lmy.df 

lmy.df2 <- lmy.df[lmy.df$first==1 | lmy.df$last == 1,] 
lmy.df2 

# id d gr mm area first last 
#1 15 1 2 3.4 1  1 2 
#2 15 1 1 4.9 2  2 1 
#3 21 1 1 4.0 2  1 2 
#4 21 1 2 3.8 2  2 1 
#5 22 1 1 4.0 2  1 2 
#6 22 1 1 6.0 2  2 1 
#7 23 1 1 2.7 2  1 2 
#8 23 1 2 3.0 2  2 1 
#9 24 1 1 3.0 2  1 2 
#10 24 1 2 2.0 3  2 1 

risposta originale:

my.seq <- data.frame(rle(my.df$id)$lengths) 

my.df$first <- unlist(apply(my.seq, 1, function(x) seq(1,x))) 
my.df$last <- unlist(apply(my.seq, 1, function(x) seq(x,1,-1))) 

my.df2 <- my.df[my.df$first==1 | my.df$last == 1,] 
my.df2 

    id d gr mm area first last 
1 15 1 2 3.4 1  1 4 
4 15 1 1 5.5 2  4 1 
5 21 1 1 4.0 2  1 2 
6 21 1 2 3.8 2  2 1 
7 22 1 1 4.0 2  1 3 
9 22 1 2 4.6 2  3 1 
10 23 1 1 2.7 2  1 3 
12 23 1 2 3.0 2  3 1 
13 24 1 1 3.0 2  1 4 
16 24 1 2 2.0 3  4 1 
Problemi correlati