De un dataframe como este

test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10

> test
    id string
 1   1      A
 2   1      F
 3   2      B
 4   2      G
 5   3      C
 6   3      H
 7   4      D
 8   4      I
 9   5      E
 10  5      J

Quiero crear uno nuevo con la primera fila de cada id /par de cadena. Si sqldf aceptado R código dentro de ella, la consulta podría tener este aspecto:

res <- sqldf("select id, min(rownames(test)), string 
              from test 
              group by id, string")

> res
    id string
 1   1      A
 3   2      B
 5   3      C
 7   4      D
 9   5      E

Existe una solución a corto de la creación de una nueva columna como

test$row <- rownames(test)

y ejecución de la misma sqldf consulta con min(fila)?

InformationsquelleAutor dmvianna | 2012-11-07

5 Comentarios

  1. 98

    Puede utilizar duplicated hacer esto muy rápidamente.

    test[!duplicated(test$id),]

    Puntos de referencia, para los fanáticos de la velocidad:

    ju <- function() test[!duplicated(test$id),]
    gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1))
    gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
    jply <- function() ddply(test,.(id),function(x) head(x,1))
    jdt <- function() {
      testd <- as.data.table(test)
      setkey(testd,id)
      # Initial solution (slow)
      # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
      # Faster options :
      testd[!duplicated(id)]               # (1)
      # testd[, .SD[1L], by=key(testd)]    # (2)
      # testd[J(unique(id)),mult="first"]  # (3)
      # testd[ testd[,.I[1L],by=id] ]      # (4) needs v1.8.3. Allows 2nd, 3rd etc
    }
    
    library(plyr)
    library(data.table)
    library(rbenchmark)
    
    # sample data
    set.seed(21)
    test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
    test <- test[order(test$id), ]
    
    benchmark(ju(), gs1(), gs2(), jply(), jdt(),
        replications=5, order="relative")[,1:6]
    #     test replications elapsed relative user.self sys.self
    # 1   ju()            5    0.03    1.000      0.03     0.00
    # 5  jdt()            5    0.03    1.000      0.03     0.00
    # 3  gs2()            5    3.49  116.333      2.87     0.58
    # 2  gs1()            5    3.58  119.333      3.00     0.58
    # 4 jply()            5    3.69  123.000      3.11     0.51

    Vamos a intentar de nuevo, pero con sólo los contendientes de la primera calor y con más datos y más repeticiones.

    set.seed(21)
    test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
    test <- test[order(test$id), ]
    benchmark(ju(), jdt(), order="relative")[,1:6]
    #    test replications elapsed relative user.self sys.self
    # 1  ju()          100    5.48    1.000      4.44     1.00
    # 2 jdt()          100    6.92    1.263      5.70     1.15
    • El ganador: el sistema.tiempo(dat3[!duplicado(dat3$id),]) sistema de usuario transcurrido 0.07 0.00 0.07
    • Oi! Donde la sqldf? ;7)
    • Yo no lo tengo instalado y no tenía ganas de molestarse con él. 🙂
    • Estamos seguro de que mis datos.código de tabla es tan eficiente como sea posible? No estoy seguro de mi capacidad para obtener el mejor rendimiento de la herramienta.
    • Caray S. O. se mueve rápidamente. Eso es bastante ineficiente. Sin una clave sólo DT[,.SD[1L],by=id]. O setkey y el mismo, o DT[J(unique(id)),mult="first"]. Pero DT[!duplicated(id)] es probablemente la forma más rápida para este caso, como ju(). Pero lapply‘ing un S3 genric (head), por grupo, que va a ser lento.
    • Eso es lo que pensé…gracias!
    • También, creo que, si se va a contrastar los datos.tabla de incrustación se debe incluir la forma de ordenar por id en la base de las llamadas.
    • Oh, sí, lo echaba de menos!
    • He comparativo de los mult = 'first' solución sin la llave / de la orden, tanto en mi respuesta
    • Genial, genial!
    • por favor, no edites tus preguntas en respuestas, sólo corregir los errores o comentario y esperar una respuesta.
    • Supongo que se supone que la configuración de la clave en una columna que ya estaba ordenado sería casi sin costo.
    • Una pregunta más: ¿por qué es la primera frase que necesitaba decir, la suposición de que los datos ya están ordenados. !duplicated(x) encuentra el primero de cada grupo, incluso si no está ordenada, iiuc.
    • Eres muy correcta, no es necesario. Gran captura!
    • Oye Josh, que realmente debe agregar un archivo LÉAME.md a su Github repos, que hacer algunas cosas realmente bien!! Por cierto, la velocidad de la prueba es muy útil e instructivo). R está moviendo más y más hacia el big data… oído hablar de SparkR?

  2. 15

    Lo que acerca de

    DT <- data.table(test)
    setkey(DT, id)
    
    DT[J(unique(id)), mult = "first"]

    Editar

    También hay un método único para data.tables que va a regresar a la primera fila de clave

    jdtu <- function() unique(DT)

    Creo que, si usted está ordenando test fuera el punto de referencia, entonces usted puede quitar el setkey y data.table conversión de la referencia (como la setkey, básicamente, tipo de identificación, la misma que order).

    set.seed(21)
    test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
    test <- test[order(test$id), ]
    DT <- data.table(DT, key = 'id')
    ju <- function() test[!duplicated(test$id),]
    
    jdt <- function() DT[J(unique(id)),mult = 'first']
    
    
     library(rbenchmark)
    benchmark(ju(), jdt(), replications = 5)
    ##    test replications elapsed relative user.self sys.self 
    ## 2 jdt()            5    0.01        1      0.02        0        
    ## 1  ju()            5    0.05        5      0.05        0         

    y con más datos

    ** Editar con el único método**

    set.seed(21)
    test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
    test <- test[order(test$id), ]
    DT <- data.table(test, key = 'id')
           test replications elapsed relative user.self sys.self 
    2  jdt()            5    0.09     2.25      0.09     0.00    
    3 jdtu()            5    0.04     1.00      0.05     0.00      
    1   ju()            5    0.22     5.50      0.19     0.03        

    El único método es el más rápido aquí.

    • +1 jdtu buena idea. Me había olvidado de la unique(DT).
    • Usted incluso no tiene que establecer la clave. unique(DT,by="id") trabaja directamente
    • FYI como de data.table versión >= 1.9.8, el valor predeterminado by argumento para unique es by = seq_along(x) (todas las columnas), en lugar de la predeterminada anterior by = key(x)
  3. 12

    Estoy a favor de la dplyr enfoque.

    group_by(id) seguida de

    • filter(row_number()==1) o
    • slice(1) o
    • top_n(n = -1)
      • top_n() utiliza internamente el rango de la función.
        Negativo selecciona a partir de la parte inferior del rango.

    En algunos casos la organización de los identificadores después de la group_by puede ser necesario.

    library(dplyr)
    
    # using filter(), top_n() or slice()
    
    m1 <-
    test %>% 
      group_by(id) %>% 
      filter(row_number()==1)
    
    m2 <-
    test %>% 
      group_by(id) %>% 
      slice(1)
    
    m3 <-
    test %>% 
      group_by(id) %>% 
      top_n(n = -1)

    Los tres métodos devuelven el mismo resultado

    # A tibble: 5 x 2
    # Groups:   id [5]
         id string
      <int> <fct> 
    1     1 A     
    2     2 B     
    3     3 C     
    4     4 D     
    5     5 E
    • Vale la pena dar un grito a slice así. slice(x) es un acceso directo para filter(row_number() %in% x).
    • Muy elegante. ¿Sabes por qué tengo que convertir mi data.table a un data.frame para que esto funcione?
    • Yo no soy un experto en el de todas las diferencias. Pero data.table hereda de la data.frame por lo que en muchos casos se puede utilizar dplyr de comandos en un data.table. El ejemplo anterior correo.g también funciona si test es un data.table. Ver, por ejemplo, stackoverflow.com/questions/13618488/… para una más profunda explanantion
  4. 11

    Un simple ddply opción:

    ddply(test,.(id),function(x) head(x,1))

    Si la velocidad es un problema, un enfoque similar con data.table:

    testd <- data.table(test)
    setkey(testd,id)
    testd[,.SD[1],by = key(testd)]

    o este podría ser considerablemente más rápido:

    testd[testd[, .I[1], by = key(testd]$V1]
    • Sorprendentemente, sqldf lo hace más rápido: 1.77 0.13 1.92 vs 10.53 0.00 10.79 con los datos.tabla
    • Yo no necesariamente recuento de los datos.tabla. Yo no soy un experto con esa herramienta, así que mis datos.tabla de código puede no ser la forma más eficaz de llevarlo a cabo.
    • Yo upvoted este prematuramente. Cuando me encontré en una de datos de gran tamaño.mesa, era ridículamente lento y no funcionó: el número de filas era el mismo después.
    • Esto lo escribí hace mucho tiempo, el paquete ha cambiado mucho, y yo casi no uso de datos.tabla a todos. Si usted encontrar la manera correcta de hacerlo con ese paquete, siéntase libre de sugerir una edición para hacerlo mejor.
    • por ejemplo stackoverflow.com/q/16325641/324364
  5. 7

    (1) SQLite ha construido en rowid pseudo-columna, de manera que esto funciona:

    sqldf("select min(rowid) rowid, id, string 
                   from test 
                   group by id")

    dando:

      rowid id string
    1     1  1      A
    2     3  2      B
    3     5  3      C
    4     7  4      D
    5     9  5      E

    (2) También sqldf en sí tiene un row.names= argumento:

    sqldf("select min(cast(row_names as real)) row_names, id, string 
                  from test 
                  group by id", row.names = TRUE)

    dando:

      id string
    1  1      A
    3  2      B
    5  3      C
    7  4      D
    9  5      E

    (3) Una tercera alternativa que mezcla los elementos de los dos anteriores podría ser aún mejor:

    sqldf("select min(rowid) row_names, id, string 
                   from test 
                   group by id", row.names = TRUE)

    dando:

      id string
    1  1      A
    3  2      B
    5  3      C
    7  4      D
    9  5      E

    Tenga en cuenta que todos los tres de estos se basan en una SQLite extensión de SQL donde el uso de min o max se garantiza que el resultado en las otras columnas de la misma fila. (En otras basadas en SQL bases de datos que no pueden ser garantizadas.)

    • Gracias! Esto es mucho mejor que el aceptado la respuesta de la OMI, porque es generalizable a tomar el primer/último elemento en un agregado de paso el uso de múltiples funciones de agregado (es decir, tomar la primera de esta variable, se suma que la variable, etc).

Dejar respuesta

Please enter your comment!
Please enter your name here