En este notebook vamos a ver algunas herramientas básicas de R
, como precalentamiento para arrancar el curso. Suponemos que nociones básicas de programación ya son conocidas, por lo que el foco está puesto en familiarizarse con la sintaxis. Para una introducción al uso de RStudio, refierance a los videos de la materia.
En R
hay una variedad extremadamente amplia de formatos posibles para las variables, como en cualquier lenguaje de programación moderno. Aquí vamos a enforcarnos en las más elementales. La función que nos indica qué tipo de variable estamos considerando es class
:
print(class(1))
[1] "numeric"
print(class('a'))
[1] "character"
print(class('abc'))
[1] "character"
print(class(TRUE))
[1] "logical"
print(class(FALSE))
[1] "logical"
La función print
aquí nos ayuda a imprimir en pantalla. En este ejemplo tenemos elementos numericos (1
), caracteres/strings (a
y abc
), y lógicos (TRUE
y FALSE
). Hay otros tipos de objetos, que pueden servir para representar matrices, datos, imágenes, geografías, grafos, modelos y muchas otras cosas.
Operaciones habituales como suma (+
), resta (-
), producto (*
), división (/
) y potencia (**
ó ^
) se utilizan como en cualquier calculadora.
(1+2**2)/25**.5
[1] 1
Las funciones sqrt(x)
(raíz cuadrada), exp
(exponencial), sin
,cos
(seno, coseno), entre otras, vienen incluidas por default:
sin(pi/4)**2 + cos(pi/4)**2
[1] 1
Las comparaciones las realizamos con los operadores mayor >
, menor <
, mayor o igual >=
, menor o igual <=
o idéntico ==
.
10>5
[1] TRUE
En R
no hay operaciones como +
que nos sirvan para unir caracteres. La función básica para este propósito es paste
:
paste('ari','el',sep='') # sep es el separador entre caracteres
[1] "ariel"
Para reemplazar partes del texto, usamos la función sub
(para reemplazar sólo una vez) o gsub
(para reemplazar todas las apariciones)
sub(pattern='ari',replacement = 'manu',x = 'ariel')
[1] "manuel"
gsub(pattern='o',replacement = 'a',x = 'nono')
[1] "nana"
Para partir caracteres tenemos la función strsplit
:
strsplit('ariel','i')
[[1]]
[1] "ar" "el"
Por último, tolower
y toupper
cambian todos los caracteres a minúscula o mayúscula respectivamente.
Los elementos lógicos típicamente son comparados con las operaciones AND / y (&
) y OR / ó (|
). Estas operaciones funcionan de forma similar a como funcionan las operaciones numéricas. La negación la realizamos con el signo de exclamación !
.
(TRUE | FALSE) & !FALSE
[1] TRUE
También podemos sumarlas y multiplicarlas, con lo cual se convertirán en números:
TRUE + TRUE + TRUE + TRUE*FALSE
[1] 3
Para cambiar un objeto de un formato a otro tenemos las funciones as.
:
print(class(as.character(1)))
[1] "character"
print(class(as.numeric('1')))
[1] "numeric"
print(class(as.logical(1)))
[1] "logical"
En general, distintos formatos incluiran funciones as.X, en la medida que esto sea posible y tenga sentido.
Para preguntarle a R
si un objeto es de un formato dado, tenemos las funciones que empiezan con is
. Algunos ejemplos:
is.integer(1)
[1] FALSE
is.integer(1.1)
[1] FALSE
is.numeric(pi)
[1] TRUE
is.character('a')
[1] TRUE
is.character(1)
[1] FALSE
is.logical(TRUE)
[1] TRUE
Además de estos formatos típicos, tenemos algunos valores especiales que sirven para situaciones particulares.
NA
indica la ausencia de un valor a representar. Este valor será muy común cuando trabajemos con datasets en los que haya datos faltantes. También aparecera cuando hagamos alguna operación restringida como 0/0
. Muchas operaciones al ser usadas con NA
retornan NA
. Otras pueden tener resultado igualmente. Detectamos si un objeto es NA
usando la función is.na
.NA + 10
[1] NA
NA*2
[1] NA
NA & TRUE
[1] NA
NA | TRUE
[1] TRUE
NA & FALSE
[1] FALSE
NULL
representa un elemento ausente. Aparecera cuando tengamos funciones que no retornen ningún objeto. También puede ser útil para inicializar vectores. Una característica interesante es que tiene longitud 0 (length(NULL)
=0). Otra forma de identificarlo es con la función is.null
. Con NULL
cualquier operación retorna un vector vacío. Los vectores vacíos son indicados con su tipo y un 0 entre paréntesis.NULL + 10
numeric(0)
NULL | FALSE
logical(0)
NULL & FALSE
logical(0)
Inf
representa el infinito. Puede ser positivo o negativo. Lo podemos considerar como un límite. En casos que la operación incluyendo Inf
tenga un resultado indeterminado, el resultado será NA
(por ejemplo Inf-Inf
). Para identificarlo, podemos usar la función is.finite
. Las operaciones que involucran infinito son similares a lo que resultaría de hacer un límite:Inf*2
[1] Inf
2^{-Inf}
[1] 0
La asignación de variables se realiza usando el operador =
, aunque también puede realizarse usando la flecha a izquierda <-
. Es una buena práctica usar sólo uno de estos dos operadores a lo largo del código.
x = 1
y <- 2
Las variables (y en general los elementos de R
) deben tener nombres que no incluyan números al principio (1x
no, x1
sí), símbolos pesos (x$2
no), arrobas (x@y
no) ni espacios (x y
no). Tampoco ninguno de los símbolos empleados para las operaciones. Esto se debe a que el compilador no puede decidir si es el nombre de una variable o una operación.
En R
llamamos vectores a tiras de elementos (numéricos o de otro tipo). Para construir uno, usamos la función c
, separando los elementos con comas:
z=c(1,2,3)
z
[1] 1 2 3
Para acceder a los elementos del vector usamos los corchetes []
. Los elementos se numeran desde 1
hasta la longitud del vector length(z)
. Podemos agarrar varios elementos indexando al vector con otro vector:
print(z[1])
[1] 1
print(z[c(2,3)])
[1] 2 3
También podemos acceder a los elementos de un vector mediante un vector lógico de igual longitud al vector:
z[c(TRUE,FALSE,TRUE)]
[1] 1 3
O podemos ponerle nombrs a los elementos del vector y usar sus nombres para indexar:
names(z) = c('a','b','c')
z['b']
b
2
A la inversa, podemos usar la indexación para cambiar un único elemento del vector (vale con cualquier tipo de indexación):
z['a']=10
Los vectores no tienen por qué sólo ser numerícos. Pueden incluir elementos lógicos o caracteres:
c('a','b','c')
[1] "a" "b" "c"
R
va a convertir vectores mixtos automáticamente al formato que considere más adecuado.
c(1,'b','c')
[1] "1" "b" "c"
c(1,TRUE,FALSE)
[1] 1 1 0
c('a',TRUE,FALSE)
[1] "a" "TRUE" "FALSE"
Cuando realizamos operaciones sobre un vector, hay que tener en cuenta que:
z+2
a b c
12 4 5
z*z
a b c
100 4 9
R
intentara ciclar la operación, dando un aviso o warning en caso de que las longitudes no sean múltiplos:c(z,z)*z
a b c a b c
100 4 9 100 4 9
c(z,1,2)*z
longer object length is not a multiple of shorter object length
a b c
100 4 9 10 4
Las operaciones lógicas &
y |
tienen un funcionamiento análogo al de la suma, multiplicación y demás operaciones numéricas.
Cuando consideramos otro tipo de función, hay que analizar en cada caso como se aplica al vector. Por ejemplo, si usamos la función paste
entre dos vectores los pega elemento a elemento:
paste(z,z)
[1] "10 10" "2 2" "3 3"
Pero la función que reemplazar un elemento en otro no puede ser usada con ambos elementos como vectores
# Esto va a usar el primer elemento y da un aviso
letras = c('e','i','o','u')
gsub('a',letras,'anana')
argument 'replacement' has length > 1 and only the first element will be used
[1] "enene"
# Esto va a usar el primer elemento y da un aviso
gsub(letras,'a','anana')
argument 'pattern' has length > 1 and only the first element will be used
[1] "anana"
# Esto sí funciona correctamente:
palabras = c('anana','baston','palabra')
gsub('a','u',palabras)
[1] "ununu" "buston" "pulubru"
Hay algunas formas estadarizadas de obtener vectores que ahorran tiempo:
X:Y
devuelve la secuencia de números enteros que comienza en X
y termina en Y
.1:10
[1] 1 2 3 4 5 6 7 8 9 10
seq(from,to,by,length.out)
devuelve una secuencia numérica que empieza en from
, termina en to
y avanza a paso by
. También puede indicarse la longitud length.out
en vez de el paso by
:seq(0,1,by=.1)
[1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9
[11] 1.0
letters[X:Y]
devuelve las letras del alfabeto en minúscula en las posiciones X
a Y
. LETTERS
hace lo mismo pero en mayúscula.letters[3:8]
[1] "c" "d" "e" "f" "g" "h"
La función rep
nos permite armar un vector repitiendo un mismo elemento (puede ser cualquier tipo de elemento):
rep('a',5)
[1] "a" "a" "a" "a" "a"
Hay varias operaciones que nos permiten operar sobre vectores que nos simplifican la escritura de otras
Podemos escribir la sumatoria de los elementos de un vector con sum
y la productoria con prod
. Veremos muchas más funciones como estas durante la materia.
sum(1:5)
[1] 15
Las funciones que vimos inicialmente se pueden aplicar a vectores, con los recaudos adecuados. El único agregado que mencionaremos aquí es que la función paste
permite colapsar una tira de caracteres:
paste(c('a','r','i'),collapse='.')
[1] "a.r.i"
Cuando queremos comparar muchos elementos lógicos a la vez las funciones todos (all
) y algun (any
) son muy útiles:
all(c(TRUE,TRUE,TRUE))
[1] TRUE
any(c(TRUE,FALSE,FALSE))
[1] TRUE
Aunque es válido para todos los objetos, en este apartado incluimos la posibilidad de agregar atributos a un objeto (en este caso un vector). Los atributos pueden ser útiles si, por ejemplo, estamos considerando unidades:
z
[1] 10 15
attr(,"units")
[1] "m"
Debemos especificar tanto el vector al cual le agregamos atributos (z
), como sus atributos (units
), y el valor del atributo (m
)
Las matrices en R
se definen mediante el comando matrix
:
A = matrix(c(1,2,3,
1,3,2,
3,1,2),nrow=3,ncol=3)
A
debemos indicar también la cantidad de filas nrow
y la cantidad de columnas ncol
. Los elementos de la matriz se incluyen como un vector de nrow*ncol
elementos. Podemos indicar si la matriz se completa por filas o por columnas con el argumento byrow
.
Podemos indicar nombres para las filas y columnas de la matriz con las funciones colnames
y rownames
:
colnames(A) = c('a','b','c')
rownames(A) = c('A','B','C')
A
Para indexar una matriz usamos dos números. El primero corresponde a la fila y el segundo a la columna:
A[1,2]
[1] 1
A['A','b']=100
A
a b c
A 1 100 3
B 2 3 1
C 3 2 2
Multiplicamos matrices usando %*%
(noten que *
hace producto elemento a elemento)
A%*%A
a b c
A 210 406 109
B 11 211 11
C 13 310 15
A*A
a b c
A 1 10000 9
B 4 9 1
C 9 4 4
y invertirlas usando solve
:
solve(A)
A B C
a -0.036036036 1.74774775 -0.81981982
b 0.009009009 0.06306306 -0.04504505
c 0.045045045 -2.68468468 1.77477477
La diagonal de una matriz la encontramos con diag
, el determinante lo calculamos con det
, y la traspuesta con t
.
det(A-t(A)) - sum(diag(A))
[1] -6
Para agregar una fila a una matriz, usamos la función rbind
. Para agregar una columna, cbind
:
A = matrix(1:3,nrow=1)
A = rbind(A,10:12)
print(A)
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 10 11 12
A = cbind(A,-2:-1)
print(A)
[,1] [,2] [,3] [,4]
[1,] 1 2 3 -2
[2,] 10 11 12 -1
Una operación que da por resultado una matriz, partiendo de dos vectores, es el producto exterior. Dados dos vectores \(v\) y \(w\) con elementos \(i=1,\dots,n\), su producto exterior es una matriz \(A\) con componentes \(A_{ij} = v_i w_j\). En R
lo calculamos con outer
outer(1:3,-1:1)
[,1] [,2] [,3]
[1,] -1 0 1
[2,] -2 0 2
[3,] -3 0 3
Por último, las dimensiones de la matriz (y de otros objetos) la obtenemos mediante la función dim
dim(A)
[1] 2 4
Un paso más allá de las matrices, tenemos los arrays. Los arrays son estructuras similares a las matrices pero con más dimensiones (si pensamos en una matriz de \(R^{n \times m}\), un array podría estar en \(R^{n \times m \times l \times...}\)). Los definimos mediante la función array
A = array(1:27,dim=c(3,3,3))
print(A)
, , 1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
, , 2
[,1] [,2] [,3]
[1,] 10 13 16
[2,] 11 14 17
[3,] 12 15 18
, , 3
[,1] [,2] [,3]
[1,] 19 22 25
[2,] 20 23 26
[3,] 21 24 27
Una lista es muy similar a un vector, en el sentido de que en cada elemento contiene un objeto. Sin embargo, tienen varias diferencias fundamentales:
A diferencia de un vector, que es de la clase que sean los objetos que contiene, una lista es de clase list. Dos listas no se pueden sumar ni multiplicar
Cada elemento de la lista puede contener un objeto de cualquier tipo: un número, letra, vector, matriz o otra lista.
Creamos una lista con la función list
. Podemos indicar desde el principio elementos, o agregarlos luego:
L = list('a'=1,'b'=c(1,2,3),'c'=matrix(0,2,2))
L
$a
[1] 1
$b
[1] 1 2 3
$c
[,1] [,2]
[1,] 0 0
[2,] 0 0
Podemos indexar la lista usando el signo $
o el nombre. Algo a tener cuidado: si indexamos usando corchetes, para obtener el elemento necesitamos un doble corchete. Si no, lo que obtenemos es una lista con ese elemento dentro.
L$b
[1] 1 2 3
L[['b']]
[1] 1 2 3
L[[2]]
[1] 1 2 3
L['b']
$b
[1] 1 2 3
Podemos agregar elementos a la lista de forma similar a los otros casos:
L$d = list(L)
L[['e']] = 10
L
$a
[1] 1
$b
[1] 1 2 3
$c
[,1] [,2]
[1,] 0 0
[2,] 0 0
$d
$d[[1]]
$d[[1]]$a
[1] 1
$d[[1]]$b
[1] 1 2 3
$d[[1]]$c
[,1] [,2]
[1,] 0 0
[2,] 0 0
$e
[1] 10
Por último, para desarmar una lista usamos unlist
, que la convierte en un vector
unlist(L,recursive = FALSE) # A tener en cuenta que desarma TODO, la matriz también
$a
[1] 1
$b1
[1] 1
$b2
[1] 2
$b3
[1] 3
$c1
[1] 0
$c2
[1] 0
$c3
[1] 0
$c4
[1] 0
$d
$d$a
[1] 1
$d$b
[1] 1 2 3
$d$c
[,1] [,2]
[1,] 0 0
[2,] 0 0
$e
[1] 10
Las funciones union
,intersect
,unique
,setdiff
y is.element
, nos permiten pensar a dos vectores como si fueran conjuntos.
unique
nos retorna un nuevo vector con un único elemento de cada tipo.a = c(1,1,1,2,3,3)
unique(a)
union
y intersect
nos devuelven union e intersección respectivamente. Noten que el resultado tienen un único elemento de cada tipo. setdiff
devuelve la resta de conjuntos.b = c(3,3,4,4,5,5,6,6)
union(a,b)
intersect(a,b)
is.element(a,b)
devuelve un vector de la longitud de a
, diciendo si ese elemento está presente o no en b
is.element(a,b)
Dado un vector, puede interesarnos ordenarlo, y buscar algún elemento en él.
Para ordenar un vector, R
incluye la función sort
que nos devuelve el mismo vector, pero ordenado. Otra opción es usar la función order
que nos devuelve un vector con los índices de los elementos ordenados:
x = c(2,3,1,0,5,4,7)
print(sort(x))
print(order(x))
## Para comparar:
print(sort(x))
print(x[order(x)])
Otra función interesante es la función rev
, que nos permite invertir un vector:
rev(1:10)
Para buscar la posición de un elemento en particular, tenemos varias opciones:
==
puede funcionar. Combinandola con la función which
podemos encontrar el o los índices de interés:x = c(1:10,1:10)
print(x==3)
print(which(x==3))
grep(pattern,x)
. Nos devuelve las posiciones en las que encontro el texto que indicamos en pattern
(aunque haya otro texto)x = c('ariel','juan','esteban','roman','rafael')
grep('an',x)
Por último, si queremos comparar dos vectores para encontrar coincidencias entre ellos, la función match
es muy útil:
a = c(1,10,22,33,123,2,2)
b = c(1,0,22,123,2,10)
print(match(a,b))
print(match(b,a))
La función retorna NA
en los casos que no encontró un match.
En R
definimos las funciones mediante la sentencia function
. A function
le indicamos cuales serán los argumentos de la función. Noten que una función podría no tener argumentos, en caso de no necesitar nada del medio externo para funcionar.
una_funcion = function(x,y,z){
# Alguna operación
}
En R
las funciones no operan sobre variables externas, por lo que es necesario que retornen algo con lo que podamos trabajar:
x = 1
y = 2
z = 3
una_funcion = function(x,y,z){
x = x+y+z
}
una_funcion(x,y,z) # No va a hacer ningun cambio en nada
print(x)
una_funcion = function(x,y,z){
x = x+y+z
return(x)
}
x = una_funcion(x,y,z) # Ahora sí
print(x)
Dentro de la función se crea un ambiente ( enviroment ) propio. Todas las operaciones realizadas dentro de la función no afectan a los valores externos. En dirección contraria, si una función usa una variable que no fue pasada como argumento pero se encuentra en el enviroment externo, R
la tomará de allí:
a = 10
funcion = function(x){
return(x+a)
}
print(funcion(10))
a=20
print(funcion(10))
Los loops de control for
, while
y if
están incluido en R
. Se usan bajo la siguiente sintaxis:
for
: El for
lleva por argumento una sentencia del tipo a in b
, donde a
es el nombre que identificará a la variable dentro del loop, y b
es un conjunto del cual se toman elementos en orden (no necesariamente numérico)a = 0
for(x in 1:10){
a = a +x
print(a)
}
a = ''
for(x in letters[1:10]){
a = paste(a,x)
print(a)
}
if
: El if
lleva por argumento una sentencia lógica. Si el valor es TRUE
, el contenido del if
se ejecuta. El loop else
puede encadenarse con el if
:a = sqrt(25/3)*45/sin(22) + exp(-3)/log(2)
if(a>0){
print(1)
}else if(a<2){
print(2)
}else{
print(3)
}
while
: El while
también lleva una sentencia lógica por argumento. El contenido del while
se ejecuta una y otra vez mientras que el valor de la sentencia sea TRUE
a=.1
pasos = 0
while(a<1){
print(a)
pasos = pasos +1
a = a + sin(a)
}
print(pasos)
print(a)
apply
En R
tenemos un comando (que es muy útil dado que su sintaxis aparece luego en muchas extensiones) que nos permite aplicar una misma función sobre un vector o una lista de elementos.
El comando se llama apply
, y tiene variantes como lapply
(pensado para retornar listas), sapply
(que retorna listas o matrices según sea posible) y vapply
(que retorna el resultado de forma pre-especificada).
Todos requieren especificar una función y un objeto que pueda “recorrerse”.
Un ejemplo de apply
:
A = matrix(c(1,2,3,4,5,6),nrow=2,byrow=TRUE)
# Sobre la matriz A, aplico sobre su primer dimensión (filas) y sobre su segunda dimensión (columnas), la función sum
apply(A,1,sum)
apply(A,2,sum)
Un ejemplo de lapply
:
A = matrix(c(1,2,3,4,5,6),nrow=2,byrow=TRUE)
# En este caso, lo que comanda es la cantidad de filas que tiene A (nrow(A))
lapply(1:nrow(A),function(fila) rep(max(A[fila,]),sum(A[fila,])))
El formato data.frame es la representación más básica de lo que vamos a considerar un set de datos estructurados. Está un paso por arriba de la matriz, ya que permite considerar a la vez datos de distintos formatos, y acceder a sus columnas de una forma más orgánica.
Definimos un data.frame de forma similar a como construimos una lista
Noten que podemos completar el data.frame con tiras de una misma longitud, o con elementos aislados. La presentación en pantalla del data.frame ya remite más claramente a lo que esperamos de una tabla de datos.
Para acceder a los elementos del data.frame, podemos usar un formato estilo matriz, o el simbolo $
:
df$letras[2]
[1] "b"
Noten también que a diferencia de lo que ocurría con la matriz, en este caso cada columna tiene un formato distinto:
class(df$letras)
[1] "character"
attach
y detach
Estos dos comandos nos permiten convertir las columnas de un data.frame en variables en nuestro entorno (o sacarlas, en el caso de detach
). Esto es especialmente útil cuando realizamos un análisis de datos
attach(df)
numeros
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
[20] 20
detach(df)
numeros
Error: object 'numeros' not found
help
y otros extrasPara consultar información sobre una función podemos usar help
o ?
de la siguiente forma:
?sqrt
help(sqrt)
Para eliminar variables del entorno podemos usar rm
. ls
me dice los nombres de todas las variables del entorno.
A = 10
rm(A) # Ahora A ya no está
rm(list=ls()) # Ahora borré todo
Lo que mostramos aquí es lo más elemental de `R``. Muchísimas funcionalidades pueden ampliarse mediante paquetes externos que incluyen nuevas funciones y tipos de objetos. Para instalar el paquete MASS por ejemplo hacemos:
install.packages('MASS')
y luego para cargarlo:
library(MASS)
require(MASS)
Mientras que library
vuelve a cargar el paquete siempre, require
sólo lo carga si aun no fue cargado. Una vez cargado el paquete, todas las funciones que incluye quedan disponibles. Por otro lado, podemos acceder directamente a una función especifica mediante el símbolo ::
. Por ejemplo
MASS::fractions(0.5)