La ricchezza di R

Keywords: #R #programmazione #statistica

Qualche curiosità

R è

a free software environment for statistical computing and graphics

ben conosciuto nel mondo accademico e scientifico. Fa parte di quella costellazione di strumenti che chi si occupa di data science, in senso lato intesa, maneggia quotidianamente. Non è il mio caso, però sporadicamente mi è stata utile. Come è brutto vizio di molti, non ho letto preliminarmente tutta la documentazione introduttiva come andrebbe fatto per disciplina e risparmio di tempo. Ma prima o poi l’ardua salita si incontra sulla strada e va affrontata. Ho preso un tono troppo epico, diciamo che mi sono sorte alcune curiosità e ho cercato il modo di soddisfarle. Per esempio, quanti pacchetti attualmente ci sono nella repo per macos di R?

avpack <- available.packages(repos="https://mac.r-project.org")
dim(avpack)
[1] 20294    17

Adesso so che sono disponibili circa 20000 pacchetti su CRAN, ma esistono anche repository specialistiche con pacchetti usati, per esempio, solo all’interno di una istituzione.

Un tema dibattuto quasi in un clima da tifo calcistico è quali pacchetti sono migliori, quanto sono veloci, quante dipendenze (altri pacchetti) si portano dietro. Il derby del momento è tra base-R e tidyverse, ma un nuovo contendente è pronto a fare da terzo incomodo e si chiama fastverse, in realtà dovrebbero essere conosciuti tutti, ma vediamo qualche dato.

Quante dipendenze si caricano scegliendo tidyverse.

tools::package_dependencies(c("tidyverse"), db = avpack, recursive = TRUE)
$tidyverse
  [1] "broom"         "conflicted"    "cli"           "dbplyr"       
  [5] "dplyr"         "dtplyr"        "forcats"       "ggplot2"      
  [9] "googledrive"   "googlesheets4" "haven"         "hms"          
 [13] "httr"          "jsonlite"      "lubridate"     "magrittr"     
 [17] "modelr"        "pillar"        "purrr"         "ragg"         
 [21] "readr"         "readxl"        "reprex"        "rlang"        
 [25] "rstudioapi"    "rvest"         "stringr"       "tibble"       
 [29] "tidyr"         "xml2"          "backports"     "ellipsis"     
 [33] "generics"      "glue"          "lifecycle"     "utils"        
 [37] "memoise"       "blob"          "DBI"           "methods"      
 [41] "R6"            "tidyselect"    "vctrs"         "withr"        
 [45] "data.table"    "grDevices"     "grid"          "gtable"       
 [49] "isoband"       "MASS"          "mgcv"          "scales"       
 [53] "stats"         "gargle"        "uuid"          "cellranger"   
 [57] "curl"          "ids"           "rematch2"      "cpp11"        
 [61] "pkgconfig"     "mime"          "openssl"       "timechange"   
 [65] "fansi"         "utf8"          "systemfonts"   "textshaping"  
 [69] "clipr"         "crayon"        "vroom"         "tzdb"         
 [73] "progress"      "callr"         "fs"            "knitr"        
 [77] "rmarkdown"     "selectr"       "stringi"       "processx"     
 [81] "rematch"       "rappdirs"      "evaluate"      "highr"        
 [85] "tools"         "xfun"          "yaml"          "graphics"     
 [89] "cachem"        "nlme"          "Matrix"        "splines"      
 [93] "askpass"       "prettyunits"   "bslib"         "fontawesome"  
 [97] "htmltools"     "jquerylib"     "tinytex"       "farver"       
[101] "labeling"      "munsell"       "RColorBrewer"  "viridisLite"  
[105] "bit64"         "sys"           "bit"           "base64enc"    
[109] "sass"          "fastmap"       "digest"        "lattice"      
[113] "colorspace"    "ps"           

Scorrendo la lista prodotta troviamo data.table1 e dplyr. Indaghiamo ancora.

tools::package_dependencies(c("data.table"), db = avpack,  recursive = TRUE)
$data.table
[1] "methods"
tools::package_dependencies(c("dplyr"), db = avpack,  recursive = TRUE)
$dplyr
 [1] "cli"        "generics"   "glue"       "lifecycle"  "magrittr"  
 [6] "methods"    "pillar"     "R6"         "rlang"      "tibble"    
[11] "tidyselect" "utils"      "vctrs"      "fansi"      "utf8"      
[16] "pkgconfig"  "withr"      "grDevices"  "graphics"  

Interessante risultato: 20 a 1. Ma voglio saperne di più sul quasi nuovo arrivato collapse.

tools::package_dependencies(c("fastverse"), db = avpack,  recursive = TRUE)
$fastverse
[1] "data.table" "collapse"   "kit"        "magrittr"   "Rcpp"      
[6] "methods"    "utils"     
tools::package_dependencies(c("collapse"), db = avpack,  recursive = TRUE)
$collapse
[1] "Rcpp"    "methods" "utils"  

Anche questo pacchetto è parco con le dipendenze e sfrutta codice c++ come si evince dalla presenza di Rcpp, e questo dovrebbe garantire buone performance in termini di velocità di calcolo.

Quanti modi per dire…

Il seguente codice applica una funzione a una colonna filtrata per gruppi.

set.seed(9876)
df <- data.frame(carrier = factor(sample(seq(20), 10^3, replace = TRUE)), air_time = runif(10^3))
dt <- data.table(df)
setkey(dt, carrier)
t1 <- tapply(df$air_time, df$carrier, mean)
t2 <- aggregate(air_time ~ carrier, df, mean, simplify = TRUE)
t3 <- dt[, lapply(.SD, mean), .SDcols = c("air_time"), by = carrier]
t4 <- fmean(dt[, 2], g = dt[, 1])
t5 <- df %>% group_by(carrier) %>% summarise(mean = mean(air_time))
t6 <- dt %>% group_by(carrier) %>% summarise(mean = mean(air_time))

Per t1 e t2 ho usato base-R, per t3 data.table, per t4 collapse, per t5 e t6 dplyr.

Gli esempi che seguono sono un po’ più complessi e uniscono due dataset con inner join. Il primo è in base-R, il secondo usa dplyr e il terzo data.table.

weather_over_90 <- weather[weather$temp > 90, c("origin", "year", "month", "day", "hour")]
weather_over_90 <- weather_over_90[complete.cases(weather_over_90), ]
flights_minimum <- flights[
  flights$origin %in% weather_over_90$origin &
    flights$year %in% weather_over_90$year &
    flights$month %in% weather_over_90$month &
    flights$day %in% weather_over_90$day &
    flights$hour %in% weather_over_90$hour,
  c("origin", "year", "month", "day", "hour", "tailnum")
  ]
flights_minimum <- flights_minimum[complete.cases(flights_minimum), ]

with_merge <- function() {
  unique(merge(weather_over_90, flights_minimum, by = c("origin", "year", "month", "day", "hour"))$tailnum)
}
with_merge()
join_smarter <- function() {
  flights %>%
    select(origin, year, month, day, hour, tailnum) %>%
    inner_join(weather %>%
                 select(origin, year, month, day, hour, temp) %>%
                 filter(temp > 90),
               by = c("origin", "year", "month", "day", "hour")) %>%
    select(tailnum) %>%
    drop_na() %>%
    pull(tailnum) %>%
    unique()
}
join_smarter()
con_DT  <- function() {
voli <- data.table(flights)
meteo <- data.table(weather)
voli[meteo[temp > 90,], on = c("origin", "year", "month", "day", "hour"), allow.cartesian = TRUE][order(tailnum)][, unique(tailnum)]
}
con_DT()

Volendo si possono eseguire test di velocità, ma il tutto si svolge sempre in tempi ridottissimi.

Come si vede il menu può soddisfare una vasta platea.

Per approfondire consiglio questi link:


  1. data.table è un’estensione di base-R e viene utilizzato ampiamente anche al di fuori di tidyverse. ↩︎