2 Aspectos básicos de R
Fecha última actualización: 2024-07-14
Instalación/carga librerías utilizadas
if (!require(utils)) install.packages('utils')
library(utils)
if (!require(tidyverse)) install.packages('tidyverse')
library(tidyverse)
2.1 Tipos y estructuras de datos
El operador de asignación en R
es el símbolo =
o más comúnmente <-
. Por ejemplo a <- 5
asigna a a
el valor 5. Los operadores =
y <-
producen en la mayoría de los casos el mismo resultado, pero no son idénticos y en ciertas ocasiones usar uno en lugar de otro puede producir un error.
Si se realiza mediante código cualquier operación o llamada a una función y no se asigna a ninguna variable, el resultado se imprime y no se almacena.
Tipos de datos
Tipo | Ejemplos |
---|---|
character |
|
factor |
|
numeric |
|
logical |
|
dato no disponible |
|
date |
|
La coerción nos permite cambiar los tipos de los datos. Por ejemplo as.Date("2022-12-31")
pasa el campo de texto “2022-12-31” a un dato tipo fecha. Otros ejemplos de funciones de coerción son as.numeric()
, o as.character()
.
Cuando leemos los datos de una tabla, debemos prestar especial atención a examinar los tipos asignados a los campos y comprobar, en particular, que los campos con números y fechas se han identificado como tales. En caso contrario, lo primero que hay que hacer es resolver esa incidencia.
El tipo factor
se utiliza para clasificar, y se asocia, a las denominadas variables categóricas. Por ejemplo, en una tabla con información de países, la información sobre continentes es típicamente una variable categórica tipo factor
. Para acelerar su gestión, las variables tipo factor
se almacenan internamente como una mezcla entre caracteres y números, y por tanto no son lo mismo que las variables tipo character
y esto puede ocasionar algún problema al comparar ambos tipos de variables.
Manipulación de strings
Un caso especialmente importante de tipo de datos son los strings
de caracteres que con frecuencia tenemos que manipular para obtener los resultados que necesitamos. Por ello vamos a ver a continuación algunos procesos básicos en la manipulación de strings
.
Función paste
y paste0
La función paste
permite combinar texto y variables para construir un string
## [1] "la velocidad es 5 km2"
La función paste
inserta espacios blancos entre los strings o variables que combina. paste0
hace lo mismo que paste
pero sin insertar espacios en blanco.
Extraer una palabra de un string
Cuando nuestro string
es una frase con palabras separadas por espacios en blanco, la función word
de la librería tidyverse
nos permite extraer la palabra que ocupa una posición determinada en la frase. Por ejemplo, el siguiente código extrae la tercera palabra del string s
declarado antes.
## [1] "un"
Distancia entre strings
con adist()
En ocasiones, tenemos que comparar strings
que son parecidos pero no idénticos. Por ejemplo, podemos querer que se reconozcan como similares los strings “Antonio”, “San antonio” y “S. Antonio”. La función adist
de la librería utils
nos permite calcular una distancia entre dos strings
que vale cero cuando son idénticos y que va siendo mayor cuanto más difieran. Esta función tiene diversos parámetros para ajustar como queremos comparar los strings
. Utilizaremos en el futuro esta función para obtener, dado un string
, el más cercano a él en un vector de strings
.
## [,1]
## [1,] 5
## [,1]
## [1,] 3
## [,1]
## [1,] 4
# comparación del primer string con un trozo del segundo
adist("Antonio","S. antonio",ignore.case=TRUE,partial=TRUE)
## [,1]
## [1,] 0
# con la opción partial=TRUE, el orden de los strings afecta al resultado
adist("S. antonio","Antonio",ignore.case=TRUE,partial=TRUE)
## [,1]
## [1,] 3
Manejo de fechas
Dado que, con frecuencia, los datos tienen asociadas fechas, mostraremos a continuación algunos ejemplos de manejo de formato de fechas:
## [1] "2022-12-25"
## [1] "2022-12-25"
## [1] "domingo, diciembre, 25, 22"
## [1] "do., dic., 25, 2022, día dentro de la semana: 7"
## [1] "Nº semana del año: 51, día del año : 359"
Para más información sobre los formatos de fecha ejecutar en la consola ??strptim
y elegir la opción base::format.POSIXct
.
Hay programas que codifican las fechas como números a partir de una fecha de referencia (Por ejemplo en EXCEL la fecha de referencia es “1899-12-30”). Podemos pasar esta codificación numérica al formato fecha con la instrucción:
## [1] "1899-12-30"
## [1] "2023-03-15"
En ocasiones, las fechas asociadas a los datos vienen dadas por meses, por ejemplo “2008-02” o por trimestres, por ejemplo “2008 Q2”. Para manejar fechas de esta manera la librería lubridate suministra algunas herramientas útiles. Veamos algunos ejemplos:
## [1] "2008-02-01"
## [1] "2008-02-29"
## [1] "2008-02-01"
## [1] "2008-02-01"
## [1] "2008-02-01"
## [1] "2008-04-01"
## [1] "2008-06-30"
## [1] "2008-04-01"
Vectores
Creación de un vector usando fechas consecutivas
## [1] "2022-12-31" "2023-01-01" "2023-01-02" "2023-01-03"
Con frecuencia es necesario crear un vector de fechas espaciadas por meses, semanas, etc.. Esto lo podemos conseguir con la función seq
. Por ejemplo, veamos como se construye un vector con los primeros seis meses del año 2023
## [1] "2023-01-01" "2023-02-01" "2023-03-01" "2023-04-01" "2023-05-01"
## [6] "2023-06-01"
Reemplazar elementos de un vector
Vamos a usar la función gsub
para reemplazar elementos de un vector.
## [1] "your book" "your car" "your car"
## [1] "my book" "my tree" "your tree"
Encontrar la posición de un valor en un vector
Vamos a usar la función which
para encontrar la posición de un elemento en un vector
## [1] 2
Dataframes
y Tibbles
Un dataframe
es una matriz que puede combinar diferentes tipos de datos, por ejemplo fechas y valores numéricos. La estructura tibble
es una versión mejorada de dataframe
que se encuentra en la librería tidyverse
. En este curso utilizaremos principalmente esta estructura de datos para manejar tablas. De hecho, salvo indicación de lo contrario, cuando hablemos en el curso de tablas, nos estaremos refiriendo a estructuras tibbles
. Cuando hablemos de campos o variables nos estaremos refiriendo a las columnas (col) de la tabla y cuando hablemos de registros, nos estaremos refiriendo a las filas (row) de la tabla.
2.1.0.1 Creación manual de un tibble
tb <- tibble(
date=as.Date("2022-12-31")+0:5,
value=c(9,6,1,7,5,8))
tb # impresión por consola de tb
## # A tibble: 6 × 2
## date value
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## 4 2023-01-03 7
## 5 2023-01-04 5
## 6 2023-01-05 8
## [1] "El segundo valor del campo date del tibble es 2023-01-01"
## [1] "El tercer valor del campo value del tibble es 1"
## [1] "El tercer valor del campo value del tibble es 1"
2.1.0.2 Acceso primeros y últimos valores deltibble
## # A tibble: 3 × 2
## date value
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## # A tibble: 4 × 2
## date value
## <date> <dbl>
## 1 2023-01-02 1
## 2 2023-01-03 7
## 3 2023-01-04 5
## 4 2023-01-05 8
Manejo básico de filas y columnas
## [1] 6
## [1] 2
## # A tibble: 3 × 2
## date value
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## # A tibble: 6 × 1
## value
## <dbl>
## 1 9
## 2 6
## 3 1
## 4 7
## 5 5
## 6 8
## # A tibble: 3 × 1
## value
## <dbl>
## 1 9
## 2 6
## 3 1
## [1] "date" "value"
## # A tibble: 6 × 2
## date_new value_new
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## 4 2023-01-03 7
## 5 2023-01-04 5
## 6 2023-01-05 8
2.1.0.3 Añadir un registro (fila) a un tibble
## # A tibble: 7 × 2
## date value
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## 4 2023-01-03 7
## 5 2023-01-04 5
## 6 2023-01-05 8
## 7 2023-01-25 11
Concatenar los registros de varios tibbles
La función rbind()
nos permite concatenar los registros de varios tibbles
que tengan los mismos campos. Veamos un ejemplo:
tb1 <- tibble(
date=as.Date("2022-12-31")+0:2,
value=c(9,6,1))
tb2 <- tibble(
date=as.Date("2022-12-31")+3:5,
value=c(7,5,8))
rbind(tb1,tb2)
## # A tibble: 6 × 2
## date value
## <date> <dbl>
## 1 2022-12-31 9
## 2 2023-01-01 6
## 3 2023-01-02 1
## 4 2023-01-03 7
## 5 2023-01-04 5
## 6 2023-01-05 8
2.1.0.4 Añadir un campo (columna) a un tibble
Para añadir un campo a untibble
basta crear un vector del tamaño del numero de filas deltibble
y añadir el campo altibble
## # A tibble: 6 × 3
## date value new_value
## <date> <dbl> <int>
## 1 2022-12-31 9 1
## 2 2023-01-01 6 2
## 3 2023-01-02 1 3
## 4 2023-01-03 7 4
## 5 2023-01-04 5 5
## 6 2023-01-05 8 6
Cambiar un valor en todo el tibble
## # A tibble: 6 × 3
## date value new_value
## <date> <dbl> <int>
## 1 2022-12-31 9 1
## 2 2023-01-01 6 2
## 3 2023-01-02 1 3
## 4 2023-01-03 7 4
## 5 2023-01-04 NA NA
## 6 2023-01-05 8 6
2.1.0.5 Seleccionar registros usando slice
## # A tibble: 3 × 3
## date value new_value
## <date> <dbl> <int>
## 1 2023-01-01 6 2
## 2 2023-01-02 1 3
## 3 2023-01-03 7 4
Hacer una operación en todos los elementos de campos seleccionados
Vamos a convertir en tipo character
todos los valores de los campos value
y new_value
## # A tibble: 6 × 3
## date value new_value
## <date> <chr> <chr>
## 1 2022-12-31 9 1
## 2 2023-01-01 6 2
## 3 2023-01-02 1 3
## 4 2023-01-03 7 4
## 5 2023-01-04 <NA> <NA>
## 6 2023-01-05 8 6
la función mutate
pertenece a la librería dplyr
y se verá en detalle más adelante
Eliminar un registro de un tibble
## # A tibble: 5 × 3
## date value new_value
## <date> <dbl> <int>
## 1 2022-12-31 9 1
## 2 2023-01-02 1 3
## 3 2023-01-03 7 4
## 4 2023-01-04 NA NA
## 5 2023-01-05 8 6
2.1.0.6 Imprimir información de un tibble
Además de ejecutar el nombre del tibble
en la consola, otras formas de obtener información sobre el tibble
por consola son:
## # A tibble: 6 × 3
## date value new_value
## <date> <dbl> <int>
## 1 2022-12-31 9 1
## 2 2023-01-01 6 2
## 3 2023-01-02 1 3
## # ℹ 3 more rows
## tibble [6 × 3] (S3: tbl_df/tbl/data.frame)
## $ date : Date[1:6], format: "2022-12-31" "2023-01-01" ...
## $ value : num [1:6] 9 6 1 7 NA 8
## $ new_value: int [1:6] 1 2 3 4 NA 6
## date value new_value
## Min. :2022-12-31 Min. :1.0 Min. :1.0
## 1st Qu.:2023-01-01 1st Qu.:6.0 1st Qu.:2.0
## Median :2023-01-02 Median :7.0 Median :3.0
## Mean :2023-01-02 Mean :6.2 Mean :3.2
## 3rd Qu.:2023-01-03 3rd Qu.:8.0 3rd Qu.:4.0
## Max. :2023-01-05 Max. :9.0 Max. :6.0
## NA's :1 NA's :1
Listas
Una lista es una colección de objetos que pueden tener estructuras y tamaños distintos. Nosotros haremos poco uso de las listas en este curso dado que nuestra principal fuente de información van a ser tablas de datos que vamos a manejar con tibbles
.
## $date
## [1] "2022-12-25"
##
## $value1
## [1] 1 2 3
##
## $value2
## [1] 1 2 3 4
2.2 Funciones en R
Las funciones permiten simplificar y organizar el desarrollo de software empaquetando código para ser utilizado posteriormente de forma compacta y sencilla. En R
, las funciones se suelen escribir en ficheros con extensión .R
. Para poder usar las funciones creadas en un fichero .R
desde cualquier código R
hay que poner al principio de dicho código R
la instrucción
En general, las funciones poseen unos parámetros de entrada, realizan algún tipo de operación y devuelven algo. La forma general de definir una función es
A continuación crearemos una función para sumar 2 números y la utilizaremos para calcular 2+3
## [1] 5
La mayor parte de las funciones de las librerías estándar como paste
, as.Date
, etc.., cuando reciben como argumento un vector, devuelven otro vector aplicando la función a cada elemento del vector. Por ejemplo:
## [1] "la función ym() convierte 2008-01 en 2008-01-01"
## [2] "la función ym() convierte 2014-10 en 2014-10-01"
## [3] "la función ym() convierte 2023-12 en 2023-12-01"
De la misma manera, cuando la función recibe 2 argumentos, si estos argumentos son vectores, entonces la función puede devolver un dataframe
con el resultado de aplicar la función a cada combinación de los elementos de ambos vectores. Veamos un ejemplo con la función adist
para comparar strings:
## [,1] [,2] [,3]
## [1,] 3 6 9
## [2,] 4 5 7
Esta capacidad de las funciones para actuar en función del tipo de argumento que recibe depende de como se haya implementado la función.
Estructuras de control
Las siguientes estructuras de control permiten controlar el flujo de ejecución de un código:
if
-else
permite control el flujo a través de una condición.for
genera un bucle con un iteradorwhile
genera un bucle con una condición de paradabreak
fuerza la terminación de un buclenext
fuerza el paso a la siguiente iteración de un bucle.
Operadores lógicos
==
idénticox | y
x OR yx & y
x AND y!x
not xisTRUE(x)
comprueba si x es TRUEis.na(x)
comprueba si x es NA
Ejemplos de funciones
Vamos a implementar una función que calcule la media de un vector de números dejando fuera los posibles valores no disponibles NA
MiMedia <- function(V){
suma=0
Nvalores=0
for(i in 1:length((V))){
if(is.na(V[i])==TRUE) next
suma=suma+V[i]
Nvalores=Nvalores+1
}
return(suma/Nvalores)
}
V <- c(1.,NA,2.,3.)
MiMedia(V)
## [1] 2
Este mismo resultado se puede obtener llamando a la función mean
pero previamente omitiendo los NA
del cálculo.
## [1] 2
La función na.omit
se usa mucho cuando queremos hacer cálculos numéricos en vectores que pueden contener NA
.
Vamos ahora a implementar una función para calcular la primera posición en un vector de strings, sV
donde se encuentra el string s
. Si no se encuentra el string se devuelve NA
FindString <- function(s,sV){
i=1
result<-NA
while(i<=length(sV)){
if(s==sV[i]){
result <- i
break
}
i=i+1
}
return(result)
}
sV <- c("abc","cde","efg")
FindString("cde",sV)
## [1] 2
## [1] NA
Este mismo resultado se puede obtener directamente usando la función which
, con la salvedad de que si no encuentra el string devuelve un cero en lugar de NA
:
## [1] 2
## integer(0)
El ámbito de las funciones
Las funciones en cualquier lenguaje son la base para compartir código y funcionalidades entre programas y usuarios. En un primer nivel las funciones en R
se almacenan en ficheros
con extensión .R
. Por ejemplo nosotros hemos creado el fichero utilidades.R donde hemos implementado algunas funciones que necesitaremos usar. Para usar en mi código las funciones implementadas en utilidades.R
pongo antes de usar las funciones
la instrucción :
En un segundo nivel las funciones se empaquetan en librerías que podemos compartir con otros usuarios, habitualmente a través de un repositorio personal usando GitHub o del repositorio CRAN. Hay miles de librerías en R
y un problema que surge con cierta frecuencia es que varias librerías que usamos al mismo tiempo tienen funciones que se llaman igual pero hacen cosas distintas.
Normalmente, por defecto, la función que usa el código es la de la última librería que se ha cargado, pero a veces, la última que se cargó no es la que nos interesa utilizar en ese momento. Por ejemplo la librería dplyr
tiene funciones como filter()
o rename()
que entran fácilmente en conflicto con funciones
llamadas igual creadas por otras librerías. Estos conflictos generan fácilmente errores en la ejecución de los scripts. Cuando esto se produce, lo que hay que hacer es especificar en la llamada a la función la librería que queremos usar. Por ejemplo,
cuando llamamos a la función dplyr::filter()
estaremos especificando que queremos usar la función filter()
de la librería dplyr
. De esta forma evitamos estos posibles conflictos. Lo ideal sería en cualquier llamada a una función,
especificar la librería usada, pero esto no se suele hacer porque alarga mucho la escritura de código. Pero si queremos publicar nuestro código en el repositorio CRAN nos obligarán a hacerlo para evitar conflictos entre las librerías. Lo normal es cargar las librerías en orden inverso a sus importancia. Por ejemplo tidyverse
es muy importante y debe ser de las últimas en cargar.
Hay tanta gente que trabaja en R
que, con frecuencia, nos encontramos que la misma funcionalidad está implementada en varias librerías. Para decidir que librería utilizar podemos combinar dos criterios: el primero es el número de descargas que ha tenido la librería en CRAN y el segundo es si la librería ha sido actualizada recientemente.
La forma más inmediata de obtener ayuda sobre el uso de una función es ejecutar en la consola el comando help(NombreFuncion)
La familia de funciones apply
Supongamos que tenemos 2 vectores de strings, sV1
y sV2
que queremos comparar. Usando la función FindString(s,sV)
implementada anteriormente podríamos hacer un bucle y recorrer todos los elementos de sV1
y compararlos con sV2
. Sin embargo hay una forma más eficiente de hacer esta comparación usando la funciones de la familia apply
que permiten hacer bucles y otro tipo de operaciones anidadas de forma eficiente. Por ejemplo, las funciones sapply
y lapply
se pueden usar para hacer un bucle aplicando la función FindString(s,sV)
. La diferencia entre ellas es que lapply
devuelve una lista y sapply
un vector. Veamos como se aplica sapply
para aplicar, con una sola instrucción, la función FindString(s,sV)
a todos los elementos de sV1
## abc cde efg
## 2 3 1
Los parámetros de sapply()
son : (1) el vector sV1
, (2) la función FindString
que actúa sobre los elementos de sV1
y (3) sV=sV2
el segundo argumento de la función FindString
.
Las funciones de la familia apply
permiten hacer operaciones más complejas que no vamos a estudiar en este curso.
El operador pipe %>%
o |>
Algunos paquetes de R
tienen implementado el operador pipe %>%
(también denotado por |>
) que permite concatenar las llamadas a funciones de forma más clara y sin necesidad de usar paréntesis anidados. Es decir, si normalmente escribimos la concatenación como
función2(función1(x)))
usando el operador pipe %>
, la misma llamada se escribiría como:
Como veremos, en muchas ocasiones, esta forma de escribir la llamada a las funciones concatenando la salida de una operación con la entrada de la siguiente, resulta más práctica y natural que la forma habitual que presenta el problema de que lo último que hacemos (la llamada a la última función) es lo primero que tenemos que escribir, es decir, escribimos en el orden opuesto al que vamos haciendo las operaciones.
Referencias
[Bo] Juan Bosco Mendoza. R para principiantes.
[Pe15] Roger D. Peng. R Programming for Data Science, Lulu, 2015.