2010-02-23 9 views
9

Tengo algunos datos de tipo mixto que me gustaría almacenar en una estructura de datos R de algún tipo. Cada punto de datos tiene un conjunto de atributos fijos que pueden ser 1-d numéricos, factores o caracteres, y también un conjunto de datos de longitud variable. Por ejemplo:¿La mejor manera de almacenar datos de longitud variable en un R data.frame?

id phrase     num_tokens token_lengths 
1 "hello world"    2   5 5 
2 "greetings"    1   9 
3 "take me to your leader" 4   4 2 2 4 6 

Los valores reales no son todos computables entre sí, pero ese es el sabor de los datos. Las operaciones que voy a querer hacer incluyen subdividir los datos basados ​​en funciones booleanas (por ejemplo, algo como nchar(data$phrase) > 10 o lapply(data$token_lengths, length) > 2). También me gustaría indexar y promediar valores en la porción de longitud variable por índice. Esto no funciona, pero algo como: mean(data$token_lengths[1], na.rm=TRUE))

que he encontrado que puedo calzador "token_lengths" en un hoja.de.datos por lo que es una matriz:?

d <- data.frame(id=c(1,2,3), ..., token_lengths=as.array(list(c(5,5), 9, c(4,2,2,4,6))) 

pero es ésta la mejor manera

+0

En promedio Tal vez usted quiere 'lapply ($ token_lengths datos, significan, na.rm = VERDADERO)'? Pero no entiendo completamente lo que quieres. – Marek

Respuesta

1

Desde el La estructura de trama de datos R se basa libremente en la tabla SQL, teniendo cada elemento del cuadro de datos ser cualquier cosa que no sea un tipo de datos atómicos es poco común. Sin embargo, se puede hacer, como lo ha demostrado, y este post enlazado describe tal aplicación implementada en una escala mayor.

Una alternativa es almacenar sus datos como una cadena y tener una función para recuperarlos, o crear una función separada a la que se adjuntan los datos y extraerla usando índices almacenados en su marco de datos.

> ## alternative 1 
> tokens <- function(x,i=TRUE) Map(as.numeric,strsplit(x[i],",")) 
> d <- data.frame(id=c(1,2,3), token_lengths=c("5,5", "9", "4,2,2,4,6")) 
> 
> tokens(d$token_lengths) 
[[1]] 
[1] 5 5 

[[2]] 
[1] 9 

[[3]] 
[1] 4 2 2 4 6 

> tokens(d$token_lengths,2:3) 
[[1]] 
[1] 9 

[[2]] 
[1] 4 2 2 4 6 

> 
> ## alternative 2 
> retrieve <- local({ 
+ token_lengths <- list(c(5,5), 9, c(4,2,2,4,6)) 
+ function(i) token_lengths[i] 
+ }) 
> 
> d <- data.frame(id=c(1,2,3), token_lengths=1:3) 
> retrieve(d$token_lengths[2:3]) 
[[1]] 
[1] 9 

[[2]] 
[1] 4 2 2 4 6 
+0

Pensé en la solución pack-as-string, pero luego me resultó complicado trabajar con los datos de longitud variable. Por ahora, voy con la solución de columna de matrices y usando 'mapply()' generosamente. Por ejemplo, si quiero la longitud media del token por frase que es simplemente 'mapply (mean, d $ token_lengths)'. Si quiero el máximo de todas las longitudes de token, es 'max (mapply (max, d $ token_lengths))'. – Nick

4

Tratando de calzar los datos en un marco de datos me parece hackish. Es mucho mejor considerar cada fila como un objeto individual, luego pensar en el conjunto de datos como una matriz de estos objetos.

Esta función convierte las cadenas de datos a un formato apropiado. (Este es el código de estilo S3, es posible que prefiera utilizar uno de los sistemas orientados 'adecuadas' de objetos.)

as.mydata <- function(x) 
{ 
    UseMethod("as.mydata") 
} 

as.mydata.character <- function(x) 
{ 
    convert <- function(x) 
    { 
     md <- list() 
     md$phrase = x 
     spl <- strsplit(x, " ")[[1]] 
     md$num_words <- length(spl) 
     md$token_lengths <- nchar(spl) 
     class(md) <- "mydata" 
     md 
    } 
    lapply(x, convert) 
} 

Ahora todo el conjunto de datos se parece

mydataset <- as.mydata(c("hello world", "greetings", "take me to your leader")) 

mydataset 
[[1]] 
$phrase 
[1] "hello world" 

$num_words 
[1] 2 

$token_lengths 
[1] 5 5 

attr(,"class") 
[1] "mydata" 

[[2]] 
$phrase 
[1] "greetings" 

$num_words 
[1] 1 

$token_lengths 
[1] 9 

attr(,"class") 
[1] "mydata" 

[[3]] 
$phrase 
[1] "take me to your leader" 

$num_words 
[1] 5 

$token_lengths 
[1] 4 2 2 4 6 

attr(,"class") 
[1] "mydata" 

Se puede definir un método de impresión para hacer que esto se vea más bonito.

print.mydata <- function(x) 
{ 
    cat(x$phrase, "consists of", x$num_words, "words, with", paste(x$token_lengths, collapse=", "), "letters.") 
} 
mydataset 
[[1]] 
hello world consists of 2 words, with 5, 5 letters. 
[[2]] 
greetings consists of 1 words, with 9 letters. 
[[3]] 
take me to your leader consists of 5 words, with 4, 2, 2, 4, 6 letters. 

Las operaciones de muestra que quería hacer son bastante sencillas con los datos en este formato.

sapply(mydataset, function(x) nchar(x$phrase) > 10) 
[1] TRUE FALSE TRUE 
+1

Iba a sugerir esta solución basada en listas también. Es ciertamente lo que harías en algo que no sea R. Pero hay una manera en que la programación * all * R es "hackish", en el buen sentido, y el (sobre) uso de data.frames es una de esas formas. Podría decirse que un data.frame de formato largo puede ser la opción más eficiente del programador, incluso si es un poco tonto desde la perspectiva de las estructuras de datos. – Harlan

+0

Entonces, ¿cuál es la forma más eficiente de calcular la cantidad media de tokens? En mi ejemplo original, es simplemente 'mean (mydata $ num_tokens)'. Para la solución basada en listas, tendrías que hacer algo como 'mean (sapply (mydataset, function (x) x $ num_tokens))'. Con funciones de ayuda, eso podría ser más bonito, por supuesto. – Nick

+0

@Nick: Sí, la sintaxis es un poco más ruidosa de esta manera. Pondría la declaración sapply en una función como 'get_num_tokens <- function (x) sapply (x, function (x) x $ num_tokens)'. Luego usa 'mean (get_num_tokens (mydataset))'. –

4

Simplemente utilizaría los datos en el formato "largo".

E.g.

> d1 <- data.frame(id=1:3, num_words=c(2,1,4), phrase=c("hello world", "greetings", "take me to your leader")) 
> d2 <- data.frame(id=c(rep(1,2), rep(2,1), rep(3,5)), token_length=c(5,5,9,4,2,2,4,6)) 
> d2$tokenid <- with(d2, ave(token_length, id, FUN=seq_along)) 
> d <- merge(d1,d2) 
> subset(d, nchar(phrase) > 10) 
    id num_words     phrase token_length tokenid 
1 1   2   hello world   5  1 
2 1   2   hello world   5  2 
4 3   4 take me to your leader   4  1 
5 3   4 take me to your leader   2  2 
6 3   4 take me to your leader   2  3 
7 3   4 take me to your leader   4  4 
8 3   4 take me to your leader   6  5 
> with(d, tapply(token_length, id, mean)) 
    1 2 3 
5.0 9.0 3.6 

Una vez que los datos están en el formato largo, puede utilizar sqldf o plyr para extraer lo que quiere de él.

+1

Actualmente tengo mis datos en este formato largo y estoy intentando acortarlo porque me resulta incómodo trabajar con él. Por ejemplo, para calcular el número medio de tokens tengo que escribir algo como: 'mean (unique (d [c ('id,' num_tokens ')]) $ num_tokens)'. Si los datos no son largos, puedo escribir 'mean (d $ num_tokens)' que es mucho más legible. El principal elemento de interés aquí es la frase y resulta que tiene datos de longitud variable asociados a ella; expandir esa información lo torna incómodo. – Nick

+1

Puedes acortarlo por medio (subconjunto (d, tokenid == 1, num_tokens)), pero entiendo tu punto. Si quiere apegarse a un marco de datos, creo que puede. Solo piénselo: los marcos de datos son listas de vectores de la misma longitud. Puede hacer que el vector de tokens sea un vector de listas: df <- data.frame (a = 1: 3); df $ b <- list (1: 3,1: 2,1: 3). A R no le gusta esto, sin embargo. (Se queja si crea el marco de datos en un solo paso). No estoy seguro de por qué. –

+0

El subconjunto de tokenid es al menos un poco más atractivo. :) – Nick

4

Otra opción sería convertir su marco de datos en una matriz de lista de modos: cada elemento de la matriz sería una lista. operaciones de matriz estándar (cortar con [, aplicar(), etc. sería aplicable).

> d <- data.frame(id=c(1,2,3), num_tokens=c(2,1,4), token_lengths=as.array(list(c(5,5), 9, c(4,2,2,4,6)))) 
> m <- as.matrix(d) 
> mode(m) 
[1] "list" 
> m[,"token_lengths"] 
[[1]] 
[1] 5 5 

[[2]] 
[1] 9 

[[3]] 
[1] 4 2 2 4 6 

> m[3,] 
$id 
[1] 3 

$num_tokens 
[1] 4 

$token_lengths 
[1] 4 2 2 4 6 
0

Me gustaría también utilizar cadenas para los datos de longitud variable, pero como en el siguiente ejemplo: "c (5,5)" para la primera frase. Uno necesita usar eval(parse(text=...)) para realizar cálculos.

Por ejemplo, el mean se puede calcular de la siguiente manera:

sapply(data$token_lengths,function(str) mean(eval(parse(text=str))))

Cuestiones relacionadas