2  Vectores

Los vectores en R son secuencias de objetos del mismo tipo (p.e., números). Los encontraremos, sobre todo, como columnas de una tabla; que son, efectivamente, vectores (p.e., iris$Petal.Length). En esta sección veremos cómo crear, inspeccionar y operar con vectores.

2.1 Creación de vectores

El código siguiente crea dos vectores, uno numérico y otro categórico (del tipo factor, que veremos más adelante).

x <- 1:10
y <- iris$Species

Una vez creados estos vectores, aparecerán en el panel correspondiente de RStudio (o si ejecutas ls()).

Ejercicio 2.1 Crea el vector que numera las filas de iris (es decir, que contenga los números del 1 hasta el número de filas de iris). Nota: este es un ejercicio muy importante sobre el que abundaremos más adelante.

Previamente hemos usado el operador : para crear secuencias de números enteros. Para construir vectores arbitrarios, podemos usar la función de concatenación, c:

1:5
5:1
c(1:5, 5:1)
c(1, 5, -1, 4)
c("uno", "dos", "tres")

Recuerda: al no ser asignados a ninguna variable, los objetos anteriores, simplemente, se muestran en pantalla.

En realidad, : es una abreviatura de seq:

seq(1, 4)
[1] 1 2 3 4

Esta función está estrechamente emparentada con rep:

rep(1:4, 4)
 [1] 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
rep(1:4, each = 4)
 [1] 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4

Ejercicio 2.2 Crea el patrón 1, 1.1, 1.2,…, 2. Existen varias maneras de hacerlo. Una de ellas es utilizar el argumento by de seq.

Los siguientes ejemplos ilustran cómo crear patrones más complejos usando rep. No es necesario dominar todas las opciones disponibles en la función rep, pero sí saber que, llegado el caso, la función dispone de ellas.

rep(1:4, 2)
rep(1:4, each = 2)
rep(1:4, c(2,2,2,2))
rep(1:4, times = 4:1)
rep(1:4, c(2,1,2,1))
rep(1:4, each = 2, len = 4)
rep(1:4, each = 2, len = 10)
rep(1:4, each = 2, times = 3)

Ejercicio 2.3 Selecciona las columnas 1, 2 y 5 de iris.

Ejercicio 2.4 Selecciona las filas 1:4 y 100:104 de iris.

Ejercicio 2.5 Usa un vector de texto para seleccionar las columnas Wind y Temp de airquality.

Ejercicio 2.6 Crea una tabla que sea la primera fila de iris repetida 100 veces.

2.2 Inspección de vectores

Las siguientes funciones, algunas de ellas ya conocidas, sirven para inspeccionar el contenido de un vector:

plot(x)
length(x)
table(y)         # ¡muy importante!
summary(y)
head(x)
tail(x)

La función table cuenta los elementos de un vector por valor. Es una función sumamente importante para examinar vectores categóricos1:

table(iris$Species)

summary ofrece un pequeño resumen del contenido de un vector. De hecho, cuando aplicábamos summary a una tabla, obteníamos el summary de cada una de sus columnas2.

Ejercicio 2.7 En CO2, cuenta cuántas filas corresponden a cada tipo de planta.

Ejercicio 2.8 Ejecuta e interpreta table(table(CO2$conc)). Nota: table(table(x)) es una operación muy frecuente y útil.

2.3 Selecciones

Para seleccionar elementos de un vector se usa el corchete []. Pero, a diferencia de lo que ocurre con las tablas, como los vectores son objetos unidimensionales, sin coma. Obviamente, el corchete sigue admitiendo no solo los índices de los elementos que se quieren extraer, sino que además permite utilizar condiciones lógicas, etc.

x <- x^2
x[1:3]
x[c(1,3)]
x[x > 25]
x[3:1]
x[-(1:2)]
x[-length(x)]

Advierte en el ejemplo anterior el uso (y el efecto) del signo menos y de las condiciones lógicas dentro de los corchetes.

Ejercicio 2.9 Selecciona todos los elementos de un vector menos menos los dos últimos.

Ejercicio 2.10 Implementa la función diff (las diferencias entre cada valor de un vector y el que lo precede) a mano. Nota: la función diff existe en R; pruébala en caso de duda.

El corchete también permite seleccionar elementos de un vector por nombre. En efecto, en R se pueden asociar nombres a los elementos de un vector. Lo hace automáticamente, por ejemplo, table (para poder saber a qué etiqueta corresponde cada conteo):

z <- table(iris$Species)
z["setosa"]
setosa 
    50 
z[c("setosa", "virginica")]

   setosa virginica 
       50        50 

En cierto sentido, los vectores con nombre operan (aunque con algunas diferencias técnicas) como los diccionarios de Python o las tablas hash de otros lenguajes de programación: contienen parejas clave-valor.

Una propiedad muy importante del corchete es que permite, además de seleccionar, cambiar el contenido de los elementos seleccionados de un vector. Por ejemplo:

z <- 1:10
z[z < 5] <- 100
z
 [1] 100 100 100 100   5   6   7   8   9  10

Reemplazar subselecciones es muy útil: permitirá, por ejemplo, cambiar las edades negativas por un valor con sentido, sustituir los nulos por un determinado valor por defecto, reemplazar los valores que excedan un tope por dicho tope, etc.

El nombre de las columnas de una tabla también es un vector. Por eso, para cambiar el nombre de una columna podemos hacer lo siguiente:

mi_iris <- iris   # una copia de iris
colnames(mi_iris)[5] <- "Especie"

Lo mismo ocurre con los nombres de un vector, aunque en este caso, la función correspondiente es names:

z <- table(iris$Species)
names(z)
names(z)[1] <- "A"
names(z)

Ejercicio 2.11 Cambia (en una sola expresión) los nombres de las dos primeras columnas de mi_iris por su traducción al español.

Frecuentemente se quiere muestrear un vector, es decir, obtener una serie de elementos al azar dentro de dicho vector. Para ello se utiliza la función sample:

sample(x, 4)
sample(x, 100)                  # ¡falla!
sample(x, 100, replace = TRUE)  # manera correcta

La función sample trata el vector como una urna y a sus elementos como bolas contenidas en ella que va extrayendo al azar. Obviamente, es incapaz de extraer más elementos de los que contiene la urna. Pero existe el la opción de que el muestreo se realice con reemplazamiento, i.e., de modo que cada vez que sample extraiga una bola, la reintroduzca en la urna.

Ejercicio 2.12 Muestrea iris, es decir, extrae (p.e., 30) filas al azar de dicha tabla. Pista: recuerda que ordenar era seleccionar ordenadamente; de igual manera, en una tabla, muestrear será…

El muestreo de vectores es fundamental en diversos ámbitos. Por ejemplo, a la hora de realizar los llamados tests A/B] y determinar qué observaciones van al grupo A y cuáles al B.

Ejercicio 2.13 Parte iris en dos partes iguales (75 observaciones cada uno) con las filas elegidas al azar (¡y complementarias!).

2.4 Ordenación

Existen tres funciones fundamentales relacionadas con la ordenación de vectores: order, sort y rank. sort ordena los elementos de un vector, i.e., crea una copia de dicho vector con sus elementos ordenados.

x <- c(4, 5, 3, 2, 1, 2)
sort(x)           # ordena los elementos del vector
[1] 1 2 2 3 4 5

La función order, que ya vimos cuando ordenamos tablas, es tal que sort(x) es lo mismo que x[order(x)]. Es decir, devuelve los índices de los elementos del vector de menor a mayor:

x
[1] 4 5 3 2 1 2
order(x)
[1] 5 4 6 3 1 2
x[order(x)]
[1] 1 2 2 3 4 5

El código anterior muestra cómo para ordernar x hay que tomar primero el elemento quinto elemento de x; luego, el cuarto; después, el sexto, etc.

La función rank indica la posición de los elementos de un vector: el primero, el segundo, etc.

rank(x)
rank(x, ties = "first")

Si x contuviese los tiempos de los velocistas en los 100 metros lisos, rank(x) nos indicaría quién es el primero en llegar a la meta, quién es el segundo, etc.

rank(x) es una transformación no lineal de x que puede ser útil para normalizar datos en algunos contextos.

Ejercicio 2.14 Si x contuviese el número de puntos obtenidos en la liga de fútbol por los distintos equipos, ¿cómo usarías rank para determinar cuál es el campeón?

Ejercicio 2.15 ¿Qué otros tipos de ties existen? ¿Qué hacen?

Ejercicio 2.16 Comprueba que rank(x, ties = 'first') es equivalente a order(order(x)).

Ejercicio 2.17 Comprueba que order(order(order(x))) es equivalente a order(x).

Ejercicio 2.18 Ejecuta e interpreta tail(sort(table(CO2$uptake))). ¿Qué utilidad le ves a la expresión anterior?

2.5 Operaciones matemáticas y vectorización

R puede ser usado como una calculadora:

2+2
[1] 4
x <- 4*(3+5)^2
x / 10
[1] 25.6

En R se puede operar sobre vectores igual que se opera sobre números. De hecho, en R, un número es un vector numérico de longitud 1:

c(length(2), length(x))
[1] 1 1

Así que se pueden hacer cosas tales como:

x <- 1:10
2*x
2*x + 1
x^2
x*x

Operaciones como las anteriores están vectorizadas, i.e., admiten un vector como argumento y operan sobre cada uno de los elementos. Generalmente, las operaciones vectorizadas son muy rápidas en R. Muchos problemas de rendimiento en R se resuelven, de hecho, utilizando versiones vectorizadas del código ineficiente3.

Ejercicio 2.19 Comprueba que log es una función vectorizada aplicándosela a x.

Ejercicio 2.20 Calcula el valor medio de la longitud de los pétalos de iris usando mean.

Ejercicio 2.21 Repite el ejercicio anterior usando sum y length.

Ejercicio 2.22 Suma un millón de términos de la fórmula de Leibniz para aproximar \(\pi\). Pista: crea primero un vector con los índices (del 0 al 1000000) y transfórmalo adecuadamente.

Ejercicio 2.23 Si x <- 1:10 e y <- 1:2, ¿cuánto vale x * y? ¿Qué pasa si y <- 1:3?

La operación a la que se refiere el ejercicio anterior se denomina reciclado de vectores. Cuando se opera con dos vectores (por ejemplo, para multiplicarlos) con longitudes distintas, el más corto se recicla (es decir, se repite) tantas veces como sea necesario hasta alcanzar la longitud del más largo. Si la longitud del más corto no divide exactamente la del más largo (p.e., uno tiene longitud 10 y otro, longitud 3), R recorta la parte reciclada sobrante y lanza un warning.

El lector está invitado a consultar cómo construir con R una calculadora de hipotecas, que ilustra el uso de la vectorización en un problema menos trivial que los anteriores, en el [capítulo dedicado a ejemplos de uso][Una calculadora de hipotecas con R].

2.6 Digresión: creación de funciones

Esta sección, por su importancia, altera el flujo del libro. Lo que contiene pertenece propiamente a la sección de programación. Pero, dada la importancia de la creación de funciones en lo que sigue (y, en particular, para la sección siguiente), presenta lo que será estrictamente necesario para los capítulos intermedios.

En R hay miles de funciones que pueden aplicarse a un vector. Unas cuantas de las más comunes que pueden aplicarse a vectores númericos son:

fivenum(x)   # los "cinco números característicos" de un vector
mean(x)
max(x)
median(x)
sum(x)
prod(x)

Sin embargo, por muchas funciones que tenga R para operar sobre vectores, seguramente falta aquella que resolvería un problema dado: por ejemplo, no existe una función de serie en R que sume los cuadrados de unos números. De necesitarla, podríamos crearla así:

x <- 1:10
suma_cuadrados <- function(x) sum(x*x)
suma_cuadrados(x)
[1] 385

El código anterior crea una nueva función, suma_cuadrados, que suma los cuadrados de un vector. La nueva función es un objeto más de R4, que aparece en la pestaña Environment de RStudio y en los listados de ls(). Queda en memoria para ser utilizada posteriormente.

Ejercicio 2.24 Si no existiese prod se podría programar: usa log, exp y sum para crear una función, mi_prod que multiplique los elementos de un vector (de números, por el momento, \(> 0\)).

Ejercicio 2.25 Crea una función que cuente el número de valores negativos (<0) del vector que se le pase como argumento.

Por supuesto, a menudo queremos crear funciones que no se resuelvan en una única línea, como la de arriba. Cuando el cuerpo de la función sea más complejo, hay que encerrarlo en llaves, {}.

media <- function(x){
 longitud <- length(x)
 suma <- sum(x)
 suma / longitud
}

En R, a diferencia de otros lenguajes como Python, no es necesario el return. Una función devuelve siempre el último valor definido en su cuerpo. No obstante, como veremos después, return existe y es muy útil en algunos contextos.

2.7 La función tapply

La función tapply aplica (de ahí parte de su nombre) una función a un vector en los subvectores que define otro vector máscara:

tapply(iris$Petal.Length, iris$Species, mean)
    setosa versicolor  virginica 
     1.462      4.260      5.552 

En el ejemplo anterior, tapply aplica la función mean a un vector, la longitud del pétalo, pero de especie en especie. Responde por tanto a la pregunta de cuál es la media de la longitud del pétalo por especie. Es una operación similar a la conocida en SQL como group by.

Ejercicio 2.26 Calcula el valor medio de la temperatura en cada mes de Nueva York (usando airquality).

tapply es una de las llamadas funciones de orden superior porque acepta como argumento otra función (mean en el caso anterior). Las funciones a las que llaman tanto tapply como el resto de las funciones de orden superior pueden tener parámetros adicionales. Por ejemplo, si el vector sobre el que se quiere calcular la media contiene NA, el resultado es NA. Pero mean admite el parámetro opcional na.rm = TRUE para ignorar los nulos. Para aplicar no la función mean sino mean con la opción na.rm = TRUE hay que cambiar la llamada a tapply así:

tapply(iris$Petal.Length, iris$Species, mean, na.rm = TRUE)

El resultado es el mismo (pero la sintaxis es mucho más limpia) que haciendo:

foo <- function(x) mean(x, na.rm = TRUE)
tapply(iris$Petal.Length, iris$Species, foo)

La particularidad de tapply y otras funciones de orden superior similares de R es que aquellas opciones adicionales que se incluyen en la llamada a tapply después de la función que aplican pasan a esta última.

Ejercicio 2.27 Calcula el valor medio del ozono en cada mes de Nueva York (usando airquality).

Ejercicio 2.28 Usando mtcars, calcula la potencia mediana según el número de cilindros del vehículo.

2.8 Resumen y referencias

En esta sección hemos repasado las operaciones más habituales que involucran vectores: cómo crearlos, inspeccionarlos, transformarlos, etc. Hemos prestado atención también a un tipo muy concreto y característico de R de vectores: los factores. Finalmente hemos introducido algunos conceptos avanzados y muy característicos de R para la manipulación de datos en vectores, como la vectorización y la función tapply.

Lo que no hemos hecho en absoluto es mencionar los bucles. En muchos otros lenguajes de programación, hablar de vectores es hablar de bucles. En R, aunque también pueden usarse bucles, son innecesarios para la casi totalidad de los fines: con las operaciones vectorizadas, basta.

2.9 Ejercicios adicionales

Ejercicio 2.29 Ejecuta mean(sample(iris$Sepal.Length, replace = T)) varias veces. Comprueba que obtienes números que se parecen a la media la columna Sepal.Lenght. Nota: esto es el fundamento de una técnica estadística muy poderosa, el bootstrap, para estimar cómo puede variar una media (i.e., estimar la varianza de una media).

Ejercicio 2.30 El vector letters contiene las letras a, b,… Hasta 26 en total. Crea otro vector que tenga la letra a repetida 26 veces; la b, 25, etc.

Ejercicio 2.31 Toma el vector que has creado en el ejercicio anterior y crea otro que cuente las veces que aparecen las cinco letras más frecuentes y que agrupe el resto en otros. Nota: este ejercicio se realiza innumerables veces. Por ejemplo, cuando existe un vector con tantas categorías que representarlas todas es imposible; una solución consiste en agrupar las menos frecuentes en una sola, la del resto.

Ejercicio 2.32 En una provincia la población activa es de un millón de personas. El 10% de ellas está en el paro. Periódicamente el INE hace una encuesta sobre 1000 personas para estimar la tasa de paro. Pero esta encuesta, por estar basada en 1000 personas, está sujeta a error. Puedes tratar de medir ese error de la siguiente manera: crea un vector de longitud 1M con cien mil valores iguales a 1 y el resto, a 0. Extrae una muestra de tamaño 1000 y calcula la proporción de unos. ¿Está cerca del 10%?

Ejercicio 2.33 Repite el ejercicio anterior varias veces. ¿Cómo varían las estimaciones? ¿Qué pasa si encuestas a 10000 personas en lugar de a 1000? ¿Y si encuestas a 100?

Ejercicio 2.34 Lee la parte relevante de ?replicate. ¿Para qué sirve esta función? ¿Puede ser útil para analizar el caso propuesto en los ejemplos anteriores? Nota: la página de ayuda de la función anterior documenta varias funciones relacionadas, pero puedes ignorar por el momento todo lo que no se refiera a la función en cuestión.


  1. Por eso la usamos en la sección anterior al introducir los diagramas de barras.↩︎

  2. Esto es muy habitual en R: existen funciones, como summary que modifican su comportamiento según su argumento. summary siempre genera un resumen del objeto que se le pase, sea este de la naturaleza que sea; algo parecido sucede con plot, str y otras funciones que reciben por ello el nombre de genéricas.↩︎

  3. De hecho, uno de los errores más frecuentes de los novatos en R que tienen experiencia previa en lenguajes de programación tales como Java, C o Matlab es utilizar código no vectorizado: blucles for y similares. En R se prefiere casi siempre recurrir a la vectorización.↩︎

  4. Esta es una característica de R que lo distingue de algunos otros lenguajes de programación que distinguen estrictamente entre lo que son datos y lo que son funciones; en R, sin embargo, una función es un objeto más.↩︎