Capítulo 5 Funciones

5.1 Resumen

En este capítulo, finalizamos la introducción a algunos de los elementos básicos del trabajo estadístico en R: objetos, bases de datos y funciones. Mostramos cómo aplicar funciones existentes y cómo escribir funciones nuevas que nos ayuden a analizar datos. Al final, ilustramos el potencial de ambas cuestiones con una aplicación a la política colombiana.

  • Principales conceptos: función; datos no disponibles (NA); estadísticas resumen.
  • Funciones clave: mean(); table(); cor(); is.na(); function().

5.1.1 Librerías

Vamos a utilizar las siguientes librerías:

library(tidyverse)

5.1.2 Datos

Debemos descargar los siguientes archivos de datos y guardarlos en la carpeta /data de nuestro proyecto:

  • Datos de ingreso ficticios: link. Para descargar, hacer click derecho, “Guardar como…”.

5.2 Qué es una función

En general, queremos hacer algo con los objetos en R (o sea, con los datos). Como ya hemos visto, en R hacemos operaciones usando funciones que toman un input y producen un output tras un procedimiento. Podemos aplicale funciones a valores individuales, vectores, bases de datos, variables y otros objetos.

Recapitulando, una función toma un input, le aplica una operación y nos devuelve un output. Por ejemplo, en la función \(f(x) = x + 1\).

  • Input: la información que queremos transformar (\(x\)).
  • Operación: la transformación o relación (\(x + 1\)).
  • Output: el resultado de aplicar la función (si \(x = 1\), entonces \(f(x) = 1 + 1 = 2\)).

En R, esto se vería así:

# definimos la funcion
sumar_uno <- function(a){a + 1}
# definimos unos valores
x <- 1
# aplicamos la funcion
sumar_uno(x)
## [1] 2

En R, las funciones toman argumentos, separados por comas: nombre_funcion(argumento1 = valor, argumento2 = valor, ...). Por ejemplo, para encontrar la mediana de un vector, usamos median(). Esta función toma como primer argumento un vector para encontrar el valor mediano. Adicionalmente, puede tomar un argumento na.rm = con un valor TRUE o FALSE (na.rm = FALSE o na.rm = TRUE) que elimina los valores NA antes de hallar la mediana igual a mean(). Veamos qué pasa si tenemos un valor NA:

mis_numeros <- c(1, 1, 2, 3, 5, 8, 13, 21, 34, NA)
median(mis_numeros)
## [1] NA

Como hay un NA en mis_numeros, R no puede estimar la mediana del vector. Por eso, usamos el argumento na.rm = TRUE:

median(mis_numeros, na.rm = TRUE)
## [1] 5

Podemos consultar los argumentos de una función y sus valores por defecto ejecutando ? en la consola, seguido del nombre de la función. Por ejemplo, intenten ejecutar ?median o ?tibble:

?tibble

Es posible especificar los argumentos de una función de manera explícita o implícita. Cuando hacemos explícito un argumento, usamos el nombre del argumento, como hicimos arriba con na.rm =. Aquí hacemos lo mismo con la función rnorm() y los argumentos mean = y sd =:

set.seed(42) # para asegurar replicabilidad
head(rnorm(n = 10000, mean = 0, sd = 1))
## [1]  1.3709584 -0.5646982  0.3631284  0.6328626  0.4042683 -0.1061245

En contraste, podemos dejar los argumentos implícitos. Cada función tiene un orden predefinido de sus argumentos; podemos consultar este orden consultando la función con ?. El siguiente bloque de código es equivalente al anterior:

head(rnorm(10000, 0, 1)) # diferencias en el resultado son producto de la aleatoriedad de rnorm()
## [1]  1.1631089 -0.1902346 -0.2894558 -0.3988458  0.7092426 -1.6226473

Muchas funciones tienen valores por defecto para cada argumento. En el caso de rnorm(), mean = 0 y sd = 1 a menos que los definamos de manera diferente. Podemos comprobarlo:

normal_sim <- rnorm(10000)
mean(normal_sim)
## [1] 0.008092089
sd(normal_sim)
## [1] 1.01399

Podemos cambiar el orden en que van los argumentos si usamos sus nombres (o sea, si somos explícitos):

head(rnorm(mean = 0, sd = 1, n = 10000))
## [1]  0.680923388  1.013496371 -0.009105223  0.560053416 -1.478541110
## [6]  0.952096837

Comparen lo anterior con el siguiente código que produce 1 observación distribuida normalmente, con media de 10000 y desviación estándar de 0.

head(rnorm(1, 10000, 0))
## [1] 10000

5.3 Funciones básicas

Hay funciones básicas para crear objetos – muchas ya vienen instaladas y ya hemos usado varias. Otras funciones están en librerías adicionales que descargamos y cargamos, como las del tidyverse. Aquí hay algunos ejemplos:

  • c() pega, combina o concatena valores y objetos.
  • data.frame() crea un marco de datos; tibble() crea un tibble.
  • read_csv() y read_dta leen archivos de datos CSV o de Stata, respectivamente.
  • as_tibble para convertir un data.frame u otro objeto existente en un tibble.
  • as.factor() para convertir una variable numérica en una categórica y factor() para crear un factor (una variable categórica).
  • as.numeric(), as.character(), etc.

A continuación, revisamos algunas funciones centrales para usar R en el contexto de hacer análisis de datos.

5.3.1 Estadisticas resumen

Algunas funciones básicas resumen datos numéricos. Estas incluyen mean()y muchas más:

sum(mis_numeros, na.rm = TRUE) # suma todos los elementos del vector
## [1] 88
min(mis_numeros, na.rm = TRUE) # encontrar el valor mínimo
## [1] 1
max(mis_numeros, na.rm = TRUE) # encontrar el valor máximo
## [1] 34
range(mis_numeros, na.rm = TRUE) # rango (mínimo y máximo)
## [1]  1 34

Hablando de resumir, en R usamos la función round() para redondear variables numéricas. pi es una función de R que nos da el valor de la constante $. Especificamos el número de dígitos después del decimal con el argumento digits =:

round(pi, digits = 2) # redondea al segundo decimal
## [1] 3.14

Finalmente, podemos hacer resúmenes de objetos más grandes –por ejemplo, una base de datos– con summary(). Este resumen incluye información importante sobre cada columna/variable:

summary(mtcars)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

5.3.2 Datos NA o no disponibles

A veces, no tenemos información para una observación. Decimos entonces que el dato no está disponible. R tiene un tipo de dato específicamente para esta situación: NA. Para saber si tenemos NA en un objeto (y dónde están), usamos is.na():

is.na(mis_numeros)
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE

Si queremos contar cuántos NA tenemos en un vector, podemos aprovechar que los vectores lógicos como el producido por is.na() puede ser “sumados” y si le aplicamos la función sum(), nos arroja el número de NA en el vector:

sum(is.na(mis_numeros)) ## contar cuantos NA
## [1] 1

Es importante entender que hay una diferencia importante entre datos verdaderamente no disponibles (que R marca como NA) y celdas o valores "N/A", "No disponible" o incluso "NA". También es distinto a códigos de valores no disponibles que a veces se utilizan en bases de datos (como -99). R interpreta estos como valores, no como datos no disponibles:

is.na(c(1, 3, NA, "N/A", "No disponible", "NA", -99))
## [1] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE

5.3.3 Dimensiones de un objeto

Podemos ver cuantos elementos tiene un objeto y cuáles son sus dimensiones. Carguemos una base de datos que creamos en un capítulo anterior:

datos_ingreso <- read_csv("data/datos_ingreso.csv")
datos_ingreso
## # A tibble: 7 x 4
##   nombre     edad ciudad       ingreso
##   <chr>     <dbl> <chr>          <dbl>
## 1 José         28 Barranquilla 8000000
## 2 Antonio      25 Medellín     4000000
## 3 María        32 Medellín     9500000
## 4 Inés         30 Medellín     7300000
## 5 Pablo        35 Barranquilla 6500000
## 6 Catalina     33 Bogotá       6000000
## 7 Cristóbal    42 Bogotá       9000000

Ahora, miremos sus dimensiones:

length(datos_ingreso$nombre) # cuantos elementos en un vector (filas en una columna)
## [1] 7
nrow(datos_ingreso) # número de filas
## [1] 7
ncol(datos_ingreso) # número de columnas
## [1] 4
dim(datos_ingreso) # dimensiones (filas por columnas)
## [1] 7 4

Vemos que esta base de datos tiene 7 filas y ncol(datos_ingreso) columnas.

5.3.4 Medidas de tendencia central y de dispersión

Las medidas de tendencia central y de dispersión son dos pilares de los cursos de estadística de bachillerato y universidad. Supongamos que tenemos una variable distribuida normalmente, con una media de 5 y una desviación estándar de 2. Creemos un vector con esas características:

x <- rnorm(1000, mean = 5, sd = 2)

Tomemos ese vector y miremos dos medidas de tendencia central:

mean(x) # media aritmética
## [1] 5.054971
median(x) # mediana
## [1] 5.032139

Y ahora, dos de tendencia central:

sd(x) # desviación estándar
## [1] 1.93284
var(x) # varianza
## [1] 3.735872

La estadística descriptiva e inferencial depende crucialmente de medidas de dispersión y de tendencia central como estas.

5.3.5 Relaciones entre variables

Con frecuencia, no nos interesa saber algo sobre una variable aislada. Más bien, queremos ver cómo se relaciona con otras. Digamos que queremos calcular el coeficiente de correlación entre x como la definimos arriba y otra variable y definida así:

y <- rnorm(1000, 10, 3) # argumentos implícitos

¿Cuál es la correlación? Usemos la función cor():

cor(x, y)
## [1] -0.001761721

Podemos aprovechar las propiedades de los vectores en R para crear nuevos objetos a partir de los ya existentes, como una vector de valores z que dependen del valor de x:

z <- x + 3

En este caso, la correlación entre x y z es perfecta porque una está definida en términos de la otra:

cor(x, z)
## [1] 1

cor() funciona también con bases de datos. Por ejemplo, veamos que en nuestros datos ficticios de ingreso individual hay una correlación positiva entre edad e ingreso:

cor(datos_ingreso$edad, datos_ingreso$ingreso)
## [1] 0.5456026

5.4 Anidar funciones

No estamos limitados a utilizar una función a la vez. En R, es fácil usar múltiples funciones al mismo tiempo para realizar operaciones más complejas. Para esto, las “anidamos”. El orden de operaciones es sencillo: se leen de adentro hacia afuera. Pero esto a veces puede ser confuso – ¡hay que tener cuidado con los paréntesis!

round(mean(mis_numeros, na.rm = TRUE)) # si no especificamos, redondea al primer decimal
## [1] 10
(mean(x) - mean(y))^2
## [1] 24.00694

Más adelante, vamos a introducir el operador %>% (“pipe”, tubo o tubería) de la librería magrittr (y usado extensivamente en el tidyverse), una aproximación que no exige anidar funciones, simplifica la lectura del código y facilita realizar operaciones complejas.

5.5 Tablas y resúmenes

Hay funciones para contar o hacer resúmenes de variables cualitativas. Por ejemplo, table() crea una tabla sencilla que cuenta cuántas veces aparece cada número en el vector mis_numeros. Todos, excepto el número 1, aparecen una sola vez.

table(mis_numeros)
## mis_numeros
##  1  2  3  5  8 13 21 34 
##  2  1  1  1  1  1  1  1

A veces queremos “cruzar” variables categóricas, esto es, ver cuántas observaciones tienen distintas combinaciones de valores de dos variables. table() también sirve para hacer lo que llamamos una tabla cruzada, tabulación cruzada o tabla de contingencia entre dos variables discretas o categóricas, en este caso, el tipo de transmisión y el número de cilindros de los carros en mtcars:

table(mtcars$am, mtcars$cyl)
##    
##      4  6  8
##   0  3  4 12
##   1  8  3  2

5.6 Escribir funciones

Podemos escribir nuestras propias funciones. Es más, ya lo hemos hecho en un par de ocasiones (¿sí saben dónde?). A veces no existe la función para lo que queremos hacer. Podemos crear funciones para hacer otras operaciones o incluso construir indicadores – ¿o cómo creían que se calculaba el coeficiente de Gini o la volatilidad electoral?

Las funciones también son útiles cuando queremos hacer algo muchas veces o cuando queremos simplificar nuestro código. Algunos sugieren que si hacemos la misma operación mas de 2 veces, deberíamos escribir una función.6

El proceso para crear una función en R tiene tres pasos:

  1. Darle un nombre descriptivo a la función (calc_indicador en vez de mi_funcion).
  2. Listar los argumentos que toma.
  3. Escribir el código que especifica las operaciones que realiza.

Este proceso lo hacemos con la función function(). Escribamos una función. Digamos que queremos tomar unos datos numéricos y ponerlos en una escala que vaya de 0 a 1, donde 1 sea el valor más alto. Formalmente:

\[ f(x) = (x - min) / (max - min) \]

Donde \(x\) es un vector numérico, \(min\) es el valor mínimo del vector y \(max\) el valor máximo. Así, cada valor de \(x\) va a tener un nuevo valor entre 0 y 1.

Para crear la función en R, seguimos los tres pasos especificados arriba. Primero, le damos nombre a la función: digamos reescalar01. Segundo, definimos qué argumentos toma la función: en este caso, uno solo, que vamos a llamar x. Tercero, escribimos el código de la operación que realiza nuestra función:

# nombre y argumentos de la función
reescalar01 <- function(x){ 
  # operaciones
  minimo <- min(x, na.rm = TRUE) # encontrar el mínimo y guardarlo
  maximo <- max(x, na.rm = TRUE) # encontrar el máximo y guardarlo
  reescalados <- (x - minimo) / (maximo - minimo) # calcular
  # resultado
  return(reescalados)
}

Aplicamos nuestra función a un vector de números cualquiera:

vector_num <- c(0, 25, 60, 80)
reescalar01(vector_num)
## [1] 0.0000 0.3125 0.7500 1.0000

5.6.1 Media aritmética

Ahora supongamos que queremos hallar la media de un conjunto de datos numérico. Y ahora supongamos que esa función no existe ya en R. ¡Podemos escribir una función! Formalmente, la media \(m\) de un conjunto \(x\) se define como:

\[ m = \frac{\sum(x_i)}{n} \]

Donde \(x_i\) son todos los valores de \(x\) y \(n\) es el número de observaciones. Entonces, creamos un objeto llamado media que es la función. Esa función recibe un solo argumento, que aquí llamamos z. La función toma x, suma todos sus elementos y los divide por el numero de elementos

# nombre y argumentos de la función
media <- function(z){
  # operaciones
  med <- sum(z)/length(z)
  # resultado
  return(med)
}

Apliquemos la función para ver si sirve:

media(vector_num)
## [1] 41.25

Afortunadamente, ya sabemos que R cuenta con una función que calcula la media. Comparemos ambas. El operador lógico == evalúa si dos objetos son idénticos (estrictamente iguales), así que podemos utilizarlo para evaluar si nuestra función está funcionando como queremos:

media(vector_num) == mean(vector_num)
## [1] TRUE

5.6.2 Número efectivo de partidos

Queremos saber cuántos partidos efectivos hay en una legislatura o congreso. Escribimos una función siguiendo la formula clásica de M. Laakso y R. Taagepera.7 Formalmente:

\[ NEP = \frac{1}{\sum_{i=1}^{n}{p_i^2}} \] Donde \(NEP\) es el indicador del número efectivo de partidos, \(n\) el número de partidos \(i\) que ganaron votos o curules y \(p_i\) es la proporción de votos o de curules obtenidas por cada partido.

Para implementar esta función en R, empezamos por asignarle un nombre: nep. Siguiendo la fórmula, esta tiene un solo argumento: votos =, el número de votos o curules por partido. Vamos a suponer que no tenemos proporciones aún, solo votos o curules. Entonces, hallamos la proporción del total de votos que obtuvo cada partido: dividimos los votos de cada partido por el total de votos con sum(). Elevamos al cuadrado esa proporción usando el operador ^. Finalmente, sumamos los cuadrados de las proporciones con sum() y hallamos el inverso (dividimos sobre 1):

# nombre y argumentos de la función
nep <- function(votos){
  # operaciones
  proporcion_votos <- votos/sum(votos) # hallar prop.
  proporcion_votos2 <- proporcion_votos^2 # elevar prop. al cuadrado
  sum_proporcion_votos2 <- sum(proporcion_votos2) # sumar props.
  nep <- 1/sum_proporcion_votos2
  # resultado
  return(nep)
}

Ahora, solo necesitamos unos resultados electorales para probar nuestra función, específicamente cuántos votos o curules sacaron todos los partidos. Como primera prueba, creamos un vector con números de votos de una elección ficticia dominada por el partido B:

eleccion1 = tibble(
  partido = LETTERS[1:6],
  num_votos = c(10000, 500000, 35000, 66000, 3000, 1700)
)

Veamos estos datos:

eleccion1
## # A tibble: 6 x 2
##   partido num_votos
##   <chr>       <dbl>
## 1 A           10000
## 2 B          500000
## 3 C           35000
## 4 D           66000
## 5 E            3000
## 6 F            1700

Aplicamos la función a los datos y obtenemos el resultado, un NEP bastante bajo:

nep(eleccion1$num_votos)
## [1] 1.482585

Intentemos aplicar la función a un conjunto de datos distinto en el que los votos están distribuidos más y con más partidos:

eleccion2 <- tibble(
  partido = LETTERS[1:8],
  num_votos = c(10000, 30000, 20000, 25000, 13000, 18000, 3000, 4000)
)
eleccion2
## # A tibble: 8 x 2
##   partido num_votos
##   <chr>       <dbl>
## 1 A           10000
## 2 B           30000
## 3 C           20000
## 4 D           25000
## 5 E           13000
## 6 F           18000
## 7 G            3000
## 8 H            4000

El resultado es bastante diferente y refleja la distribución de votos en este contexto:

nep(eleccion2$num_votos)
## [1] 5.949273

  1. Wickham, Hadley, y Garrett Grolemund. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media, 2016.↩︎

  2. Laakso, Markku, y Rein Taagepera. “‘Effective’ Number of Parties: A Measure with Application to West Europe.” Comparative Political Studies, vol. 12, no. 1, Abr. 1979, pp. 3–27, doi:10.1177/001041407901200101.↩︎