2017-01-16 18 views
7

Tôi có khung dữ liệu với biến phân loại giữ liệt kê của chuỗi, có độ dài thay đổi (điều quan trọng là vì câu hỏi này sẽ trùng lặp với this hoặc this), ví dụ:R: tạo biến giả dựa trên biến phân loại * của danh sách *

df <- data.frame(x = 1:5) 
df$y <- list("A", c("A", "B"), "C", c("B", "D", "C"), "E") 
df 
x  y 
1 1  A 
2 2 A, B 
3 3  C 
4 4 B, D, C 
5 5  E 

Và hình thức mong muốn là một biến giả cho mỗi chuỗi duy nhất nhìn thấy bất cứ nơi nào trong df$y, ví dụ:

data.frame(x = 1:5, A = c(1,1,0,0,0), B = c(0,1,0,1,0), C = c(0,0,1,1,0), D = c(0,0,0,1,0), E = c(0,0,0,0,1)) 
x A B C D E 
1 1 1 0 0 0 0 
2 2 1 1 0 0 0 
3 3 0 0 1 0 0 
4 4 0 1 1 1 0 
5 5 0 0 0 0 1 

cách tiếp cận ngây thơ này hoạt động:

> uniqueStrings <- unique(unlist(df$y)) 
> n <- ncol(df) 
> for (i in 1:length(uniqueStrings)) { 
+ df[, n + i] <- sapply(df$y, function(x) ifelse(uniqueStrings[i] %in% x, 1, 0)) 
+ colnames(df)[n + i] <- uniqueStrings[i] 
+ } 

Tuy nhiên nó rất xấu xí, lười biếng và chậm chạp với khung dữ liệu lớn.

Mọi đề xuất? Một cái gì đó ưa thích từ tidyverse?


CẬP NHẬT: Tôi có 3 cách tiếp cận khác nhau bên dưới. Tôi đã thử nghiệm chúng bằng cách sử dụng system.time trên máy tính xách tay (Windows 7, 32GB RAM) trên số liệu thực, bao gồm 1M hàng, mỗi hàng chứa danh sách có độ dài từ 1 đến 4 chuỗi (trong số ~ 350 giá trị chuỗi duy nhất), 200MB tổng thể trên đĩa. Vì vậy, kết quả mong đợi là một khung dữ liệu với kích thước 1M x 350. Các phương pháp tiếp cận tidyverse (@Sotos) và base (@ joel.wilson) mất quá lâu tôi phải khởi động lại R. Phương pháp tiếp cận qdapTools (@akrun) tuy nhiên hoạt động tuyệt vời:

> system.time(res1 <- mtabulate(varsLists)) 
    user system elapsed 
    47.05 10.27 116.82 

Vì vậy, đây là phương pháp tôi sẽ đánh dấu là được chấp nhận.

+0

hoặc 'data.frame (x = df $ y, hàm (l) {bảng (yếu tố (l, mức = LETTERS [1: 5]))}))) ' – alistaire

+0

@alistaire có thể' cấp = duy nhất (không công khai (df $ y)) 'thay vì' LETTERS [1: 5] '? – Sotos

+0

@Sotos Tôi đã có điều đó, nhưng đã tìm ra điều này là ít tính toán hơn. Tuyến đường tốt nhất là lưu trữ như một biến riêng biệt, nhưng điều đó sẽ yêu cầu một dòng thứ hai ... – alistaire

Trả lời

6

Chúng ta có thể sử dụng mtabulate

library(qdapTools) 
cbind(df[1], mtabulate(df$y)) 
# x A B C D E 
#1 1 1 0 0 0 0 
#2 2 1 1 0 0 0 
#3 3 0 0 1 0 0 
#4 4 0 1 1 1 0 
#5 5 0 0 0 0 1 
+0

Đó là ấn tượng và siêu nhanh (một vài giây cho một hàng ~ 1M với ~ 350 giá trị duy nhất trên PC của tôi) . Bạn có câu trả lời không yêu cầu một gói hoàn toàn mới? Cảm ơn. –

+0

@GioraSimchoni Có vẻ như ai đó đã trả lời nó mà không cần gói – akrun

+2

@GioraSimchoni; Tôi đoán một thay thế cơ bản là 'bảng (rep (df $ x, độ dài (df $ y)), unlist (df $ y))'? –

6

ý tưởng khác,

library(dplyr) 
library(tidyr) 

df %>% 
unnest(y) %>% 
mutate(new = 1) %>% 
spread(y, new, fill = 0) 

# x A B C D E 
#1 1 1 0 0 0 0 
#2 2 1 1 0 0 0 
#3 3 0 0 1 0 0 
#4 4 0 1 1 1 0 
#5 5 0 0 0 0 1 

Tiếp tục với các trường hợp bạn nêu trong ý kiến, chúng ta có thể sử dụng dcast từ reshape2 vì nó là linh hoạt hơn spread,

df2 <- df %>% 
     unnest(y) %>% 
     group_by(x) %>% 
     filter(!duplicated(y)) %>% 
     ungroup() 

reshape2::dcast(df2, x ~ y, value.var = 'y', length) 

# x A B C D E 
#1 1 1 0 0 0 0 
#2 2 1 1 0 0 0 
#3 3 0 0 1 0 0 
#4 4 0 1 1 1 0 
#5 5 0 0 0 0 1 

#or with df$x <- c(1, 1, 2, 2, 3) 

# x A B C D E 
#1 1 1 1 0 0 0 
#2 2 0 1 1 1 0 
#3 3 0 0 0 0 1 

#or with df$x <- rep(1,5) 

# x A B C D E 
#1 1 1 1 1 1 1 
+0

cảm ơn, hãy xem điều gì xảy ra khi df $ x = rep (1, 5). "Lỗi: Số nhận dạng trùng lặp cho các hàng (1, 2), (3, 5), (4, 7)" –

+0

Kết quả mong đợi của bạn sẽ như thế nào trong trường hợp này? một cái gì đó như 'df%>% unnest (y)%>% group_by (x)%>% biến đổi (new = 1: n())%>% spread (y, x, fill = 0)'? – Sotos

+0

Cùng một kết quả giữ cột x ban đầu. Điều này, trên bản gốc 'df' cho" Lỗi: Mã định danh trùng lặp cho các hàng (1, 2) ". –

1

hóa đơn này lves không có gói bên ngoài,

# thanks to Sotos for suggesting to use `unique(unlist(df$y))` instead of `LETTERS[1!:5]` 
sapply(unique(unlist(df$y)), function(j) as.numeric(grepl(j, df$y))) 
#  A B C D E 
#[1,] 1 0 0 0 0 
#[2,] 1 1 0 0 0 
#[3,] 0 0 1 0 0 
#[4,] 0 1 1 1 0 
#[5,] 0 0 0 0 1 
+2

phần 'LETTERS' là không hợp lệ. Bạn có thể làm 'unique (unlist (df $ y))' thay vì – Sotos

+0

Không hoạt động với 'df $ x = rep (1,5)' hoặc 'df $ x = c (1,1,2,2,3) '. Nó không quan trọng 'df $ x' là gì. –

+1

@ joel.wilson hoạt động rất tốt, tôi sẽ thực hiện một số tiêu chuẩn để xem nó so sánh với các giải pháp "fancier" khác như thế nào, cảm ơn. –

Các vấn đề liên quan