R Basics

Examples by Prof. Jacob Escobar, EGADE Business School, Tec de Monterrey

Variables, Funciones y Operadores

Componentes basicos de R: * Objetos (Variables) - Tipo de dato - Estructura de datos * Acciones - Operadores - Funciones

El objeto más básico de R, son los vectores. Aunque una variable tenga un solo valor, se considera un vector de un elemento.

a = 10
b = 5

Si se almacena un dato o resultado en una variable, no se despliega, a menos que se mande llamar la variable en sí o que no se haya indicado alguna variable donde guardar un resultado.

a
## [1] 10
b
## [1] 5
c = a + b
c
## [1] 15

R es sensible a mayúsculas y minúsculas.

A = 15
a + b
## [1] 15
A + b
## [1] 20

Hasta ahorita hemos usado = para asignar valores, pero el “best practice” en R es más bien usar <- como operador de asignación.
Por cierto, aunque una varible ya tenga un valor, la podemos sobreescribir.

a <- 7
a + b
## [1] 12

Las funciones en R utilizan ( ), tal como en Excel, por ejemplo:

sqrt(9)
## [1] 3
sqrt(a)
## [1] 2.645751

Una forma de ver los nombres de las variables guardadas en el ambiente:

objects()
## [1] "a" "A" "b" "c"

Al igual que en Excel, podemos anidar (“nest”) funciones una adentro de otra. Siempre que hacemos eso, la función más interna en cuestión de paréntesis es la que se ejecuta primero, y así sucesivamente hasta llegar a la más externa.

Una forma de ver las variables guardadas en el ambiente, con todo y sus valores:

mget(objects())
## $a
## [1] 7
## 
## $A
## [1] 15
## 
## $b
## [1] 5
## 
## $c
## [1] 15

Ahora un ejemplo con funciones de texto y con interacción del usuario:

c <- "Hola"
d <- readline("¿Cuál es tu nombre? ")
## ¿Cuál es tu nombre?
paste(c, d)
## [1] "Hola "

Tipos de Datos

Hay diferentes tipos de datos en R: * Numérico (float) * Entero (integer) * Texto (character / string) * Lógico (boolean) * Fecha (date) * Tiempo (time) * Factor (factor)

Tanto funciones como operadores pueden requerir que los objetos con los que interactúan sean de cierto tipo.
Prueba en una celda de código la siguiente suma:
a + b

# Aprovecho aquí para contarte del símbolo "#", que nos sirve en R para poner
# comentarios en los scripts. 
# Estos comentarios sirven como notas, recordatorios y documentación.
# SIEMPRE incluyan comentarios en sus scripts, NUNCA están de más.

# R ignora TODO lo que esté a la derecha del #, así que también nos sirve para
# inhabilitar algunas instrucciones temporalmente, sin tener que borrarlas.

# Quita el "#" del último renglón y comprueba si funciona esa operación.
a <- 7
b <- "6"
# a + b

Los tipos de datos boleanos o lógicos son generalmente el resultado de una comparación, como veremos más delante. Sus valores son TRUE o FALSE y también se pueden abreviar como T y F respectivamente, respetando las mayúsculas.

Una convención estándar para los datos boleanos, es que TRUE = 1 y FALSE = 0. Por esta razón, sí es posible incluirlos en operaciones matemáticas.

 # TRUE y FALSE se convierten en números al usarlos en operaciones matemáticas
TRUE + 5
## [1] 6
FALSE + TRUE
## [1] 1

En ejemplos como el anterior y el siguiente, el operador o la función “fuerzan” los objetos que reciben a un tipo de dato que sí pueden utilizar, por tanto ejecutan su acción y no marcan error.

Esto se conoce como “coercing” y puede ser algo práctico en ocasiones, aunque mi recomendación es hacer las conversiones de tipos de datos explícitas para que no haya ambigüedad en el código.

# paste( ) convierte los inputs en texto para poder pegarlos
paste(TRUE, 3, "Yes")
## [1] "TRUE 3 Yes"

La función class( ) sirve para verificar el tipo de dato de un objeto guardado en el ambiente.

a <- 2
b <- "2"
c <- "TRUE"
d <- TRUE
class(a)
## [1] "numeric"
class(b)
## [1] "character"
class(c)
## [1] "character"
class(d)
## [1] "logical"

Para hacer conversiones de tipos de datos explíticamente, podemos usar funciones como las siguientes. En el caso de conversión de estructuras de datos (ej. de vector a matriz o de matriz a data frame), hay funciones similares.

# Nota la presencia o ausencia de comillas en los resultados
a <- as.numeric("4")
a
## [1] 4
b <- as.character(TRUE)
b
## [1] "TRUE"
c <- as.logical(0)
c
## [1] FALSE

Vectores

La función “c( )” (combine) nos sirve para combinar varios elementos en un solo objeto o vector.
Todos estos elementos deben ser del mismo tipo.

v1 <- c(1, 2, 3, 4)
v1
## [1] 1 2 3 4
v2 <- c("a", "b", "c", "d")
v2
## [1] "a" "b" "c" "d"

Otras formas de crear vectores utilizando patrones son:

1:5
## [1] 1 2 3 4 5
15:10
## [1] 15 14 13 12 11 10
seq(0, 10, 2)
## [1]  0  2  4  6  8 10
seq(20, 10, -2)
## [1] 20 18 16 14 12 10
rep(1:3, 4)
##  [1] 1 2 3 1 2 3 1 2 3 1 2 3
rep("Oe", 4)
## [1] "Oe" "Oe" "Oe" "Oe"

Subsetting o Slicing
Si queremos acceder a solo un elemento o un subconjunto de un vector, podemos utilizar paréntesis cuadrados [ ] para especificar su posición.

v2[1]
## [1] "a"
v2[2]
## [1] "b"
v2[1:3]
## [1] "a" "b" "c"
v2[c(1, 2, 4)]
## [1] "a" "b" "d"

Podemos utilizar la misma lógica para sobreescribir solo una parte de un vector:

v2[1] <- "w"
v2
## [1] "w" "b" "c" "d"
v2[2:4] <- c("x", "y", "z")
v2
## [1] "w" "x" "y" "z"

En el caso de matrices y tablas, dado que tienen dos dimensiones, se ocupa especificar sus coordenadas como [renglón, columna], pero esto lo veremos más delante.

Funciones

Para revisar la documentación de alguna función en R puedes utilizar ? o la función help( ):

#?seq
#help(seq)

Las funciones pueden utilizar 0, 1 o más argumentos entre sus paréntesis. Estos argumentos pueden ser tanto objetos con los que trabajará la función como parámetros que le indiquen a la función qué hacer.

Los argumentos pueden ser: * Requeridos (no tienen valor default) * Opcionales (tienen un valor default)

Los argumentos se pueden proporcionar a la función: * Por posición (de acuerdo a su documentación, como en Excel) * Por nombre (práctico cuando se tienen muchos argumentos o cuando se está aprendiendo una función nueva)

Observa como los siguientes ejemplos son idénticos:

seq(0, 12, 4) # por posición, como indica la documentación
## [1]  0  4  8 12
seq(from = 0, to = 12, by = 4) # por nombre, recomiendo este uso
## [1]  0  4  8 12
seq(by = 4, to = 12, from = 0) # por nombre no importa el orden
## [1]  0  4  8 12
seq(0, 12, by = 4) # mezcla de "por posición" y "por nombre", también es válido
## [1]  0  4  8 12

Operaciones con Vectores

Operadores y funciones en R operan con vectores sin problema.

v1
## [1] 1 2 3 4
sqrt(v1)
## [1] 1.000000 1.414214 1.732051 2.000000
v2 <- c(10, 20, 30, 40)
v1 + v2
## [1] 11 22 33 44

Cuando se usan vectores del mismo tamaño, la operación se hace “elemento por elemento” sin problema, y el vector resultante será de igual tamaño.

Cuando los vectores son de diferente tamaño, se recicla o reutiliza el vector más corto para poder hacer la operación “elemento x elemento”. Esto se conoce como “recycling” y el vector resultante tendrá la misma longitud que el vector más largo.

Esta funcionalidad puede hacer sentido en casos muy partículares, pero generalmente solo hará sentido al hacer una operación entre vectores del mismo tamaño o entre un vector de tamaño “n” y otro de tamaño 1.

v1*20
## [1] 20 40 60 80

Hasta ahora hemos usado operadores aritméticos, pero tambien podemos usar operadores relacionales.
¿Ocurre “reciclado” en alguno de estos ejemplos?

v1 > v2
## [1] FALSE FALSE FALSE FALSE
v2 > 20
## [1] FALSE FALSE  TRUE  TRUE

Para considerar varias condiciones a la vez, utilizamos operadores lógicos.

a <- 3
b <- 5
a == 3 & b == 6 # AND
## [1] FALSE
a == 3 & b != 6 # ! es NOT, != es NOT EQUAL (a diferencia de <> en Math)
## [1] TRUE
a == 3 | b == 6 # OR
## [1] TRUE
a != 3 | b == 6
## [1] FALSE

Resumen de operadores: * Aritméticos: +, -, *, /, **, %% * Relacionales: >, <, ==, !=, >=, <= * Lógicos: &, |, !

¿Reconoces todos? Prueba en un bloque de código los que no reconozcas, para que los identifiques.

Ejemplos Integradores

1. Tiendas

Supongamos que tenemos los datos de ingresos y gastos en miles de pesos, de cinco tiendas de una cadena.
Operación “elemento x elemento” sin reciclar:

ingresos <- c(100, 90, 100, 110, 105)
gastos <- c(80, 75, 90, 115, 85)
# ¿Qué pasa si utilizas un vector de gastos más corto, como el de abajo?
# gastos <- c(80, 75, 90)
utilidad <- ingresos - gastos
utilidad
## [1] 20 15 10 -5 20

Operación reciclando el vector de menor longitud. En la práctica, esto solo suele hacer sentido cuando uno de los vectores es de tamaño uno.

ingresos_MXN <- c(100, 90, 100, 110, 105)
tdc <- 20.5
ingresos_USD <- ingresos_MXN / tdc
ingresos_USD
## [1] 4.878049 4.390244 4.878049 5.365854 5.121951

Si utilizamos un vector booleano de la misma longitud que el vector que queremos filtrar, se desplegarán solo las posiciones que tengan el valor TRUE.

ingresos[c(T, F, T, F, T)]
## [1] 100 100 105
ingresos[c(1,3,5)]
## [1] 100 100 105

Usar un vector boleano de manera manual para filtrar otro no parece ser muy práctico, sobretodo si ya sabemos filtrar vectores especificando solo las posiciones que queremos mostrar. La ventaja del vector booleano es cuando lo combinamos con una comparación relacional para usar como filtro.

En la sección anterior hicimos subsetting de vectores “por posición”, pero también podemos hacer subsetting “por criterio”, para filtrar en base a una condición. Esto es mucho más práctico, dado que el vector boleano se genera en automático con la operación relacional que utilicemos.

Veamos este ejemplo donde aprovecharemos para hacer un filtrado de las tiendas:

tiendas <- c("A", "B", "C", "D", "E")
ingresos <- c(100, 90, 100, 110, 105)
altos <- ingresos > 100
altos
## [1] FALSE FALSE FALSE  TRUE  TRUE
tiendas[altos]
## [1] "D" "E"

En el bloque de código anterior generamos un vector boleano altos que nos indica las tiendas que tienen ingresos mayores a 100. Luego utilizamos ese vector para filtrar las tiendas que cumplen con ese criterio.

Al utilizar un vector boleano para filtrar otro, por default se muestran los valores que son TRUE. Observa como las siguientes instrucciones dan el mismo resultado. De hecho nos podemos saltar la parte de crear un vector boleano a parte y simplemente utilizar directamente la comparación que buscamos, como filtro.

tiendas[altos == TRUE]
## [1] "D" "E"
tiendas[ingresos > 100]
## [1] "D" "E"

También podemos filtrar lo opuesto. Las siguientes tres instrucciones dan el resultado contrario a las anteriores. Observa el uso del operador ! que significa NOT. De igual manera nos podemos evitar el crear un vector boleano independiente.

tiendas[!altos]
## [1] "A" "B" "C"
tiendas[altos == FALSE]
## [1] "A" "B" "C"
tiendas[ingresos <= 100]
## [1] "A" "B" "C"

En los ejemplos anteriores, hicimos todas las comparaciones contra un valor fijo (100) para determinar si los ingresos eran altos o no. En ese caso el valor 100 se recicló y no obtuvimos ningún mensaje de advertencia ya que es un caso de uso normal hacer una operación de vector con escalar (vector de tamaño 1).

El otro caso de uso normal es una operación con vectores del mismo tamaño. Así que ahora filtraremos comparando cada valor contra un valor correspondiente, operación elemento por elemento. Por ejemplo, supongamos que ahora queremos detectar las tiendas rentables (las que tienen ingresos mayores a gastos).

ingresos <- c(100, 90, 100, 110, 105)
gastos <- c(80, 75, 90, 115, 85)
rentables <- ingresos > gastos
rentables
## [1]  TRUE  TRUE  TRUE FALSE  TRUE
tiendas[rentables]
## [1] "A" "B" "C" "E"

También aquí nos podemos saltar el paso de generar un vector boleano intermedio (rentables) y usar la condición directamente como filtro.

tiendas[ingresos > gastos]
## [1] "A" "B" "C" "E"

2. Precios y Retornos

Supongamos que tenemos un vector de precios \(p\) de una acción, con datos para cinco días de la semana. Entonces el retorno del primer día se puede calcular de la siguiente manera:

p <- c(10, 9, 10, 11, 10)
p[2]/p[1] - 1
## [1] -0.1

Siguiendo una lógica similar, podemos calcular el retorno simple para todos los días en una sola operación, como en el caso del vector r abajo. Observa como realizamos una división entre vectores del mismo tamaño y luego le restamos 1 a todo el vector resultante.

\[ r = (p_1 - p_0)/p_0 = p_1/p_0 - 1 \]

r <- p[2:5]/p[1:4] - 1
r
## [1] -0.10000000  0.11111111  0.10000000 -0.09090909
round(r, 2) # ¿qué es el 2 en esta función?
## [1] -0.10  0.11  0.10 -0.09

Ahora calculemos el retorno logarítmico:

\[ r=ln(p_1/p_0)=ln(p_1)−ln(p_0) \]

# En R, la función log calcula el logaritmo natural
log(p[2:5]/p[1:4])
## [1] -0.10536052  0.10536052  0.09531018 -0.09531018

3. Cash Flows y Tasa de Interés

Veamos el ejemplo de un flujo de caja \(cf\) que queremos descontar n periodos en base a una tasa de interés \(i\). Supongamos que tenemos los datos de \(cf\) y de \(n\) pero no tenemos certidumbre de la tasa. Podemos verificar varios escenarios:

cf <- 100
n <- 5
i <- seq(0.025, 0.10, by = 0.005)
pv <- cf/(1+i)^n
i
##  [1] 0.025 0.030 0.035 0.040 0.045 0.050 0.055 0.060 0.065 0.070 0.075 0.080
## [13] 0.085 0.090 0.095 0.100
round(pv, 2)
##  [1] 88.39 86.26 84.20 82.19 80.25 78.35 76.51 74.73 72.99 71.30 69.66 68.06
## [13] 66.50 64.99 63.52 62.09

Dado que ya contamos con varios valores de tasa de interés y de valor presente, podemos incluso hacer una gráfica sencilla para ver esta relación inversa:

plot(x = i, y = pv, type = "l")

Estructuras de Control

Sirven para controlar el flujo de un algoritmo.

  • Estructuras selectivas: Sirven para decidir si ejecutar un bloque de código u otro(s). Le dan “inteligencia” al código. Ejemplos:
    • if
    • switch
  • Estructuras iterativas: Sirven para ejecutar un bloque de código un número determinado de veces o mientras se cumpla alguna condición. Sirven para automatizar tareas repetitivas y para algoritmos de búsqueda u optimización. Ejemplos:
    • while
    • for

Para más información puedes revisar la documentación ?Control

Los bloques de código se delimitan con corchetes { }. Con esto ya hemos visto tres tipos de paréntesis en R. Aquí un resumen:

  • ( ): función( argumentos )
  • [ ]: objeto[ subconjunto ]
  • { }: { bloque de código }

Ejemplos de IF

Observa los siguientes ejemplos de if, if-else y nested ifs (ifs anidados)

# Nota el uso de la función as.numeric( ), dado que
# readline( ) guarda el dato como texto, pero luego
# queremos hacer una comparación numérica.
age <- as.numeric(readline("Edad: "))
age <- 24
if (age < 18) {
  print("Milk for you!")
} 
if(age >= 18) {
  print("Beer for you!")
}
## [1] "Beer for you!"
if (age < 18) {
  print("Milk for you!")
} else {
  print("Beer for you!")
}
## [1] "Beer for you!"
if (age < 18) {
  print("Milk for you!")
} else if (age > 75) {
  print("Water for you!")
} else {
  print("Beer for you!")
}
## [1] "Beer for you!"

Ejemplos de WHILE

Similar a if dado que también tiene una condición, pero en este caso tenemos un ciclo dado que el código se repite mientras la condición sea TRUE.

OJO: while tiene el riesgo particular de poder generar ciclos infinitos.

¿Cómo puidiéramos tener un ciclo infinito en el siguiente ejemplo?

v2 <- c("a", "b", "c", "d", "e")
counter <- 1
while (counter <= length(v2)) {
  print(counter)
  print(v2[counter])
  counter <- counter + 1
}
## [1] 1
## [1] "a"
## [1] 2
## [1] "b"
## [1] 3
## [1] "c"
## [1] 4
## [1] "d"
## [1] 5
## [1] "e"

¿Cómo nos salimos del siguiente código?

loop <- "yes"
while (loop != "no" & loop != "n") {
  loop <- readline("Shall we continue? ")
}

Ejemplos de FOR

También genera ciclos, pero en este caso en base a los elementos de un rango que le proporcionemos. Estos elementos pueden ser números o directamente los valores de un vector.

for (i in 1:5) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
teams <- c("Bulls", "Lakers", "Pistons", "Heat", "Spurs")
for (x in 1:5) {
  print(teams[x])
}
## [1] "Bulls"
## [1] "Lakers"
## [1] "Pistons"
## [1] "Heat"
## [1] "Spurs"
teams <- c("Bulls", "Lakers", "Pistons", "Heat", "Spurs")
for (x in teams) {
  print(x)
}
## [1] "Bulls"
## [1] "Lakers"
## [1] "Pistons"
## [1] "Heat"
## [1] "Spurs"
for (n in seq(10, 1, -3)) {
  print(n)
}
## [1] 10
## [1] 7
## [1] 4
## [1] 1

Funciones Personalizadas

También es posible crear tus propias funciones en R. Esto es útil cuando tienes un bloque de código que quisieras poder llamar en diferentes partes de otro algoritmo o de un flujo más grande. Sirve para poder tener códigos más compactos y entendibles.

Las funciones personalizadas se definen como una variable, pero no se guarda un dato específico dentro de ellas sino más bien líneas de código. Dado que las funciones pueden contener varias líneas, es necesario delimitar su bloque de código entre corchetes: { }

Ejecuta los siguientes ejemplos:

hi <- function() {
  nombre <- readline("Nombre? ")
  print(paste0("Hola ", nombre, "!"))
}
hi2 <- function(nombre) {
  print(paste0("Hi ", nombre, "!"))
}
hi3 <- function(nombre = "amigo") {
  print(paste0("Hi ", nombre, "!"))
}

Prueba las tres funciones hi, hi2 y hi3 en el bloque de abajo. ¿Funcionan sin enviarles parámetros o requieren parámetros? ¿Qué otras diferencias tienen?

# Quita los # de las líneas abajo y prueba hi, hi2 y hi3

#hi()
#hi("Newton")
#hi(Newton)

Es importante resaltar lo siguiente: las variables que utilizamos dentro de las funciones se quedan ahí (son locales) y no salen al Global Environment, no se graban igual que las variables que usamos afuera de la función. Lo que sí “sale” de las funciones es lo siguiente:

  • Lo que tenga la función return( ) (al llegar a este comando se sale de la función, aunque haya líneas posteriores)
  • Todo lo que despleguemos de manera explícita con print( ), View( ) o gráficas como hist( ), plot( ), ggplot( ), etc
  • En caso de que no haya algún return( ), se regresa el resultado del último renglón

Veamos otro ejemplo, ahora con una variable interna:

pot <- function(num, exp) {
  calc <- num ** exp
  return(calc)
}

Probamos la función:

pot(2, 3)
## [1] 8

Con la siguiente instrucción puedes ver que las funciones personalizadas se guardan en el ambiente como cualquier otra variable. Y también puedes ver los valores que hayan tenido sus variables internas no se guardan en el ambiente, como es el caso de calc.

mget(objects())
## $a
## [1] 3
## 
## $A
## [1] 15
## 
## $age
## [1] 24
## 
## $altos
## [1] FALSE FALSE FALSE  TRUE  TRUE
## 
## $b
## [1] 5
## 
## $c
## [1] FALSE
## 
## $cf
## [1] 100
## 
## $counter
## [1] 6
## 
## $d
## [1] TRUE
## 
## $gastos
## [1]  80  75  90 115  85
## 
## $hi
## function() {
##   nombre <- readline("Nombre? ")
##   print(paste0("Hola ", nombre, "!"))
## }
## 
## $hi2
## function(nombre) {
##   print(paste0("Hi ", nombre, "!"))
## }
## 
## $hi3
## function(nombre = "amigo") {
##   print(paste0("Hi ", nombre, "!"))
## }
## 
## $i
## [1] 5
## 
## $ingresos
## [1] 100  90 100 110 105
## 
## $ingresos_MXN
## [1] 100  90 100 110 105
## 
## $ingresos_USD
## [1] 4.878049 4.390244 4.878049 5.365854 5.121951
## 
## $n
## [1] 1
## 
## $p
## [1] 10  9 10 11 10
## 
## $pot
## function(num, exp) {
##   calc <- num ** exp
##   return(calc)
## }
## 
## $pv
##  [1] 88.38543 86.26088 84.19732 82.19271 80.24510 78.35262 76.51344 74.72582
##  [9] 72.98808 71.29862 69.65586 68.05832 66.50454 64.99314 63.52277 62.09213
## 
## $r
## [1] -0.10000000  0.11111111  0.10000000 -0.09090909
## 
## $rentables
## [1]  TRUE  TRUE  TRUE FALSE  TRUE
## 
## $tdc
## [1] 20.5
## 
## $teams
## [1] "Bulls"   "Lakers"  "Pistons" "Heat"    "Spurs"  
## 
## $tiendas
## [1] "A" "B" "C" "D" "E"
## 
## $utilidad
## [1] 20 15 10 -5 20
## 
## $v1
## [1] 1 2 3 4
## 
## $v2
## [1] "a" "b" "c" "d" "e"
## 
## $x
## [1] "Spurs"

Estructuras de Datos

Adicional a los vectores, otras estructuras de datos muy utilizadas son las matrices y los “data frames”.

Matrices

Al igual que los vectores, las matrices son estructuras de un solo tipo de dato, pero en dos dimensiones en vez de una. El estándar de coordenadas en R cuando se tienen dos dimensiones es: [renglón, columna].

Típicamente las matrices tienen datos numéricos y su caso de uso común es para álgebra matricial, que se utiliza para resolver ecuaciones simultáneas, para optimización y para simulación.

Hay varias formas de generar matrices en R, además de leerlas de otra fuente. Las funciones típicas para crear matrices son matrix( ) y array( ). Dado que matrix( ) es la función más directa y con este propósito específico, nos enfocaremos en esa.

x <- matrix(data = 1:12, nrow = 3, ncol = 4)
x
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
## [3,]    3    6    9   12

Revisa la documentación de ?matrix, ¿cómo podemos generar la misma matriz de los ejemplo anteriores pero llenada por renglones en vez de por columnas?
Tip: Hay un argumento específico para eso.

El subsetting o slicing de una matriz (2D) es similar al de los vectores (1D), pero ahora debemos especificar las coordenadas como [renglón, columna] en vez de solo [posición]. Cuando queremos hacer un filtro o selección solo de renglones o solo de columnas pero no de ambas, simplemente dejamos en blanco la coordenada que no queremos filtrar. Algunos ejemplos a continuación.

Seleccionando solo un renglón:

x[3,]
## [1]  3  6  9 12

Seleccionando solo una columna:

x[, 2]
## [1] 4 5 6

Seleccionando una celda en específico:

x[3, 2]
## [1] 6

Seleccionando una submatriz:

x[1:3, 1:2]
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6

También es posible agregar renglones o columnas a matrices ya existentes con las funciones a continuación. Solo es cuestión de asegurarnos de que los objetos involucrados tienen el mismo tamaño en la dimensión que se pegarán: misma cantidad de renglones en el caso de rbind( ) y misma cantidad de columnas en el caso de cbind( ).

m <- matrix(1:9, 3, 3)
rbind(m, 101:103)
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9
## [4,]  101  102  103
cbind(m, 101:103)
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7  101
## [2,]    2    5    8  102
## [3,]    3    6    9  103

Álgebra matricial

Una ventaja de R sobre algunos otros lenguajes es que R base es capaz de realizar álgebra matricial sin problemas y sin necesidad de agregar paquetes externos.

Recordemos que en el caso de multiplicación de matrices el orden de los factores sí altera el producto y las dimensiones de las matrices involucradas dictaminan tanto las dimensiones del resultado como si es posible la operación en primer lugar.

Si multiplicamos \(A * B = C\): * Requisito: Cantidad de columnas de A = Cantidad de renglones de B * Renglones de C = Cantidad de renglones de A * Columnas de C = Cantidad de columnas de B

Si multiplicamos \(B * A = D\): * Requisito: Cantidad de columnas de B = Cantidad de renglones de A * Renglones de D = Cantidad de renglones de B * Columnas de D = Cantidad de columnas de A

Dado que el propósito de este curso no es enseñar álgebra matricial, sino solo demostrar que es posible en R, veremos solo algunos ejemplos para diferenciar álgebra matricial de las operaciones elemento por elemento.

r es una matriz de 5 renglones y 3 columnas. Supongamos que las columnas representan acciones y que los renglones representan retornos diarios de 5 días.

r <- matrix(runif(15), 5, 3) # ¿qué hace runif( )
r
##           [,1]       [,2]      [,3]
## [1,] 0.2847618 0.07785107 0.1040092
## [2,] 0.1886086 0.97130599 0.6418165
## [3,] 0.2658600 0.76081248 0.2450358
## [4,] 0.3027509 0.56716719 0.1724157
## [5,] 0.5656435 0.32452517 0.6896478

w es una matriz de 3 renglones y 1 columna. Supongamos que es el porcentaje que tenemos invertido en cada una de las acciones de r.

w <- matrix(c(0.30, 0.40, 0.30), 3, 1)
w
##      [,1]
## [1,]  0.3
## [2,]  0.4
## [3,]  0.3

El operador de multiplicación matricial (también conocida como producto interno o producto punto) en R es %*%. Así podemos calcular el retorno diario de un portafolio que tiene acciones con retornos r y pesos w.

r %*% w
##           [,1]
## [1,] 0.1477718
## [2,] 0.6376499
## [3,] 0.4575937
## [4,] 0.3694169
## [5,] 0.5063975

Poder utilizar multiplicación matricial con esta facilidad permite replicar las ecuaciones de diferentes métodos econométricos fácilmente.
Otras tres operaciones / funciones matriciales muy utilizadas también son:

m1 <- matrix(c(2,1,-2,3), 2, 2)
m1
##      [,1] [,2]
## [1,]    2   -2
## [2,]    1    3

La transpuesta de una matriz, con t( ):

t(m1)
##      [,1] [,2]
## [1,]    2    1
## [2,]   -2    3

La inversa de una matriz, con solve( ):

solve(m1)
##        [,1] [,2]
## [1,]  0.375 0.25
## [2,] -0.125 0.25

Comprobación: Una matriz por su inversa da la matriz identidad \(I\). Una matriz identidad tiene solo 1’s en la diagonal y 0’s fuera de la diagonal.

m1 %*% solve(m1)
##      [,1] [,2]
## [1,]    1    0
## [2,]    0    1

La descomposición de Cholesky:

chol(m1)
##          [,1]      [,2]
## [1,] 1.414214 -1.414214
## [2,] 0.000000  1.000000

No todas las matrices tienen inversa ni descomposición de Cholesky, pero esas reglas son para temas específicos de álgebra matricial que no cubre este curso. Simplemente mostramos las funciones disponibles y realizamos ya algunas comprobaciones.

En resumen: - Multiplicación elemento por elemento se realiza con * - Multiplicación matricial se realiza con %*%

Data frames

Los “data frames” son los objetos que típicamente se utilizan para guardar datos que leemos de un archivo, ya que estos no se limitan a un solo tipo de dato. Podemos pensar en los data frames como varios vectores de la misma longitud, pegados como columnas de una tabla. Estas son las reglas que tienen:

  • Cada columna debe ser de un solo tipo de dato, pero diferentes columnas pueden ser de diferente tipo de dato.
  • Todas las columnas deben tener la misma cantidad de renglones
  • Todas las columnas deben tener nombre

Construyamos un ejemplo a continuación. Empezaremos creando vectores de diferentes tipos de datos (pero de la misma longitud) y luego los consolidaremos en una tabla.

qbs <- c("Mahomes", "Garoppolo", "Brady", "Rodgers", "Brees")
teams <- c("Chiefs", "49ers", "Patriots", "Packers", "Saints")
ages <- c(24, 28, 42, 36, 41)
nfl <- data.frame(qbs, teams, ages)
nfl
##         qbs    teams ages
## 1   Mahomes   Chiefs   24
## 2 Garoppolo    49ers   28
## 3     Brady Patriots   42
## 4   Rodgers  Packers   36
## 5     Brees   Saints   41

Las formas de subsetting que vimos para matrices también aplican para data frames, puedes hacer tus propias pruebas para comprobarlo.
Sin embargo dado que las columnas de los data frames siempre tienen nombre, dado que suelen representar variables, podemos referenciarlas “por nombre” utilizando el operador $ (y también “por posición” como en el caso de las matrices, es a gusto del usuario).

nfl[,3]
## [1] 24 28 42 36 41
nfl$ages
## [1] 24 28 42 36 41
nfl[, c(1,3)]
##         qbs ages
## 1   Mahomes   24
## 2 Garoppolo   28
## 3     Brady   42
## 4   Rodgers   36
## 5     Brees   41
nfl[, c("qbs","ages")]
##         qbs ages
## 1   Mahomes   24
## 2 Garoppolo   28
## 3     Brady   42
## 4   Rodgers   36
## 5     Brees   41
nfl[nfl$ages > 25 & nfl$ages < 40,]
##         qbs   teams ages
## 2 Garoppolo   49ers   28
## 4   Rodgers Packers   36
nfl[nfl$teams == "Packers", ]
##       qbs   teams ages
## 4 Rodgers Packers   36

También podemos utilizar el operador $ para reemplazar el valor de una columna si la referenciamos y le asignamos valores nuevos, o incluso para agregar columnas nuevas si referenciamos una columna que no existe, de un data frame que ya está creado.

Ve el siguiente ejemplo en el que agregamos la columna cities (que no existía previamente) a nuestra tabla de la nfl.

nfl$cities <- c("Kansas", "San Francisco", "New England", 
                "Green Bay", "New Orleans")
nfl
##         qbs    teams ages        cities
## 1   Mahomes   Chiefs   24        Kansas
## 2 Garoppolo    49ers   28 San Francisco
## 3     Brady Patriots   42   New England
## 4   Rodgers  Packers   36     Green Bay
## 5     Brees   Saints   41   New Orleans

De igual manera podemos eliminar columnas fácilmente, pero antes haremos una distinción entre dos palabras clave que a veces causan confusión: NA y NULL.

  • NA significa “not available” o “no disponible”. Es aconsejable que los datos faltantes estén identificados con esta palabra, dado que hay funciones que los pueden tomar en cuenta para hacer el cálculo de manera diferente.

  • NULL significa vacío, esto generalmente se utiliza para eliminar algún valor.

Veamos cómo funcionan en el caso de un data frame:

nfl$yards <- NA
nfl
##         qbs    teams ages        cities yards
## 1   Mahomes   Chiefs   24        Kansas    NA
## 2 Garoppolo    49ers   28 San Francisco    NA
## 3     Brady Patriots   42   New England    NA
## 4   Rodgers  Packers   36     Green Bay    NA
## 5     Brees   Saints   41   New Orleans    NA

La instrucción anterior generó una columna con NAs, indicando que son datos faltanes. Normalmente solo tenemos algunos datos faltantes y como quiera podemos hacer uso de la información que tengamos, pero si falta toda la columna, vale la pena eliminarla.

nfl$yards <- NULL
nfl
##         qbs    teams ages        cities
## 1   Mahomes   Chiefs   24        Kansas
## 2 Garoppolo    49ers   28 San Francisco
## 3     Brady Patriots   42   New England
## 4   Rodgers  Packers   36     Green Bay
## 5     Brees   Saints   41   New Orleans

Instalación de Paquetes

R tiene ya muchas funciones base, pero se le pueden agregar todavía más funciones, datasets, estructuras de datos y documentación con paquetes instalables.

Hay un par de formas de instalar paquetes:

CRAN es el sitio oficial de R y quiere decir “Comprehensive R Archive Network”. Además de contener documentación de R, también es el repositorio de los paquetes oficiales de R. Los paquetes que ahí se encuentran ya fueron validados en cuestiones de programación, estadística, documentación, etc.

Instalamos paquetes de CRAN desde R Studio, con la función: install.packages("nombre_del_paquete")

Solo se requiere instalar paquetes una vez por dispositivo y por versión de R, no es necesario instalarlo cada vez salvo que se quiera actualizar.

Por otro lado, para poder utilizar las funciones de un paquete es necesario activarlo con la función: library(nombre_del_paquete)

Activar un paquete es algo que se requiere hacer en cada sesión que se quiera tener acceso a sus funciones, documentación, etc, incluso aunque lo acabemos de instalar.

En realidad todas las funciones que utilizamos en R pertenecen a algún paquete, pero las que usamos sin activar algo más pertencen a la coleccción de paquetes que se cargan en automático cada vez que iniciamos R.

Dos funciones útiles para ver los paquetes instalados y activados son:

rownames(installed.packages())
##   [1] "abind"                 "AER"                   "agricolae"            
##   [4] "AlgDesign"             "aod"                   "arm"                  
##   [7] "ash"                   "askpass"               "assertthat"           
##  [10] "backports"             "base"                  "base64enc"            
##  [13] "bayestestR"            "bdsmatrix"             "Bessel"               
##  [16] "BH"                    "bit"                   "bit64"                
##  [19] "bitops"                "blob"                  "boot"                 
##  [22] "broom"                 "bslib"                 "cachem"               
##  [25] "callr"                 "car"                   "carData"              
##  [28] "caret"                 "caTools"               "cellranger"           
##  [31] "censReg"               "checkmate"             "chron"                
##  [34] "class"                 "classInt"              "cli"                  
##  [37] "clipr"                 "cluster"               "coda"                 
##  [40] "codetools"             "colorspace"            "combinat"             
##  [43] "commonmark"            "compiler"              "COMPoissonReg"        
##  [46] "config"                "conflicted"            "conquer"              
##  [49] "corpcor"               "corrplot"              "cpp11"                
##  [52] "crayon"                "crosstalk"             "curl"                 
##  [55] "data.table"            "datasauRus"            "datasets"             
##  [58] "datawizard"            "DBI"                   "dbplyr"               
##  [61] "deldir"                "descr"                 "dials"                
##  [64] "DiceDesign"            "digest"                "DistributionUtils"    
##  [67] "DMwR2"                 "doParallel"            "dplyr"                
##  [70] "dtplyr"                "e1071"                 "earth"                
##  [73] "ecm"                   "effectsize"            "ellipsis"             
##  [76] "emmeans"               "estimability"          "evaluate"             
##  [79] "expm"                  "fansi"                 "farver"               
##  [82] "fastICA"               "fastmap"               "fBasics"              
##  [85] "fda"                   "fda.usc"               "fdapace"              
##  [88] "fds"                   "ff"                    "fGarch"               
##  [91] "finreportr"            "FinTS"                 "FNN"                  
##  [94] "forcats"               "foreach"               "forecast"             
##  [97] "forecastHybrid"        "foreign"               "Formula"              
## [100] "fracdiff"              "fs"                    "furrr"                
## [103] "future"                "gargle"                "gbm"                  
## [106] "gdata"                 "GeneralizedHyperbolic" "generics"             
## [109] "ggeffects"             "ggplot2"               "glmmML"               
## [112] "glmnet"                "globals"               "glue"                 
## [115] "gmodels"               "gmp"                   "googledrive"          
## [118] "googlesheets4"         "googleVis"             "gower"                
## [121] "GPfit"                 "gplots"                "graphics"             
## [124] "grDevices"             "grid"                  "gridExtra"            
## [127] "gss"                   "gtable"                "gtools"               
## [130] "hardhat"               "haven"                 "hdrcde"               
## [133] "highr"                 "HistData"              "Hmisc"                
## [136] "hms"                   "htmlTable"             "htmltools"            
## [139] "htmlwidgets"           "hts"                   "httpuv"               
## [142] "httr"                  "hunspell"              "ids"                  
## [145] "igraph"                "infer"                 "insight"              
## [148] "ipred"                 "isoband"               "iterators"            
## [151] "janeaustenr"           "jpeg"                  "jquerylib"            
## [154] "jsonlite"              "keras"                 "kernlab"              
## [157] "KernSmooth"            "klaR"                  "KMsurv"               
## [160] "knitr"                 "ks"                    "kutils"               
## [163] "labeling"              "labelled"              "later"                
## [166] "lattice"               "latticeExtra"          "lava"                 
## [169] "lazyeval"              "leaps"                 "LearnBayes"           
## [172] "learnr"                "lhs"                   "lifecycle"            
## [175] "listenv"               "lme4"                  "lmtest"               
## [178] "locfit"                "lpSolve"               "ltsa"                 
## [181] "lubridate"             "magrittr"              "manipulateWidget"     
## [184] "maptools"              "markdown"              "MASS"                 
## [187] "Matrix"                "matrixcalc"            "MatrixModels"         
## [190] "matrixStats"           "maxLik"                "mclogit"              
## [193] "mclust"                "memisc"                "memoise"              
## [196] "methods"               "Metrics"               "mgcv"                 
## [199] "mi"                    "mice"                  "mime"                 
## [202] "miniUI"                "minqa"                 "misc3d"               
## [205] "miscTools"             "mlbench"               "mnormt"               
## [208] "modeldata"             "ModelMetrics"          "modelr"               
## [211] "modeltools"            "moments"               "multcomp"             
## [214] "multicool"             "munsell"               "mvtnorm"              
## [217] "nbconvertR"            "network"               "nlme"                 
## [220] "nloptr"                "NLP"                   "nnet"                 
## [223] "numDeriv"              "onehot"                "openssl"              
## [226] "openxlsx"              "parallel"              "parallelly"           
## [229] "parameters"            "parsnip"               "pbkrtest"             
## [232] "pcaPP"                 "pequod"                "performance"          
## [235] "pglm"                  "pillar"                "pkgconfig"            
## [238] "plm"                   "plot3D"                "plotmo"               
## [241] "plotrix"               "pls"                   "plyr"                 
## [244] "png"                   "polspline"             "pracma"               
## [247] "prettyunits"           "pROC"                  "processx"             
## [250] "prodlim"               "progress"              "promises"             
## [253] "proxy"                 "ps"                    "pscl"                 
## [256] "psych"                 "purrr"                 "quadprog"             
## [259] "quantmod"              "quantreg"              "questionr"            
## [262] "R.cache"               "R.methodsS3"           "R.oo"                 
## [265] "R.utils"               "R6"                    "rainbow"              
## [268] "randomForest"          "rappdirs"              "raster"               
## [271] "rbibutils"             "RColorBrewer"          "Rcpp"                 
## [274] "RcppArmadillo"         "RcppEigen"             "RCurl"                
## [277] "Rdpack"                "readr"                 "readxl"               
## [280] "recipes"               "rematch"               "rematch2"             
## [283] "renv"                  "repr"                  "reprex"               
## [286] "reshape2"              "reticulate"            "rgl"                  
## [289] "rio"                   "rJava"                 "rjson"                
## [292] "rlang"                 "rmarkdown"             "rmgarch"              
## [295] "Rmpfr"                 "rms"                   "rockchalk"            
## [298] "ROCR"                  "rpart"                 "rprojroot"            
## [301] "rsample"               "Rsolnp"                "rstudioapi"           
## [304] "rugarch"               "RUnit"                 "rvest"                
## [307] "s2"                    "sandwich"              "sass"                 
## [310] "scales"                "scatterplot3d"         "selectr"              
## [313] "sem"                   "sf"                    "shape"                
## [316] "shiny"                 "shinyjs"               "simfinR"              
## [319] "sjlabelled"            "sjmisc"                "sjPlot"               
## [322] "sjstats"               "SkewHyperbolic"        "slam"                 
## [325] "slider"                "sna"                   "SnowballC"            
## [328] "sourcetools"           "sp"                    "SparseM"              
## [331] "spatial"               "spatialreg"            "spd"                  
## [334] "spData"                "spdep"                 "splines"              
## [337] "SQUAREM"               "stabledist"            "stargazer"            
## [340] "statmod"               "statnet.common"        "stats"                
## [343] "stats4"                "stochvol"              "stringi"              
## [346] "stringr"               "styler"                "survival"             
## [349] "sys"                   "systemfit"             "tcltk"                
## [352] "TeachingDemos"         "tensorflow"            "tfruns"               
## [355] "TH.data"               "thief"                 "tibble"               
## [358] "tidymodels"            "tidyr"                 "tidyselect"           
## [361] "tidytext"              "tidyverse"             "timeDate"             
## [364] "timeSeries"            "tinytex"               "tm"                   
## [367] "tmvnsim"               "tokenizers"            "tools"                
## [370] "topicmodels"           "tree"                  "truncnorm"            
## [373] "truncreg"              "TSA"                   "tscount"              
## [376] "tseries"               "TTR"                   "tune"                 
## [379] "tzdb"                  "units"                 "urca"                 
## [382] "UsingR"                "utf8"                  "utils"                
## [385] "uuid"                  "vctrs"                 "VGAM"                 
## [388] "viridis"               "viridisLite"           "vroom"                
## [391] "warp"                  "webshot"               "whisker"              
## [394] "withr"                 "wk"                    "wooldridge"           
## [397] "workflows"             "workflowsets"          "XBRL"                 
## [400] "xfun"                  "xlsx"                  "xlsxjars"             
## [403] "xml2"                  "xtable"                "xts"                  
## [406] "yaml"                  "yardstick"             "zeallot"              
## [409] "zip"                   "zoo"
search()
## [1] ".GlobalEnv"        "package:stats"     "package:graphics" 
## [4] "package:grDevices" "package:utils"     "package:datasets" 
## [7] "package:methods"   "Autoloads"         "package:base"

También es posible instalar paquetes de otros sitios. Esto nos abre incluso a más variedad de funciones novedosas, sin embargo es también más riesgoso ya que probablemente sean paquetes menos validados. Afortunadamente, para instalar paquetes de otros sitios se requiere una función diferente, por lo que no es algo que harás accidentalmente. Por ahora, install.packages( ) nos es más que suficiente.

Algunos paquetes ocupan otros paquetes para funcionar, se dice que tienen “dependencias”. Otros paquetes son en realidad “familias” de paquetes relacionados. En ambos casos (dependencias / familias) notarás que al instalar un paquete se instalan en automático otros adicionales, es normal.

Un ejemplo de una familia de paquetes que además son muy útiles para manejo y visualización de datos, es tidyverse. Recomiendo que lo instales ya que nos servirá en este y otros ejercicios.

Instalación de tidyverse:

# Si estás trabajando en Google Colab, ya está instalado
# pero si estás en una instalación nueva de R entonces
# sí se requiere instalarlo la primera vez que se quiera usar

#install.packages("tidyverse")

Activación de tidyverse:

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.3     v dplyr   1.0.7
## v tidyr   1.1.3     v stringr 1.4.0
## v readr   2.0.0     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Además de un gran número de funciones especializadas para manejo de texto que nos agrega tidyverse, también agrega un operador nuevo e importante que vale la pena resaltar, dado que se usa en muchos códigos y se lo pueden topar al continuar estudiando por su cuenta. De hecho otros paquetes también instalan este operador conocido en inglés como “pipe” (pronunciado “paip”): %>%. “Tubería” sería algo cercano en español, pero se usa el término en inglés.

El operador “pipe” sirve para anidar funciones de manera más legible. Dado que es simplemente otra alternativa de anidar funciones, queda a consideración de cada quién si decide usarlo o no. Con el uso encontrarás las situaciones donde vale la pena. Aquí van unos ejemplos:

# sin pipes
round(3.1416, 1)
## [1] 3.1
# con un pipe
3.1416 %>% round(1)
## [1] 3.1
x <- 3
# sin pipes
round(exp((sqrt(x))), 1)
## [1] 5.7
# con pipes
x %>% sqrt() %>% exp() %>% round(1)
## [1] 5.7

Nota como en el segundo ejemplo podemos leer la instrucción de izquierda a derecha (lo normal al leer) y entender el orden en que se ejecutan las funciones. En el caso donde no usamos pipes, ocupamos leer la instrucción de los paréntesis más internos hacia los externos. Para quienes ya están acostumbrados a esa manera de leer las funciones, puede no haber mucha diferencia al anidar unas cuantas, pero en algún punto puede serles más claro utilizar pipes.

Por default, %>% manda el objeto o resultado que tiene a su izquierda, como primer argumento de lo que tenga a la derecha.
En caso de que requiramos que el objeto de la izquierda se utilice como un argumento diferente al primero de la función de la derecha, podemos utilizar un punto “.” para especificar donde queremos que se utilice. Un ejemplo sencillo:

1 %>% round(3.1416, digits = .)
## [1] 3.1