4 Conceptos

Para mí, el mejor libro para aprender R es R for Data Science, escrito por el gran Hadley Wickham (existe una versión en español, de la que hablaré luego).

Wickham decidió que el tema para abrir su libro fuese el de visualización de datos:

“La visualización de datos es un gran lugar para empezar porque la recompensa es muy clara: gráficos elegantes e informativos que ayudan a entender los datos.” –Hadley Wickham’s R for Data Science.

Si una persona cuenta con cierto grado de instrucción en R, ciertamente este sería un buen comienzo. ¿Se puede decir lo mismo de las personas que hace apenas cinco minutos instalaron R por primera vez?

Uno de los primeros bloques de código que aparecen en R for Data Science es el siguiente:

## Plot
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

Sí, funciona. Uno escribe ese código, lo ejecuta y R devuelve el gráfico. Recuerdo la satisfacción de correr ese código por primera vez y ver el gráfico hacerse… Y recuerdo también que no entendía absolutamente nada de lo que hay en ese bloque de código.

Aunque la apuesta de ir directamente a la visualización de datos me parece valiosa, creo que una persona completamente debutante en programación necesita primero consolidar cierto vocabulario.

R pre-introductorio tiene como objetivo consolidar todo ese vocabulario necesario para afrontar libros mejores, como los de Hadley Wickham.

4.1 R aplica funciones a objetos

Para empezar, necesitamos entender cómo funciona R. Indiqué antes que R nos sirve para trabajar con datos, para realizar análisis estadístico. Pero saber para qué sirve R no nos dice nada sobre cómo funciona R, y no saber cómo funciona realmente este lenguaje fue tal vez lo que más empinó mi proceso de aprendizaje al inicio.

Ok. ¿Cómo funciona R? Aquí vamos: R aplica funciones a objetos. Y nada más… eso es todo lo que hace R, de hecho: aplicar funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

R aplica funciones a objetos.

Miremos, por ejemplo, qué sucede cuando hacemos que R le aplique la función sum() a los objetos 3 y 4:

## Compute
sum(3, 4)
#> [1] 7

¡Los sumó! En R, las funciones tienen esos paréntesis así, abiertos, libres, desocupados, como esperando que uno les ponga algo adentro…

De modo que sum() es una función; en su calidad de función, la podemos aplicar sobre los objetos que depositemos dentro de los paréntesis.

## Compute
sum(1 * 8 - 10)
#> [1] -2

R aplica funciones, sum(), a objetos, (1 * 8 - 10). ¿Sí ven? No exagero si digo que lo estudiado hasta acá cubre ya bastante de esta pre-introducción. Lo que sigue en adelante es conocer más sintaxis para potenciar aún más la destreza funcional de R.

Va otro ejemplo:

## Compute 
sum(3, 4) |> as.character()
#> [1] "7"

Usamos el native pipe operator |> para que R, primero, sumara los números con la función sum(), y, después, transformara el resultado numérico 7 en el texto "7".

El operador |> lo vamos a encontrar seguido porque es el que nos permite anidar funciones y así el código es más fácil de leer y de escribir. Si no lo queremos usar, podemos meter una función dentro de la otra:

## Compute
as.character(sum(3, 4))
#> [1] "7"

En este caso da bastante lo mismo porque el código no era díficil de leer en primer lugar, pero si uno necesitara anidar muchas funciones seguidas sí se complicaría bastante la lectura.

En suma, estamos aprendiendo que R le aplica funciones a objetos, cada una con algún efecto específico sobre los objetos en cuestión: algunas cargan datos, otras son para limpiarlos, otras los transforman, los modelan, los visualizan, etcétera.

Programar en R es, básicamente, aplicar funciones a objetos:

## Find the  max
max(4, 100, -1) 
#> [1] 100

## Find the min
min(4, 100, -1) 
#> [1] -1

## Transform
toupper("quiero pasar este texto a mayúscula, menos mal existe la función toupper()")
#> [1] "QUIERO PASAR ESTE TEXTO A MAYÚSCULA, MENOS MAL EXISTE LA FUNCIÓN TOUPPER()"

## Transform
tolower("PUES LO CONTRARIO")
#> [1] "pues lo contrario"

En otras palabras, las funciones toman objetos como inputs y los convierten en algo diferente.

Tengo un objeto "No sé si le voy a coger cariño a R" al que le aplico la función sub(), y esta función hasta cuenta con parámetros internos que me permiten indicarle a R exactamente qué quiero que haga con ese objeto:

sub(x = "No sé si le voy a coger cariño a R", 
    pattern = "No sé si", 
    replacement = "Sé que sí")
#> [1] "Sé que sí le voy a coger cariño a R"

Acabamos de aprender que R es tan gentil que las funciones poseen argumentos para que las podemos configurar y acomodarlas a nuestro deseo y necesidad. En el caso anterior, pattern, replacement y x son argumentos con los que indicamos, respectivamente, qué fracción de un objeto (texto) cambiar, con qué cambiarla, y cuál es el objeto en sí (y no menos importante es apreciar cómo el código se puede picar en las comas para que se lea más fácil -si lo dejara todo corrido, se pone muy largo-).

Habíamos iniciado explorando R en su -poco impresionante- rol de calculadora:

## Compute
100 + 100
#> [1] 200

## Compute
"100" + "100"
#> Error in "100" + "100": non-numeric argument to binary operator

Y cada vez vamos conociendo mejor su potencial:

## Load data
data(World) # tmap

## Filter
north_america <- World |> 
  filter(continent == "North America")

## Plot
tm_shape(north_america) +
  tm_polygons("income_grp", palette = "-Blues") + 
  tm_legend(legend.position = c("left", "bottom"))

Les digo que de R será difícil aburrirse…

Y si se aburren, pues configuran RStudio distinto y se pasan a practicar Python; de paso, miren cómo "100" + "100", que no se podía ejecutar en R, sí es aceptable en Python (aunque no es una suma exactamente):

## THIS IS PYTHON!!!!
"100" + "100"
#> '100100'

La programación se va complicando enriqueciendo conforme uno va integrando más sintaxis, pero por muy complejo que parezca nunca deja de ser básicamente lo mismo: un interplay entre objetos y funciones.

4.2 Objetos

Los objetos almacenan datos (voy a decir elementos). Hay múltiples tipos de elementos y múltiples estructuras de objetos. Atención a esto: tipo y estructura no son lo mismo.

¿Por qué es importante la distinción entre tipos y estructuras? Porque si bien programar en R consiste en aplicar funciones a objetos, cuáles funciones es posible aplicar a un objeto determinado depende enteramente de la estructura del objeto y del tipo de elementos que ese objeto almacene.

Lo anterior se entiende mejor si se pone en práctica:

## Compute
sqrt(4) 
#> [1] 2

Sacarle la raíz cuadrada a ese objeto numérico funcionó.

## Compute
sqrt("dude, what u doing?")
#> Error in sqrt("dude, what u doing?"): non-numeric argument to mathematical function

¿Sacarle la raíz cuadrada a ese objeto textual? Pues claro que no iba a funcionar.

Sacarle la raíz cuadrada a un número tiene sentido, sacársela a un texto no:

## Explore
typeof(4)
#> [1] "double"
typeof("dude, what u doing?")
#> [1] "character"

En R, el tipo de los objetos textuales es character y el de los objetos numéricos es numeric. Los objetos numéricos se subdividen en double, si tienen decimales, e integer, si son enteros.

Esto está muy fácil.

Va de nuevo: cuál función puedo aplicar depende enteramente de cómo sea el objeto al que se la pienso aplicar. Y este será uno de los errores más frecuentes que van a experimentar al principio: siempre que topen con un error, descarten primero que no se deba a que la función aplicada no es apropiada para esa estructura de objeto o para el tipo de elementos que el objeto contiene… Un buen número de veces el error viene justo de ahí, ya verán que sí.

Sigamos mirando ejemplos medio burdos pero necesarios de cómo los objetos almacenan datos. Puedo crear un objeto llamado my_last_name y guardar ahí mis apellidos:

## Create object
my_last_name <- "Alvarado-Mena"

Bien. Logré que R almacenara un dato, "Alvarado-Mena", en un objeto; además, hice que R le asignara un nombre, my_last_name, a ese objeto.

Los objetos que uno crea con el operador <- se van enlistando en el environment (región superior derecha).

Tengo dos formas de imprimir los objetos que he creado. Primero, simplemente llamándolos por su nombre (si ya los había creado antes, obvio):

## Print 
my_last_name
#> [1] "Alvarado-Mena"

La segunda alternativa es meterlo todo entre paréntesis al momento mismo de crear el objeto (a mí me encanta hacer esto):

## Create object
(my_last_name <- "Alvarado-Mena")
#> [1] "Alvarado-Mena"

Ahora voy a crear otro objeto name para guardar allí los nombres de mis boxeadores favoritos, un objeto surname para sus apodos, un objeto birthday para sus fechas de nacimiento, un objeto age para sus edades, y un objeto active para indicar si están activos actualmente:

## Create objects
name <- c("Saúl Álvarez", "Román González", "Roberto Durán", "Mike Tyson")
name
#> [1] "Saúl Álvarez"   "Román González" "Roberto Durán" 
#> [4] "Mike Tyson"

surname <- c("Canelo", "Chocolatito", "Mano de Piedra", "Iron Mike")
surname
#> [1] "Canelo"         "Chocolatito"    "Mano de Piedra"
#> [4] "Iron Mike"

birthday <- c("1990-07-18", "1987-06-17", "1951-06-16", "1966-06-30")
birthday
#> [1] "1990-07-18" "1987-06-17" "1951-06-16" "1966-06-30"

age <- c(32, 35, 71, 56)
age
#> [1] 32 35 71 56

active <- c(T, T, F, F)
active
#> [1]  TRUE  TRUE FALSE FALSE

Esa función c() es absolutamente vital, pero de ella me ocuparé luego.

Con estos tres ejemplos, espero, se va entendiendo mejor cómo los objetos almacenan datos en R.

¿Recuerdan que mencioné que los elementos dentro de los objetos tienen un tipo? Pues miren que todos los elementos en name son del mismo tipo (llevan comillas porque son texto); y en age hay puro número; los que hay en active son todos de una clase que R denomina logical (toman los valores TRUE o FALSE). Hay más tipos (clases, mejor dicho, como factor, shape, etcétera) que por hoy vamos a ignorar para no complicarnos demasiado.

Yo puedo recurrir a la función class() y obtener como resultado la clase a la que pertenece un objeto; hay otras funciones similares, aunque no iguales, que capturan diferencias conceptuales que a nuestros efectos no son importantes pero es mejor que ustedes estén al tanto:

## Inspect 
class(name)
#> [1] "character"
class(surname)
#> [1] "character"
class(birthday)
#> [1] "character"
class(age)
#> [1] "numeric"
class(active)
#> [1] "logical"

## Inspect
typeof(name)
#> [1] "character"
class(name)
#> [1] "character"
mode(name)
#> [1] "character"
typeof(iris) 
#> [1] "list"
class(iris$Sepal.Length)
#> [1] "numeric"

¿Por qué es importante todo esto? Bueno, ya dije por qué: cuáles funciones es posible aplicar a un objeto depende enteramente de la estructura del objeto y del tipo de elementos que éste almacene.

Usaré tipo y clase más o menos de manera intercambiable; es una simplificación conveniente y necesaria. No son lo mismo, sin embargo (menos mal su distinción no es relevante para empezar a programar en R). La estructura sí es una cosa aparte; la estudiaremos en breve.

En determinadas circunstancias, uno puede cambiar la clase de los objetos. Por ejemplo, hay una clase específica para fechas: date. Claramente el objeto birthday debería ser de ese tipo, así que lo vamos a convertir:

## Transform 
as.Date(birthday)
#> [1] "1990-07-18" "1987-06-17" "1951-06-16" "1966-06-30"

Ahora voy a verificar que la transformación sucedió:

## Inspect 
class(birthday)
#> [1] "character"

No, no sucedió. Sigue siendo character. ¿Por qué no cambió? No cambió porque, a pesar de que ejecuté correctamente as.Date(birthday), debí haber salvado el resultado. Y esto de salvar resultados es clave: otra fuente inagotable de errores para cualquier debutante es no salvar resultados, o, al contrario, salvar resultados inadvertidamente, salvarlos cuando no era esa la intención (por ejemplo, si hubiésemos hecho un cambio en el objeto birthday que realmente no planeábamos llevar a cabo).

A efectos de guardar resultados, una opción es reescribir el nombre del objeto:

## Transform 
birthday <- as.Date(birthday)

## Inspect 
class(birthday)
#> [1] "Date"

Ahora sí funcionó: transformé un objeto de una clase (character) a otra (date).

Llevo todo el texto insistiendo en que programar en R es fundamentalmente aplicar funciones a objetos; así, por ejemplo, existe una función para calcular el promedio, mean(), y nada me impide aplicarle esa función al objeto age para obtener la edad promedio:

## Compute
mean(age)
#> [1] 48.5

Ahora sé que estos sujetos tienen una edad promedio de 48.5 años. Ese código funcionó perfectamente bien porque age almacena números: su clase es numeric y, tal como lo abordamos atrás, tiene sentido sacar el promedio de un conjunto de números, ¿cierto?

Por el contrario, si aplicara la función mean() al objeto name, R generará un error (una advertencia, más exactamente) porque name no almacena números sino palabras (su clase es character) y, evidentemente, no tiene sentido buscar el promedio de un objeto que no está constituido por números:

## Compute
mean(name)
#> Warning in mean.default(name): argument is not numeric or
#> logical: returning NA
#> [1] NA

El ejemplo anterior demuestra, una vez más, que los objetos pueden contener elementos de una clase u otra, y que dependiendo de cuál sea su clase les es posible soportar unas funciones y no otras.

Hasta ahora, me he estado ocupando de la clase de los objetos, no de su estructura. Hablemos de la estructura de los objetos, pues.

Objetos como name, age y active sólo pueden contener elementos de una única clase.

A los objetos que sólo pueden almacenar objetos de una misma clase se les conoce como vector (o atomic vectors, para ponerlo exacto). Los vectores -la estructura más elemental en R- son colecciones ordenadas de elementos, como decir un contenedor. La función length() arroja el número de elementos contenidos en un vector.

## Print
surname
#> [1] "Canelo"         "Chocolatito"    "Mano de Piedra"
#> [4] "Iron Mike"

## Compute
length(surname)
#> [1] 4

R tiene varias estructuras de objetos más. list, por ejemplo, refiere a objetos que, al contrario de los vectores, sí pueden almacenar elementos de distintas clases:

## Create list
list <- list("Saúl Álvarez", "Canelo", as.Date("1990-07-18"), 32, T)

Por cierto, aprecien cómo puedo partir el código a la altura de las comas para que se mire bonito:

## Create list
list <- list("Saúl Álvarez", 
             "Canelo", 
             as.Date("1990-07-18"), 
             32, 
             T)

Las listas, como la que acabo de crear, pueden contener elementos de varias clases distintas: un nombre y un apodo (character), una edad (numeric), una condición lógica (logical), y de paso usé la función as.Date() para que la fecha R la convirtiera de character a date.

Estamos aprendiendo que las listas son objetos heterogéneos (les entra cualquier dato, independientemente de su clase), mientras los vectores son objetos homogéneos (sólo aceptan datos de una misma clase). ¿Todo fácil, verdad?

Entonces los vectores y las listas tienen una característica que los hace diferentes (la que acabo de resaltar: un vector sólo almacena elementos de una misma clase, mientras una lista puede almacenar elementos de distintas clases). Pero los vectores y las listas poseen una característica en la que sí se parecen: ambos son unidimensionales. La unidimensionalidad es medio difícil de explicar en seco, así que voy a pasar a otra cosa por un segundo y luego retomo la idea.

Más allá de las listas y los vectores, otra estructura muy común en R es data frame; de hecho, es la estructura más común: quienes trabajamos con R pasamos el día entero sobando data frames.

Es bien fácil crear un data frame y lo vamos a ejemplificar con exactamente los mismos datos que usamos hace un rato:

## Create data frame
df <- data.frame(
  name = c("Saúl Álvarez", "Román González", "Roberto Durán", "Mike Tyson"),
  surname = c("Canelo", "Chocolatito", "Mano de Piedra", "Iron Mike"),
  birthday = c("1990-07-18", "1987-06-17", "1951-06-16", "1966-06-30"),
  age = c(32, 35, 71, 56),
  active = c(T, T, F, F)
)

## Print
df
#>             name        surname   birthday age active
#> 1   Saúl Álvarez         Canelo 1990-07-18  32   TRUE
#> 2 Román González    Chocolatito 1987-06-17  35   TRUE
#> 3  Roberto Durán Mano de Piedra 1951-06-16  71  FALSE
#> 4     Mike Tyson      Iron Mike 1966-06-30  56  FALSE

Haré una pausa para destacar cuatro hechos de máxima relevancia sobre la estructura de los objetos. Necesito que ahora mismo conglomeren toda su atención aquí porque las siguientes cuatro lecciones, si se asimilan bien, son el cheat code para que el proceso de aprendizaje adelante varias pantallas de golpe:

  • Primero, noten que cada estructura de objeto tiene su propia función creadora: c() para crear vectores, list() para crear listas, data.frame() para crear data frames.

  • Segundo, observen (vayan al environment de R y den click en el objeto df para abrirlo) que los data frames poseen dos dimensiones: tienen filas (observaciones) y columnas (variables), como decir las spreadsheets de Microsoft Excel. Tener dos dimensiones es una característica que no tienen ni los vectores ni las listas, y no lo tienen porque que son unidimensionales (esto es lo que no pude explicar en seco hace unos minutos).

  • Tercero, ¿sí notaron que las columnas del data frame que creé arriba son todas de diferentes clases? Son character, numeric, logical, date. O sea, los data frames y las listas son similares en ese aspecto: son tipos de objetos capaces de albergar múltiples clases de datos, algo que está vedado para los vectores.

  • Cuarto, aunque los data frames son bidimensionales y pueden albergar columnas de variedad de clases, las columnas en sí mismas son (1) unidimensionales y (2) almacenan elementos de una única clase, lo que quiere decir que ¡las columnas de los data frames son vectores! El hecho de que los data frames sean algo así como racimos de vectores -esto es bastante evidente en nuestro ejemplo pues, de hecho, las columnas en nuestro data frame son exactamente los mismos vectores que antes fuimos creando cada uno por separado- es clave a la hora de someter nuestros datos a selecciones y transformaciones.

Bonus track: ¡Los data frames son listas de vectores! Pero por ahora no es importante comprender esto. La metáfora de los data frames como “racimos de vectores” es más que suficiente.

En resumen, R dispone de al menos tres estructuras de objetos, cada una propicia para ciertos usos y no otros, cada una compatible con ciertas funciones y no otras:

  • Unidimensionales con elementos de una única clase: vector.
  • Unidimensionales con elementos de varias clases: list.
  • Bidimensionales con elementos de varias clases: data frame.

Por si se capta mejor con una tabla:

Tabla 1. - Tipología de estructuras de objetos
Un tipo nada más Varios tipos
Una dimensión Vectores Listas
Dos dimensiones Matrices Data frames

Y de los vectores sale la estructura matrix. La función matrix() simplemente toma un vector y lo acomoda en dos dimensiones. Las matrices serán fundamentales para cualquier R user enfocado en Data Science porque hay muchísimos conceptos estadísticos que se expresan en álgebra de matrices.

En fin, así es como uno manipula datos en R: a veces manipulando una lista, a veces un data frame, a veces un vector. Dije “manipulando” como pude haber dicho “aplicando funciones”… Programar en R es aplicar funciones a objetos, y tanto la estructura de los objetos como el tipo de elementos que almacenen son los factores determinantes de cuáles funciones es válido aplicar a un objeto específico.

Tan mínima como parezcan las diferencias entre estructuras y tipos de objetos, no ser plenamente consciente de ellas desordenó mi proceso de aprendizaje y me costó tiempo valioso.

Consideremos este ejemplo: digamos que uno ya aprendió que la función length() es la que se aplica para obtener el número de elementos almacenados en un objeto. Perfecto. Le aplico la función length() al objeto (vector) en el que había guardado cuatro nombres y el comando corre espléndidamente, me devuelve un 4 que me realiza y me convence de que nací para programar:

## Get the number of elements
length(name)
#> [1] 4

Otro día vuelvo a estar en una necesidad similar, pero esta vez el objeto que tengo es un data frame, no un vector, y lo que necesito es contarle las filas. Si uno es un novato programador que no se fija tanto en eso de tipos y estructuras y simplemente toma el objeto que le ponen por delante y le aplica la función que medio se sabe, es probable que el primer intento sea con la misma función length() porque esa es la que uno recuerda que hace algo parecido a contarle las patas al animal:

## Get the number of rows
length(df)
#> [1] 5

Sin embargo, R devuelve un valor, 5, que no corresponde al número de filas sino al de las columnas. Entonces todo se convierte en frustración, en bronca con uno mismo. ¿Por qué no da el resultado correcto una función que hace nada sí me sirvió para resolver una necesidad similar? ¿Por qué R es tan insufrible? Fácil: el problema aquí es que length() no es exactamente una función para aplicar en data frames sino en vectores.

Dichosamente existe otra función, nrow(), que sí es apta para data frames y que consigue bastante bien lo que yo tenía en mente, es decir, contar el número de filas:

## Get the number of rows
nrow(df)
#> [1] 4

La programación en R está llena de frustraciones como la anterior. Por eso he de insistir: siempre que se opera sobre un objeto, hay que tener completamente claro de qué estructura es tal objeto y de qué clase son los elementos que contiene. Si se trata de vectores, se utiliza la función class() para determinar la clase de sus elementos. Si se trata de data frames, lo primordial es explorar la clase de las columnas pues las columnas son las variables.

Por eso siempre que inicio un trabajo de una vez creo esta función customGlimpse() -que no la programé yo, de algún lado que no recuerdo la habré tomado, porque programar es en gran medida andar robando cosas de internet- para pedirle a R que me dé el detalle las columnas de un data frame cada vez que haga falta:

## Create function to display variables
customGlimpse <- function(df){
  data.frame(
    col_name = colnames(df),
    col_index = 1:ncol(df),
    col_class = sapply(df, class),
    row.names = NULL
  )
}

Ese código de arriba tiene mucha cosa que no es importante entender de momento. Pero miren qué bonito devuelve el nombre de la columna, su índice (i.e., qué número ocupa en el data frame) y la clase de sus elementos:

## Explore variables
customGlimpse(df)
#>   col_name col_index col_class
#> 1     name         1 character
#> 2  surname         2 character
#> 3 birthday         3 character
#> 4      age         4   numeric
#> 5   active         5   logical

Ahora admirémosla aplicada al conjunto de datos starwars del paquete tidyverse:

## Explore variables
customGlimpse(starwars) 
#>      col_name col_index col_class
#> 1        name         1 character
#> 2      height         2   integer
#> 3        mass         3   numeric
#> 4  hair_color         4 character
#> 5  skin_color         5 character
#> 6   eye_color         6 character
#> 7  birth_year         7   numeric
#> 8         sex         8 character
#> 9      gender         9 character
#> 10  homeworld        10 character
#> 11    species        11 character
#> 12      films        12      list
#> 13   vehicles        13      list
#> 14  starships        14      list

Tremenda función. Por cierto, ¿sí notaron que la existencia de la función customGlimpse() implica que R nos deja crear nuestras propias funciones?

4.3 Funciones

Sobre las funciones voy a divagar menos pues lo dicho hasta acá ya las ha iluminado bastante.

Las funciones se extraen de paquetes (los paquetes también pueden traer data sets, como starwars) o uno mismo las puede crear (y customGlimpse() es un claro ejemplo de una función que uno mismo programó). También R trae un montón de funciones cargadas desde su instalación, un tema que ampliaré en el próximo capítulo.

Además de ajustarse a la estructura y la clase del objeto al que se apliquen, las funciones requieren de ciertos ajustes internos. Para ello, las funciones poseen argumentos; los argumentos son los que acomodan la función a nuestro objetivo específico.

Algunos de los argumentos están definidos por default y otros es uno quien debe especificarlos. seq() es una función que ejemplifica con claridad qué son los argumentos (from, to, by):

## Create sequence from 1 to 10 with an increment of 2
seq(from = 1, to = 10, by = 2)
#> [1] 1 3 5 7 9

El output de esa función no ingresa al environment si no se lo asigno a un objeto. Si quiero guardar ese output entonces tendría que utilizar el operador <- que ya hemos visto aparecer varias veces arriba:

## Create sequence from 1 to 10 with an increment of 2
seq <- seq(from = 1, to = 10, by = 2)

## Print
seq
#> [1] 1 3 5 7 9

Y de paso recordemos la otra forma de imprimir un objeto (encerrándolo todo entre paréntesis):

## Create sequence from 1 to 10 with an increment of 2
(seq <- seq(from = 1, to = 10, by = 2))
#> [1] 1 3 5 7 9

Si no sabemos cómo se usa una función, RStudio cuenta con un atajo para accesar a toda su documentación. En el caso de seq(), simplemente habría que correr en la consola el comando ?seq para desplegar una ventana con los pormenores de la función, entre ellos, sus argumentos.

La documentación de R puede ser un poco abrumadora y no siempre es clara, pero en general es excelente y es una de sus mejores características, esto gracias a que los requisitos para crear y dar mantenimiento a los paquetes son exigentes y ello resulta en que el ecosistema de R exhiba altos niveles de calidad.

?? funciona para búsquedas más generales. Pongamos que uno quiere explorar qué hay en R sobre estadística bayesiana, entonces conviene ejecutar ??bayes para que nos lleven al menú temático.

Recordemos que podemos crear nuestras propias funciones:

## Create function
add <- function(one_number, any_other_number){ # R, take these inputs
  sum <- one_number + any_other_number # Do this to them
  return(sum) # And gimme this output!
}

## Compute
add(10, 12)
#> [1] 22

Y, por último, recordemos que uno debe siempre prestar atención a cuál es el return value de la función (en el caso anterior, un vector numérico). ¿Por qué? Ya se la saben: porque las funciones devuelven objetos, y qué se puede hacer luego con ese objeto depende enteramente de su estructura y su clase:

## Compute
sqrt(add(add(10, 10), add(6, 6)) + 4)
#> [1] 6

Ahora vamos a hablar de otro concepto medular: los operadores.

4.4 Operadores

Los operadores son, en realidad, funciones. Son funciones de uso tan frecuente que ameritó crearles una sintaxis más simple, un atajo.

4.4.1 <-

El assignment operator es para asignar objetos a nombres, de modo tal que luego podemos usar los nombres en su lugar:

## Assign 
four <- 4

## Print, option A
(four <- 4)
#> [1] 4

## Print, option B
four
#> [1] 4

## Compute
sqrt(four) 
#> [1] 2

Aunque en ciertas ocasiones = también sirve para asignar, es mejor que nos quedemos con <- pues es el que funciona en todos los casos. Reservemos = para la asignación de los argumentos de una función; por ejemplo, cuando calculamos el promedio de un vector con missing values (valores faltantes: NA), R nos va a esperar que explícitamente le indiquemos que omita los NA; de lo contrario, nos arrojará otro NA:

## Compute
mean(c(1, 2, NA, 4))
#> [1] NA

Ahora miren cómo sí funciona:

## Compute
mean(c(1, 2, NA, 4), na.rm = TRUE)
#> [1] 2.333333

¿La diferencia? na.rm = TRUE

4.4.2 [], $

R cuenta con buen arsenal de sintaxis para accesar elementos específicos dentro de un objeto. Los indexing operators son grandes asistidores. Su uso es posible porque R indexa los elementos que componen un objeto. ¿Y qué significa eso? Significa que cada elemento tiene una posición, y a esa posición se puede accesar por su número.

La indexación (i.e., que cada elemento posee un número y que podemos sacarlo del objeto si lo llamamos por ese número) se aprecia mejor con un ejemplo:

## Create vector
some_letters <- c("j", "h", "x", "y", "l")

## Index
some_letters[1]
#> [1] "j"
some_letters[2]
#> [1] "h"
some_letters[3]
#> [1] "x"
some_letters[4]
#> [1] "y"
some_letters[5]
#> [1] "l"

R cuenta los elementos a partir de 1 -a diferencia de Python y básicamente cualquier otro lenguage de programación, que cuentan desde 0-. Arriba, empleamos [] para indexar con base en la posición de los elementos dentro del vector.

Atención con :, una variedad de operador (integer-sequence operator) que nos asiste al hacer secuencias de números enteros y del que normalmente tomaremos ventaja para indexar varios elementos a la vez:

## Create integer sequences
14:21
#> [1] 14 15 16 17 18 19 20 21
21:14
#> [1] 21 20 19 18 17 16 15 14
-1:1
#> [1] -1  0  1
1:length(15:25)
#>  [1]  1  2  3  4  5  6  7  8  9 10 11

## Index
some_letters[3:5] 
#> [1] "x" "y" "l"

Ya aprendimos cómo se indexa un vector. Indexar un data frame es parecido: dado que los data frames poseen dos dimensiones, hemos de especificar la fila y la columna de interés, mediante una sintaxis tipo [fila, columna]:

## Index
starwars[4, 1] # Row 4, Column 1
#> # A tibble: 1 × 1
#>   name       
#>   <chr>      
#> 1 Darth Vader

Si quisiera extraer toda la fila, el código sería este:

## Index
starwars[4, ]
#> # A tibble: 1 × 14
#>   name    height  mass hair_…¹ skin_…² eye_c…³ birth…⁴ sex  
#>   <chr>    <int> <dbl> <chr>   <chr>   <chr>     <dbl> <chr>
#> 1 Darth …    202   136 none    white   yellow     41.9 male 
#> # … with 6 more variables: gender <chr>, homeworld <chr>,
#> #   species <chr>, films <list>, vehicles <list>,
#> #   starships <list>, and abbreviated variable names
#> #   ¹​hair_color, ²​skin_color, ³​eye_color, ⁴​birth_year
#> # ℹ Use `colnames()` to see all variable names

Y si quisiera toda la columna, son dos mis opciones. La primera es llamar a la columna por su número (|> head() no es necesario; lo utilizo únicamente para que me imprima unos poquitos ejemplos en lugar de todas las filas) :

## Index
starwars[, 1] |> head()
#> # A tibble: 6 × 1
#>   name          
#>   <chr>         
#> 1 Luke Skywalker
#> 2 C-3PO         
#> 3 R2-D2         
#> 4 Darth Vader   
#> 5 Leia Organa   
#> 6 Owen Lars

O bien, puedo usar el operador $ para llamar a la columna por su nombre (no por su número):

## Index
starwars$name 
#>  [1] "Luke Skywalker"        "C-3PO"                
#>  [3] "R2-D2"                 "Darth Vader"          
#>  [5] "Leia Organa"           "Owen Lars"            
#>  [7] "Beru Whitesun lars"    "R5-D4"                
#>  [9] "Biggs Darklighter"     "Obi-Wan Kenobi"       
#> [11] "Anakin Skywalker"      "Wilhuff Tarkin"       
#> [13] "Chewbacca"             "Han Solo"             
#> [15] "Greedo"                "Jabba Desilijic Tiure"
#> [17] "Wedge Antilles"        "Jek Tono Porkins"     
#> [19] "Yoda"                  "Palpatine"            
#> [21] "Boba Fett"             "IG-88"                
#> [23] "Bossk"                 "Lando Calrissian"     
#> [25] "Lobot"                 "Ackbar"               
#> [27] "Mon Mothma"            "Arvel Crynyd"         
#> [29] "Wicket Systri Warrick" "Nien Nunb"            
#> [31] "Qui-Gon Jinn"          "Nute Gunray"          
#> [33] "Finis Valorum"         "Jar Jar Binks"        
#> [35] "Roos Tarpals"          "Rugor Nass"           
#> [37] "Ric Olié"              "Watto"                
#> [39] "Sebulba"               "Quarsh Panaka"        
#> [41] "Shmi Skywalker"        "Darth Maul"           
#> [43] "Bib Fortuna"           "Ayla Secura"          
#> [45] "Dud Bolt"              "Gasgano"              
#> [47] "Ben Quadinaros"        "Mace Windu"           
#> [49] "Ki-Adi-Mundi"          "Kit Fisto"            
#> [51] "Eeth Koth"             "Adi Gallia"           
#> [53] "Saesee Tiin"           "Yarael Poof"          
#> [55] "Plo Koon"              "Mas Amedda"           
#> [57] "Gregar Typho"          "Cordé"                
#> [59] "Cliegg Lars"           "Poggle the Lesser"    
#> [61] "Luminara Unduli"       "Barriss Offee"        
#> [63] "Dormé"                 "Dooku"                
#> [65] "Bail Prestor Organa"   "Jango Fett"           
#> [67] "Zam Wesell"            "Dexter Jettster"      
#> [69] "Lama Su"               "Taun We"              
#> [71] "Jocasta Nu"            "Ratts Tyerell"        
#> [73] "R4-P17"                "Wat Tambor"           
#> [75] "San Hill"              "Shaak Ti"             
#> [77] "Grievous"              "Tarfful"              
#> [79] "Raymus Antilles"       "Sly Moore"            
#> [81] "Tion Medon"            "Finn"                 
#> [83] "Rey"                   "Poe Dameron"          
#> [85] "BB8"                   "Captain Phasma"       
#> [87] "Padmé Amidala"

Ahora bien, miren que el output no es exactamente el mismo (hay que escoger cuál función emplear según la necesidad del momento):

## Compare
typeof(starwars[, 1])
#> [1] "list"
typeof(starwars$name)
#> [1] "character"

La indexación habilita la posibilidad de borrar elementos o modificarlos:

## Print the original object
some_letters
#> [1] "j" "h" "x" "y" "l"

## Delete value
some_letters[-2]
#> [1] "j" "x" "y" "l"

## Delete more than one value
some_letters[-c(2, 3)]
#> [1] "j" "y" "l"

## Change value
some_letters[2] <- "M"

## Inspect
some_letters
#> [1] "j" "M" "x" "y" "l"

4.4.3 |>, %>%, +

Re importantes los pipe operators. Arriba ya hemos tomado ventaja de los pipes para anidar funciones, de modo tal que el output de una se convierte en el input de la siguiente:

## Wrangle data
df.wc |> 
  filter(NameTeam == "Costa Rica") |> # (1)
  group_by(player) |> # (2)
  summarize( # (3)
    offersToReceiveSUM = sum(OffersToReceiveTotal
    )) |> 
  arrange(desc(offersToReceiveSUM)) |> # (4)
  head(5) # (5)
#> # A tibble: 5 × 2
#>   player         offersToReceiveSUM
#>   <chr>                       <dbl>
#> 1 Joel CAMPBELL                 136
#> 2 Celso BORGES                  127
#> 3 Bryan OVIEDO                   99
#> 4 Keysher FULLER                 94
#> 5 Yeltsin TEJEDA                 93

En este ejemplo, tomé un conjunto de datos del Mundial Qatar 2022 y anidé funciones:

  • filter() para filtrar sólo los jugadores que corresponden a la Selección de Costa Rica.

  • group_by() para agrupar las estadísticas por jugador -la unidad de análisis de este conjunto de datos es jugador-por-partido, de modo tal que incluye, por ejemplo, una fila para Joel Campbell contra España, una para Joel contra Japón, y otra para Joel contra Alemania; en el ejercicio que estoy haciendo ahora mismo quiero agrupar los datos de cada jugador para crear una variable que agregue una medición en particular-.

  • summarize() y sum para sumar -en este caso- todas las veces que cada jugador pidió la bola en los tres partidos, y este resultado lo asigné a una variable nueva -offersToReceiveSUM–.

  • arrange() para ordenarlos de mayor a menor, como en un ranking.

  • head(), finalmente, para imprimir nada más que los primeros cinco, con lo cual creé el top 5 de jugadores de la Sele que más veces pidieron la bola durante el Mundial y, para sorpresa de nadie, el que más la pidió fue Joel Campbell.

A los pipes podemos darle uso más en corto:

## Compute
sum(30, 6) |> sqrt()
#> [1] 6

Sin embargo, tal como lo ilustra el análisis de los jugadores de la Sele que más pidieron la bola en el Mundial, los pipes son particularmente útiles a la hora de someter nuestros datos a selecciones y transformaciones (data wrangling). Si no fuera por los pipes, el código se nos volvería muy confuso y error-prone y costaría montones leerlo.

Algunos paquetes anidan funciones con otro pipe distinto. Las funciones de ggplot2, el icónico y muy poderoso paquete para visualizar datos, utiliza el operador +:

## Plot
ggplot(mtcars, aes(x = as.factor(cyl), y = mpg)) + 
    geom_boxplot(fill = "pink") +
  ggtitle("Feo, porque pulir visualizaciones toma DEMASIADO tiempo") +
  theme_get()

Un último comentario: el pipe %>% se ve más frecuentemente que el pipe |>. Por razones técnicas que aquí no tienen lugar, les aconsejo quedarse con |> pues ese es el que viene integrado en R (si no lo tienen, ¡actulicen R y RStudio!).

4.4.4 >, <, ==

Pues no hay mucho qué decir: estos operadores ejecutan operaciones lógicas:

## Compute
1 == 2
#> [1] FALSE

2^2 == 4
#> [1] TRUE

3 > 4
#> [1] FALSE

3 < 4
#> [1] TRUE

Antes vimos que = es un operador para asignar nombres a objetos, de modo que para usarlo en compaciones lo escribimos doble: ==. Este detalle toma rato asimilarlo y les va a deparar un significativo caudal de errores mientras tanto.

Miren la utilidad de estos operadores a la hora de filtrar datos:

## Filter
starwars |>
  filter(height > 230) |>
  arrange(height)
#> # A tibble: 2 × 14
#>   name    height  mass hair_…¹ skin_…² eye_c…³ birth…⁴ sex  
#>   <chr>    <int> <dbl> <chr>   <chr>   <chr>     <dbl> <chr>
#> 1 Tarfful    234   136 brown   brown   blue         NA male 
#> 2 Yarael…    264    NA none    white   yellow       NA male 
#> # … with 6 more variables: gender <chr>, homeworld <chr>,
#> #   species <chr>, films <list>, vehicles <list>,
#> #   starships <list>, and abbreviated variable names
#> #   ¹​hair_color, ²​skin_color, ³​eye_color, ⁴​birth_year
#> # ℹ Use `colnames()` to see all variable names

4.4.5 &, |, TRUE, FALSE

Podemos combinar los operadores de comparación con los operadores booleanos TRUE, FALSE, & y | para ejecutar filtrados más exigentes:

## Filter
starwars |>
  filter(height > 200 & gender == "masculine" & mass > 135) |>
  arrange(height) |>
  select(name, height, gender, mass)
#> # A tibble: 3 × 4
#>   name        height gender     mass
#>   <chr>        <int> <chr>     <dbl>
#> 1 Darth Vader    202 masculine   136
#> 2 Grievous       216 masculine   159
#> 3 Tarfful        234 masculine   136

Le acabamos de encargar a R encontrar los sujetos que cumplieran estos tres atributos: más altos que cierto número, y de cierto género y más pesados que cierto otro número.

Aquí abajo, en cambio, le pedimos a R encontrar los sujetos que fuesen de una especie en particular o que tuviesen un nombre específico:

## Filter
starwars |>
  filter(species == "Droid" | name == "Darth Vader") |>
  select(species, name)
#> # A tibble: 7 × 2
#>   species name       
#>   <chr>   <chr>      
#> 1 Droid   C-3PO      
#> 2 Droid   R2-D2      
#> 3 Human   Darth Vader
#> 4 Droid   R5-D4      
#> 5 Droid   IG-88      
#> 6 Droid   R4-P17     
#> 7 Droid   BB8

4.4.6 ~

El operador ~ significa “en función de” y es común encontrarlo en las funciones que corren regresiones:

## Compute
lm(mass ~ height, data = starwars)
#> 
#> Call:
#> lm(formula = mass ~ height, data = starwars)
#> 
#> Coefficients:
#> (Intercept)       height  
#>    -13.8103       0.6386

Este es el output de una regresión lineal. Hay un capítulo entero sobre esta materia más adelante.

El orden de las variables es importante: la que está antes de ~ es la variable dependiente y todas las que vengan después son las variables independientes: Y ~ X.

4.4.7 %in%, in

Estos operadores son perfectos para filtrar datos cuando tenemos varios targets. Sólo comparen las siguientes dos tareas:

## Filter
starwars |>
  filter(name == "Luke Skywalker")
#> # A tibble: 1 × 14
#>   name    height  mass hair_…¹ skin_…² eye_c…³ birth…⁴ sex  
#>   <chr>    <int> <dbl> <chr>   <chr>   <chr>     <dbl> <chr>
#> 1 Luke S…    172    77 blond   fair    blue         19 male 
#> # … with 6 more variables: gender <chr>, homeworld <chr>,
#> #   species <chr>, films <list>, vehicles <list>,
#> #   starships <list>, and abbreviated variable names
#> #   ¹​hair_color, ²​skin_color, ³​eye_color, ⁴​birth_year
#> # ℹ Use `colnames()` to see all variable names
  
## Filter
starwars |>
  filter(name %in% c("Darth Maul", "Obi-Wan Kenobi", "Luke Skywalker"))
#> # A tibble: 3 × 14
#>   name    height  mass hair_…¹ skin_…² eye_c…³ birth…⁴ sex  
#>   <chr>    <int> <dbl> <chr>   <chr>   <chr>     <dbl> <chr>
#> 1 Luke S…    172    77 blond   fair    blue         19 male 
#> 2 Obi-Wa…    182    77 auburn… fair    blue-g…      57 male 
#> 3 Darth …    175    80 none    red     yellow       54 male 
#> # … with 6 more variables: gender <chr>, homeworld <chr>,
#> #   species <chr>, films <list>, vehicles <list>,
#> #   starships <list>, and abbreviated variable names
#> #   ¹​hair_color, ²​skin_color, ³​eye_color, ⁴​birth_year
#> # ℹ Use `colnames()` to see all variable names

Y son perfectos para ensamblar for loops, por citar apenas un ejemplo más:

## Compute
for(i in c(3, 4, 5)){
  print(i ^ 2)
}
#> [1] 9
#> [1] 16
#> [1] 25

Bonus track: los loops son un ingrediente esencial de la programación. Si observan con cuidado, van a ver que el código anterior nos sirvió para repetir la operación i ^ 2 para valores de i que iban cambiando a lo largo del vector c(3, 4, 5); si no fuera por los loops, habríamos programado algo así:

## Compute
3 ^ 2
#> [1] 9
4 ^ 2
#> [1] 16
5 ^ 2
#> [1] 25

No parece difícil, podría pensar alguien. Pero tan sólo imagínense qué perdida de tiempo sería elevar al cuadrado todos los números del 1 al 50… En cambio, con un for loop es bien sencillo:

## Create empty vector
empty_vector <- vector(length = 50)

## Compute
for(i in 1:length(empty_vector)){
  empty_vector[[i]] <- i ^ 2
}

## Print
empty_vector
#>  [1]    1    4    9   16   25   36   49   64   81  100  121
#> [12]  144  169  196  225  256  289  324  361  400  441  484
#> [23]  529  576  625  676  729  784  841  900  961 1024 1089
#> [34] 1156 1225 1296 1369 1444 1521 1600 1681 1764 1849 1936
#> [45] 2025 2116 2209 2304 2401 2500

4.4.8 !, -

Los operadores de negación son para cambiar de TRUE a FALSE, y viceversa:

## Create vector
(this_vector_is_true <- TRUE)
#> [1] TRUE

## Transform
!this_vector_is_true
#> [1] FALSE

La negación nos simplifica la vida cuando, por poner un ejemplo, queremos encontrar los valores faltantes (NA) dentro de un vector o data frame:

## Prepare data
example <- c(1, 2, NA, 4, NA)

## Determine whether there are NA
is.na(example)
#> [1] FALSE FALSE  TRUE FALSE  TRUE

## Single them out, option A
example[is.na(example)]
#> [1] NA NA

## Single them out, option B
example[which(is.na(example))]
#> [1] NA NA

## Remove them, option A
example[!is.na(example)]
#> [1] 1 2 4

## Remove them, option B
example[-which(is.na(example))]
#> [1] 1 2 4

## Save 
(example_A <- example[!is.na(example)])
#> [1] 1 2 4
(example_B <- example[-which(is.na(example))])
#> [1] 1 2 4

Cuando nos enfrentamos a un data frame del cual nos interesan prácticamente todas las variables, y para descartar las pocas que no vamos a necesitar, podemos seleccionar en negativo esas poquitas que no son de nuestro interés en lugar de fastidiarnos la vida escribiendo el nombre de todas las variables que sí queremos conservar:

## Select
starwars |>
  select(!c(films, vehicles, hair_color))
#> # A tibble: 87 × 11
#>    name    height  mass skin_…¹ eye_c…² birth…³ sex   gender
#>    <chr>    <int> <dbl> <chr>   <chr>     <dbl> <chr> <chr> 
#>  1 Luke S…    172    77 fair    blue       19   male  mascu…
#>  2 C-3PO      167    75 gold    yellow    112   none  mascu…
#>  3 R2-D2       96    32 white,… red        33   none  mascu…
#>  4 Darth …    202   136 white   yellow     41.9 male  mascu…
#>  5 Leia O…    150    49 light   brown      19   fema… femin…
#>  6 Owen L…    178   120 light   blue       52   male  mascu…
#>  7 Beru W…    165    75 light   blue       47   fema… femin…
#>  8 R5-D4       97    32 white,… red        NA   none  mascu…
#>  9 Biggs …    183    84 light   brown      24   male  mascu…
#> 10 Obi-Wa…    182    77 fair    blue-g…    57   male  mascu…
#> # … with 77 more rows, 3 more variables: homeworld <chr>,
#> #   species <chr>, starships <list>, and abbreviated
#> #   variable names ¹​skin_color, ²​eye_color, ³​birth_year
#> # ℹ Use `print(n = ...)` to see more rows, and `colnames()` to see all variable names

4.5 Más particularidades

4.5.1 Aritmética vectorizada

Si a R le pedimos una división entre dos vectores con el mismo número de elementos, R divide entre sí aquellos que tienen el mismo índice:

## Compute
c(2, 6, 8, 9) / c(1, 2, 4, 3)
#> [1] 2 3 2 3

A veces los vectores no tienen el mismo número de elementos. Abajo, R divide cada elemento del vector largo entre el único elemento del otro vector:

## Compute
c(2, 6, 8, 9) / c(2)
#> [1] 1.0 3.0 4.0 4.5

Es decir, cuando los vectores son de diferente largo, R “recicla” el más pequeño para lograr que ambos vectores calcen:

## Compute
c(2, 6, 8, 9) / c(2, 3)
#> [1] 1 2 4 3

## Compute
c(2, 3) / c(2, 6, 8, 9)
#> [1] 1.0000000 0.5000000 0.2500000 0.3333333

No obstante, para poder reciclar vectores (como en los dos casos anteriores), R necesita que el largo de un vector (e.g., 4) sea múltiplo del largo del otro vector (e.g., 2). Si esto no se cumple, R nos lo hará saber:

## Compute
c(2, 6, 8, 9) / c(2, 3, 4)
#> Warning in c(2, 6, 8, 9)/c(2, 3, 4): longer object length is
#> not a multiple of shorter object length
#> [1] 1.0 2.0 2.0 4.5

4.5.2 Missing values

En R, los valores faltantes se representan como NA, que quiere decir Not Available. Un NA transmite que, pese a la expectativa de contar con un dato, lo desconocemos:

## Explore data
starwars |>
  select(name, hair_color) |>
  head()
#> # A tibble: 6 × 2
#>   name           hair_color 
#>   <chr>          <chr>      
#> 1 Luke Skywalker blond      
#> 2 C-3PO          <NA>       
#> 3 R2-D2          <NA>       
#> 4 Darth Vader    none       
#> 5 Leia Organa    brown      
#> 6 Owen Lars      brown, grey

Con los NA básicamente lo que R nos quiere decir es que no sabe qué va ahí. De ahí este comportamiento tan interesante:

## Compare
NA == NA
#> [1] NA

Capaz uno esperaría que NA == NA sea TRUE, pero R se abstiene de ello pues al no saber qué es NA, NA podría entonces ser cualquier cosa.

En teoría, debemos quitar los NA antes de analizar los datos. Algunas veces corremos el riesgo de perder una significativa cantidad de observaciones. Esto es muy sensible y escapa de los alcances de R pre-introductorio. Sólo puedo recomendar que antes de tomar este tipo de decisiones (remover una fila, remover una columna) valoren con precaución cuántos datos van a perder y si otras estrategias son mejores (imputar los datos faltantes, remover la fila o la variable sólo para ciertos análisis, etcétera).

Por otro lado, NaN significa Not a Number y podemos topar con uno de esos, por ejemplo, si intentamos hacer un cálculo que matemáticamente no es válido:

## Compute
0/0
#> [1] NaN

4.5.3 Importar datos

R pre-introductorio recurre mayoritariamente a data frames que vienen integrados en paquetes.

Cuando trabajamos en R por motivos labores o académicos, todas las veces necesitamos cargar archivos que están en la computadora. Y como eso puede complicarse un poco, es bueno presentar un par de estrategias.

Mi primera recomendación es trabajar en el marco de un R project.

Por ejemplo, dado que todo el material de R pre-introductorio lo mantenemos en la misma carpeta donde está ubicado el archivo pre_intro_r.Rproj, se nos hace re fácil navegar la computadora con tan sólo colocar el cursor justo en el medio de las comillas de una función que carga datos y pulsar la tecla tab:

## Load data
read_csv(file = "")

Inténtenlo ustedes. ¿Qué pasó? ¿Hermoso, cierto?

Otras buena opción puede ser here::here(), que les recomiendo googlear, y un salvavidas es file.choose(), una función que abre una ventana para que uno pueda buscar el archivo manualmente (pero esta es una mala práctica que sólo les aconsejaría si están en una urgencia):

## Load data
read_csv(file.choose())

Tómese en cuenta que hay muchas funciones para cargar datos y cuál llamar depende enteramente del tipo de archivo que planeen analizar. Los ejemplos anteriores usan read_csv(), una función que nos hará el trabajo si el archivo es de tipo CSV. Sin importar el tipo de archivo, sepan desde ya que Google tiene todas las respuestas.

4.6 Un vocabulario

Regresemos al bloque de código del principio, el que tomé del libro de Hadley Wickham:

## Plot
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

Ahora que podemos mirarlo con otros ojos, esto es lo que encontramos: tres funciones: ggplot(), geom_point() y aes(); varios objetos (como mpg, asignado al argumento data = de la función ggplot()); hay un operador + que, en este caso, está creando un pipeline… Y el conjunto de todo ese código es el que produce la visualización.

O sea, ahora nos damos cuenta de que ese código diminuto estaba repleto de sintaxis que simplemente no podíamos distinguir antes… Ya la distinguimos, ya estamos en condiciones de leer los libros de Hadley Wickham y sacarles provecho de veras.