Pivoting {tidyr}

Author

Domenico Vistocco

Prepariamo l’ambiente di lavoro

Le funzioni per il pivoting sono contenuti nella libreria tidyr, automaticamente caricata quando si carica tidyverse. Per mostrare il funzionamento delle due funzioni principali per il pivoting carico quest’ultimo package unitamente al package janitor che sfrutto per uniformare i nomi di una delle due tabelle, ed al package `gapminder` che contiene una delle due tabelle dati.

# package utilizzati ------------------------------------------------------
library(tidyverse)
library(janitor)
library(gapminder)

Le due tabelle di esempio

I dati utilizzati per questo mostrare le operazioni di pivoting sono due tabelle disponibili in R ({datasets} e gapminder). Il primo dataset è però caricato da un file esterno di tipo testo air-passengers.txt perché il formato disponibile in R chiede alcune trasformazioni che non sono interessanti ai fini di questa presentazione:

# preparo le tabelle usate per gli esempi ---------------------------------

# un dataset sui numeri mensili di passeggeri di una compagnia aerea
# (il classico Box & Jenkins airline dataset)
air_passengers <- read_delim("../data/air-passengers.txt", delim = " ")
Rows: 12 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: " "
dbl (13): year, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
air_passengers
# A tibble: 12 × 13
    year   Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  1949   112   118   132   129   121   135   148   148   136   119   104   118
 2  1950   115   126   141   135   125   149   170   170   158   133   114   140
 3  1951   145   150   178   163   172   178   199   199   184   162   146   166
 4  1952   171   180   193   181   183   218   230   242   209   191   172   194
 5  1953   196   196   236   235   229   243   264   272   237   211   180   201
 6  1954   204   188   235   227   234   264   302   293   259   229   203   229
 7  1955   242   233   267   269   270   315   364   347   312   274   237   278
 8  1956   284   277   317   313   318   374   413   405   355   306   271   306
 9  1957   315   301   356   348   355   422   465   467   404   347   305   336
10  1958   340   318   362   348   363   435   491   505   404   359   310   337
11  1959   360   342   406   396   420   472   548   559   463   407   362   405
12  1960   417   391   419   461   472   535   622   606   508   461   390   432
gap_data <- clean_names(gapminder)
gap_data
# A tibble: 1,704 × 6
   country     continent  year life_exp      pop gdp_percap
   <fct>       <fct>     <int>    <dbl>    <int>      <dbl>
 1 Afghanistan Asia       1952     28.8  8425333       779.
 2 Afghanistan Asia       1957     30.3  9240934       821.
 3 Afghanistan Asia       1962     32.0 10267083       853.
 4 Afghanistan Asia       1967     34.0 11537966       836.
 5 Afghanistan Asia       1972     36.1 13079460       740.
 6 Afghanistan Asia       1977     38.4 14880372       786.
 7 Afghanistan Asia       1982     39.9 12881816       978.
 8 Afghanistan Asia       1987     40.8 13867957       852.
 9 Afghanistan Asia       1992     41.7 16317921       649.
10 Afghanistan Asia       1997     41.8 22227415       635.
# ℹ 1,694 more rows

Per avere informazioni sul contenuto delle due tabelle è possibile consultare l’help:

help("AirPassengers")
help("gapminder")

Tabella air_passenger: pivot_longer

La tabella air_passengers contiene i numeri di passeggeri classificati per anni (righe) e mesi (colonne).

E’ possibile sfruttare i verbi base di dplyr per calcolare le sintesi sulle colonne. Ecco un esempio con la funzione sum:

air_passengers |> summarise(across(-year, sum))
# A tibble: 1 × 12
    Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1  2901  2820  3242  3205  3262  3740  4216  4213  3629  3199  2794  3142

Tramite la funzione rowwise() è possibile usare la grammatica per la manipolazione dati lavorando sulle righe, ma è sicuramente più macchinoso:

air_passengers |>
  rowwise() |>
  mutate(somma = sum(across(everything()))) |>
  select(year, somma)
# A tibble: 12 × 2
# Rowwise: 
    year somma
   <dbl> <dbl>
 1  1949  3469
 2  1950  3626
 3  1951  3993
 4  1952  4316
 5  1953  4653
 6  1954  4821
 7  1955  5363
 8  1956  5895
 9  1957  6378
10  1958  6530
11  1959  7099
12  1960  7674

Si può naturalmente usare la sintassi base di R per lavorare sulle righe. Ad esempio posso accedere ad una riga, qui la prima:

air_passengers[1, ]
# A tibble: 1 × 13
   year   Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1  1949   112   118   132   129   121   135   148   148   136   119   104   118

da cui escludo la prima colonna contenente l’anno ed effettuo la somma dei passeggeri:

sum(air_passengers[1, -1])
[1] 1520

Altre due varianti possibili per sommare gli elementi su una riga (la funzione rowSums e la funzione più generale apply che può essere utilizzata per “applicare” qualunque funzione):

rowSums(air_passengers[, -1])
 [1] 1520 1676 2042 2364 2700 2867 3408 3939 4421 4572 5140 5714
apply(air_passengers[,-1], 1, sum)
 [1] 1520 1676 2042 2364 2700 2867 3408 3939 4421 4572 5140 5714

Queste due ultime funzioni possono essere utilizzate anche per lavorare sulle colonne:

colSums(air_passengers[, -1])
 Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec 
2901 2820 3242 3205 3262 3740 4216 4213 3629 3199 2794 3142 
apply(air_passengers[,-1], 2, sum)
 Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec 
2901 2820 3242 3205 3262 3740 4216 4213 3629 3199 2794 3142 

Oltre alla somma, esiste una funzione per il calcolo veloce della media (rowMeans) su un oggetto tabellare. Eccola in azione di seguito, con lo stesso risultato ottenuto poi con la funzione apply:

rowMeans(air_passengers[, -1])
 [1] 126.6667 139.6667 170.1667 197.0000 225.0000 238.9167 284.0000 328.2500
 [9] 368.4167 381.0000 428.3333 476.1667
apply(air_passengers[,-1], 1, mean)
 [1] 126.6667 139.6667 170.1667 197.0000 225.0000 238.9167 284.0000 328.2500
 [9] 368.4167 381.0000 428.3333 476.1667

Ed ecco anche l’utilizzo delle analoghe funzioni sulle colonne:

colMeans(air_passengers[, -1])
     Jan      Feb      Mar      Apr      May      Jun      Jul      Aug 
241.7500 235.0000 270.1667 267.0833 271.8333 311.6667 351.3333 351.0833 
     Sep      Oct      Nov      Dec 
302.4167 266.5833 232.8333 261.8333 
apply(air_passengers[,-1], 2, mean)
     Jan      Feb      Mar      Apr      May      Jun      Jul      Aug 
241.7500 235.0000 270.1667 267.0833 271.8333 311.6667 351.3333 351.0833 
     Sep      Oct      Nov      Dec 
302.4167 266.5833 232.8333 261.8333 

La funzione pivot_longer può essere utilizzata per trasformare la tabella in formato longer:

air_passengers_long <- pivot_longer(data = air_passengers,
                                    cols = Jan:Dec,
                                    names_to = "month",
                                    values_to = "nr_passengers")
air_passengers_long
# A tibble: 144 × 3
    year month nr_passengers
   <dbl> <chr>         <dbl>
 1  1949 Jan             112
 2  1949 Feb             118
 3  1949 Mar             132
 4  1949 Apr             129
 5  1949 May             121
 6  1949 Jun             135
 7  1949 Jul             148
 8  1949 Aug             148
 9  1949 Sep             136
10  1949 Oct             119
# ℹ 134 more rows

L’argomento cols richiede le colonne di cui si vuole effettuare il pivoting, o in alternativa le colonne da escludere utilizzando il segno -:

air_passengers_long <- pivot_longer(data = air_passengers,
                                    cols = -year,
                                    names_to = "month",
                                    values_to = "nr_passengers")

Una volta trasformata in formato longer, la tabella è più semplice da manipolare per calcolare sintesi. Ecco ad esempio il calcolo del totale dei passeggeri per anno:

air_passengers_long |>
  group_by(year) |>
  summarise(sum(nr_passengers))
# A tibble: 12 × 2
    year `sum(nr_passengers)`
   <dbl>                <dbl>
 1  1949                 1520
 2  1950                 1676
 3  1951                 2042
 4  1952                 2364
 5  1953                 2700
 6  1954                 2867
 7  1955                 3408
 8  1956                 3939
 9  1957                 4421
10  1958                 4572
11  1959                 5140
12  1960                 5714

oppure dei passeggeri medi per ciascun mese:

air_passengers_long |>
  group_by(month) |>
  summarise(mean(nr_passengers))
# A tibble: 12 × 2
   month `mean(nr_passengers)`
   <chr>                 <dbl>
 1 Apr                    267.
 2 Aug                    351.
 3 Dec                    262.
 4 Feb                    235 
 5 Jan                    242.
 6 Jul                    351.
 7 Jun                    312.
 8 Mar                    270.
 9 May                    272.
10 Nov                    233.
11 Oct                    267.
12 Sep                    302.

La trasformazione da formato wide a formato long è spesso necessario per sfruttare la logica di ggplot2 per le rappresentazioni grafiche:

air_passengers_long |>
  ggplot() +
  aes(x = year, y = nr_passengers, col = month) +
  geom_line()

L’operazione inversa, pivot_wider, riporta la tabella da formato long a formato wide. Ecco l’effetto sulla tabella (per un altro esempio si rimanda al successivo paragrafo):

air_passengers_long |>
  pivot_wider(id_cols = year,
              names_from = month,
              values_from = nr_passengers)
# A tibble: 12 × 13
    year   Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  1949   112   118   132   129   121   135   148   148   136   119   104   118
 2  1950   115   126   141   135   125   149   170   170   158   133   114   140
 3  1951   145   150   178   163   172   178   199   199   184   162   146   166
 4  1952   171   180   193   181   183   218   230   242   209   191   172   194
 5  1953   196   196   236   235   229   243   264   272   237   211   180   201
 6  1954   204   188   235   227   234   264   302   293   259   229   203   229
 7  1955   242   233   267   269   270   315   364   347   312   274   237   278
 8  1956   284   277   317   313   318   374   413   405   355   306   271   306
 9  1957   315   301   356   348   355   422   465   467   404   347   305   336
10  1958   340   318   362   348   363   435   491   505   404   359   310   337
11  1959   360   342   406   396   420   472   548   559   463   407   362   405
12  1960   417   391   419   461   472   535   622   606   508   461   390   432

Tabella gapminder: pivot_longer

La tabella gapminder è invece in classico formato long, per cui è agevole sfruttare i verbi del package dplyr:

gap_data |>
  group_by(continent, year) |>
  summarise(life_exp_avg = mean(life_exp))
`summarise()` has grouped output by 'continent'. You can override using the
`.groups` argument.
# A tibble: 60 × 3
# Groups:   continent [5]
   continent  year life_exp_avg
   <fct>     <int>        <dbl>
 1 Africa     1952         39.1
 2 Africa     1957         41.3
 3 Africa     1962         43.3
 4 Africa     1967         45.3
 5 Africa     1972         47.5
 6 Africa     1977         49.6
 7 Africa     1982         51.6
 8 Africa     1987         53.3
 9 Africa     1992         53.6
10 Africa     1997         53.6
# ℹ 50 more rows

Utilizziamo la tabella gapminder anche per illustrare la logica del package ggplot2. Può essere a volta utile trasformare la tabella di sintesi appena calcolato in formato wide per inserirla ad esempio in un report. Possiamo ottenerla velocemente usando la funzione pivot_wider:

gap_data |>
  group_by(continent, year) |>
  summarise(life_exp_avg = mean(life_exp)) |>
  pivot_wider(id_cols = year,
              names_from = continent,
              values_from = life_exp_avg)
`summarise()` has grouped output by 'continent'. You can override using the
`.groups` argument.
# A tibble: 12 × 6
    year Africa Americas  Asia Europe Oceania
   <int>  <dbl>    <dbl> <dbl>  <dbl>   <dbl>
 1  1952   39.1     53.3  46.3   64.4    69.3
 2  1957   41.3     56.0  49.3   66.7    70.3
 3  1962   43.3     58.4  51.6   68.5    71.1
 4  1967   45.3     60.4  54.7   69.7    71.3
 5  1972   47.5     62.4  57.3   70.8    71.9
 6  1977   49.6     64.4  59.6   71.9    72.9
 7  1982   51.6     66.2  62.6   72.8    74.3
 8  1987   53.3     68.1  64.9   73.6    75.3
 9  1992   53.6     69.6  66.5   74.4    76.9
10  1997   53.6     71.2  68.0   75.5    78.2
11  2002   53.3     72.4  69.2   76.7    79.7
12  2007   54.8     73.6  70.7   77.6    80.7