2016-07-07 17 views
5

Chciałbym użyć funkcji dplyr mutate_if() do konwertowania kolumn list do kolumn-ramek danych, ale napotkasz dziwny błąd, gdy próbuję Zrób tak. Używam dplyr 0.5.0, purrr 0.2.2, R 3.3.0.Mutowanie kolumn ramki danych na podstawie funkcji predykatu (dplyr :: mutate_if)

Podstawowa konfiguracja wygląda następująco: Mam ramki danych d, którego niektóre kolumny są wykazy:

d <- dplyr::data_frame(
    A = list(
    list(list(x = "a", y = 1), list(x = "b", y = 2)), 
    list(list(x = "c", y = 3), list(x = "d", y = 4)) 
), 
    B = LETTERS[1:2] 
) 

chciałbym przekonwertować kolumnę list (w tym przypadku d$A) do gniazda kolumna ramek danych z następującą funkcję:

tblfy <- function(x) { 
    x %>% 
    purrr::transpose() %>% 
    purrr::simplify_all() %>% 
    dplyr::as_data_frame() 
} 

oznacza to, że ja jak lista odbierającej d$A być zastąpiony wykazem lapply(d$A, tblfy), która

[[1]] 
# A tibble: 2 x 2 
     x  y 
    <chr> <dbl> 
1  a  1 
2  b  2 

[[2]] 
# A tibble: 2 x 2 
     x  y 
    <chr> <dbl> 
1  c  3 
2  d  4 

Oczywiście w tym prostym przypadku mógłbym po prostu dokonać prostego przypisania. Chodzi jednak o to, że chciałbym to zrobić programowo, najlepiej za pomocą dplyr, w ogólnie obowiązujący sposób, który mógłby zająć się dowolną liczbą kolumn z listy.

Oto gdzie potykam: Kiedy próbuję przekonwertować listy-kolumn z danymi-Frame-kolumn stosując następującą aplikację

d %>% dplyr::mutate_if(is.list, funs(tblfy)) 

otrzymuję komunikat o błędzie, że nie wiem jak interpretować:

Error: Each variable must be named. 
Problem variables: 1, 2 

Dlaczego mutate_if() nie? Jak mogę go właściwie zastosować, aby uzyskać pożądany rezultat?

Uwaga

commenter wskazał, że funkcja tblfy() należy wektorowy. To rozsądna sugestia. Ale - o ile nie mam wektoryzacji niepoprawnie - nie wydaje się, aby znalazło się u źródła problemu. Podłączając vectorized wersja tblfy(),

tblfy_vec <- Vectorize(tblfy) 

do mutate_if() nie powiedzie się z powodu błędu

Error: wrong result size (4), expected 2 or 1 

aktualizacji

Po zdobyciu pewnego doświadczenia z Purrr, teraz znalazły się następujące podejście naturalne, jeśli jest nieco przewlekły:

d %>% 
    map_if(is.list, ~ map(., ~ map_df(., identity))) %>% 
    as_data_frame() 

To jest mniej lub bardziej identyczne z rozwiązaniem @ alistaire, poniżej, ale używa map_if(), odp. map(), zamiast mutate_if(), wzgl. Vectorize().

+2

Czym dokładnie jest oczekiwany wynik? Chcesz zmienić A z listy list na listę przekąsek? – MrFlick

+1

Twoja funkcja nie jest wektoryzowana, przyjmuje tylko jedną listę. Spójrz na 'tblfy (d $ A)'. Wystąpił błąd, ponieważ istnieją dwie listy w 'd $ A'. Nie porównujesz jabłek do jabłek. W twoim 'lapply (d $ A, tblfy)' nadajesz swojej funkcji jedną listę na raz, dlatego to działa. 'tblfy (d $ A [[1]])' i 'tblfy (d $ A [[2]])'. W twojej funkcji dplyr dostarczasz dwie listy. Zmień 'tblfy', aby zaakceptować więcej niż jedną listę, lub zmień wywołanie dplyr. Lub jak pyta MrFlick, szerzej myśl o tym, co budujesz. –

+0

@MrFlick Dokonałem edycji pytania, aby uzyskać pożądany wynik. Czy teraz jest jasne? – egnha

Odpowiedz

5

Oryginalne tblfy funkcyjne błędy dla mnie (nawet kiedy jego elementy są przykuty bezpośrednio), więc niech go odbudować nieco, dodając wektoryzacji a także, co pozwala nam uniknąć skądinąd niezbędne uprzedniej rowwise() połączenia:

tblfy <- Vectorize(function(x){x %>% purrr::map_df(identity) %>% list()}) 

teraz możemy użyć mutate_if ładnie:

d %>% mutate_if(purrr::is_list, tblfy) 
## Source: local data frame [2 x 2] 
## 
##    A  B 
##   <list> <chr> 
## 1 <tbl_df [2,2]>  A 
## 2 <tbl_df [2,2]>  B 

... a jeśli unnest aby zobaczyć, co tam jest,

d %>% mutate_if(purrr::is_list, tblfy) %>% tidyr::unnest() 
## Source: local data frame [4 x 3] 
## 
##  B  x  y 
## <chr> <chr> <dbl> 
## 1  A  a  1 
## 2  A  b  2 
## 3  B  c  3 
## 4  B  d  4 

Kilka uwag:

  • map_df(identity) wydaje się być bardziej skuteczne w budowania tibble niż którykolwiek z alternatywnych preparatów. Wiem, że wywołanie identity wydaje się niepotrzebne, ale większość wszystkiego się psuje.
  • Nie jestem pewien, jak szeroko użyteczny będzie tblfy, ponieważ jest on w pewnym stopniu zależny od struktury list w kolumnie listy, która może być bardzo różna. Jeśli masz dużo o podobnej strukturze, przypuszczam, że jest to przydatne.
  • Może być jakiś sposób, aby to zrobić z pmap zamiast z Vectorize, ale nie mogę go uruchomić przy niektórych pobieżnych próbach.
+1

Dzięki, to robi! Twoja wersja 'tblfy()' przy użyciu 'map_df()' jest bardziej zwięzła niż moja. Nie myślałem o zrobieniu tego w ten sposób. W rzeczywistości, patrząc na kod źródłowy dla 'map_df()' wyjaśnia dlaczego twoje rozwiązanie działa, a szczególnie, dlaczego 'list()' jest konieczne (co początkowo mnie zaintrygowało): ponieważ 'map_df' jest właściwie' mapą ', po której następuje 'bind_rows', pomijając' list() 'spowoduje ramkę danych o wymiarach 4 x 2. – egnha

6

W miejscu konwersja bez kopiowania:

library(data.table) 

for (col in d) if (is.list(col)) lapply(col, setDF) 

d 
#Source: local data frame [2 x 2] 
# 
#    A B 
#1 <S3:data.frame> A 
#2 <S3:data.frame> B 
+0

Nie odpowiada na oryginalne pytanie dotyczące' mutate_if', ale bardzo miłe alternatywne rozwiązanie podstawowego problemu. data.table jest dla mnie nowością. Dzięki! – egnha

Powiązane problemy