2013-03-06 15 views
7

Ludzie,Łączenie zmiennych w listę

Jestem zaskoczony następującym wyzwaniem. Mam zestaw danych, który wygląda tak:

BuyerID Fruit.1  Fruit.2 Fruit.3 Amount.1 Amount.2 Amount.3 
879  Banana  Apple     4   3 
765  Strawberry Apple  Orange  1   2   4 
123  Orange  Banana    1   1   1 
11  Strawberry      3 
773  Kiwi  Banana    1   2 

Co chciałbym zrobić to uprościć dane (jeśli to możliwe) i zwinąć „owoc” i „ilość” zmiennych

BuyerID Fruit        Amount  Total Count 
879  "Banana" "Apple"     4 3   7  2 
765  "Strawberry" "Apple" "Orange"  1 2 4   7  3 
123  "Orange" "Banana"     1 1 1   3  2 
11  "Strawberry"      3    3  1 
773  "Kiwi" "Banana"     1 2   3  2 

Próbowałem przy użyciu c() i rbind(), ale nie dają wyników, które chcę - wypróbowałem również tutaj: data.frame rows to a list, ale nie jestem zbyt pewny, czy jest to najlepszy sposób na uproszczenie moich danych.

Jest tak, aby łatwiej było mi radzić sobie z mniejszą liczbą zmiennych, aby zliczyć występowanie pewnych przedmiotów, na przykład (np. 60% kupujących kupiło banan).

Mam nadzieję, że jest to wykonalne - jestem także otwarty na wszelkie sugestie. Wszelkie rozwiązania doceniane!

Dziękuję.

+0

prawdopodobnie będziesz chciał użyć 'data.table' pakiet:' data.frame's może obsługiwać tylko jedną wartość komórki. –

+2

To wygląda na całkiem dobrego kandydata na klasyczne, szerokie rozwiązanie 'reshape'. @AnandaMahto - gdzie jesteś? ;-) – thelatemail

+5

@ SeñorO - 'data.frames' może mieć również listy, nie jest to tylko plik data.table. Po zdefiniowaniu musisz być nieco kreatywny. np. 'z <- data.frame (x = 1: 5, y = I (lapply (seq_len (5), seq_len))' – mnel

Odpowiedz

11

Próba replikacji danych, a przy użyciu data.table

DT <- data.frame(
    BuyerID = c(879,765,123,11,773), 
    Fruit.1 = c('Banana','Strawberry','Orange','Strawberry','Kiwi'), 
    Fruit.2 = c('Apple','Apple','Banana',NA,'Banana'), 
    Fruit.3 = c(NA, 'Orange',NA,NA,NA), 
    Amount.1 = c(4,1,1,3,1), Amount.2 = c(3,2,1,NA,2), Amount.3 = c(NA,4,1,NA,NA), 
    Total = c(7,7,3,3,3), 
    Count = c(2,3,2,1,2), 
    stringsAsFactors = FALSE) 

# reshaping to long form and data.table 

library(data.table) 
DTlong <- data.table(reshape(DT, varying = list(Fruit = 2:4, Amount = 5:7), 
    direction = 'long')) 

# create lists (without NA values) 
# also adding count and total columns 
# by using <- to save Fruit and Amount for later use 

DTlist <- DTlong[, list(Fruit <- list(as.vector(na.omit(Fruit.1))), 
         Amount <- list(as.vector(na.omit(Amount.1))), 
         Count = length(unlist(Fruit)), 
         Total = sum(unlist(Amount))), 
       by = BuyerID] 

    BuyerID      V1 V2 Count Total 
1:  879   Banana,Apple 4,3  2  7 
2:  765 Strawberry,Apple,Orange 1,2,4  3  7 
3:  123   Orange,Banana 1,1,1  2  3 
4:  11    Strawberry  3  1  3 
5:  773    Kiwi,Banana 1,2  2  3 

@RicardoSaporta EDIT:

Można pominąć etap przekształcenia, jeśli youd jak za pomocą list(list(c(....)))
Będzie prawdopodobnie zaoszczędzić sporo czasu wykonania (wada jest taka, że ​​dodaje nie puste spacje). Jednakże, jak wskazuje @Marius, powyższa DTlong jest prawdopodobnie łatwiejsza w obsłudze.

DT <- data.table(DT) 
DT[, Fruit := list(list(c( Fruit.1, Fruit.2, Fruit.3))), by=BuyerID] 
DT[, Ammount := list(list(c(Amount.1, Amount.2, Amount.3))), by=BuyerID] 

# Or as a single line 
DT[, list( Fruit = list(c(Fruit.1, Fruit.2, Fruit.3)), 
      Ammount = list(c(Amount.1, Amount.2, Amount.3)), 
      Total, Count), # other columns used 
      by = BuyerID] 
+4

@jacatra: Podczas gdy to rozwiązanie doprowadzi cię do miejsca, w którym powiedziałeś, że chcesz być, czy mogę zasugerować, że długa forma "DTlong" (która jest tworzona jako środkowy krok w tej odpowiedzi) będzie o wiele łatwiejsza w pracy z w dłuższej perspektywie? – Marius

+0

+1 bardzo ładne. @jacatra: Echo sugestii mariusza –

+0

Dziękujemy za pomoc, Marius, Mnel! Bardzo docenione !!!! – jacatra

5

To jest naprawdę zły pomysł, ale tutaj jest w bazie data.frame. Działa, ponieważ data.frame jest w rzeczywistości listą wektorów o równej długości. Możesz zmusić data.frame do przechowywania wektorów w komórkach, ale wymaga to trochę hackery. Proponuję inne formaty, w tym sugestię Mariusa lub listę.

DT <- data.frame(
    BuyerID = c(879,765,123,11,773), 
    Fruit.1 = c('Banana','Strawberry','Orange','Strawberry','Kiwi'), 
    Fruit.2 = c('Apple','Apple','Banana',NA,'Banana'), 
    Fruit.3 = c(NA, 'Orange',NA,NA,NA), 
    Amount.1 = c(4,1,1,3,1), Amount.2 = c(3,2,1,NA,2), Amount.3 = c(NA,4,1,NA,NA), 
    stringsAsFactors = FALSE) 

DT2 <- DT[, 1, drop=FALSE] 
DT2$Fruit <- apply(DT[, 2:4], 1, function(x) unlist(na.omit(x))) 
DT2$Amount <- apply(DT[, 5:7], 1, function(x) unlist(na.omit(x))) 
DT2$Total <- sapply(DT2$Amount, sum) 
DT2$Count <- sapply(DT2$Fruit, length) 

Uzyskano

> DT2 
    BuyerID      Fruit Amount Total Count 
1  879    Banana, Apple 4, 3  7  2 
2  765 Strawberry, Apple, Orange 1, 2, 4  7  3 
3  123   Orange, Banana 1, 1, 1  3  2 
4  11    Strawberry  3  3  1 
5  773    Kiwi, Banana 1, 2  3  2 
+0

Być może, ale zmuszenie to jest delikatne. Niepewny. –

+0

Twierdzę, że ta lista (1: 3,1: 3,1: 2) jest wektorem o długości 3, więc jest ok. – mnel

+1

@mnel Nie zgodziłbym się, ale brakuje mi twojego punktu. –

6

tu rozwiązanie z pakietu podstawowego. To jest jak rozwiązanie Tylera, ale z pojedynczą aplikacją.

res <- apply(DT,1,function(x){ 
    data.frame(Fruit= paste(na.omit(x[2:4]),collapse=' '), 
      Amount = paste(na.omit(x[5:7]),collapse =','), 
      Total = sum(as.numeric(na.omit(x[5:7]))), 
      Count = length(na.omit(x[2:4]))) 
}) 
do.call(rbind,res) 
        Fruit Amount Total Count 
1   Banana Apple 4, 3  7  2 
2 Strawberry Apple Orange 1, 2, 4  7  3 
3   Orange Banana 1, 1, 1  3  2 
4    Strawberry  3  3  1 
5    Kiwi Banana 1, 2  3  2 

Chciałbym również zmienić numer indeksu przez grep, coś jak ten

Fruit = gregexpr('Fruit[.][0-9]', colnames(dat)) > 0 
Amount = gregexpr('Amount[.][0-9]', colnames(dat)) > 0 

x[2:4] replace by x[which(Fruit)].... 

EDIT dodać benchmarking.

library(microbenchmark) 
library(data.table) 
microbenchmark(ag(),mn(), am(), tr()) 
Unit: milliseconds 
    expr  min  lq median  uq  max 
1 ag() 11.584522 12.268140 12.671484 13.317934 109.13419 
2 am() 9.776206 10.515576 10.798504 11.437938 137.44867 
3 mn() 6.470190 6.805646 6.974797 7.290722 48.68571 
4 tr() 1.759771 1.929870 2.026960 2.142066 7.06032 

Za niewielką data.frame, Tyler Rinker jest zwycięzcą !! Jak wyjaśnić to (tylko odgadnąć)

  1. dane: rozwiązanie tabeli cierpią z powodu zmiany kształtu i ogólnie danych.tabela jest szybsza w przypadku dużych danych.
  2. Rozwiązanie badania Ag jest wolniejsze ze względu na podzbiory dla każdego wiersza, a nie rozwiązanie Tylera, którego podzbiór przed użyciem stosuje się.
  3. am rozwiązaniem jest powolne z powodu użycia zmienią i scalić ..
+0

+1 Tak, dokładnie, 'reshape' jest funkcją non' data.table', więc 'mn()' nie jest czystym rozwiązaniem 'data.table'. –

+0

@agstudy nie zapomnij, że wybrałeś również opcję "wklej", która jest wolniejsza od opcji "unlist". –

+0

Nie sądzę, aby można było wprowadzić wektory do komórek za pomocą tej techniki. To, co masz, to ciąg w komórce, który jest dość łatwy do zrobienia, ale straciłeś możliwość łatwego operowania na wektorze jako wektor wektorów numerycznych; na przykład nie możesz zrobić 'sapply (Amount, max)', ponieważ masz teraz wektory znaków. –

4

Dodawanie do już istniejących wielkich odpowiedzi, tutaj jest inny (trzymanie się zasady R):

with(DT, { 
    # Convert to long format 
    DTlong <- reshape(DT, direction = "long", 
        idvar = "BuyerID", varying = 2:ncol(DT)) 
    # aggregate your fruit columns 
    # You need the `do.call(data.frame, ...)` to convert 
    # the resulting matrix-as-a-column into separate columns 
    Agg1 <- do.call(data.frame, 
        aggregate(Fruit ~ BuyerID, DTlong, 
          function(x) c(Fruit = paste0(x, collapse = " "), 
              Count = length(x)))) 
    # aggregate the amount columns 
    Agg2 <- aggregate(Amount ~ BuyerID, DTlong, sum) 
    # merge the results 
    merge(Agg1, Agg2) 
}) 
# BuyerID    Fruit.Fruit Fruit.Count Amount 
# 1  11    Strawberry   1  3 
# 2  123   Orange Banana   2  3 
# 3  765 Strawberry Apple Orange   3  7 
# 4  773    Kiwi Banana   2  3 
# 5  879   Banana Apple   2  7 

Podstawowa koncepcja jest:

  1. użytkowania reshape aby uzyskać dane w dłuższej formie (czyli tam, gdzie myślę, że należy zatrzymać, faktycznie)
  2. Użyj dwóch różnych poleceń aggregate, jednego do agregowania kolumn owoców i jednego do agregowania kolumn z kwotami. Podejście oparte na formule aggregate powoduje usunięcie NA, ale można określić pożądane zachowanie za pomocą argumentu na.action.
  3. Użyj kombinacji merge.
0

Nie istniało, gdy zadano pytanie, ale tidyr działa dobrze.

Ponowne wykorzystanie danych użytkownika @ mnel odpowiedź,

library(tidyr) 
separator <- ' ' 
DT %>% 
    unite(Fruit, grep("Fruit", names(.)), sep = separator) %>% 
    unite(Amount, grep("Amount", names(.)), sep = separator) 

# BuyerID     Fruit Amount Total Count 
# 1  879   Banana Apple NA 4 3 NA  7  2 
# 2  765 Strawberry Apple Orange 1 2 4  7  3 
# 3  123  Orange Banana NA 1 1 1  3  2 
# 4  11  Strawberry NA NA 3 NA NA  3  1 
# 5  773   Kiwi Banana NA 1 2 NA  3  2 
Powiązane problemy