3  Objetos y ambiente

En este capítulo exploramos los fundamentos de R como un lenguaje basado en objetos. Aprenderemos qué es un objeto, los diferentes tipos de datos que R maneja y cómo se organizan dentro de vectores atómicos. También veremos cómo crear nuevos objetos en R y cómo estos componenen al ambiente global. Estos conceptos son esenciales para trabajar de manera eficiente y comprender el comportamiento del lenguaje en futuras aplicaciones.

3.1 Objetos

Anteriormente usamos R para realizar cálculos matemáticos como:

5 * 6
[1] 30

Los operandos en ese cálculo y su resultado no están guardados en ningún lugar de nuestra computadora. Podríamos decir que lo que vemos ahí son las huellas de algunos números que existieron brevemente en la memoria de nuestra computadora pero ya desaparecieron. Si queremos usar ese resultado para realizar otro cálculo, tendremos que pedirle a R que calcule 5 * 6 de nuevo.

Claramente, al resolver problemas complejos no podemos trabajar con resultados o valores efímeros. Tenemos que guardarlos en algún lugar para poder reutilizarlos. En R podemos hacer:

x <- 5 * 6

En este caso que estamos analizando, x es un ejemplo de un objeto creado por nosotros.

Los objetos son estructuras capaces de almacenar las distintas piezas de información, (o datos), que podemos manipular para resolver una tarea de programación. Dependiendo de las características de un objeto en particular, podemos hacer con él diferentes operaciones. Nuestro programa, a lo largo de su ejecución, va creando o modificando los objetos existentes.

Un nuevo objeto se crea con el operador de asignación (<-, “operador flecha”). Como lo vamos a usar muchísimas veces, es conveniente recordar su atajo para escribirlo rápidamente con el teclado: Alt + - (teclas Alt y guión medio). De esta forma, informalmente la línea x <- 5 * 6 puede ser leída así: “creamos un objeto llamado x que contiene al resultado de la operación 5 * 6 (30)”.

Una vez que almacenamos un valor en x, podemos utilizarlo en nuevos cálculos:

x * 100
[1] 3000
80 - x
[1] 50

Hay distintos tipos de objetos, algunos con estructuras muy simples (como aquellos que sólo almacenan un único valor, al igual que x en el ejemplo) y otros mucho más complejos (como aquellos que sirven para representar conjuntos de datos completos o resultados de algún análisis). Cada lenguaje de programación propone su propio catálogo de tipos de objetos y cada programador puede crear otros tipos nuevos. Durante las primeras unidades sólo emplearemos el tipo de objeto más sencillo que R ofrece.

3.2 Vectores atómicos de R

El tipo de objeto más simple y básico en R se llama vector atómico (atomic vector). De hecho, en el ejemplo, x es un vector atómico que hospeda un único valor numérico. En Matemática, la palabra “vector” hace referencia a un conjunto de números ordenados. Los vectores atómicos de R también pueden contener más de un valor, pero por ahora sólo consideraremos situaciones donde cada vector atómico contiene un sólo valor (como x). A los objetos que se usan para almacenar un solo valor a veces también se les dice variables (porque suele ocurrir que ese valor va cambiando a lo largo del programa).

Un vector atómico puede guardar otras cosas además de números. R define seis tipos básicos de vectores atómicos dependiendo de cómo son los datos que guardan: doubles, integers, characters, logicals, complex y raw1. Los últimos dos sirven para guardar números complejos y bytes en crudo, respectivamente y muy rara vez son usados para tareas de análisis de datos, por lo cual no los volveremos a nombrar. Vamos a ver a los otros, uno por uno.

3.2.1 Doubles (dobles)

Un vector atómico de tipo double almacena números reales (positivos o negativos, grandes o chicos, con decimales o sin decimales). Casi siempre que usamos números para analizar datos en R, empleamos este tipo de vector. Con estos objetos podemos realizar operaciones aritméticas comunes, como en los ejemplos que ya vimos antes.

La función typeof() se usa para preguntar a R de qué tipo es un objeto:

typeof(x)
[1] "double"

3.2.2 Integers (enteros)

Un vector atómico de tipo integer también almacena números, pero sólo números enteros. Muchos lenguajes de programación tratan a los números enteros de forma diferente al resto de los números porque utilizan estrategias especiales para almacenarlos en la memoria de la compu (ocupan menos espacio y su representación tiene mayor precisión).

Para distinguir a los integers de los doubles, R los muestra con una L al costado. De hecho, podemos crear un vector atómico de tipo integer agregando la L

y <- 1L
typeof(y)
[1] "integer"

En aplicaciones de análisis de datos, es raro que los números con los que se trabaja sean sólo enteros, por lo que podemos prestarle poca atención a esto por ahora. Es bueno saber que existen, porque en algunas salidas a veces aparece esa L y ahora sabemos de qué se trata.

En algunos contextos, R directamente les dice numeric tanto a los doubles como a los integers.

3.2.3 Characters (caracteres)

Un vector atómico de tipo character almacena texto, es decir, una cadena de caracteres (una palabra o palabras). No es posible hacer operaciones matemáticas con este tipo de vector. Para crear un vector de tipo character debemos encerrar entre comillas el texto que será almacenado en él:

z <- "Hola, ¿cómo estás?"
z
[1] "Hola, ¿cómo estás?"
typeof(z)
[1] "character"

3.2.4 Logicals (lógicos)

Un vector atómico de tipo logical puede almacenar el valor lógico TRUE (verdadero) o el valor lógico FALSE (falso). Si bien es poco común que este tipo de valores aparezca de forma natural en conjuntos de datos a analizar, son sumamente importantes. Permiten hacer comparaciones y entender su resultado, así como también evaluar condiciones para decidir o no implementar algunas acciones en el proceso que estamos llevando adelante.

Podemos crear un vector logical así:

v <- TRUE
v
[1] TRUE
typeof(v)
[1] "logical"

Notar que los valores lógicos no se escriben entre comillas, puesto que no son cadenas de texto. Tal vez crear vectores lógicos no es lo más común; es más frecuente que los valores lógicos surjan como resultado de algunas operaciones. Por ejemplo:

x > y
[1] TRUE
x < y
[1] FALSE
is.logical(x)
[1] FALSE
is.logical(v)
[1] TRUE
is.numeric(x)
[1] TRUE

¿Para qué necesitamos distintos tipos de vectores atómicos? Para poder establecer qué operaciones se pueden realizar con unos y otros, y que todo tenga sentido. Por ejemplo, podemos hacer sumas aritméticas con vectores numéricos pero no con vectores carácter:

# Se puede:
x * 100
[1] 3000
x + y
[1] 31
# No se puede:
x + z
Error in x + z: non-numeric argument to binary operator

Seleccionar la respuesta correcta.

¿Cuál de los siguientes es un valor numérico?

¿Cuál de los siguientes es un valor lógico?

3.3 Nombres de los objetos

De manera general, al nombre de un objeto se le dice identificador, ya que se trata de secuencia de caracteres que sirve para identificarlo a lo largo de un programa. Nombrar los objetos hace posible referirse a los mismos. La elección de los identificadores es una tarea del programador, pero cada lenguaje tiene sus propias reglas. Por ejemplo, en R los nombres de los objetos:

  • deben empezar con una letra o un punto (no pueden empezar con un número);
  • sólo pueden contener letras, números, guiones bajos y puntos; y
  • no se pueden usar las siguientes palabras como nombres, ya que son están reservadas por el lenguaje: break, else, FALSE, for, function, if, Inf, NA, NaN, next, repeat, return, TRUE, while.

Es aconsejable elegir un nombre que sea representativo de la información que va a guardar el objeto, ya que esto facilita la lectura y la comprensión tanto del algoritmo como del programa. Por ejemplo, si se necesita un objeto para guardar el valor numérico del precio de algún producto, el identificador p sería una mala elección, mientras que precio sería mejor. Si se necesitan varios identificadores para distinguir los precios de diversos productos, podríamos usar algo como precio_manzana, precio_banana, etc.

Por otro lado, no es posible usar como identificador a precio manazana, puesto que un nombre no puede tener espacios. Otra opción podría ser preciomanzana o precioManzana, pero en este curso seguimos la convención de usar guiones bajos para facilitar la lectura de nombres compuestos por más de una palabra (esto se conoce como snake case).

Es importar que R distingue entre mayúsculas y minúsculas, por lo que precio y Precio pueden referirse a distintos objetos, con diferentes valores almacenados:

precio <- 15
Precio <- 2
precio + Precio
[1] 17

No es aconsejable tener objetos cuyos nombres sólo difieran en mayúsculas o minúsculas como en el ejemplo anterior, puesto que sirve para confusión. En general, preferimos evitar usar mayúsculas.

3.4 Actualizar la información guardada en un objeto

El valor guardado en un objeto puede cambiar en cualquier momento del programa. Además, podemos usar otros objetos para calcular el valor que será guardado. Por ejemplo, imaginemos que un programa contabiliza el stock disponible de un artículo en un comercio. Inicialmente había 43 artículos, pero en el día se vendieron 29 y se compraron otros 12 al proveedor para reponer. Al finalizar la jornada, para saber cuántos hay en stock hay que tomar la cantidad disponible original, restar la cantidad que se vendió y sumar la cantidad que se compró. El código podría lucir así:

stock <- 43
ventas <- 29
compras <- 12
stock <- stock - ventas + compras
stock
[1] 26
cat("Hay un stock de", stock, "artículos disponibles.")
Hay un stock de 26 artículos disponibles.

En el ejemplo anterior usamos por primera vez la función cat() para emitir un mensaje, concatenando cadenas de texto encerradas entre comillas y el valor de una variable a la cual hacemos referencia por su nombre (stock).

El valor 43 que originalmente estaba guardado en stock se perdió para siempre en el preciso momento cuando se ejecutó la línea stock <- stock - ventas + compras, que “sobrescribió” su valor. Es importante sobrescribir el valor de un objeto sólo si estamos seguros de que es lo correcto.

También se debe tener en cuenta que podemos reemplazar el objeto representado con un nombre por otro de un tipo diferente, y a R no le va a molestar:

# x es un vector atómico de tipo numeric:
x <- 100
x
[1] 100
# ahora x pasa a ser un vector atómico de tipo caracter:
x <- "hola"
x
[1] "hola"

Otros lenguajes no admiten este comportamiento. Por el contrario, requieren se “declare” el nombre y el tipo de cada objeto antes de ser usados y, si bien se puede actualizar su valor, éste siempre debe ser del mismo tipo. R es un lenguaje dinámico que no tiene este recaudo.

Expresiones como “crear un objeto llamado x que contiene el valor 100” o “sobreescribir, actualizar o reemplazar su valor” nos permiten imaginar lo que sucede y encarar tareas generales de programación en R, pero en realidad son formas simplificadas y poco precisas de describir procesos más complejos relacionados al funcionamiento de R. Estudiantes con experiencia en programación pueden opcionalmente referirse a este material para leer más.

3.5 Ambiente

En R, un environment (entorno o ambiente) es un espacio donde se almacenan los objetos creados durante una sesión de trabajo. Se trata una estructura clave en la gestión de variables y funciones dentro de un programa.

Durante la evaluación de un programa coexisten muchos environments. Vamos a hablar un poco más de esto cuando aprendamos a crear nuevas funciones, pero en general podemos programar en R sin preocuparnos por este tema, que es bastante complejo y avanzado.

Lo fundamental es saber que el conjunto de objetos que vamos creando en nuestro programa forman parte de un ambiente llamado Global Environment y es el que vemos en la pestaña Environment del panel superior derecho de RStudio (Figura 14.1).

Figura 3.1: Captura de pantalla del Global Environment.

También podemos ver en la consola un listado de todos los nombres de los objetos que existen en el ambiente con la función ls():

ls()
[1] "v"               "x"              "y"               "z" 

Los objetos que aparecen listados en esta salida o en el panel Environment son los que podemos usar para programar, porque están disponibles en nuestro ambiente. Si por error queremos usar un objeto que aún no fue definido en el ambiente global, obtenemos un error así:

w * 10
Error: object 'w' not found

Si necesitamos borrar un objeto del ambiente global podemos usar rm() indicando como argumento el nombre del objeto a eliminar:

rm(x)

Si necesitamos borrar todos los objetos del ambiente podemos usar el ícono de la escoba en la pestaña Environment o ejecutar:

rm(list = ls())

A pesar de que el ambiente nos muestre todos los objetos creados durante el trabajo o análisis, es fundamental que el código que los generó esté siempre escrito y guardado en un script. Con un script siempre es posible recrear el entorno de trabajo, pero si sólo tenemos los objetos en el ambiente, no podemos adivinar con qué código fueron creados. Para asegurar que nuestros scripts sean la referencia principal y es el respaldo de todo lo que se hace en un análisis de datos, se recomienda configurar RStudio para que no guarde automáticamente el ambiente cuando se cierra. Esto se logra yendo al menú Tools > Global Options y estableciendo las opciones de configuración tal como se observa en la Figura 3.2. Aunque al principio puede resultar incómodo, ya que cada vez que reinicies RStudio deberás volver a ejecutar el código para generar tus objetos, esta práctica evita problemas a largo plazo. Dejar solo los resultados en el entorno sin registrar el código que los generó puede dificultar la reproducibilidad del análisis en el futuro.

Figura 3.2: Estas opciones garantizan que el ambiente de trabajo esté limpio cada vez que iniciamos RStudio.

  1. Los nombramos en inglés puesto que esos son sus nombres formales.↩︎