2013-06-01 4 views
7

У меня есть фрейм данных с одного столбца, который представляет собой список, например так:Преобразование столбца «списка» типа в несколько столбцов в кадре данных

>head(movies$genre_list) 
[[1]] 
[1] "drama" "action" "romance" 
[[2]] 
[1] "crime" "drama" 
[[3]] 
[1] "crime" "drama" "mystery" 
[[4]] 
[1] "thriller" "indie" 
[[5]] 
[1] "thriller" 
[[6]] 
[1] "drama" "family" 

Я хочу, чтобы преобразовать этот один столбец кратному столбцы, по одному для каждого уникального элемента по спискам (в данном случае, жанрам) и иметь их как двоичные столбцы. Я ищу элегантное решение, которое не предполагает сначала выяснить, сколько жанров существует, а затем создать столбец для каждого, а затем проверить каждый элемент списка, чтобы затем заполнить столбцы жанра. Я пробовал unlist, но он не работает с вектором списков так, как я хочу.

Спасибо!

+0

В каждом элементе списка всегда есть уникальные жанры? Другими словами, может ли запись быть «драма, действие, романтика, действие»? – A5C1D2H2I1M1N2O1R2T1

ответ

4

Вот несколько подходов:

movies <- data.frame(genre_list = I(list(
    c("drama", "action", "romance"), 
    c("crime", "drama"), 
    c("crime", "drama", "mystery"), 
    c("thriller", "indie"), 
    c("thriller"), 
    c("drama", "family")))) 

Update, года спустя ....

Вы можете использовать функцию mtabulate от "qdapTools" или неэкспортируемая charMat функции от моего " splitstackshape ".

Синтаксис будет:

library(qdapTools) 
mtabulate(movies$genre_list) 
# action crime drama family indie mystery romance thriller 
# 1  1  0  1  0  0  0  1  0 
# 2  0  1  1  0  0  0  0  0 
# 3  0  1  1  0  0  1  0  0 
# 4  0  0  0  0  1  0  0  1 
# 5  0  0  0  0  0  0  0  1 
# 6  0  0  1  1  0  0  0  0 

или

splitstackshape:::charMat(movies$genre_list, fill = 0) 
#  action crime drama family indie mystery romance thriller 
# [1,]  1  0  1  0  0  0  1  0 
# [2,]  0  1  1  0  0  0  0  0 
# [3,]  0  1  1  0  0  1  0  0 
# [4,]  0  0  0  0  1  0  0  1 
# [5,]  0  0  0  0  0  0  0  1 
# [6,]  0  0  1  1  0  0  0  0 

Обновление: Несколько более прямых методов

Улучшенный вариант 1: Используйте table несколько непосредственно:

table(rep(1:nrow(movies), sapply(movies$genre_list, length)), 
     unlist(movies$genre_list, use.names=FALSE)) 

Улучшенный вариант 2: Используйте петлю for.

x <- unique(unlist(movies$genre_list, use.names=FALSE)) 
m <- matrix(0, ncol = length(x), nrow = nrow(movies), dimnames = list(NULL, x)) 
for (i in 1:nrow(m)) { 
    m[i, movies$genre_list[[i]]] <- 1 
} 
m 

Ниже OLD ответ

Преобразование списка в список table с (в свою очередь, преобразуется в data.frame с):

tables <- lapply(seq_along(movies$genre_list), function(x) { 
    temp <- as.data.frame.table(table(movies$genre_list[[x]])) 
    names(temp) <- c("Genre", paste("Record", x, sep = "_")) 
    temp 
}) 

Использование Reduce до merge в результате списка. Если я правильно понимаю вашу конечная цель правильно, это приводит к транспонированной форме результата вы заинтересованы.

merged_tables <- Reduce(function(x, y) merge(x, y, all = TRUE), tables) 
merged_tables 
#  Genre Record_1 Record_2 Record_3 Record_4 Record_5 Record_6 
# 1 action  1  NA  NA  NA  NA  NA 
# 2 drama  1  1  1  NA  NA  1 
# 3 romance  1  NA  NA  NA  NA  NA 
# 4 crime  NA  1  1  NA  NA  NA 
# 5 mystery  NA  NA  1  NA  NA  NA 
# 6 indie  NA  NA  NA  1  NA  NA 
# 7 thriller  NA  NA  NA  1  1  NA 
# 8 family  NA  NA  NA  NA  NA  1 

Транспонирование и преобразование NA в 0 довольно прост.Просто поместите первый столбец и повторно использовать его в качестве столбца names для нового data.frame

movie_genres <- setNames(data.frame(t(merged_tables[-1])), merged_tables[[1]]) 
movie_genres[is.na(movie_genres)] <- 0 
movie_genres 
3

Используя тот же вход, как и в других ответах здесь несколько вариантов:

1) фактор/таблица/rbind

> levs <- levels(factor(unlist(movies[[1]]))) 
> as.data.frame(do.call(rbind, lapply(lapply(movies[[1]], factor, levs), table))) 
    action crime drama family indie mystery romance thriller 
1  1  0  1  0  0  0  1  0 
2  0  1  1  0  0  0  0  0 
3  0  1  1  0  0  1  0  0 
4  0  0  0  0  1  0  0  1 
5  0  0  0  0  0  0  0  1 
6  0  0  1  1  0  0  0  0 

2) make.groups/xtabs

> library(lattice) 
> m <- do.call(make.groups, movies[[1]]) 
> as.data.frame.matrix(xtabs(~ which + data, m)) 
           action crime drama family indie mystery romance thriller 
c("drama", "action", "romance")  1  0  1  0  0  0  1  0 
c("crime", "drama")     0  1  1  0  0  0  0  0 
c("crime", "drama", "mystery")  0  1  1  0  0  1  0  0 
c("thriller", "indie")    0  0  0  0  1  0  0  1 
thriller        0  0  0  0  0  0  0  1 
c("drama", "family")     0  0  1  1  0  0  0  0 

2a) make.groups/dcast Это одна вариация на альтернативной 2 с помощью dcast из reshape2 вместо as.data.frame.matrix и xtabs. Расплавленный кадра m данных из альтернативной 2.

library(reshape2) 
dcast(m, which ~ data, fun.aggregate = length, value.var = "which") 

ОБНОВЛЕНИЕ: Добавлена ​​альтернатива 2.

ОБНОВЛЕНИЕ 2: Добавлен альтернативный 2a.

+0

Спасибо! Мне нравится решение 1 больше всего! Я просто не привык к «решетке», достаточной для решения grok 2. –

+0

@New, разделите # 2 на две строки, чтобы можно было проверить плавный фрейм данных 'm'. Это может улучшить понятность. –

Смежные вопросы