seleccionar - tablas en r
¿Cómo seleccionar la primera y la última fila dentro de una variable de agrupación en un marco de datos? (3)
Aquí hay una solución en la base R
Si hay varios grupos con la misma id
este código devuelve la primera y la última fila para cada uno de esos grupos individuales.
EDITAR: 12 de enero de 2017
Esta solución puede ser un poco más intuitiva que mi otra respuesta más abajo:
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
EDITAR: 18 de junio de 2016
Desde que publiqué mi respuesta original, he aprendido que es mejor utilizar la lapply
que apply
. Esto se debe a que apply
no funciona si cada grupo tiene el mismo número de filas. Ver aquí: Error al numerar filas por grupo
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
Aquí hay un ejemplo en el que cada grupo tiene dos filas:
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
Respuesta original:
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
¿Cómo puedo seleccionar la primera y la última fila para cada id
única en el siguiente marco de datos?
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
Una solución plyr ( tmp
es su marco de datos):
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 (ver también 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
Una solución rápida y corta de data.table
:
tmp[, .SD[c(1,.N)], by=id]
donde .SD
representa cada (S) ubset de (D) ata, .N
es el número de filas en cada grupo y tmp
es un data.table
; por ejemplo, como se proporciona mediante fread()
de forma predeterminada o mediante la conversión de un data.frame
utilizando setDT()
.
Tenga en cuenta que si un grupo solo contiene una fila, esa fila aparecerá dos veces en el resultado porque esa fila es la primera y la última fila de ese grupo. Para evitar la repetición en ese caso, gracias a @Thell:
tmp[, .SD[unique(c(1,.N))], by=id]
Alternativamente, lo siguiente hace que la lógica sea explícita para el caso especial .N==1
:
tmp[, if (.N==1) .SD else .SD[c(1,.N)], by=id]
No necesita .SD[1]
en la primera parte de if
porque en ese caso .N
es 1
así que .SD
debe ser solo una fila de todos modos.
Puede envolver j
en {}
y tener una página completa de código dentro de {}
si lo desea. Siempre que la última expresión dentro de {}
devuelva un objeto similar a una list
para apilarse (como una list
simple, data.table
o data.frame
).
tmp[, { ...; if (.N==1) .SD else .SD[c(1,.N)] } , by=id]