Pakiet tidyr jest częścią jednego z najpopularniejszych pakietów w języku R — tidyverse.
Głównym celem pakietu jest uporządkowanie danych.
Na Habrze jest już poświęcona temu pakietowi, ale datuje się na 2015 rok. A ja chcę opowiedzieć o najważniejszych zmianach, o których kilka dni temu poinformował jego autor Hadley Wickham.

SJK: Czy funkcje gather() i spread() będą uznawane za przestarzałe?
Hadley Wickham: W pewnym sensie. Przestaniemy zalecać użycie tych funkcji i poprawiać błędy w nich, ale nadal będą obecne w pakiecie w aktualnym stanie.
Spis treści
Jeśli interesujesz się analizą danych, to mogą Cię zainteresować moje i kanały. Większość ich treści poświęcona jest językowi R.
Koncepcja TidyData
Cel tidyr — pomóc ci uporządkować dane w tzw. sposób tidy. Uporządkowane dane to dane, w których:
- Każda zmienna znajduje się w kolumnie.
- Każde obserwacja to wiersz.
- Każda wartość to komórka.
Z danymi uporządkowanymi znacznie łatwiej i wygodniej pracować podczas analizy.
Podstawowe funkcje wchodzące w skład pakietu tidyr
tidyr zawiera zestaw funkcji przeznaczonych do przekształcania tabel:
fill()— wypełnianie brakujących wartości w kolumnie poprzednimi wartościami;separate()— dzieli jedno pole na kilka przez separator;unite()— wykonuje operację łączenia kilku pól w jedno, działanie odwrotne do funkcjiseparate();pivot_longer()— funkcja przekształcająca dane z formatu szerokiego do długiego;pivot_wider()— funkcja przekształcająca dane z formatu długiego do szerokiego. Operacja odwrotna do tej, którą wykonuje funkcjapivot_longer().gather()przestarzała — funkcja przekształcająca dane z formatu szerokiego do długiego;spread()przestarzała — funkcja przekształcająca dane z formatu długiego do szerokiego. Operacja odwrotna do tej, którą wykonuje funkcjagather().
Nowa koncepcja przekształcania danych z formatu szerokiego do długiego i odwrotnie
Wcześniej do podobnego rodzaju przekształceń używano funkcji gather() i spread(). Z lat używania tych funkcji stało się oczywiste, że dla większości użytkowników, w tym autora pakietu, nazwy tych funkcji oraz ich argumenty były dość niejasne, co powodowało trudności w ich wyszukiwaniu i zrozumieniu, która z tych funkcji przekształca ramkę danych z formatu szerokiego na długi, i odwrotnie.
W związku z tym w tidyr dodano dwie nowe, istotne funkcje, które mają na celu transformację ramek danych.
Nowe funkcje pivot_longer() i pivot_wider() zostały stworzone pod wpływem kilku funkcji z pakietu cdata, stworzonego przez Johna Mounta i Ninę Zumeł.
Instalacja najnowszej wersji tidyr 0.8.3.9000
Aby zainstalować najnowszą wersję pakietu tidyr 0.8.3.9000, w której dostępne są nowe funkcje, skorzystaj z następującego kodu.
devtools::install_github("tidyverse/tidyr")
W momencie pisania tego artykułu te funkcje są dostępne tylko w wersji dev pakietu na GitHubie.
Przejście na nowe funkcje
Zresztą, przetłumaczenie starych skryptów na nowe funkcje nie jest trudne; aby lepiej to zrozumieć, wezmę przykład z dokumentacji starych funkcji i pokażę, jak te same operacje wykonuje się przy użyciu nowych pivot_*() funkcje.
Przekształcanie formatu szerokiego na długi.
Przykład kodu z dokumentacji funkcji gather
# example
library(dplyr)
stocks <- data.frame(
time = as.Date('2009-01-01') + 0:9,
X = rnorm(10, 0, 1),
Y = rnorm(10, 0, 2),
Z = rnorm(10, 0, 4)
)
# old
stocks_gather <- stocks %>% gather(key = stock,
value = price,
-time)
# new
stocks_long <- stocks %>% pivot_longer(cols = -time,
names_to = "stock",
values_to = "price")
Przekształcanie formatu długiego na szeroki.
Przykład kodu z dokumentacji funkcji spread
# old
stocks_spread <- stocks_gather %>% spread(key = stock,
value = price)
# new
stock_wide <- stocks_long %>% pivot_wider(names_from = "stock",
values_from = "price")
Ponieważ w powyższych przykładach pracy z pivot_longer() i pivot_wider(), w oryginalnej tabeli stocks brakuje kolumn wymienionych w argumentach names_to i values_to ich nazwy należy podawać w cudzysłowie.
Tabela, z pomocą której najłatwiej będzie Ci zrozumieć, jak przejść na nową koncepcję tidyr.

Notatka od autora
Cały poniższy tekst jest adaptacyjny, a wręcz powiedziałbym, że swobodnym tłumaczeniem z oficjalnej strony biblioteki tidyverse.
Prosty przykład przekształcania danych z formatu szerokiego do długiego
pivot_longer () — przekształca zbiory danych w dłuższe, zmniejszając liczbę kolumn i zwiększając liczbę wierszy.

Aby wykonać przykłady przedstawione w artykule, najpierw należy załadować potrzebne pakiety:
library(tidyr)
library(dplyr)
library(readr)Załóżmy, że mamy tabelę z wynikami ankiety, w której (między innymi) pytano ludzi o ich religię i roczne dochody:
#> # A tibble: 18 x 11
#> religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Agnostic 27 34 60 81 76 137
#> 2 Atheist 12 27 37 52 35 70
#> 3 Buddhist 27 21 30 34 33 58
#> 4 Catholic 418 617 732 670 638 1116
#> 5 Don’t k… 15 14 15 11 10 35
#> 6 Evangel… 575 869 1064 982 881 1486
#> 7 Hindu 1 9 7 9 11 34
#> 8 Histori… 228 244 236 238 197 223
#> 9 Jehovah… 20 27 24 24 21 30
#> 10 Jewish 19 19 25 25 30 95
#> # … with 8 more rows, and 4 more variables: `$75-100k` <dbl>,
#> # `$100-150k` <dbl>, `>150k` <dbl>, `Don't know/refused` <dbl>Ta tabela zawiera dane o religii respondentów w wierszach, a poziom dochodu jest rozmieszczony w nazwach kolumn. Liczba respondentów w każdej kategorii jest przechowywana w wartościach komórek na przecięciu religii i poziomu dochodu. Aby doprowadzić tabelę do schludnego, poprawnego formatu, wystarczy użyć pivot_longer():
pew %>%
pivot_longer(cols = -religion, names_to = "income", values_to = "count")pew %>%
pivot_longer(cols = -religion, names_to = "income", values_to = "count")
#> # A tibble: 180 x 3
#> religion income count
#>
#> 1 Agnostic 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> # … with 170 more rowsArgumenty funkcji pivot_longer()
- Pierwszy argument cols, opisuje, które kolumny należy połączyć. W tym przypadku wszystkie kolumny, z wyjątkiem time.
- Argument names_to nadaje nazwę zmiennej, która zostanie utworzona z nazw kolumn, które połączyliśmy.
- values_to nadaje nazwę zmiennej, która zostanie utworzona z danych przechowywanych w wartościach komórek połączonych kolumn.
Specyfikacje
To nowa funkcjonalność pakietu tidyr, która wcześniej podczas pracy z przestarzałymi funkcjami była niedostępna.
Specyfikacja to ramka danych, gdzie każdy wiersz odpowiada jednej kolumnie w nowej wyjściowej ramce danych oraz dwóm specjalnym kolumnom, które zaczynają się od:
- .name zawiera oryginalną nazwę kolumny.
- .value zawiera nazwę kolumny, do której będą wchodziły wartości komórek.
Pozostałe kolumny specyfikacji odzwierciedlają, jak w nowej kolumnie będzie wyświetlana nazwa skompresowanych kolumn z .name.
Specyfikacja opisuje metadane przechowywane w nazwie kolumny, z jednym wierszem dla każdej kolumny i jedną kolumną dla każdej zmiennej połączonej z nazwą kolumny; być może obecnie taka definicja wydaje się skomplikowana, ale po przeanalizowaniu kilku przykładów wszystko stanie się znacznie jaśniejsze.
Istota specyfikacji polega na tym, że można wyciągać, modyfikować i przypisywać nowe metadane do przekształcanego ramka danych.
Do pracy ze specyfikacjami przy przekształcaniu tabeli z formatu szerokiego na długi służy funkcja pivot_longer_spec().
Ta funkcja działa w ten sposób, że bierze dowolną ramkę danych i formuje jej metadane w opisany powyżej sposób.
Na przykład, weźmy zestaw danych who, który jest dostarczany razem z pakietem. tidyrTen zestaw danych zawiera informacje dostarczane przez Światową Organizację Zdrowia na temat zachorowalności na gruźlicę.
who
#> # A tibble: 7,240 x 60
#> country iso2 iso3 year new_sp_m014 new_sp_m1524 new_sp_m2534
#> <chr> <int> <int> <int> <int>
#> 1 Afghan… AF AFG 1980 NA NA NA
#> 2 Afghan… AF AFG 1981 NA NA NA
#> 3 Afghan… AF AFG 1982 NA NA NA
#> 4 Afghan… AF AFG 1983 NA NA NA
#> 5 Afghan… AF AFG 1984 NA NA NA
#> 6 Afghan… AF AFG 1985 NA NA NA
#> 7 Afghan… AF AFG 1986 NA NA NA
#> 8 Afghan… AF AFG 1987 NA NA NA
#> 9 Afghan… AF AFG 1988 NA NA NA
#> 10 Afghan… AF AFG 1989 NA NA NA
#> # … z 7,230 więcej wierszy i 53 więcej zmiennychZbudujemy jego specyfikację.
spec <- who %>%
pivot_longer_spec(new_sp_m014:newrel_f65, values_to = "count")#> # A tibble: 56 x 3
#> .name .value name
#> <chr> <chr> <chr>
#> 1 new_sp_m014 count new_sp_m014
#> 2 new_sp_m1524 count new_sp_m1524
#> 3 new_sp_m2534 count new_sp_m2534
#> 4 new_sp_m3544 count new_sp_m3544
#> 5 new_sp_m4554 count new_sp_m4554
#> 6 new_sp_m5564 count new_sp_m5564
#> 7 new_sp_m65 count new_sp_m65
#> 8 new_sp_f014 count new_sp_f014
#> 9 new_sp_f1524 count new_sp_f1524
#> 10 new_sp_f2534 count new_sp_f2534
#> # … with 46 more rowsPola kraj, iso2, iso3 są już zmiennymi. Naszym zadaniem jest odwrócenie kolumn z new_sp_m014 w newrel_f65.
W nazwach tych kolumn przechowywane są następujące informacje:
- Prefiks
new_oznacza, że kolumna zawiera dane o nowych przypadkach zachorowań na gruźlicę, a bieżący ramka danych zawiera informacje tylko o nowych zachorowaniach, dlatego ten prefiks w kontekście nie niesie żadnego znaczenia. sp/rel/sp/epopisuje sposób diagnozowania choroby.m/fpłci pacjenta.014/1524/2535/3544/4554/65zakres wiekowy pacjenta.
Możemy rozdzielić te kolumny przy użyciu funkcji extract(), wykorzystując wyrażenie regularne.
spec <- spec %>%
extract(name, c("diagnosis", "gender", "age"), "new_?(.*)_(.)(.*)")#> # A tibble: 56 x 5
#> .name .value diagnosis gender age
#> <chr> <chr> <chr> <chr> <chr>
#> 1 new_sp_m014 count sp m 014
#> 2 new_sp_m1524 count sp m 1524
#> 3 new_sp_m2534 count sp m 2534
#> 4 new_sp_m3544 count sp m 3544
#> 5 new_sp_m4554 count sp m 4554
#> 6 new_sp_m5564 count sp m 5564
#> 7 new_sp_m65 count sp m 65
#> 8 new_sp_f014 count sp f 014
#> 9 new_sp_f1524 count sp f 1524
#> 10 new_sp_f2534 count sp f 2534
#> # … with 46 more rowsZauważ, że kolumna .name powinna pozostać niezmieniona, ponieważ to jest nasz indeks w nazwach kolumn pierwotnego zestawu danych.
Płeć i wiek (kolumny gender i age) mają ustalone i znane wartości, dlatego zaleca się przekształcenie tych kolumn w czynniki:
spec <- spec %>%
mutate(
gender = factor(gender, levels = c("f", "m")),
age = factor(age, levels = unique(age), ordered = TRUE)
) Na koniec, aby zastosować stworzoną przez nas specyfikację do pierwotnej ramki danych who musimy użyć argumentu spec w funkcji pivot_longer().
who %>% pivot_longer(spec = spec)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <fct> <ord> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rowsWszystko, co właśnie zrobiliśmy, może być schematycznie przedstawione w następujący sposób:

Specyfikacja z wykorzystaniem wielu wartości (.value)
W podanym powyżej przykładzie kolumna specyfikacji .value zawierała tylko jedną wartość, co w większości przypadków ma miejsce.
Jednak czasami może zaistnieć sytuacja, w której trzeba będzie zebrać wartości z kolumn o różnych typach danych. Dzięki przestarzałej funkcji spread() byłoby to dość trudne.
Poniższy przykład został zapożyczony z pakietu data.table.
Utwórzmy przykładowy dataframe.
family <- tibble::tribble(
~family, ~dob_child1, ~dob_child2, ~gender_child1, ~gender_child2,
1L, "1998-11-26", "2000-01-29", 1L, 2L,
2L, "1996-06-22", NA, 2L, NA,
3L, "2002-07-11", "2004-04-05", 2L, 2L,
4L, "2004-10-10", "2009-08-27", 1L, 1L,
5L, "2000-12-05", "2005-02-28", 2L, 1L,
)
family % mutate_at(vars(starts_with("dob")), parse_date)#> # A tibble: 5 x 5
#> family dob_child1 dob_child2 gender_child1 gender_child2
#> <int> <date> <date> <int> <int>
#> 1 1 1998-11-26 2000-01-29 1 2
#> 2 2 1996-06-22 NA 2 NA
#> 3 3 2002-07-11 2004-04-05 2 2
#> 4 4 2004-10-10 2009-08-27 1 1
#> 5 5 2000-12-05 2005-02-28 2 1Utworzony dataframe w każdej linii zawiera dane o dzieciach jednej rodziny. W rodzinach może być jedno lub dwoje dzieci. Dla każdego dziecka podawane są dane o dacie urodzenia i płci, przy czym dane dla każdego dziecka znajdują się w osobnych kolumnach, a naszym zadaniem jest przekształcenie tych danych do odpowiedniego formatu analitycznego.
Zauważ, że mamy dwie zmienne z informacjami o każdym dziecku: jego płeć i data urodzenia (kolumny z prefiksem dob zawierają datę urodzenia, kolumny z prefiksem gender zawierają płeć dziecka). W oczekiwanym wyniku muszą one znajdować się w osobnych kolumnach. Możemy to osiągnąć, generując specyfikację, w której kolumna .value będzie miała dwie różne wartości.
spec %
pivot_longer_spec(-family) %>%
separate(col = name, into = c(".value", "child")) %>%
mutate(child = parse_number(child))
#> # A tibble: 4 x 3
#> .name .value child
#> <chr> <chr> <dbl>
#> 1 dob_child1 dob 1
#> 2 dob_child2 dob 2
#> 3 gender_child1 gender 1
#> 4 gender_child2 gender 2Zatem przeanalizujmy krok po kroku działania, jakie wykonuje powyższy kod.
pivot_longer_spec(-family)— tworzymy specyfikację, która łączy wszystkie dostępne kolumny, z wyjątkiem kolumny family.separate(col = name, into = c(".value", "child"))— dzielimy kolumnę .name, która zawiera nazwy oryginalnych pól, według podkreślenia i umieszczamy uzyskane wartości w kolumnach .value i child.mutate(child = parse_number(child))— przekształcamy wartości pola child z tekstowego na numeryczny typ danych.
Teraz możemy zastosować otrzymaną specyfikację do oryginalnego dataframe'u i przekształcić tabelę do pożądanego formatu.
family %>%
pivot_longer(spec = spec, na.rm = T)#> # A tibble: 9 x 4
#> family child dob gender
#> <int> <dbl> <date> <int>
#> 1 1 1 1998-11-26 1
#> 2 1 2 2000-01-29 2
#> 3 2 1 1996-06-22 2
#> 4 3 1 2002-07-11 2
#> 5 3 2 2004-04-05 2
#> 6 4 1 2004-10-10 1
#> 7 4 2 2009-08-27 1
#> 8 5 1 2000-12-05 2
#> 9 5 2 2005-02-28 1Używamy argumentu na.rm = TRUE, ponieważ obecny kształt danych zmusza do tworzenia dodatkowych wierszy dla nieistniejących obserwacji. Ponieważ rodzina 2 ma tylko jedno dziecko, na.rm = TRUE zapewnia, że rodzina 2 będzie miała jeden wiersz w danych wyjściowych.
Przekształcanie ramki danych z formatu długiego na szeroki
pivot_wider() — jest odwrotną transformacją, a zatem zwiększa liczbę kolumn ramki danych kosztem zmniejszenia liczby wierszy.

Tego rodzaju transformacja jest rzadko stosowana do porządkowania danych, niemniej jednak ta technika może być przydatna do tworzenia tabel przestawnych wykorzystywanych w prezentacjach lub do integracji z innymi narzędziami.
Funkcje pivot_longer() i pivot_wider() są symetryczne i wykonują odwrotne działania, tzn: df %>% pivot_longer(spec = spec) %>% pivot_wider(spec = spec) i df %>% pivot_wider(spec = spec) %>% pivot_longer(spec = spec) zwróci oryginalny df.
Najprostszy przykład przekształcania tabeli do formatu szerokiego
W celu demonstrowania działania funkcji pivot_wider() będziemy używać zestawu danych fish_encounters, w którym przechowywane są informacje o tym, jak różne stacje rejestrują ruch ryb w rzece.
#> # A tibble: 114 x 3
#> fish station seen
#> <fct> <fct> <int>
#> 1 4842 Release 1
#> 2 4842 I80_1 1
#> 3 4842 Lisbon 1
#> 4 4842 Rstr 1
#> 5 4842 Base_TD 1
#> 6 4842 BCE 1
#> 7 4842 BCW 1
#> 8 4842 BCE2 1
#> 9 4842 BCW2 1
#> 10 4842 MAE 1
#> # … with 104 more rowsW większości przypadków, tabela ta będzie bardziej infromatywna i wygodna w użyciu, gdy informacje o każdej stacji zostaną przedstawione w osobnej kolumnie.
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 NA NA NA NA NA
#> 5 4847 1 1 1 NA NA NA NA NA NA NA
#> 6 4848 1 1 1 1 NA NA NA NA NA NA
#> 7 4849 1 1 NA NA NA NA NA NA NA NA
#> 8 4850 1 1 NA 1 1 1 1 NA NA NA
#> 9 4851 1 1 NA NA NA NA NA NA NA NA
#> 10 4854 1 1 NA NA NA NA NA NA NA NA
#> # … z 9 innymi wierszami i 1 inną zmienną: MAWTen zestaw danych rejestruje informacje tylko w przypadkach, gdy ryba została wykryta przez stację, tzn. jeśli jakaś ryba nie została zarejestrowana przez jakąś stację, tych danych w tabeli nie będzie. Oznacza to, że dane wyjściowe będą wypełnione NA.
Jednak w tym przypadku wiemy, że brak wpisu oznacza, że ryba nie została zauważona, dlatego możemy użyć argumentu values_fill w funkcji pivot_wider() aby uzupełnić te brakujące wartości zerami:
fish_encounters %>% pivot_wider(
names_from = station,
values_from = seen,
values_fill = list(seen = 0)
)#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 0 0 0 0 0
#> 5 4847 1 1 1 0 0 0 0 0 0 0
#> 6 4848 1 1 1 1 0 0 0 0 0 0
#> 7 4849 1 1 0 0 0 0 0 0 0 0
#> 8 4850 1 1 0 1 1 1 1 0 0 0
#> 9 4851 1 1 0 0 0 0 0 0 0 0
#> 10 4854 1 1 0 0 0 0 0 0 0 0
#> # … with 9 more rows, and 1 more variable: MAW <int>Generowanie nazwy kolumny z kilku zmiennych źródłowych
Wyobraź sobie, że mamy tabelę zawierającą kombinację produktu, kraju i roku. Aby wygenerować testowy zbiór danych, można wykonać następujący kod:
df %
filter((product == "A" & country == "AI") | product == "B") %>%
mutate(value = rnorm(nrow(.)))#> # A tibble: 45 x 4
#> product country year value
#> <chr> <chr> <int> <dbl>
#> 1 A AI 2000 -2.05
#> 2 A AI 2001 -0.676
#> 3 A AI 2002 1.60
#> 4 A AI 2003 -0.353
#> 5 A AI 2004 -0.00530
#> 6 A AI 2005 0.442
#> 7 A AI 2006 -0.610
#> 8 A AI 2007 -2.77
#> 9 A AI 2008 0.899
#> 10 A AI 2009 -0.106
#> # … with 35 more rowsNaszym zadaniem jest rozszerzenie zbioru danych, aby jedna kolumna zawierała dane dla każdej kombinacji produktu i kraju. W tym celu wystarczy przekazać do argumentu names_from wektor, zawierający nazwy łączonych pól.
df %>% pivot_wider(names_from = c(product, country),
values_from = "value")#> # A tibble: 15 x 4
#> year A_AI B_AI B_EI
#> <int> <dbl> <dbl> <dbl>
#> 1 2000 -2.05 0.607 1.20
#> 2 2001 -0.676 1.65 -0.114
#> 3 2002 1.60 -0.0245 0.501
#> 4 2003 -0.353 1.30 -0.459
#> 5 2004 -0.00530 0.921 -0.0589
#> 6 2005 0.442 -1.55 0.594
#> 7 2006 -0.610 0.380 -1.28
#> 8 2007 -2.77 0.830 0.637
#> 9 2008 0.899 0.0175 -1.30
#> 10 2009 -0.106 -0.195 1.03
#> # … with 5 more rowsMożesz również stosować specyfikacje do funkcji pivot_wider(). Ale przy podawaniu w pivot_wider() specyfikacja wykonuje przekształcenie, przeciwne do pivot_longer(): tworzone są kolumny, wskazane w .name, wykorzystując wartości z .value i innych kolumn.
Dla tego zbioru danych możesz wygenerować niestandardową specyfikację, jeśli chcesz, aby każda możliwa kombinacja kraju i produktu miała swoją własną kolumnę, a nie tylko te, które są obecne w danych:
spec %
expand(product, country, .value = "value") %>%
unite(".name", product, country, remove = FALSE)#> # A tibble: 4 x 4
#> .name product country .value
#> <chr> <chr> <chr> <chr>
#> 1 A_AI A AI value
#> 2 A_EI A EI value
#> 3 B_AI B AI value
#> 4 B_EI B EI valuedf %>% pivot_wider(spec = spec) %>% head()#> # A tibble: 6 x 5
#> year A_AI A_EI B_AI B_EI
#> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 2000 -2.05 NA 0.607 1.20
#> 2 2001 -0.676 NA 1.65 -0.114
#> 3 2002 1.60 NA -0.0245 0.501
#> 4 2003 -0.353 NA 1.30 -0.459
#> 5 2004 -0.00530 NA 0.921 -0.0589
#> 6 2005 0.442 NA -1.55 0.594Kilka zaawansowanych przykładów pracy z nową koncepcją tidyr
Uporządkowanie danych na przykładzie zestawu danych dotyczącego dochodów i czynszów w USA
Zbiór danych us_rent_income zawiera informacje o średnich dochodach i czynszach dla każdego stanu w USA za 2017 rok (zbiór danych jest dostępny w pakiecie tidycensus).
us_rent_income
#> # A tibble: 104 x 5
#> GEOID NAME variable estimate moe
#>
#> 1 01 Alabama income 24476 136
#> 2 01 Alabama rent 747 3
#> 3 02 Alaska income 32940 508
#> 4 02 Alaska rent 1200 13
#> 5 04 Arizona income 27517 148
#> 6 04 Arizona rent 972 4
#> 7 05 Arkansas income 23789 165
#> 8 05 Arkansas rent 709 5
#> 9 06 California income 29454 109
#> 10 06 California rent 1358 3
#> # … z 94 dodatkowymi wierszamiW takiej formie, w jakiej są przechowywane dane w zbiorze danych us_rent_income praca z nimi jest niezwykle niewygodna, dlatego chcielibyśmy stworzyć zbiór danych z kolumnami: rent, rent_moe, come, income_moe. Istnieje wiele sposobów na stworzenie tej specyfikacji, ale najważniejsze jest to, że musimy wygenerować każdą kombinację wartości zmiennej i estimate/moe, a następnie wygenerować nazwę kolumny.
spec %
expand(variable, .value = c("estimate", "moe")) %>%
mutate(
.name = paste0(variable, ifelse(.value == "moe", "_moe", ""))
)#> # A tibble: 4 x 3
#> variable .value .name
#> <chr> <chr> <chr>
#> 1 income estimate income
#> 2 income moe income_moe
#> 3 rent estimate rent
#> 4 rent moe rent_moeDostarczenie tej specyfikacji pivot_wider() daje nam wynik, którego szukamy:
us_rent_income %>% pivot_wider(spec = spec)
#> # A tibble: 52 x 6
#> GEOID NAME income income_moe rent rent_moe
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 01 Alabama 24476 136 747 3
#> 2 02 Alaska 32940 508 1200 13
#> 3 04 Arizona 27517 148 972 4
#> 4 05 Arkansas 23789 165 709 5
#> 5 06 California 29454 109 1358 3
#> 6 08 Colorado 32401 109 1125 5
#> 7 09 Connecticut 35326 195 1123 5
#> 8 10 Delaware 31560 247 1076 10
#> 9 11 District of Columbia 43198 681 1424 17
#> 10 12 Florida 25952 70 1077 3
#> # … with 42 more rowsBank Światowy
Czasami przekształcenie zestawu danych do odpowiedniego formatu wymaga kilku kroków.
Zestaw danych world_bank_pop zawiera dane Banku Światowego dotyczące populacji każdego kraju w latach 2000-2018.
#> # A tibble: 1,056 x 20
#> country indicator `2000` `2001` `2002` `2003` `2004` `2005` `2006`
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ABW SP.URB.T… 4.24e4 4.30e4 4.37e4 4.42e4 4.47e+4 4.49e+4 4.49e+4
#> 2 ABW SP.URB.G… 1.18e0 1.41e0 1.43e0 1.31e0 9.51e-1 4.91e-1 -1.78e-2
#> 3 ABW SP.POP.T… 9.09e4 9.29e4 9.50e4 9.70e4 9.87e+4 1.00e+5 1.01e+5
#> 4 ABW SP.POP.G… 2.06e0 2.23e0 2.23e0 2.11e0 1.76e+0 1.30e+0 7.98e-1
#> 5 AFG SP.URB.T… 4.44e6 4.65e6 4.89e6 5.16e6 5.43e+6 5.69e+6 5.93e+6
#> 6 AFG SP.URB.G… 3.91e0 4.66e0 5.13e0 5.23e0 5.12e+0 4.77e+0 4.12e+0
#> 7 AFG SP.POP.T… 2.01e7 2.10e7 2.20e7 2.31e7 2.41e+7 2.51e+7 2.59e+7
#> 8 AFG SP.POP.G… 3.49e0 4.25e0 4.72e0 4.82e0 4.47e+0 3.87e+0 3.23e+0
#> 9 AGO SP.URB.T… 8.23e6 8.71e6 9.22e6 9.77e6 1.03e+7 1.09e+7 1.15e+7
#> 10 AGO SP.URB.G… 5.44e0 5.59e0 5.70e0 5.76e0 5.75e+0 5.69e+0 4.92e+0
#> # … with 1,046 more rows, and 11 more variables: `2007` <dbl>,
#> # `2008` <dbl>, `2009` <dbl>, `2010` <dbl>, `2011` <dbl>, `2012` <dbl>,
#> # `2013` <dbl>, `2014` <dbl>, `2015` <dbl>, `2016` <dbl>, `2017` <dbl>Naszym celem jest stworzenie uporządkowanego zestawu danych, w którym każda zmienna znajduje się w osobnej kolumnie. Na razie nie jest jasne, jakie dokładnie kroki są potrzebne, ale zaczniemy od najbardziej oczywistego problemu: rok jest rozłożony na kilka kolumn.
Aby to naprawić, należy użyć funkcji pivot_longer().
pop2 %
pivot_longer(`2000`:`2017`, names_to = "year")#> # A tibble: 19,008 x 4
#> country indicator year value
#> <chr> <chr> <chr> <dbl>
#> 1 ABW SP.URB.TOTL 2000 42444
#> 2 ABW SP.URB.TOTL 2001 43048
#> 3 ABW SP.URB.TOTL 2002 43670
#> 4 ABW SP.URB.TOTL 2003 44246
#> 5 ABW SP.URB.TOTL 2004 44669
#> 6 ABW SP.URB.TOTL 2005 44889
#> 7 ABW SP.URB.TOTL 2006 44881
#> 8 ABW SP.URB.TOTL 2007 44686
#> 9 ABW SP.URB.TOTL 2008 44375
#> 10 ABW SP.URB.TOTL 2009 44052
#> # … with 18,998 more rowsKolejnym krokiem jest rozważenie zmiennej indicator.
pop2 %>% count(indicator)
#> # A tibble: 4 x 2
#> indicator n
#> <chr> <int>
#> 1 SP.POP.GROW 4752
#> 2 SP.POP.TOTL 4752
#> 3 SP.URB.GROW 4752
#> 4 SP.URB.TOTL 4752Gdzie SP.POP.GROW to wzrost populacji, SP.POP.TOTL to całkowita liczba ludności, a SP.URB. * to samo, ale tylko dla obszarów miejskich. Podzielmy te wartości na dwie zmienne: area — obszar (total lub urban) oraz zmienną zawierającą faktyczne dane (population lub growth):
pop3 %
separate(indicator, c(NA, "area", "variable"))#> # A tibble: 19,008 x 5
#> country area variable year value
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 ABW URB TOTL 2000 42444
#> 2 ABW URB TOTL 2001 43048
#> 3 ABW URB TOTL 2002 43670
#> 4 ABW URB TOTL 2003 44246
#> 5 ABW URB TOTL 2004 44669
#> 6 ABW URB TOTL 2005 44889
#> 7 ABW URB TOTL 2006 44881
#> 8 ABW URB TOTL 2007 44686
#> 9 ABW URB TOTL 2008 44375
#> 10 ABW URB TOTL 2009 44052
#> # … with 18,998 more rowsTeraz musimy tylko podzielić zmienną variable na dwie kolumny:
pop3 %>%
pivot_wider(names_from = variable, values_from = value)#> # A tibble: 9,504 x 5
#> country area year TOTL GROW
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 ABW URB 2000 42444 1.18
#> 2 ABW URB 2001 43048 1.41
#> 3 ABW URB 2002 43670 1.43
#> 4 ABW URB 2003 44246 1.31
#> 5 ABW URB 2004 44669 0.951
#> 6 ABW URB 2005 44889 0.491
#> 7 ABW URB 2006 44881 -0.0178
#> 8 ABW URB 2007 44686 -0.435
#> 9 ABW URB 2008 44375 -0.698
#> 10 ABW URB 2009 44052 -0.731
#> # … with 9,494 more rowsLista kontaktów
Ostatni przykład, wyobraź sobie, że masz listę kontaktów, którą skopiowałeś i wkleiłeś ze strony internetowej:
contacts <- tribble(
~field, ~value,
"name", "Jiena McLellan",
"company", "Toyota",
"name", "John Smith",
"company", "google",
"email", "john@google.com",
"name", "Huxley Ratcliffe"
)Przekształcenie tej listy do formatu tabelarycznego jest dość trudne, ponieważ nie ma zmiennej, która identyfikuje, które dane należą do którego kontaktu. Możemy to naprawić, zauważając, że dane dla każdego nowego kontaktu zaczynają się od nazwy ("name"), więc możemy stworzyć unikalny identyfikator i zwiększać go o jeden za każdym razem, gdy w kolumnie field pojawi się wartość “name”:
contacts %
mutate(
person_id = cumsum(field == "name")
)
contacts#> # A tibble: 6 x 3
#> field value person_id
#> <chr> <chr> <int>
#> 1 name Jiena McLellan 1
#> 2 company Toyota 1
#> 3 name John Smith 2
#> 4 company google 2
#> 5 email john@google.com 2
#> 6 name Huxley Ratcliffe 3Teraz, gdy mamy unikalny identyfikator dla każdego kontaktu, możemy obrócić pole i wartość w kolumny:
contacts %>%
pivot_wider(names_from = field, values_from = value)#> # A tibble: 3 x 4
#> person_id name company email
#> <int> <chr> <chr> <chr>
#> 1 1 Jiena McLellan Toyota <NA>
#> 2 2 John Smith google john@google.com
#> 3 3 Huxley Ratcliffe <NA> <NA>Podsumowanie
Osobiście sądzę, że nowa koncepcja tidyr jest naprawdę bardziej intuicyjna i znacznie przewyższa funkcje przestarzałe spread() i gather(). Mam nadzieję, że ten artykuł pomógł ci zrozumieć pivot_longer() i pivot_wider().
Źródło: habr.com
