3.2 Vectores y operaciones
Quizá ya hayas escuchado lo siguiente en algún lado: R es un lenguaje vectorial.
El comportamiento al que hace referencia esta frase será de mucha ayuda en la construcción de rutinas avanzadas de programación que veremos más adelante, por ahora la implicación más relevante reside en el hecho de que la estructura básica en R serán justamente vectores.
3.2.1 Vectores atómicos
En todos los lenguajes de programación se tienen diferentes tipos de datos primitivos; por ejemplo en Java se tiene int
, long
, char
, byte
, etc. Para Python: String
, Float
, Boolean
, entre otros. En R se tienen 6 tipos de datos primitivos, también conocidos como “atómicos”. En R, en general todo estará compuesto de vectores o de estructuras hechas con vectores y los datos primitivos no son la excepción, ya que estos también son vectores.
- Lógicos (boolean):
TRUE
,FALSE
- Enteros (integer):
5
- Double (doble):
5.54
- Es interesante preguntarse el porqué, en general, un valor en coma flotante se le llama (traducido) doble.
- En el siguiente enlace se hace este interrogante en la página Quora.
- Los enteros y double son categorizados como vectores numéricos(numeric).
- Caracteres (character):
"word"
- Complejos (complex):
5 + 7i
- Crudos o sin procesamiento (raw):
raw(5)
- Este tipo de dato se usa para representar una secuencia de bytes.
- Este tipo de dato, así como los complejos generalmente no se utilizan.
- En el libro A Data Scientist’s Guide to Acquiring, Cleaning, and Managing Data in R de Buttrey y Whitaker se muestra un ejemplo del uso de este tipo de vectores.
NOTA: A partir de aquí, véase el uso de distintas funciones.
Existen algunas funciones de R para saber el tipo de dato que se está usando: typeof()
, class()
y mode()
. Las tres funciones tienen diferentes criterios para determinar que tipo de vector atómico es con el que se está tratando; typeof()
identifica el tipo de dato visto desde el punto de vista de R y es la función más usada,mode()
es compatible con S y class()
identifica el tipo de datos visto desde una POO en R (específicamente el sistema S3).
cat(paste(class(TRUE),
class(5),
typeof(5.54),
typeof(5L),
mode("word"),
class(charToRaw("5")),
typeof(5 + 7i) ,sep = "\n"))
logical
numeric
double
integer
character
raw
complex
En la siguiente tabla se muestra las posibles salidas con todas las variables de prueba.
typeof() | class() | mode() | |
---|---|---|---|
TRUE | logical | logical | logical |
5 | double | numeric | numeric |
5.4 | double | numeric | numeric |
5L | integer | integer | numeric |
word | character | character | character |
charToRaw(“5”) | raw | raw | raw |
5 + 7i | complex | complex | complex |
Con lo anterior surge la siguiente pregunta: ¿Hay alguna diferencia entre un vector de tipo integer
y un tipo double
?
En general, cuando se están trabajando con datos de punto flotante, se esta considerando una aproximación del verdadero número, tanta aproximación como lo permita la memoria, por lo que pueden existir errores de precisión. De hecho, una manera más adecuada de comparar valores con punto decimal sería utilizando la función dplyr::near()
en lugar de ==
. Después se tratará el tema de las funciones.
Otro punto interesante es que, dependiendo del tipo de vector, se tiene un distinto representante para los valores faltantes (Missing values); para un entero se tiene NA
pero con un vector tipo double
se tienen NA
, NaN
, Inf
y –Inf
. Un ejemplo de esto se puede obtener al dividir distintos números entre 0.
- Para un profundizar en esto, se puede ver la documentación oficial.
- Algunos enlaces útiles sobre los vectores numéricos:
3.2.2 Otros tipos de datos
Además de los vectores atómicos, se tienen otro tipo de vector llamado factor. Un factor es un tipo especial de dato en el cual se puede establecer una jerarquía en variables categóricas.
Supongase que se tiene una variable correspondiente al tamaño de un objeto donde los posibles valores son “Big”, “Little”, “Medium”, “Jumbo”. Intuitivamente se sabe que esta variable es categórica ordinal por lo que se desearía dejar establecido esta jerarquía en el vector que contenga esta información.
factor(c("Big", "Little", "Medium","Jumbo"))
[1] Big Little Medium Jumbo
Levels: Big Jumbo Little Medium
Como se puede observar, se tiene un atributo (más adelante se verán los atributos de un vector) indicando los niveles del factor. Los niveles se establecieron en orden alfabético pero sin ningún orden realmente. Para establecer los niveles se puede dar este atributo directamente desde la creación o modificarlo después de la creación del factor.
factor(c("Big", "Little", "Medium","Jumbo"), levels = c("Little", "Medium", "Big", "Jumbo"))
[1] Big Little Medium Jumbo
Levels: Little Medium Big Jumbo
De manera análoga, se puede dar el orden a los niveles.
factor(c("Big", "Little", "Medium","Jumbo"), levels = c("Little", "Medium", "Big", "Jumbo"), ordered = TRUE)
[1] Big Little Medium Jumbo
Levels: Little < Medium < Big < Jumbo
- ¿Qué sucede si se agregan más datos pero que pertenecen a los niveles?
- ¿Qué sucede cuando no se agrega un nivel?
- ¿Qué pasará con los niveles en datos numéricos?
- ¿Qué pasará con los niveles en datos numéricos pero dados como caracteres?
- ¿Qué obtenemos al usar
class()
ytypeof()
con un factor? - ¿Cómo puede ayudar la función
rev()
en un factor?
En la siguiente liga se puede ver un poco más de información sobre este tipo de datos.
En la sección 15 del libro R para ciencia de Datos (traducción al español de R for Data Science de Wickham y Grolemund) se mencionan algunas fuentes para aprender más acerca de los factores, el cual es un tema bastante útil de dominar ya que existe una librería llamada forcats
con la que se puede trabajar de manera adecuada estos tipos de datos que son de gran ayuda al momento de hacer algún tipo de análisis descriptivo, graficación y creación de modelos. Aquí se enlistan los artículos correspondientes.
- Wrangling categorical data in R
- stringsAsFactors: An unauthorized biography
- stringsAsFactors = <sigh>
Otro tipo de dato con el que se cuenta es aquel que no está definido, es decir, como en Java, un objeto nulo: NULL
. En las siguientes líneas de código se muestra como hacer comparaciones con este tipo de dato.
is.null(NULL)
[1] TRUE
0 == NULL
logical(0)
NA == NULL
logical(0)
Otro punto muy común al analizar datos es encontrarse con información faltante. Aquí, como en otros lenguajes, los valores perdidos son distintos a un objeto no definido; ya que un valor perdido bien puede indicar un error humano al momento del registro de la información pero con la característica de que se puede inferir un posible valor para ese registro faltante. Un NA
es una constante que puede ser asignada a cualquier tipo de vector a excepción de un vector de tipo raw
.
Al tener esta ausencia de datos se desea identificarlos de manera rápida y esto puede ser complicado en una base de datos con cientos o miles de registros; al igual que si se tiene el conocimiento de la existencia de estos, se desea que no afecten en operaciones o aplicaciones de funciones en el resto de los datos. Véase lo siguiente
sum(c(1,NA,3), c(1,2,3))
[1] NA
Es decir, que cuando se tiene algún valor perdido, la operación suma (más adelante se verá a detalle las operaciones en este lenguaje) queda determinada con NA
. ¿Sucede lo mismo con otro tipo de operaciones?
Como se había mencionado, se desea que si se tiene conocimiento de la existencia de al menos un NA
, este no afecte a las operaciones
sum(c(1,NA,3), c(1,2,3), na.rm = TRUE)
[1] 10
Técnicamente, existen cinco missing values para cada uno de los vectores atómicos (a excepción de raw
claro): NA
, NA_interger_
, NA_real_
, NA_character_
y NA_complex_
, pero eso solo es de manera interna.
3.2.3 Asignación de nombres y atributos
Como en cualquier lenguaje de programación, existen identificadores para guardar o asignar el valor de los tipos de datos primitivos o resultados y así utilizarlos posteriormente. En el caso de R, la asociación de un nombre con un cierto valor se puede realizar mediante la función <-()
y gracias a que R es un lenguaje interpretado, no es necesario declarar el tipo de dato antes de darle una instanciación. Por ejemplo, véase que en las siguientes líneas de código se asignan a algunos vectores atómicos un identificador.
5+7i
a <- 5L
b <- 10
c <- TRUE
d <- NA e <-
Y en este caso, basta con llamar a la variable por su nombre para poder usarla.
a
[1] 5+7i
c
[1] 10
e
[1] NA
También se puede asignar un valor a una variable con el operador =
pero esto no es nada recomendable ya que, por buenas prácticas, en este lenguaje, se acostumbra a usar el igual para dar valores a parámetros. ¿Qué pasará cuando se ejecute x <- NULL
? ¿Qué pasará si antes x
tenía otro tipo de dato?
Aquí se dejan otros ejemplos útiles, más adelante se verá la función assign()
.
c(0.1, 0.2, 0.3)
roma <-
assign("mora", c(0.4, 0.5, 0.6))
c(0.7, 0.8, 0.9) -> z
c(roma, 0, 0, 0, roma) mora <-
N.B. Los vectores en R no solo pueden ser numéricos también los hay aquellos con cadenas de texto.
3] mora[
[1] 0.3
-3] mora[
[1] 0.1 0.2 0.0 0.0 0.0 0.1 0.2 0.3
c(1,5,7)] mora[
[1] 0.1 0.0 0.1
Algo que hay que entender sobre R es que este lenguaje esta basado en el paradigma de programación orientada a objetos, aunque también tiene ciertas características de un lenguaje funcional. Existen muchos sistemas para hacer todo lo que se tiene conocimiento de POO en R, como lo es el encapsulamiento, herencia y polimorfismo. Aquí se evitará en la mayor medida posible el tema pero se harán menciones en la marcha de esto.
Para el caso de los vectores, se tienen ciertos atributos fijos desde su creación: la longitud y el tipo de dato. Más adelante, en las estructuras de datos, se detallará sobre este tema; por el momento, véase lo siguiente
c(1,2,3,4,5)
a <-paste0("longitud: ", length(a), ".Tipo de dato: ", typeof(a))
[1] "longitud: 5.Tipo de dato: double"
Aclaremos ciertas cosas:
- La manera más rápida de crear un vector es con el constructor
c()
y agregando datos del mismo tipo dentro de él separados con una coma.- Si
v_1
es un vector de un cierto tipo y se desea hacer otro vectorv_2
del mismo tipo que contenga el contenido dev_1
más otros elementos ¿Se puede hacerv_2 <- c(v_1,...)
?
- Si
- Otra forma de crear cualquier otro vector es con la función
vector("type", length)
.- En cierto sentido, usar la última función es una mejor manera de trabajar ya que así se reserva un espacio de memoria para un vector fijo, mientras que con
c()
este puede ir variando. - Para crear un vector vacío de una cierta longitud, la función
vector()
es la ideal.
- En cierto sentido, usar la última función es una mejor manera de trabajar ya que así se reserva un espacio de memoria para un vector fijo, mientras que con
- Se pueden crear vectores atómicos de cierta longitud con sus valores por defecto al usar los constructores
double(length)
,interger(length)
,character(length)
, etc. - Arriba se creo una secuencia de números de manera rudimentaria, ya que en R se pueden crear secuencias de números con la función
:
, por ejemplo: 1:5. - Con
seq_along()
se puede crear también una secuencia de números; esta la podemos usar en lugar de1:length(v)
conv
un vector.- ¿Qué pasa en las siguientes instrucciones?
y <- vector("double", 0)
;seq_along(y); 1:length(y)
.
- ¿Qué pasa en las siguientes instrucciones?
- Finalmente, se pueden hacer secuencias cada \(n\) números con la función
seq()
:seq(1,10,by=2)
.
Ya que R es un lenguaje de código libre y orientado a objetos, podemos ver el contenido de sus funciones. Para las funciones que están escritas en R podemos ejecutar directamente el nombre de la función omitiendo los paréntesis.
integer
function (length = 0L)
.Internal(vector("integer", length))
<bytecode: 0x7fda80bff4e0>
<environment: namespace:base>
¿Cómo están hechos los constructores para double, character, boolean y raw? ¿Cómo están hechos los constructores para un complejo y un factor?
Muchas veces es útil comparar distintos tipos de variables o verificar si estas son de algún tipo en específico. Para ello existen ciertas funciones como is.character()
, is.na()
, is.double()
, etc. En general se tiene este tipo de funciones para cualquier objeto, incluso se tiene is.function()
. En el caso de los posibles valores perdidos de un double, hay que evita usar ==
para verificar los posibles valores como Inf
y –Inf
. En su lugar usar las funciones is.finite()
, is.infinite()
, e is.nan()
.
3.2.4 Operaciones básicas en R
Es importante que existan operaciones en los lenguajes de programación, y en este caso se tienen las más comunes y algunas especiales.
- Adición:
5 + 7
;sum(5,7)
. - Sustracción:
5 - 7
. - Multiplicación:
5*7
;prod(5,7)
. - División:
5 / 7
. - Modulo:
5 %% 7
- División entera (piso):
5 %/% 7
. - Potencia:
5 ^ 7
;5 ** 7
. - Multiplicación Matricial:
%*%
Al igual que los clásicos operadores booleanos o relacionales
TRUE & TRUE
.TRUE & FALSE
.TRUE | TRUE
.TRUE | FALSE
.!TRUE
.TRUE == FALSE
.TRUE != FALSE
.
En resumen, tenemos la siguiente tabla
Aritméticos | Comparación | Lógicos |
---|---|---|
Suma (+) | Menor que (<) | NOT (!) |
Resta (-) | Mayor que (>) | AND (&) |
Multiplicación (*) | Menor o igual que (<=) | OR (|) |
División (/) | Mayor o igual que (>=) | Cierto (TRUE) |
Potencia (^) | Igual (==) | Falso (FALSE) |
Modulo (%%) | Diferente (!=) | |
División entera (%/%) |
Ahora, lo poderoso de R es poder trabajar de manera vectorial, por lo que las operaciones actúan también de manera vectorial. Los siguientes ejemplos muestran el comportamiento asociado a un lenguaje vectorial que mencionamos:
2*roma+mora+1
v <- v
[1] 1.3 1.6 1.9 1.2 1.4 1.6 1.3 1.6 1.9
1:5 + 2:6
[1] 3 5 7 9 11
1:5 * 2:6
[1] 2 6 12 20 30
- ¿Qué pasará con las siguientes operaciones?
1:5 ^ 1:5
(1:6) ^ c(2,3)
s <- seq(1,10, by = 2); s + 7
sqrt(s)
1:10 + 1:5
1:10 * 1:5
1:10 * 10
Lo que sucedió arriba se conoce como reciclaje, lo cual tiene el efecto de repetir de manera ordenada los vectores más pequeños para que ambos vectores que se están operando tengan la misma longitud, esto resulta muy útil cuando se quiere repetir ciertos parámetros en una operación cada \(n\) cantidad de pasos. Por ejemplo, si se desea obtener el cuadrado de todos los pares de números del \(1\) al \(100\), no es necesario hacer algún proceso usando condicionales y algún bucle, sino sólo operar de manera vectorial y usar reciclaje.
seq(1,100)^c(1,2)
[1] 1 4 3 16 5 36 7 64 9 100 11 144
[13] 13 196 15 256 17 324 19 400 21 484 23 576
[25] 25 676 27 784 29 900 31 1024 33 1156 35 1296
[37] 37 1444 39 1600 41 1764 43 1936 45 2116 47 2304
[49] 49 2500 51 2704 53 2916 55 3136 57 3364 59 3600
[61] 61 3844 63 4096 65 4356 67 4624 69 4900 71 5184
[73] 73 5476 75 5776 77 6084 79 6400 81 6724 83 7056
[85] 85 7396 87 7744 89 8100 91 8464 93 8836 95 9216
[97] 97 9604 99 10000
Hay que tener cuidado en algunos casos, por ejemplo 1:4^c(2,4)
.
Ejercicios
El siguiente vector contiene una muestra referente al genero en un grupo de personas:
c('H','M','M','H','H','M','H','M','H','H','M','M','M','M','M','M','M','M','H','H','H','M','H','M','M','M',
'M','H','H','H','H','M','M','M','H','M','H','H','M','H','M','H','M','H','M','M','H','H','M','H')
. Mediante la funcióntable()
determina la frecuencia de cada categoría usando un factor.Convierte el siguiente vector de caracteres en un vector numérico y obtén la suma de todos sus elementos.
c('7', '50', '7', '18', '42', '37', '20', '11', '33', '38', '28', '3', '16', '40', '3', '40', '36', '37', '1',
'6', '8', '23', '25', '48', '5', '22', '21', '21', '1', '5', '8', '48', '34', '16', '4')
. Guárdalo con el nombrev
.La función
table()
también funciona con elementos numéricos, así que crea una tabla de frecuencias dev
.Ordena el vector
v
de forma descendente.Se desea categorizar a
v
en 3 grupos (los menores a 4, los mayores o igual a 4 pero menores a 31 y los restantes), por lo que hay que hacer lo siguiente
- Crea distintos vectores que contengan sólo los elementos que caen dentro de cada categoría.
- Agregar nombres a los vectores de acuerdo al grupo que pertenecen. Pueden usar la función
rep()
. Los nombres serán el número de grupo (Uno, Dos y Tres). - Une los anteriores vectores en un nuevo vector (puede ser un factor o no).
- Obtén una tabla de frecuencias para mostrar la cantidad de elementos por cada categoría.
Obtén los nombres del vector que se obtuvo al final del punto 5 y guárdalo como una nueva variable. Crea un vector numérico con los datos 1, 2, 3 y con nombres “Uno”, “Dos” y “Tres”. Utiliza character subsetting para obtener un vector de 1, 2 y 3 con el vector de nombres que se menciono al inicio de este ejercicio.
Obtén la raíz cuadrada de los primeros 100 números naturales.
Crea un vector numérico y eleva cada elemento a la potencia determinada por la longitud de ese vector.
Crea un vector numérico y eleva cada uno de sus elementos al cubo sin usar el operador
^
ni**
.Con los datos del inciso 1 y con la función
seq_along()
, crea un vector con los datos antes mencionados pero que sus nombres sean una indexación de estos.Con los siguientes datos
c('14', '1', '12', '27', '5', '38', '9', '29', '21', '19', '34', '18', '22', '1','26',
'18','36', '28', '24','39','36', '21', '36', '28', '8', '13')
, crea un vector booleano determinando que elementos son menores a 26 y con ese vector booleano concluye cuantos datos son menores a 26 y cuantos no lo son.Crea una lista con cinco elementos donde, al menos dos, deben ser listas que contengan listas a su vez.
Se desea crear una lista de 10 elementos con las siguientes secuencias de números:
c(1, 2, 3, ..., 10),
c(11, 12, ..., 20), ..., c(91, 92, ..., 100)
. Además cada elemento debe tener nombre.En la lista anterior, cambia el vector
c(40, ..., 50)
porc(50, 49, ..., 40)
sin escribir explícitamente ese vector.Agrega tu nombre como un atributo a cualquier vector anterior y a la lista del punto 13 y convierte la lista a un vector.