Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

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ż publikacja 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.

Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

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 telegram i youtube 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 funkcji separate();
  • 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 funkcja pivot_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 funkcja gather().

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.

Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

Notatka od autora

Cały poniższy tekst jest adaptacyjny, a wręcz powiedziałbym, że swobodnym tłumaczeniem winylet 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.

Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

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 rows

Argumenty 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 zmiennych

Zbudujemy 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 rows

Pola 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/ep opisuje sposób diagnozowania choroby.
  • m/f płci pacjenta.
  • 014/1524/2535/3544/4554/65 zakres 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 rows

Zauważ, ż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 rows

Wszystko, co właśnie zrobiliśmy, może być schematycznie przedstawione w następujący sposób:

Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

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 winylet 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             1

Utworzony 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     2

Zatem 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      1

Uż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.

Pakiet R tidyr i jego nowe funkcje pivot_longer oraz pivot_wider

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 rows

W 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ą: MAW

Ten 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 rows

Naszym 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 rows

Moż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      value

df %>% 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.594

Kilka 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 wierszami

W 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_moe

Dostarczenie 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 rows

Bank Ś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 rows

Kolejnym 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  4752

Gdzie 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 rows

Teraz 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 rows

Lista 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         3

Teraz, 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

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster