/ / Konwersja danych z formatu długiego na szeroki z wieloma kolumnami miar - r, ramka danych, plyr

Konwertuj dane z formatu długiego na szeroki z wieloma kolumnami miar - r, ramka danych, plyr

Mam problem z wymyśleniem najbardziej eleganckiego i elastycznego sposobu przełączania danych z długiego formatu na szeroki format, gdy mam więcej niż jedną zmienną miary, którą chcę przynieść.

Na przykład tutaj jest prosta ramka danych w długim formacie. ID jest przedmiotem, TIME jest zmienną czasową, a X i Y są pomiarami z ID w TIME:

> my.df <- data.frame(ID=rep(c("A","B","C"), 5), TIME=rep(1:5, each=3), X=1:15, Y=16:30)
> my.df

ID TIME  X  Y
1   A    1  1 16
2   B    1  2 17
3   C    1  3 18
4   A    2  4 19
5   B    2  5 20
6   C    2  6 21
7   A    3  7 22
8   B    3  8 23
9   C    3  9 24
10  A    4 10 25
11  B    4 11 26
12  C    4 12 27
13  A    5 13 28
14  B    5 14 29
15  C    5 15 30

Gdybym tylko chciał zamienić wartości CZASU na nagłówki kolumn zawierające X, wiem, że mogę użyć rzutowania z pakietu przekształcania (lub dcastu z pliku reshape2):

> cast(my.df, ID ~ TIME, value="X")
ID 1 2 3  4  5
1  A 1 4 7 10 13
2  B 2 5 8 11 14
3  C 3 6 9 12 15

Ale tak naprawdę chcę zrobić, to także wprowadzić Y jako kolejną zmienną pomiarową, a nazwy kolumn odzwierciedlają zarówno nazwę zmiennej miary, jak i wartość czasu:

  ID X_1 X_2 X_3  X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7   10  13  16  19  22  25  28
2  B   2   5   8   11  14  17  20  23  26  29
3  C   3   6   9   12  15  18  21  24  27  30

(FWIW, nie dbam o to, czy wszystkie X są poprzedzane przez Y, czy też są przeplatane jako X_1, Y_1, X_2, Y_2itp.)

Mogę się do tego zbliżyć cast- dwa razy długie dane i łączenie wyników, chociaż nazwy kolumn wymagają trochę pracy i muszę je poprawić, jeśli potrzebuję dodać trzecią lub czwartą zmienną oprócz X i Y:

merge(
cast(my.df, ID ~ TIME, value="X"),
cast(my.df, ID ~ TIME, value="Y"),
by="ID", suffixes=c("_X","_Y")
)

Wygląda na kombinację funkcji w reshape2 i / lub plyr powinno być w stanie to zrobić bardziej elegancko niż moja próba, a także bardziej płynnie zarządzać zmiennymi o wielu miarach. Coś jak cast(my.df, ID ~ TIME, value=c("X","Y"))To nie jest ważne, ale nie udało mi się tego rozgryźć.

Czy jakikolwiek R-magów może mi pomóc? Dzięki.

Odpowiedzi:

14 dla odpowiedzi № 1

Aby obsłużyć wiele zmiennych, jak chcesz, musisz melt dane, które masz przed oddaniem.

library("reshape2")

dcast(melt(my.df, id.vars=c("ID", "TIME")), ID~variable+TIME)

co daje

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7  10  13  16  19  22  25  28
2  B   2   5   8  11  14  17  20  23  26  29
3  C   3   6   9  12  15  18  21  24  27  30

EDYCJA na podstawie komentarza:

Ramka danych

num.id = 10
num.time=10
my.df <- data.frame(ID=rep(LETTERS[1:num.id], num.time),
TIME=rep(1:num.time, each=num.id),
X=1:(num.id*num.time),
Y=(num.id*num.time)+1:(2*length(1:(num.id*num.time))))

daje inny wynik (wszystkie wpisy to 2), ponieważ ID/TIME kombinacja nie wskazuje unikalnego wiersza. W rzeczywistości są dwa rzędy z każdym ID/TIME kombinacje. reshape2 przyjmuje pojedynczą wartość dla każdej możliwej kombinacji zmiennych i zastosuje funkcję podsumowania w celu utworzenia pojedynczej zmiennej, jeśli istnieje wiele wpisów. Właśnie dlatego pojawia się ostrzeżenie

Aggregation function missing: defaulting to length

Możesz dostać coś, co działa, jeśli dodasz inną zmienną, która przełamie tę nadmiarowość.

my.df$cycle <- rep(1:2, each=num.id*num.time)
dcast(melt(my.df, id.vars=c("cycle", "ID", "TIME")), cycle+ID~variable+TIME)

Działa to, ponieważ cycle/ID/time teraz jednoznacznie definiuje wiersz w my.df.


15 dla odpowiedzi nr 2
   reshape(my.df,
idvar = "ID",
timevar = "TIME",
direction = "wide")

daje

  ID X.1 Y.1 X.2 Y.2 X.3 Y.3 X.4 Y.4 X.5 Y.5
1  A   1  16   4  19   7  22  10  25  13  28
2  B   2  17   5  20   8  23  11  26  14  29
3  C   3  18   6  21   9  24  12  27  15  30

11 dla odpowiedzi nr 3

Używając data.table_1.9.5, można to zrobić bez melt ponieważ może obsłużyć wiele value.var kolumny. Możesz go zainstalować z here

 library(data.table)
dcast(setDT(my.df), ID~TIME, value.var=c("X", "Y"))
#   ID 1_X 2_X 3_X 4_X 5_X 1_Y 2_Y 3_Y 4_Y 5_Y
#1:  A   1   4   7  10  13  16  19  22  25  28
#2:  B   2   5   8  11  14  17  20  23  26  29
#3:  C   3   6   9  12  15  18  21  24  27  30

5 dla odpowiedzi № 4

Oto rozwiązanie z tidyr pakiet, który zasadniczo zastąpił przefasonować i reshape2. Tak jak w przypadku tych dwóch pakietów, strategia polega na tym, aby zestaw danych był dłuższy, a następnie szerszy.

library(magrittr); requireNamespace("tidyr"); requireNamespace("dplyr")
my.df %>%
tidyr::gather_(key="variable", value="value", c("X", "Y")) %>%  # Make it even longer.
dplyr::mutate(                                                  # Create the spread key.
time_by_variable   = paste0(variable, "_", TIME)
) %>%
dplyr::select(ID, time_by_variable, value) %>%                  # Retain these three.
tidyr::spread(key=time_by_variable, value=value)                # Spread/widen.

Po tidyr::gather() call, pośredni zbiór danych to:

ID TIME variable value
1   A    1        X     1
2   B    1        X     2
3   C    1        X     3
...
28  A    5        Y    28
29  B    5        Y    29
30  C    5        Y    30

Ostateczny wynik to:

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7  10  13  16  19  22  25  28
2  B   2   5   8  11  14  17  20  23  26  29
3  C   3   6   9  12  15  18  21  24  27  30

tidyr::unite() jest alternatywą, sugerowaną przez @JWillimana. Jest to funkcjonalnie równoważne z dplyr::mutate() i dplyr::select() połączenie powyżej, kiedy remove parametr ma wartość true (co jest wartością domyślną).

Jeśli nie jesteś przyzwyczajony do tego rodzaju manipulacji, to tidyr::unite() może być małą przeszkodą, ponieważ jest to jeszcze jedna funkcja, którą musisz zapamiętać i zapamiętać, jednak jej zalety to (a) bardziej zwięzły kod (to znaczy, cztery linie są zastępowane przez jeden) i (b) mniej miejsc do powtarzania nazw zmiennych (to znaczy, nie musisz powtarzać / modyfikować zmiennych w dplyr::select() klauzula).

my.df %>%
tidyr::gather_(key="variable", value="value", c("X", "Y")) %>%  # Make it even longer.
tidyr::unite("time_by_variable", variable, TIME, remove=T) %>%  # Create the spread key `time_by_variable` while simultaneously dropping `variable` and `TIME`.
tidyr::spread(key=time_by_variable, value=value)                # Spread/widen.