Empezando en R (IV). Operando con datos

Es hora de continuar con nuestro aprendizaje.

Ya habiamos visto los diferentes tipos de objetos con los que trabajamos en R así como algunos tipos de datos especiales. Ahora ha llegado el momento de aprender a operar con esos datos ¿no?.

¿Qué a que me refiero con operar? ¿Me dejas que te cuente?

¿Qué es operar?

Si váis un momento a la entrada anterior, cuando hablamos de valores lógicos, ya apareció la palabra “operador”. Allí hablábamos de cómo comparar valores para tener como resultado un valor lógico o de como trabajar con varios valores lógicos obteniendo un resultado final de verdadero (TRUE) o falso (FALSE). Pues bien, vamos a generalizar esta idea:

En matemáticas, cuando hablamos de operar o de realizar una operación en el fondo estamos hablando de aplicar una función que convierte una serie de valores de entrada en un valor de salida, al que llamamos resultado.

Lo entendemos mejor si hablamos de la suma, de la resta, de la división o de la multiplicación. Esto es, varios valores sumados (restados, multiplicados, etc.) dan otro valor como resultado.

Operadores aritméticos

En general, a estos operadores los conocemos como operadores aritméticos y podemos utilizarlos mediante los siguientes símbolos:

  • +
  • *
  • /

Veamos algunos ejemplos:

> #Asignamos a la variable x el valor 3
> x <- 3
> 
> #A x le sumamos 12 y se lo asignamos a z
> z <- x + 12
> z
## [1] 15
> #Multiplicamos el valor de x por el valor de z y da 45
> d <- x * z 
> d
## [1] 45
> #Dividimos d entre 5
> d/5
## [1] 9

Pero evidentemente, con la definición que hemos dado al principio, estos no son los únicos operadores posibles.

Un pasito más allá

Algunas operaciones que podemos realizar en R de forma sencilla son las potencias que conseguimos con los simbolos ^ o **.

> x^2
## [1] 9
> x**2
## [1] 9

Otra operación que nos puede ser útil y que se consigue con un operador en R es la parte entera de una división, es decir, el resultado de la división pero sin decimales. Para hacerlo más visual, pensad que sería el número máximo que pondríais bajo la “cajita” en una división hecha a mano antes de pasar a poner decimales. Este valor lo obtenemos con el operador %/% , y ojo porque esto de poner % delante y detrás de un operador lo vamos a usar más veces para modificar ligeramente el uso de este. Pero veamos un ejemplo de parte entera:

> d %/% 2
## [1] 22

Volviendo a la división “a mano”, si no hemos llegado a poner decimales es posible que nos quede un resto que no hemos llegado a dividir, ¿verdad? Pues ese resto lo podemos recuperar con otro operador: %%. Veamos cómo un ejemplo:

> d%%2
## [1] 1

En definitiva, tenemos algunos símbolos con los que podemos operar con números en R. Sin embargo, ¿qué sucede cuando lo que queremos es operar con vectores o con matrices?

Operaciones con matrices

Sumar, restar o multiplicar matrices por un número es algo relativamente sencillo e intuitivo, también en R. Lo hacemos simplemente sumando o restando posición a posición de la matriz o multiplicando cada valor por el número deseado. En R basta con usar los símbolos vistos hasta ahora, exceptuando la división:

> #Creamos una matriz de 5 columnas y 4 filas con los valores del 1 al 20
> X <- matrix(1:20, ncol=5)
> 
> #Otra con los valores del 21 al 40
> Y <- matrix(21:40, ncol=5)
> 
> #Las sumamos 
> X+Y
##      [,1] [,2] [,3] [,4] [,5]
## [1,]   22   30   38   46   54
## [2,]   24   32   40   48   56
## [3,]   26   34   42   50   58
## [4,]   28   36   44   52   60
> #Las restamos
> Y-X
##      [,1] [,2] [,3] [,4] [,5]
## [1,]   20   20   20   20   20
## [2,]   20   20   20   20   20
## [3,]   20   20   20   20   20
## [4,]   20   20   20   20   20
> #Las multiplicamos por 2 y por 1/2 respectivamente
> X*2
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    2   10   18   26   34
## [2,]    4   12   20   28   36
## [3,]    6   14   22   30   38
## [4,]    8   16   24   32   40
> Y*1/2
##      [,1] [,2] [,3] [,4] [,5]
## [1,] 10.5 12.5 14.5 16.5 18.5
## [2,] 11.0 13.0 15.0 17.0 19.0
## [3,] 11.5 13.5 15.5 17.5 19.5
## [4,] 12.0 14.0 16.0 18.0 20.0

Sin embargo, si recordáis un poco de vuestro paso por el instituto (o cuando fuese que utilizaseis las matrices por última vez), el cálculo matricial va un poco más allá, y multiplicar vectores y matrices tiene su miga. Lo primero es que las dimensiones tienen que cumplir ciertas condiciones, esto es, si quiero multiplicar dos matrices, el número de columnas de la primera tiene que ser el mismo que el número de filas de la segunda.

En nuestro ejemplo de antes no podríamos, por ejemplo, multiplicar X e Y porque la primera tiene 5 columnas y la segunda 4 filas.

> X%*%Y
> #Error in X %*% Y : non-conformable arguments

Para solucionarlo podemos hacer algo antes… podemos transponer la matriz, esto es cambiar sus filas por sus columnas, y lo hacemos con la función t():

> t(Y)
##      [,1] [,2] [,3] [,4]
## [1,]   21   22   23   24
## [2,]   25   26   27   28
## [3,]   29   30   31   32
## [4,]   33   34   35   36
## [5,]   37   38   39   40

Y ahora sí, sólo tenemos que usar el operador que multiplica matrices: %*% (ves, el símbolo de la multiplicación modificado por el %):

> X %*% t(Y)
##      [,1] [,2] [,3] [,4]
## [1,] 1465 1510 1555 1600
## [2,] 1610 1660 1710 1760
## [3,] 1755 1810 1865 1920
## [4,] 1900 1960 2020 2080

Ojo, porque el resultado de una multiplicación de matrices es otra matriz, no un único valor. Esto es común en las operaciones, dos objetos de un tipo pueden dar un objeto del mismo tipo como resultado.

También podemos multiplicar vectores usando el mismo operador y, en esta ocasión, sólo es necesario que los dos vectores tengan la misma dimensión:

> x <- 1:10
> y <- rep(1,10)
> x%*%y
##      [,1]
## [1,]   55

Fíjate un momento en el resultado de multiplicar un vector por otro de la misma dimensión que solo contiene unos. Sí, eso es, el resultado es la suma de los valores del vector, algo que también podíamos conseguir con la función sum():

> sum(x)
## [1] 55

¿Qué por qué te cuento esto último?, pues porque realmente los cálculos matriciales puede parecer que no son útiles en el análisis de datos pero, en realidad sí lo son. Los cálculos matriciales o vectoriales permiten hacer de forma eficiente operaciones que de otro modo serían muy complicadas.

Por ejemplo, imaginad que he recogido el peso de la cosecha de naranjas en toneladas por hectárea y lo he hecho para las 13 parcelas de una comarca.

> peso
##  [1] 31 40 32 32 29 34 34 33 35 36 33 31 33

Además, tenemos la extensión en hectáreas de cada una de esas parcelas.

> ha
##  [1] 0.50 0.45 0.29 0.86 0.60 0.65 0.41 0.57 1.10 0.80 0.41 0.19 0.42

Si queremos recuperar el total de toneladas recogidas es tan fácil como multiplicar cada valor de toneladas por hectaréa por el número de hectáreas de esa parcela y sumar los resultados. Podríamos rebuscar la forma de hacerlo en R valor a valor pero, matemáticamente, esa operación es una multiplicación de vectores:

> peso%*%ha
##        [,1]
## [1,] 243.13

Mucho más eficiente, ¿no te parece? Y es que el uso de vectores y del cálculo matricial es una de las mejores formas de trabajar cuando se trata de programar.

Pero, antes de seguir profundizando en como aprovechar al máximo la estructura vectorial, déjame que haga un inciso sobre algo que lleva apareciendo una y otra vez desde el principio y que también podemos usar para operar: las funciones de R.

Funciones para operar

Funciones hay de todo tipo, aunque todas coinciden en su estructura: un nombre seguido de unos paréntesis entre los que ponemos argumentos y que, al ejecutarla, da un resultado (a veces un valor, otras veces un objeto o varios). Parecido a los operadores, ¿no?

Efectivamente, muchas operaciones podemos conseguirlas usando funciones. Ya hemos visto algunas, como sum(), que permite sumar todos los valores de un vector, o t(), que permite trasponer una matriz.

Otras funciones del mismo estilo son prod(), que realiza el producto de todos los valores de un vector, round(), que redondea el resultado al número de decimales deseado, abs() que sirve para dar el valor absoluto, es decir, sin signo…

Si nos centramos en el análisis de datos, podemos pensar en las funciones max() o min(), que sirven para obtener el máximo o el mínimo de un vector de datos respectivamente, la función mean(), que se usa para calcular la media de un vector o la función sd(), que calcula la desviación estándar, y así muchas más que nos permiten obtener lo que en Estadística llamamos “estadísticos” y que no son más que valores resumen de los datos.

Pero, ¿qué pasa cuando queremos aplicar estas funciones sistemáticamente a todas las columnas de una matriz o, dicho de otra forma, a todas las variables de un banco de datos?

Hablemos del mundo apply.

Repetir una operación

En muchas ocasiones el análisis de datos requiere realizar las mismas operaciones en diversas variables o para varios grupos. Llegar a realizar estos cálculos múltiples se puede conseguir en R de diferentes formas, una de las más conocidas es mediante el uso de bucles, tema que trataremos en la próxima entrada, ahora toca las funciones apply.

Las funciones apply aprovechan la estructura de los objetos de R para hacer que algunos cálculos se puedan realizar con una sola línea de código y de la forma más eficiente.

Sin embargo, cabe mencionar que estas funciones no son siempre fáciles de utilizar y que, a veces, no está mal dar un par de rodeos para llegar a destino si con ellos no te pierdes.

Aun así, dejadme que os introduzca un poco en el uso de estas funciones.

Apply para arrays

La primera y más básica de estas funciones es la función apply(). A esta función le pasaremos como argumentos: * Una matriz o un array: X * La dimensión por la que queremos trabajar: MARGIN. Esto es, si tenemos una matriz indicaremos un 1 para trabajar por filas y un 2 para trabajar por columnas. Por último indicaremos cual es la función que queremos aplicar a esas filas o esas columnas: FUN.

Para ver como funciona volvamos al ejemplo que utilizamos al hablar de matrices y arrays. Se trataba del nivel de precipitaciones en 5 ciudades españolas para los 12 meses del año.

> precip_mat2 
##            ene   feb  mar  abr  may  jun  jul  ago  sep   oct   nov   dic
## yecla     14.1  16.5 18.5 23.9 21.0 15.6  4.8  7.3 21.4  30.8  25.0  18.5
## valencia  20.7  21.9 23.6 27.9 24.2 13.2  5.1 11.7 32.9  43.7  35.6  26.2
## ibiza     36.8  32.5 29.9 34.4 26.2 10.3  5.4 13.3 39.2  51.8  57.4  43.8
## vigo     131.0 100.7 86.3 93.6 72.3 38.6 19.8 25.6 64.6 136.9 144.2 147.8
## tenerife  16.7  14.4 10.1  5.2  1.6  0.5  0.3  1.5  4.1   9.2  15.0  19.0

Imagina que quieres calcular la media por ciudad. Dicho de otra forma, queremos calcular la media por cada vector fila. Para eso basta con usar:

> apply(X = precip_mat2, MARGIN = 1, FUN = mean)
##     yecla  valencia     ibiza      vigo  tenerife 
## 18.116667 23.891667 31.750000 88.450000  8.133333

Para incluir algún otro argumento de la función mean, como por ejemplo queremos decirle que no tenga en cuenta si hay un valor faltante, lo único que tenemos que hacer es añadir el valor de ese argumento a continuación, como un argumento más del apply:

> apply(X = precip_mat2, MARGIN = 1, FUN = mean, na.rm = TRUE)
##     yecla  valencia     ibiza      vigo  tenerife 
## 18.116667 23.891667 31.750000 88.450000  8.133333

Si quisieramos saber cual ha sido la lluvia máxima por mes bastaría con cambiar el argumento MARGIN de 1 a 2 y la función mean por la función max:

> apply(X = precip_mat2, MARGIN = 2, FUN = max)
##   ene   feb   mar   abr   may   jun   jul   ago   sep   oct   nov   dic 
## 131.0 100.7  86.3  93.6  72.3  38.6  19.8  25.6  64.6 136.9 144.2 147.8

En el caso de tener un array que, por ejemplo, tiene los datos anteriores pero repetidos para 4 años, podemos especificar más de una dimensión para, por ejemplo, calcular la media por ciudad y mes en los diversos años medidos, o por año, o por mes y año, etc.

Por ejemplo, calculemos la media por año teniendo en cuenta que eso significa promediar por la dimensión 3:

> apply(X = precip_anual_array,  MARGIN = 3, FUN = mean)
##     2010     2011     2013     2014 
## 34.06833 34.06833 34.06833 34.06833

O podemos promediar por ciudad y año (dimensiones 2 y 3):

> apply(X = precip_anual_array,  MARGIN = c(2,3), FUN = mean)
##           anualidad
## ciudades        2010      2011      2013      2014
##   yecla    18.116667 18.116667 18.116667 18.116667
##   valencia 23.891667 23.891667 23.891667 23.891667
##   ibiza    31.750000 31.750000 31.750000 31.750000
##   vigo     88.450000 88.450000 88.450000 88.450000
##   tenerife  8.133333  8.133333  8.133333  8.133333

La cuestión es que esta función nos es muy útil con arrays y matrices… pero, ¿qué pasa cuando tenemos listas? En este caso pasamos a la función lapply().

Lapply para listas

Para aplicar una función a todos los componentes de una lista tenemos la función lapply().

En este caso la idea es aplicar la misma función a todos los elementos de una lista, obteniendo como resultado una nueva lista con los respectivos resultados. Así pues, los argumentos principales serán dos:

  • Una lista: X
  • La función a utilizar: FUN

Es importante tener en cuenta que para poder hacerlo necesitamos que la función pueda aplicarse específicamente a todos los elementos de la lista.

Veamos un ejemplo:

> ejemplolista <- list(nombre="Pedro", casado=TRUE, no.hijos=3, edad.hijos=c(4,7,9))
> lapply(X = ejemplolista,FUN = length)
## $nombre
## [1] 1
## 
## $casado
## [1] 1
## 
## $no.hijos
## [1] 1
## 
## $edad.hijos
## [1] 3

En este ejemplo hemos calculado la longitud (length) de cada uno de los elementos de la lista y tenemos una nueva lista con estas longitudes.

En este caso quizás no tiene mucho sentido que el formato de salida sea una lista, pues se trata de valores que podríamos tener perfectamente en un vector. Para conseguir eso basta con usar la versión sapply() de la misma función:

> sapply(X = ejemplolista, FUN = length)
##     nombre     casado   no.hijos edad.hijos 
##          1          1          1          3

Quizás estés pensando que esto está muy bien pero que tú no vas a usar a menudo arrays o listas… bueno, lo cierto es que sí porque el formato data.frame no es más que una evolución de estas.

Apply y bancos de datos

Cuando queremos aplicar una función a las variables de un banco de datos (columnas) o a las diferentes unidades muestrales (filas), en el fondo estamos haciendo un poco lo mismo que cuando aplicamos la función apply a un array.

Además, podemos considerar que las diferentes columnas del data.frame no son más que elementos de una lista y, por tanto, para aplicar una función a todas las variables basta con usar lapply o sapply.

Eso sí, de nuevo, es importante que la función que vayamos a usar sea imputable a todos los elementos a los que la queremos aplicar. Veamos un ejemplo, para eso usaremos el banco de datos cars:

> data(cars)

Este banco de datos contiene 50 observaciones de dos variables, speed y dist. Si queremos calcular la media de cada una de estas variables podemos hacerlo así:

> apply(X = cars, MARGIN = 2, FUN = mean)
## speed  dist 
## 15.40 42.98

o así:

> lapply(X = cars, FUN = mean)
## $speed
## [1] 15.4
## 
## $dist
## [1] 42.98

Y en este último caso, si no queremos que nos devuelva una lista, recurrir a:

> sapply(X = cars, FUN = mean)
## speed  dist 
## 15.40 42.98

Pero imagina ahora que que tuviesemos una tercera variable que contenga el tipo de vehículo:

> cars$type <- factor(rep(c("motorbike", "car"), each = 25))

Y ahora no te interesa calcular la media de cada variable, si no la media por coche. Recurramos a operar por grupos.

Operar por grupos

Son muchas muchas las ocasiones en las que necesitaremos calcular algo separando por grupos, que vendrán especificados por un factor o determinados por el valor de una variable.

En ese caso hay varias funciones a las que podemos recurrir y, entre ellas, se encuentra tapply().

tapply, separar grupos

La función tapply() requiere tres argumentos principales:

  • Un objeto que se pueda “partir” en grupos: X. Típicamente será un vector aunque también puede ser un banco de datos, una matriz, etc.
  • Una lista con uno o más factores de longitud igual a la de X, que nos dividan las observaciones en grupos: INDEX
  • La función a aplicar para cada grupo: FUN

Ademas, también podemos especificar el argumento simplify como TRUE o FALSE. En el primer caso el resultado será un array, en el segundo obtendremos una lista. Veámoslo en el ejemplo:

> tapply(X = cars$speed, INDEX = cars$type, FUN = mean)
##       car motorbike 
##     19.72     11.08

Además de tapply() también podemos usar la función by(), que hace exactamente lo mismo pero quizás tiene un nombre algo más intuitivo:

> by(data = cars$speed, INDICES = cars$type, FUN = mean)
## cars$type: car
## [1] 19.72
## ------------------------------------------------------------ 
## cars$type: motorbike
## [1] 11.08

Además, esta función permite aplicar funciones que son típicamente para bancos de datos completos (como summary), separando por grupos:

> by(data = cars[,1:2], INDICES = cars[, "type"], FUN = summary)
## cars[, "type"]: car
##      speed            dist       
##  Min.   :15.00   Min.   : 32.00  
##  1st Qu.:18.00   1st Qu.: 42.00  
##  Median :19.00   Median : 54.00  
##  Mean   :19.72   Mean   : 59.52  
##  3rd Qu.:22.00   3rd Qu.: 70.00  
##  Max.   :25.00   Max.   :120.00  
## ------------------------------------------------------------ 
## cars[, "type"]: motorbike
##      speed            dist      
##  Min.   : 4.00   Min.   : 2.00  
##  1st Qu.:10.00   1st Qu.:17.00  
##  Median :12.00   Median :26.00  
##  Mean   :11.08   Mean   :26.44  
##  3rd Qu.:13.00   3rd Qu.:34.00  
##  Max.   :15.00   Max.   :80.00

Y con esto, hemos visto como podemos realizar muchas de las operaciones sencillas habituales en R. Te dejo que practiques y nos leemos en un par de semanas con más.

¡Espero que te haya sido útil! Si es así, te agradezco que compartas para llegar a más gente.

Indice del curso

 

  6 Replies to “Empezando en R (IV). Operando con datos”

  1. Salvador Rodríguez
    04/02/2023 at 22:32

    Hola Anabel. Creo que hay otra errata es este párrafo:
    «Qué por qué te cuento esto último?, pues porque realmente los cálculos matriciales pueden parecer poco útiles en el análisis de datos pero, en realidad no lo son. Los cálculos matriciales o vectoriales permiten hacer de forma eficiente operaciones que de otro modo serían muy complicadas.»

    Creo que lo que quieres decir es: «EN REALIDAD, LO SON»

    Saludos y gracias por tu curso. Yo, de momento, sigo R que R 😉

    • 13/04/2023 at 07:43

      ummm… como pone «poco útiles» yo quería decir que «no lo son», vamos, que no son poco útiles si no muy útiles. Pero tienes razón que no se entiende del todo. Voy a darle una vuelta a la frase.
      Gracias por el apunte!
      Un saludo
      Anabel

  2. 15/01/2023 at 22:59

    Me he peleado con manuales en los ultimos seis meses, per con tus explicaciones da gusto avanzar, gracias por hacerlo asi de facil,

    Solo una duda: en esta frase «… si quiero multiplicar dos matrices, el número de columnas de la primera tiene que ser el mismo que el número de columnas de la segunda.» … quisiste decir el numero de columnas de la primera ha de ser igual al de filas de la segunda?

  3. 10/01/2023 at 17:39

    <>
    Para mí que en esa frase tienes una errata…

    Y aprovecho para darte las gracias por esta introducción a R que estás colgando. Me ha animado a ponerme con ello 😀

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: