3.1 Vectores

Los vectores son objetos que contienen varios elementos relacionados (del mismo tipo o clase). Estos elementos estan indexados: esto es, cada elemento tiene asociado un numero que determina la posicion del elemento en el vector, comenzando desde el \(1\). Las variables creadas asignando un solo valor a ellas son vectores de longitud uno. A estos se les llama atómicos.

Los vectores se pueden crear usando la función c(), como en c("AC/DC", "Led Zeppelin", Guns & Roses"). Se pueden añadir tantos elementos como se quiera. Para acceder a los elementos de un vector, se utilizan los operadores de indexado [ y ], encerrando el número que identifica la posición del elemento deseado. Por ejemplo:

    bandas <- c("AC/DC", "Led Zeppelin", "Guns & Roses", "Blink 182")
    bandas[4] # Devuelve "Blink 182"

Empezando a contar desde el 1, el elemento "AC/DC", el elemento 4 sería el elemento "Blink 182".

Es posible saber la longitud de cualquier vector usando la función length. La longitud es el número total de elementos contenidos en el vector. Por ejemplo, el resultado de usar length(bandas) arroja \(4\).

En la práctica, es usual crear vectores que sean secuencias de números o vectores de elementos que se repiten. En por ello que existen funciones especiales que permiten crear este tipo de vectores. Si se quiere crear una secuencia de números se puede usar seq de la siguiente forma:

    my_seq <- seq(0, 100) # Primeros 100 numeros naturales 
    pares <- seq(0, 100, by=2) # Numeros pares menores que 100

En las declaraciones anteriores, se especifica con el primer y segundo argumento el comienzo y final de la secuencia, respectivamente. El tercer argumento es opcional: si se omite, cada elemento de la secuencia es mayor al anterior en solo una unidad (el caso de my_seq), de lo contario, cada elemento es el anterior + el numero de unidades que dice by (en el caso de pares, cada elemento es dos veces el anterior). También es posible crear secuencias en la que los elementos esten en orden decreciente. Por ejemplo, seq(-1, -100, by=-2) genera una secuencia de enteros impares negativos mayores que \(-100\), comenzando con \(-1\), \(-3\), \(-5\), \(\ldots\).

La creación de secuencias de enteros uno a uno (by=1 o by=-1) es tan usual, que existe un atajo para crearlos, usando la notación a:b. Por ejemplo, es posible crear el vector my_seq del ejemplo anterior como 0:100, o uno en sentido descendente como 10:-10.

Para crear vectores de repeticiones se usa la función rep de la siguiente forma:

    my_rep <- rep("Oh!", 3) # "Oh!" "Oh!" "Oh!"
    my_vec_rep <- rep(c("Wham", "Bam!"), 2) # "Wham" "Bam!" "Wham" "Bam!"

El segundo argumento dice el número de veces que se repite el elemento de entrada. Como se ve en la segunda linea, si el argumento a repetir es un vector, todo el vector se repite el numero de veces que dice el segundo argumento. Se ve que que el resultado es el vector original, seguido del mismo vector, seguido del mismo vector, …, el numero de veces que dice el segundo argumento. Hay otra forma de hacer repeticiones, usando un tercer argumento, each, de la siguiente forma:

    my_vec_rep <- rep(c("Wham", "Bam!"), each=2) # "Wham" "Wham" "Bam!" "Bam!"

En este caso, el argumento each dice el numero de veces que cada elemento del vector de entrada se repite.

3.1.1 Operaciones sobre vectores.

Las operaciones especificas para un tipo de dato particular, se pueden realizar sobre vectores con elementos de ese tipo de dato. Por ejemplo, las operaciones ariméticas también son válidas sobre vectores, solo se debe conocer la forma como esas operaciones se llevan a cabo. Para empezar sencillo, digamos que queremos crear un vector de los primeros 10 múltiplos de 2. Como vimos antes, se podría hacer con una secuencia, pero aqui estamos interesados en ver como funcionan las operaqciones con vectores. Al realizar operaciones de un vector con un escalar (un atómico), el resultado es un vector en el que cada elemento en la \(i\)-esima posición es el elemento \(i\) del vector inicial operado con el escalar (atómico). Para dejar esto más clar, el siguiente ejemplo muestra la multiplicación de un escalar por un vector:

    my_vector <- 1:10
    my_vector * 2

Como se puede ver, el resultado de c(1, 2, 3, ..., 10) * 2 es c(1 * 2, 2 * 2, ..., 10 * 2), esto es, cada elemento de my_vector se multuplica pór 2. De manera general, esto ocurre siempre de esta forma. Las operaciones sobre vectores ocurren elemento a elemento. Un caso más complicado surge cuadno queremos realizar operaciones sobre dos vectores de igual longitud: digamos sumar los primeros 10 numeros pares a los primeros 10 numeros impares.

    first_10_even <- 1:10 * 2
    first_10_odd <-first_10_even - 1
    first_10_even + first_10_odd

Noten el uso de la aritmética de vectores por un escalar para crear los primeros dos vectores. El resultado final muestra que la aritmetica de vectores se hace tomando el elemento en la posición \(i\) del primer vector, y opera sobre el elemento en la misma posicion del segundo vector. De forma que al realizar c(2, 4, 6, ..., 20) + c(1, 3, 5, ..., 19), el resultado es c(2 + 1, 4 + 3, ..., 20 + 19).

Ahora, ¿qué sucedería si los vectores fuera de longitud distinta?. Por ejemplo, el siguinte ejemplo:

    1 : 10 * c(10, 100)

muestra que el resultado sigue usando el mismo procedimiento descrito antes, solo que ahora, al llegar al ultimo elemento del vector de menor longitud, se vuelve a usar el primer elemento de ese vector para continuar con la operación. En el caso anterior, la operacion se interpreta como c(1 * 10, 2 * 100, 3 * 10, 4 * 100, 5 * 10, ..., 9 * 10, 10 * 100). A este proceso se le llama reciclado.

Cuando se trata de hacer operaciones con vectores de longitud distinta, estas siempre son posibles hacerlas. Sin embargo, si la longitud de un vector no es multiplo de la longitud del otro vector, el proceso de reciclado ocurre de forma incompleta y R lanza una advertencia para que tengas cuidado con lo que esperas. Por ejemplo, si el caso anterior se modifica un poco:

    1 : 11 * c(10, 100)

Esto se interpreta igual que antes, solo que el ultimo elemento es 11 * 1, el ultimo elemento del vector por el primero del segundo. La advertencia surge debido a que, durante el reciclado, R trata de usar multiplicar los elementos del vector de menor longitud tantas veces como es posible. Pero como el primer vector no tiene suficientes elementos para lograr esto, se lanza la advertencia sobre la diferenciaq de longitudes.

Ahora, de igual forma que las operaciones aritméticas son posibles con vectores. las operaciones lógicas también lo son, permitiendo obtener vectores lógicos: vectores cuyos elementos son todos TRUE o FALSE. Por ejemplo:

    1:10 >= 5 

Al igual que con los vectores de números, las operaciones se hacen elemento a elemento, y en el caso de longitudes distintas, se recicla. Y, al igual que con los vectores numéricos, es posible llevar a cabo operaciones lógicas con vectores de caracteres. Por ejemplo:

    c("Eliana", "Marcelo", "Darvin", "Jesus", "Arianna") != "Marcelo" 

Una operación lógica que resulta bastante conveniente, es la que se realiza usando el operador %in%. Este operador lo que hace es chequear si el objeto u objetos (contenidos en un vector) se encuentran representado en los elementos de un vector de posibilidades. Por ejemplo:

    bands <- c("AC/DC", "Led Zeppelin", "Guns & Roses", "Blink 182", "Neck Deep", "State Champs", "Green Day", "The Offsprings")
    bands %in% c("AC/DC", "Led Zeppelin", "Guns & Roses", "The Offsprings")

El resultado es un vector con un TRUE en las posiciones en las que se encuentra un elemento que coincide con alguno de los elementos en el vector a la derecha del operador %in%.

3.1.2 Vectorización y funciones.

La razón por la que es posible realizar operaciones de forma tan sencilla en R, es debido a que el lenguaje esta vectorizado. Esto quiere decir, que las operaciones ocurren en todos los elemento de un vector sin la necesidad de iterar18 por cada uno de los elementos, uno a la vez.

La vectorización es también lo que hace posible el uso de funciones sobre vectores. Por ejemplo, si se tiene un vector con los primeros 100 enteros, y se quiere saber la raíz cuadrada de cada uno de ellos, solo es necesario aplicar la función al vector: sqrt(1:100). Esto se interpreta como: c(sqrt(1), sqrt(2), ..., sqrt(100)). De igual forma, todas las funciones matemáticas en R estan vectorizadas para operar sobre todos los elementos a la vez.

Muchas funciones en R estan vectorizadas. Es lo que lo hace tan conveniente. Y muchas funciones que realizan tareas usualmente encontradas en análisis de datos y otras aplicaciones, estan disponibles en R para facilitar el trabajo y no tener que reinventar la rueda cada vez.

Estas funciones operan sobre vectores, devolviendo un atómico como resultado. Por ejemplo, varias funciones usuales que operan sobre vectores son las siguientes:

min(-10:10) # minimo de un numero
max(-10:10) # Maximo de un numero
prod(1:10) # Factorial
sum(1:10) # Sumatoria

La ultima función es particularmente útil cuando quieres contar el numero de elementos que cumplen una condición. Por ejemplo, digamos que queremos saber cual es el número de elementos mayores a \(50\) en una muestra de \(100\) elementos tomados al azar de un vector de numeros enteros en el intervalo \([1, 100]\). El código para realizar esta tarea es el siguiente:

my_sample <- sample(1:100, 100, replace=TRUE)
sum(my_sample > 50)

La función sample recibe un vector y un numero que dice cuantos elementos se tomaran de muestra del vector. El argumento replace dice que una vez que se toma un elemento, este se anota y se devuelve de forma que este disponible para poder tomarlo una vez más19 Luego, la condición dentro de la función sum genera un vector lógico con TRUE donde haya elementos mayores a \(50\). Como los vectores lógicos se interpretan como \(1\) (TRUE) y \(0\) (FALSE) internamente; cuando se aplica sum, esta se encarga de sumar solo 1’s y 0’s. De esta forma, es fácil realizar conteos en R.

Otras funciones útiles son any y all, las cuales devuelven un valor lógico. any devuelve TRUE si un vector lógico contiene al menos un TRUE, de otra forma regresa FALSE. all devuelve TRUE si todos los elementos de un vector son TRUE, de otra froma regresa FALSE. Estas funciones son muy útiles en combianción con operadores lógicos:

my_sample <- c(1, 15, 8, 2, 14, 7, 2, 14, 29)
any(sum(my_sample %% 7) > 0) # TRUE ya que hay multiplos de cero en my_sample
all(sum(my_sample %% 2) > 0) # FALSE porque no todos los elementos de my_sample son pares

La función sample es como otras funciones en R que reciben un vector (y posiblemente otros argumentos) y devuelven más vectores como resultado. Por ejemplo, podemos tener un vector de elementos desordenados, y puede que queramos ordenarlo de forma creciente:

cat("Antes de ordenar:\n",
    my_sample,
    "\nDespues de ordenar:\n",
    sort(my_sample))

La función cat se encarga de imprimir objetos en R, combinándolos primero.

Funciones estadísticas

Existen un conjunto de cálculos matemáticos en el análisis de datos que son tan frecuentes, que ya están programadas por omisión dentro de R. Estas funciones son mean, var, sd, median, range, quantile y cor, las cuales reciben un vector numérico, y devuelven la media, varianza, desviación estándar, mediana, rango, y los cuantiles, respectivamente. El código siguiente muestra el calculo de las medidas de tendencia central y dispersión para un vector.

c(Media=mean(my_sample), Mediana=median(my_sample), Var=var(my_sample), Std.Error=sd(my_sample))

La función range devuelve un vector con dos elementos, los valores con magnitud máxima y minima

range(my_sample)

La funcion quantile devuelve los cuartiles calculados a partir de la muestra. Noten que el \(Q_{50\%}\) es la mediana, y \(Q_{0\%}\) y \(Q_{100\%}\) son el valor mínimo y máximo devueltos por range.

quantile(my_sample)

Ahora, podemos ver en mas detalle la función sample. Esta es muy útil para realizar simulaciones. Pero también lo es en el análisis de datos, ya que permite seleccionar de forma aleatoria un conjunto de datos que puede servir como un conjunto de datos independiente del ajuste de un modelo por ejemplo.

Esta funcion permite tomar una muestra aleatoria a partir de un vector que es dado como argumento a la función. El tamaño de la muestra a tomar se puede especificar como el segundo argumento, size, el cual por omisión es igual a la longitud del vector. El argumento replace es un valor lógico que indica si el muestreo se hace con reemplazo o sin reemplazo, y el ultimo argumento, prob, es un vector de probabilidades que permite especificar la frecuencia que se espera tenga un elemento en el vector. Por omisión, esta es igual para todos los elementos.

Datos Faltantes (Missing).

Como ya vimos anteriormente, NA es un objeto especial en R que significa que hay un dato faltante. Vimos que es posible comprobar si un objeto es un dato faltante usando la función is.na(), la cual devuelve TRUE si es NA, y FALSE de otra forma. Los datos faltantes son comunes en el análisis de datos y deben tratarse de manera adecuada durante el proceso previo al análisis, e incluso, durante el análisis. Por ejemplo, digamos que se quiere calcular la media del siguiente vector:

vec_w_NA <- c(1, 17, 25, NA, 42, 23, 8, 27, NA, 17)
mean(vec_w_NA)

Como se observa, el vector tiene dos datos faltantes, y al calcular la media, el resultado es un NA. La razón de esto, es que no es posible realizar cálculos con datos faltan tes. Es por ello que se deben eliminar antes de realizar el calculo. Sin embargo, no es necesario hacerlo de manera explicita, dado que las funciones estadísticas contienen argumentos que permiten eliminar de forma automática los NA del calculo.

mean(vec_w_NA, na.rm=TRUE)

En la siguiente parte, veremos como podemos utilizar expresiones lógicas para obtener un conjunto de datos libre de NA.

3.1.3 Indexado usando expresiones lógicas.

En R, una expresión es toda frase o segmento de código que puede ejecutarse. Al escribir una linea de código, se esta creando una expresión de R que puede ser evaluada (al oprimir Enter). Las expresiones evaluadas pueden mantenerse en memoria o tener desaparecer inmediatamente, dependiendo de si se almacena el resultado de la evaluación en una variable u objeto. En este caso, la expresión es denominada asignación.

Expresiones lógicas

Una expresión lógica es un tipo especial de expresión de R que utiliza operadores lógicos de comparación para crearlas, y cuyo valor de retorno es siempre un valor lógico (TRUE o FALSE). Ademas, también hace uso de los operadores lógicos & (and), | (or) y ! (not):

  • & (and): se escribe expresion_1 & expresion_2, donde expresion_1 y expresion_2 son ambas expresiones lógicas. Devuelve TRUE si ambos expresion_1 y expresion_2 son TRUE, y devuelve FALSE, si una de las dos, o ambas expresiones, son FALSE.
  • | (or): se escribe expresion_1 | expresion_2, donde expresion_1 y expresion_2 son ambas expresiones lógicas. Devuelve TRUE si alguna o ambas expresiones son verdaderas, y FALSE si ambas son FALSE.
  • ! (not): se escribe !expresion_1 (es un operador unario, a diferencia de los anteriores. unario significa que recibe un solo operando), donde expresion_1 es una expresión lógica. Devuelve TRUE si la expresión es FALSE, y devuelve FALSE si la expresión es TRUE.

Por ejemplo, digamos que tenemos dos vectores numéricos y queremos realizar una acción sobre ellos. Pero antes de realizar cualquier acción, se necesita asegurar que todos los valores sean positivos y que al menos alguno sea mayor a \(50\). Podemos verificar ambas expresiones lógicas como sigue:

my_sample <- sample(1:100, 100, replace=TRUE)
all(my_sample > 0) & any(my_sample >= 50)

También es posible utilizar el operador | en el ejemplo anterior, y el resultado podría variar dependiendo de my_sample. En el caso del operador !, se puede usar como sigue:

!all(my_sample > 0)

Los operadores lógicos indicados evalúan ambas expresiones antes de arrojar un resultado final. Existen otro par de operadores lógicos que no necesariamente evalúan ambas expresiones: los operadores && y ||. Estos operadores permiten ahorrar tiempo a la hora de evaluar expresiones lógicas, dado que se puede escapar tempranamente de la expresión si la condición de la izquierda no cumple una condición.

Un ejemplo que facilita el entendimiento de estos operadores es la división entre cero. Podemos intentar evaluar si \(sen(1/x)\) es igual a cero, sin(1 / x) == 0. Pero dado que la división entre cero no esta definida, hay que verificar que \(x\) no sea cero, y en caso de serlo, no realizar la evaluación de sin(1 / x) == 0. Si se hiciera de la forma usual:

x <- 0
(x == 0) | (sin(1 / x) == 0)

el resultado es TRUE ya que el operando de la derecha es NA, por lo que se ignora al evaluar la expresión TRUE | NA.
Al usar la expresión:

(x == 0) || (sin(1 / x) == 0)

no arroja ninguna advertencia debido a que ahora, la expresión de la derecha no se evalúa hasta que la expresión de la izquierda no es FALSE. La razón de esto, es que cuando se usa el operador or, si la primera expresión es FALSE, todavía hay posibilidad de que toda la expresión expresion_1 || expresion_2, sea TRUE. Por lo tanto, la expresión de la derecha solo se evalúa si aun hay oportunidad de que la expresión total sea TRUE.

El operador && funciona de forma similar, pero la expresión de la izquierda solo se evalúa si la expresión de la derecha es TRUE, dado que si esto ocurre, aun hay oportunidad de que al expresión completa sea FALSE, si la expresión de la derecha es FALSE.

Indexado por expresiones lógicas.

Las expresiones lógicas son muy útiles para indexar vectores basado en el resultado de la expresión. El indexado devuelve solo los elementos del vector para los cuales el resultado de la expresión es TRUE. Esto requiere que la longitud del resultado de la expresión lógica sea igual; a la longitud del vector que se busca indexar. Por ejemplo, podemos seleccionar los elementos de un vector diferentes de NA usando:

vec_w_NA[!is.na(vec_w_NA)]
## [1]  1 17 25 42 23  8 27 17

En el análisis estadístico de datos, es sencillo seleccionar aquellos elementos por encima de la media, o mas importante aun, seleccionar datos atípicos de una muestra. Un dato se considera atípico si cae a mas de dos desviaciones estándar de la media. De forma que se pueden seleccionar usando:

my_sample <- c(1, 3, 11, 5, 15, 8, 2, 14, 7, 2, 14, 29, 55)
my_sample[(my_sample - mean(my_sample)) / sd(my_sample) > 2]
## [1] 55

Los NA causan problemas en el indexado de expresiones logicas, y por tanto afectan el resultado de la evaluacion de expresiones de indexado:

vec_w_NA[vec_w_NA > mean(vec_w_NA, na.rm=TRUE)]
## [1] 25 NA 42 23 27 NA

El resultado, como se espera contiene NA, porque son ignorados en la expresion de indexado. Para evitar esto, es mejor utilizar la funcion subset, la cual permite ignorar los NA en el vector:

subset(vec_w_NA, vec_w_NA > mean(vec_w_NA, na.rm=TRUE))
## [1] 25 42 23 27

Otra función de importancia en el indexado por expresiones lógicas es which. Esta recibe una expresión lógica, y devuelve las posiciones en el vector resultante de la expresión lógica en los cuales el resultado es TRUE. Por ejemplo:

which(my_sample > mean(my_sample))
## [1]  5  8 11 12 13

Como esta devuelve posiciones en el vector donde se cumple una condición, es posible usarlo en para indexar el vector, u otro vector relacionado, usando my_sample[which(my_sample > mean(my_sample))].


  1. La iteración se hace con unas estructuras de control llamados loops (bucle), como lo son los ciclos for o while. En R, estas estructuras estan disponibles, pero la vectorización no las hace indispensables en muchas aplicaciones.↩︎

  2. A este proceso se le llama tomar una mustra con reemplazo en la teoría de muestras.↩︎