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
<- function(a){a + 1}
sumar_uno # definimos unos valores
<- 1
x # 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
:
<- c(1, 1, 2, 3, 5, 8, 13, 21, 34, NA)
mis_numeros 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:
<- rnorm(10000)
normal_sim 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()
yread_dta
leen archivos de datos CSV o de Stata, respectivamente.as_tibble
para convertir undata.frame
u otro objeto existente en un tibble.as.factor()
para convertir una variable numérica en una categórica yfactor()
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:
<- read_csv("data/datos_ingreso.csv")
datos_ingreso 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:
<- rnorm(1000, mean = 5, sd = 2) x
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í:
<- rnorm(1000, 10, 3) # argumentos implícitos y
¿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
:
<- x + 3 z
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:
- Darle un nombre descriptivo a la función (
calc_indicador
en vez demi_funcion
). - Listar los argumentos que toma.
- 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
<- function(x){
reescalar01 # operaciones
<- min(x, na.rm = TRUE) # encontrar el mínimo y guardarlo
minimo <- max(x, na.rm = TRUE) # encontrar el máximo y guardarlo
maximo <- (x - minimo) / (maximo - minimo) # calcular
reescalados # resultado
return(reescalados)
}
Aplicamos nuestra función a un vector de números cualquiera:
<- c(0, 25, 60, 80)
vector_num 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
<- function(z){
media # operaciones
<- sum(z)/length(z)
med # 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
<- function(votos){
nep # operaciones
<- votos/sum(votos) # hallar prop.
proporcion_votos <- proporcion_votos^2 # elevar prop. al cuadrado
proporcion_votos2 <- sum(proporcion_votos2) # sumar props.
sum_proporcion_votos2 <- 1/sum_proporcion_votos2
nep # 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
:
= tibble(
eleccion1 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:
<- tibble(
eleccion2 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
Wickham, Hadley, y Garrett Grolemund. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media, 2016.↩︎
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.↩︎