Vai al contenuto principale

Cinque consigli per migliorare il tuo codice R

Cinque suggerimenti utili per migliorare efficacemente il tuo codice R, dall'uso di seq() per creare sequenze al dimenticare which() e molto altro!
· 8 min leggi

Scopri con l'IA

@drsimonj qui con cinque semplici trucchi che mi ritrovo a condividere spesso con altri utenti R per migliorare il loro codice!

1. Più divertente sequenziare da 1

La prossima volta che usi l'operatore due punti per creare una sequenza da 1 come 1:n, prova seq().

# Sequence a vector
x <- runif(10)
seq(x)
#>  [1]  1  2  3  4  5  6  7  8  9 10

# Sequence an integer
seq(nrow(mtcars))
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24 25 26 27 28 29 30 31 32

L'operatore due punti può produrre risultati imprevisti che possono creare ogni sorta di problemi senza che tu te ne accorga! Guarda cosa succede quando vuoi creare la sequenza della lunghezza di un vettore vuoto:

# Empty vector
x <- c()

1:length(x)
#> [1] 1 0

seq(x)
#> integer(0)

Noterai anche che questo ti evita di usare funzioni come length(). Applicato a un oggetto di una certa lunghezza, seq() crea automaticamente una sequenza da 1 alla lunghezza dell'oggetto.

2. vector() quello che c()

La prossima volta che crei un vettore vuoto con c(), prova a sostituirlo con vector("type", length).

# A numeric vector with 5 elements
vector("numeric", 5)
#> [1] 0 0 0 0 0

# A character vector with 3 elements
vector("character", 3)
#> [1] "" "" ""

Così facendo migliori l'uso della memoria e aumenti la velocità! Spesso sai in anticipo che tipo di valori andranno in un vettore e quanto sarà lungo. Usare c() significa che R deve determinare entrambi questi aspetti in modo lento. Dagli una spinta con vector()!

Un buon esempio di questo vantaggio è in un ciclo for. Spesso si scrivono cicli dichiarando un vettore vuoto e facendolo crescere con c() così:

x <- c()
for (i in seq(5)) {
  x <- c(x, i)
}
#> x at step 1 : 1
#> x at step 2 : 1, 2
#> x at step 3 : 1, 2, 3
#> x at step 4 : 1, 2, 3, 4
#> x at step 5 : 1, 2, 3, 4, 5

Invece, predefinisci tipo e lunghezza con vector() e riferisciti alle posizioni per indice, così:

n <- 5
x <- vector("integer", n)
for (i in seq(n)) {
  x[i] <- i
}
#> x at step 1 : 1, 0, 0, 0, 0
#> x at step 2 : 1, 2, 0, 0, 0
#> x at step 3 : 1, 2, 3, 0, 0
#> x at step 4 : 1, 2, 3, 4, 0
#> x at step 5 : 1, 2, 3, 4, 5

Ecco un rapido confronto di velocità:

n <- 1e5

x_empty <- c()
system.time(for(i in seq(n)) x_empty <- c(x_empty, i))
#>    user  system elapsed 
#>  15.238   2.327  17.650

x_zeros <- vector("integer", n)
system.time(for(i in seq(n)) x_zeros[i] <- i)
#>    user  system elapsed 
#>   0.007   0.000   0.007

Direi che è abbastanza convincente!

3. Dimentica which()

La prossima volta che usi which(), prova a farne a meno! Spesso si usa which() per ottenere indici da una condizione booleana e poi selezionare i valori a quegli indici. Non è necessario.

Ottenere gli elementi del vettore maggiori di 5:

x <- 3:7

# Using which (not necessary)
x[which(x > 5)]
#> [1] 6 7

# No which
x[x > 5]
#> [1] 6 7

Oppure contare il numero di valori maggiori di 5:

# Using which
length(which(x > 5))
#> [1] 2

# Without which
sum(x > 5)
#> [1] 2

Perché dovresti dimenticare which()? Spesso è superfluo e i vettori booleani sono tutto ciò che ti serve.

Per esempio, R ti permette di selezionare gli elementi contrassegnati come TRUE in un vettore booleano:

condition <- x > 5
condition
#> [1] FALSE FALSE FALSE  TRUE  TRUE
x[condition]
#> [1] 6 7

Inoltre, combinati con sum() o mean(), i vettori booleani possono essere usati per ottenere il conteggio o la proporzione dei valori che soddisfano una condizione:

sum(condition)
#> [1] 2
mean(condition)
#> [1] 0.4

which() ti dice gli indici dei valori TRUE:

which(condition)
#> [1] 4 5

E anche se i risultati non sono sbagliati, semplicemente non è necessario. Ad esempio, spesso vedo combinare which() e length() per verificare se almeno un valore o tutti i valori sono TRUE. Invece, ti basta any() o all():

x <- c(1, 2, 12)

# Using `which()` and `length()` to test if any values are greater than 10
if (length(which(x > 10)) > 0)
  print("At least one value is greater than 10")
#> [1] "At least one value is greater than 10"

# Wrapping a boolean vector with `any()`
if (any(x > 10))
  print("At least one value is greater than 10")
#> [1] "At least one value is greater than 10"

# Using `which()` and `length()` to test if all values are positive
if (length(which(x > 0)) == length(x))
  print("All values are positive")
#> [1] "All values are positive"

# Wrapping a boolean vector with `all()`
if (all(x > 0))
  print("All values are positive")
#> [1] "All values are positive"

Ah, e ti fa risparmiare anche un po' di tempo...

x <- runif(1e8)

system.time(x[which(x > .5)])
#>    user  system elapsed 
#>   1.156   0.522   1.686

system.time(x[x > .5])
#>    user  system elapsed 
#>   1.071   0.442   1.662

4. factor quel factor!

Ti è mai capitato di rimuovere valori da un factor e ritrovarti con vecchi livelli che non esistono più? Vedo ogni sorta di soluzione creativa per gestire questo caso. La soluzione più semplice spesso è semplicemente riavvolgerlo in factor().

Questo esempio crea un factor con quattro livelli ("a", "b", "c" e "d"):

# A factor with four levels
x <- factor(c("a", "b", "c", "d"))
x
#> [1] a b c d
#> Levels: a b c d

plot(x)

Suggerimenti R

Se elimini tutti i casi di un livello ("d"), il livello è ancora registrato nel factor:

# Drop all values for one level
x <- x[x != "d"]

# But we still have this level!
x
#> [1] a b c
#> Levels: a b c d

plot(x)

esempio

Un metodo semplicissimo per rimuoverlo è usare di nuovo factor():

x <- factor(x)
x
#> [1] a b c
#> Levels: a b c

plot(x)

esempio

Di solito è una buona soluzione a un problema che fa impazzire molte persone. Evita mal di testa e fai factor a quel factor!

5. Prima ottieni il $, poi il potere

La prossima volta che vuoi estrarre valori da una colonna di data.frame in cui le righe soddisfano una condizione, specifica prima la colonna con $ e poi le righe con [.

Mettiamo che tu voglia i cavalli (hp) per le auto con 4 cilindri (cyl), usando il dataset mtcars. Puoi scrivere una di queste due versioni:

# rows first, column second - not ideal
mtcars[mtcars$cyl == 4, ]$hp
#>  [1]  93  62  95  66  52  65  97  66  91 113 109

# column first, rows second - much better
mtcars$hp[mtcars$cyl == 4]
#>  [1]  93  62  95  66  52  65  97  66  91 113 109

Il consiglio qui è di usare il secondo approccio.

Ma perché?

Primo motivo: elimina quella fastidiosa virgola! Quando specifichi le righe prima della colonna, devi ricordarti la virgola: mtcars[mtcars$cyl == 4,]$hp. Quando specifichi prima la colonna, stai facendo riferimento a un vettore e non ti serve la virgola!

Secondo motivo: velocità! Proviamolo su un data frame più grande:

# Simulate a data frame...
n <- 1e7
d <- data.frame(
  a = seq(n),
  b = runif(n)
)

# rows first, column second - not ideal
system.time(d[d$b > .5, ]$a)
#>    user  system elapsed 
#>   0.497   0.126   0.629

# column first, rows second - much better
system.time(d$a[d$b > .5])
#>    user  system elapsed 
#>   0.089   0.017   0.107

Ne vale la pena, no?

Comunque, se vuoi affinare le tue abilità da ninja dei data frame in R, ti consiglio di imparare dplyr. Puoi farti un'idea generale sul sito di dplyr o imparare davvero le basi con corsi online come Data Manipulation in R with dplyr di DataCamp.

Chiusura

Grazie per la lettura, spero ti sia stato utile.

Per aggiornamenti sui post recenti, segui @drsimonj su Twitter, o scrivimi a [email protected] per metterti in contatto.

Se vuoi il codice che ha prodotto questo blog, dai un'occhiata al repository GitHub blogR.

Dai un'occhiata alla nostra Guida introduttiva al Tidyverse: Tutorial.

Argomenti

Correlato