2010-02-23 18 views
9

Mam niektóre dane typu mieszanego, które chciałbym przechowywać w strukturze danych R pewnego rodzaju. Każdy punkt danych ma zestaw stałych atrybutów, które mogą być 1-d numeryczne, współczynniki lub znaki, a także zbiór danych o zmiennej długości. Na przykład:Najlepszy sposób przechowywania danych o zmiennej długości w 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 

Rzeczywistych wartości nie wszystkie są obliczalne od siebie, ale to smak danych. Operacje, które zamierzam wykonać, obejmują podzbiór danych na podstawie funkcji boolowskich (np. Coś takiego jak nchar(data$phrase) > 10 lub lapply(data$token_lengths, length) > 2). Chciałbym również indeksować i uśredniać wartości w części o zmiennej długości według indeksu. ale coś takiego: mean(data$token_lengths[1], na.rm=TRUE))

Znalazłem mogę shoehorn „token_lengths” w data.frame poprzez uczynienie go tablicę:

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

ale jest to najlepszy sposób

+0

W uśredniania może chcesz 'lapply (dane $ token_lengths, znaczy, na.rm = TRUE)'? Ale nie do końca rozumiem, czego chcesz. – Marek

Odpowiedz

1

Ponieważ Struktura ramek danych R jest luźno oparta na tabeli SQL, z uwzględnieniem każdego elementu framewu danych e jest czymś innym niż atomowy typ danych, jest rzadkością. Można to jednak zrobić, jak już pokazano, a ta połączona post opisuje taką aplikację zaimplementowaną na większą skalę.

Alternatywą jest przechowywanie danych w postaci ciągu znaków i funkcji do ich pobierania lub tworzenie oddzielnej funkcji, do której dane są dołączane i wyodrębnianie za pomocą indeksów przechowywanych w ramce danych.

> ## 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

Myślałem o rozwiązaniu typu "paczkę-łańcuch", ale potem skomplikowało to pracę z danymi o zmiennej długości. Na razie idę z rozwiązaniem kolumny-tablic i używając "mapply()" swobodnie. Na przykład, jeśli chcę średniej długości tokena na wyrażenie, które jest po prostu 'mapply (średnia, d $ token_lengths)'. Jeśli chcę mieć maksymalną długość wszystkich tokenów, to jest to max (mapply (max, d $ token_lengths)) '. – Nick

4

Próba włamania danych do ramki danych wydaje mi się hackish. Znacznie lepiej jest rozważyć każdy wiersz jako pojedynczy obiekt, a następnie pomyśl o zestawie danych jako tablicy tych obiektów.

Ta funkcja konwertuje ciągi danych do odpowiedniego formatu. (Jest to S3 kod styl, może wolisz skorzystać z jednej z „właściwe” systemów obiektowych.)

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) 
} 

Teraz całość zbioru danych wygląda

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" 

Można zdefiniować metodę drukowania do spraw, aby wyglądała ładniej.

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. 

Przykładowe operacje, które chciałeś wykonać, są dość proste dzięki danym w tym formacie.

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

Zaproponowałem też rozwiązanie oparte na listach. Z pewnością to, co robisz w czymś innym niż R. Ale jest sposób, w jaki * wszystko * R programowanie jest "hackowe", w dobry sposób, i (nad) użyciem data.frames jest jednym z tych sposobów. Możliwe, że data.frame o długim formacie może być najbardziej wydajną programistycznie opcją, nawet jeśli jest trochę głupio z perspektywy struktur danych. – Harlan

+0

Jaki jest więc skuteczny sposób obliczania średniej liczby tokenów? W moim oryginalnym przykładzie jest to po prostu 'mean (mydata $ num_tokens)'. Dla rozwiązania opartego na listach, musisz zrobić coś w stylu 'mean (sapply (mydataset, function (x) x $ num_tokens))'. Dzięki funkcjom pomocniczym może być oczywiście ładniej. – Nick

+0

@Nick: Tak, składnia jest trochę bardziej zagmatwana w ten sposób. Umieściłbym sapply oświadczenie w funkcji jak 'get_num_tokens <- function (x) sapply (x, function (x) x $ num_tokens)'. Następnie użyjesz 'mean (get_num_tokens (mydataset))'. –

4

Po prostu użyłbym danych w "długim" formacie.

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 

Gdy dane są w długim formacie, można użyć narzędzia sqldf lub plyr, aby wyodrębnić z niego żądane dane.

+1

Mam już moje dane w tym długim formacie i staram się je skrócić, ponieważ pracuję z tym niezręcznie. Na przykład, aby obliczyć średnią liczbę tokenów, muszę napisać coś w rodzaju: 'mean (unique (d [c ('id,' num_tokens ')]) $ num_tokens)'. Jeśli dane nie są długie, mogę po prostu napisać 'mean (d $ num_tokens)', który jest znacznie bardziej czytelny. Podstawowym przedmiotem zainteresowania jest fraza i po prostu ma ona powiązane z nią dane o zmiennej długości; rozszerzenie danych sprawia, że ​​jest to niezręczne. – Nick

+1

Można go skrócić o średnią (podzbiór (d, tokenid == 1, num_tokens)), ale rozumiem. Jeśli chcesz trzymać się ramki danych, myślę, że możesz. Pomyśl o tym: ramki danych są listami wektorów o tej samej długości. Możesz uczynić wektor tokenów wektorem list: df <- data.frame (a = 1: 3); df $ b <- list (1: 3,1: 2,1: 3). R jednak tego nie lubi. (Narzeka, że ​​tworzymy ramkę danych w jednym kroku.) Nie wiem dlaczego. –

+0

Podzbiór tokenid jest co najmniej trochę bardziej atrakcyjny. :) – Nick

4

Inną opcją byłaby konwersja ramki danych na macierz listy trybów - każdy element macierzy byłby listą. standardowe operacje tablicowe (może być stosowane cięcie z użyciem [, apply() itd.).

> 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

że również używać łańcuchów dla danych zmiennych długości, ale podobnie jak w następujących przykładach: „c (5,5)” dla pierwszego wyrażenia. Do wykonywania obliczeń należy użyć eval(parse(text=...)).

Na przykład mean można obliczyć w następujący sposób:

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