Dataframes

Un dataframe es la forma más común de almacenar datos en R y, en general, es la estructura de datos que se usa con más frecuencia para realizar tareas de análisis de datos. Básicamente, un dataframe es una lista de vectores de igual longitud. Cada elemento de la lista se puede considerar como una columna y la longitud de cada elemento de la lista es el número de filas. Como resultado, los dataframes pueden almacenar diferentes tipos de objetos (datos) en cada columna (es decir, numérico, carácter, factor, fechas…). En esencia, la manera más fácil de pensar en un dataframe es como una hoja de cálculo tipo Excel que contiene columnas de diferentes tipos de datos, pero todas son filas de igual longitud. Esta estructura de datos nos permite tener varias informaciones de múltiples observaciones.

Creación y guardado

Existen múltiples formas de construir un dataframe a continuación describiremos las más usadas:

Desde un archivo

No existe una función que nos permita cargar datos de cualquier formato de archivo, ya que como sabéis, cada tipo de archivo posee una estructura interna propia ( más o menos consistente) y por lo tanto necesitaremos funciones diseñadas para los diferentes formatos:

texto: Para crear un dataframe a partir de un fichero de texto simple que contenga una tabla de datos, usaremos la función read.table. Podemos aplicar esta función al nombre del fichero, si está en el directorio de trabajo de R, o a su url, si está en Internet; en ambos casos, entre comillas. Esta función read.table tiene algunos parámetros que evitan errores en la creación del dataframe.

csv: Tenemos la función read.csv que necesita el path (dirección) del archivo como parámetro de entrada: nombre_dataframe = read.csv("path/del/archivo/nombre.csv"). Esta función tiene un gran nombre de parámetros para ajustarla a las características de cada fichero, por ejemplo, el parámetro header tiene valor booleano e indica si la primera fila del archivo se corresponde con el nombre de las columnas.

json: Podremos construir dataframes a partir de otros formatos de archivo, como por ejemplo json mediante la instalación de librerías externas. Para más información podemos consultar la siguiente referencia.

A partir de una colección de vectores

Supongamos que tenemos la siguiente colección de vectores con idéntica longitud:

temp = c(20.37, 18.56, 18.4, 21.96, 29.53, 28.16, 36.38, 36.62, 40.03, 27.59, 22.15, 19.85)
humedad = c(88, 86, 81, 79, 80, 78, 71, 69, 78, 82, 85, 83)
precipitaciones = c(72, 33.9, 37.5, 36.6, 31.0, 16.6, 1.2, 6.8, 36.8, 30.8, 38.5, 22.7)
mes = c("enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre")

Podemos transformarlos en un dataframe usando la función data.frame de la siguiente manera:

meteo = data.frame(mes = mes, temperatura = temp, humedad = humedad, precipitaciones = precipitaciones)

El resultado que obtendremos tras ejecutar esta segunda operación es que habremos almacenado el dataframe en la variable meteo.

Dataframes preconstruidos

También podemos hacer uso de los dataframes de ejemplo que R nos proporciona, estos son muy útiles para realizar tareas de aprendizaje y pequeñas prácticas. Para buscarlos, puedes llamar a la función data. Una vez que hemos ejecutado esta función se abrirá una ventana con una lista de conjuntos de datos disponibles, si queremos usar el conjunto de datos CO2 deberemos ejecutar el siguiente código:

> data(CO2)
> CO2

como resultado tendremos el dataframe en la variable CO2.

Por otra parte, también será posible obtener conjuntos de datos externos mediante la función data aunque necesitaremos realizar algún paso previo para descargar los datos a nuestro ordenador, este paso típicamente consiste en la instalación de alguna libreria.

Guardado

Para poder guardar un dataframe en un fichero podemos usar la función write.table muy similar a la función read.table. Esta función recibe como mínimo 2 parámetros: el dataframe a guardar y el nombre que tendrá el archivo (un string). Si no queremos que este se guarde en la carpeta predeterminada, podemos especificar el path (absoluto o relativo) antes del nombre del archivo como parte del string.

Inspección básica

Cuando hemos cargado nuestros datos lo primero que hay que hacer es inspeccionarlo para poder comprobar que este se corresponde con aquello que esperamos, a continuación encontraremos una lista de las funciones que nos pueden ayudar a realizar esta tarea:

  • View: recibe un dataframe por parámetro, muestra el dataframe al que se aplica en la ventana de ficheros.

  • str: recibe un dataframe por parámetro, da la estructura global de un objeto de datos.

  • head: recibe un dataframe y un número \(n\) como parámetros, nos muestra las \(n\) primeras filas del dataframe. Por defecto, si no pasamos ningún valor, este es 5.

  • tail: recibe un dataframe y un número \(n\) como parámetros, nos muestra las \(n\) últimas filas del dataframe. Por defecto este valor es 5.

  • names: recibe un dataframe por parámetro, sirve para obtener un vector con los nombres de las columnas de un dataframe, y también para modificar estos nombres si esta función recibe un vector.

  • dim: recibe un dataframe por parámetro, proporciona una lista donde la primera posición es el número de filas y la segunda posición es el número de columnas de un dataframe.

Podemos cambiar el nombre de las columnas del dataframe que hemos construido anteriormente con nombre meteo de la siguiente manera

names(meteo) = c("month", "temp", "humidity", "mm")

Ejercicios

  1. Carga el dataset llamado iris que ya se encuentra en R. Encontraremos más información de este famoso conjunto de datos en el siguiente enlace.

  2. Aplica la función str, ¿de qué tipo de datos son sus columnas?

  3. ¿Cuántas columnas tiene? ¿Cuántas observaciones?

  4. Muestra las tres últimas filas de este dataset.

  5. Descarga la información Municipios y fenómeno demográfico de les Illes Balears de la página correspondiente enlace.

    • Descarga el archivo csv separado por ; y crea un dataframe con él.

    • Haz lo mismo con el fichero en formato json.

  6. Muestra los nombres de las columnas por pantalla. Luego tradúcelos al inglés.

  7. Verifica que el cambio se ha realizado de forma correcta, aplicando la función View a este dataframe.

  8. Guarda el dataframe en tu carpeta de trabajo de la asignatura con el nombre belearic.txt. Haz lo mismo, pero ahora guárdalo en el escritorio de tu ordenador.

Indexación

La indexación de un dataframe nos permite realizar selecciones de subconjuntos de este con el objetivo de trabajar o realizar análisis con una parte datos más pequeña o especifica. De manera general podemos seleccionar una parte de un dataframe, especificando los índices de las filas y columnas que nos interesen entre corchetes. Pero como tanto las observaciones y las variables pueden tener nombres, también podemos usarlos para especificar las filas y columnas de nuestra selección.

En las siguientes secciones supondremos que hemo cargado el dataset iris con la siguiente instrucción:

> data(iris)

de esta manera podremos realizar ejemplos a medida que explicamos las diferentes posibilidades de selección.

Selección de filas

Podemos seleccionar filas de diferentes maneras:

Usando los índices de las filas: Podemos seleccionar un rango continuo de filas o crear un vector que indique los índices que queremos seleccionar.

> iris[c(1,2,3), ]

  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
> iris[c(1:5,10:12), ]

   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa
11          5.4         3.7          1.5         0.2  setosa
12          4.8         3.4          1.6         0.2  setosa

Usando los identificadores de las filas: Como las filas pueden tener nombre, podemos usar estos para realizar la selección. En el ejemplo que tenemos a continuación el nombre de la fila es su ordinal, notad que el número está entre dobles comas, ya que los nombres siempre se representan con un string.

> iris["22", ]

     Sepal.Length Sepal.Width Petal.Length Petal.Width Species
22          5.1         3.7          1.5         0.4   setosa

Indexación lógica: También podemos seleccionar filas de un dataframe mediante una condición lógica; en este caso, nos quedaremos solo con aquellas observaciones que satisfacen la condición. Por ejemplo si queremos obtener todas aquellas observaciones que tienen una longitud del Sépalo mayor a 5 deberíamos ejecutar la siguiente expresión:

> iris["Sepal.Length" > 5,]

Para realizar este tipo de selecciones deberemos recordar los operadores lógicos. También es importante destacar que podemos llevar a cabo selecciones con más de una condición. Por ejemplo, si queremos obtener aquellas observaciones que tienen una longitud del Sépalo mayor a 5 y además son de la especie Setosa podríamos ejecutar la siguiente expresión:

> iris[iris$Sepal.Length > 5 && iris$Species == "setosa",]

Selección de columnas

Para seleccionar una única columna lo podemos hacer mediante el uso del símbolo $ entre el nombre del dataframe y el nombre de la columna que queremos seleccionar. Como resultado de esta operación obtendremos un vector:

> iris$Sepal.Length
  [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8 4.3 5.8 5.7 5.4 5.1 5.7 5.1 5.4 5.1
 [23] 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0
 [45] 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7
 [67] 5.6 5.8 6.2 5.6 5.9 6.1 6.3 6.1 6.4 6.6 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3
 [89] 5.6 5.5 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1 5.7 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2
[111] 6.5 6.4 6.8 5.7 5.8 6.4 6.5 7.7 7.7 6.0 6.9 5.6 7.7 6.3 6.7 7.2 6.2 6.1 6.4 7.2 7.4 7.9
[133] 6.4 6.3 6.1 7.7 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8 6.7 6.7 6.3 6.5 6.2 5.9

De igual manera que con las filas, podemos seleccionar las columnas basándonos en sus índices o en los nombres de sus columnas. Por ejemplo, a continuación seleccionamos las dos primeras columnas mediante los índices. Debéis notar que aquí no hemos usado la coma, de esta manera el intérprete de R entiende que nos referimos a selección de columnas:

> iris[1:2]

    Sepal.Length Sepal.Width
1            5.1         3.5
2            4.9         3.0
3            4.7         3.2
4            4.6         3.1
5            5.0         3.6
6            5.4         3.9
7            4.6         3.4
8            5.0         3.4
9            4.4         2.9
10           4.9         3.1
11           5.4         3.7
.....

Este otro ejemplo nos permite seleccionar la última y la segunda columna usando sus nombres (en este orden):

> iris[c("Species", "Sepal.Width")]

Selección combinada

También podemos seleccionar filas y columnas en una sola expresión de la forma dataframe[seleccion_filas, seleccion_columnas], siguiendo las mismas pautas que para la selección de filas o columnas. Una selección muy sencilla podría ser mostrar por pantalla el elemento que se encuentra en la segunda fila y en la segunda columna. Dos expresiones válidas podrían ser:

> iris[2, 2]

pero también:

> iris[2, "Sepal.Width"]

Por otra parte, si queremos seleccionar las filas de la 30 a la 50 y las columnas Petal.Width y Species podríamos usar la siguiente expresión:

> iris[30:50, c(iris$Petal.Width, iris$Species)]

A partir de aquí podemos imaginar todas las combinaciones posibles con las diferentes selecciones que hemos visto. Por ejemplo selección de filas condicionada y selección de columnas según su nombre, ….

Ejercicios de selección

  1. Obtener todas las filas de la especie Versicolor en un nuevo dataframe, este debe llamarse: iris.vers.

  2. Obtener un vector llamado sepal.dif con la diferencia entre Sepal.Length y Sepal.Width de las plantas Versicolor.

  3. Obtener nuevo vector que contenga solo los valores impares de la columna Sepal.Length.

  4. Seleccionar todas las observaciones para las que Sepal.Length sean menor que Sepal.Width.

  5. Seleccione aquellas observaciones Versicolor cuyo Sepal.Length sea mayor que Sepal.Width promedio entre todas las especies. (+)

  6. Determinar el ancho promedio del sépalo para todas las plantas que son de la especie virginica que tienen Petal.Length menores a 5 centímetros.

Modificación de dataframes

En esta sección describiremos como modificar los valores de un dataframe y de como añadir y eliminar filas y columnas.

Modificar valores

Modificar el resultado de una selección: Para modificar un valor o un conjunto de valores resultado de una selección sobre un dataframe seguiremos el siguiente esquema:

nombre_dataframe[seleccion_filas, seleccion_columnas] = valores_a_modificar

Veamos diversos ejemplos. En primer lugar, modificaremos un valor individual:

> iris[2, 2] = 33

Para modificar la segunda fila, debemos tener en cuenta que la especie es un factor y que debemos asignarle un valor ya existente:

> iris[2, ] = c(1,2,3,4,"virginica")

Finalmente, modificaremos todas la Sepal.Width de aquellas observaciones cuyo Sepal.Length es mayor a 7 por el valor 33:

> iris[iris$Sepal.Length > 7, "Sepal.Width"] = 33

Modificar una columna: Para modificar una columna entera, hay que igualar su nombre al nuevo valor. Recordemos que para seleccionar el nombre de una columna individual añadimos al nombre del dataframe el sufijo formado por el signo $ seguido del nombre de la columna dentro del dataframe.

Si por ejemplo queremos substituir el valor la columna Sepal.Length por un valor normalizado entre 0 y 1 podemos hacer lo siguiente:

> sl = iris$Sepal.Length
> iris$Sepal.Length = (sl - min(sl)) / (max(sl) - min(sl))
> View(iris)

Esta misma sintaxis también nos permite añadir columnas individuales, si quisiéramos añadir una columna con el ordinal de cada fila, lo podríamos realizar de la siguiente manera:

> iris$ordinal = 1:150

Con esto hemos añadido una nueva columna al dataframe. Es importante tener en cuenta que la nueva columna debe tener tantos elementos como filas tiene el dataframe, si no las tiene, obtendremos un error al ejecutar el código.

Si asignamos el valor NULL a una columna, esta será eliminada del dataframe, otra opción consiste en crear un nuevo dataframe en el que solamente seleccionamos las columnas que nos interesen.

Añadir filas

Existen múltiples maneras para añadir filas a un dataframe la más sencilla es la que consiste en crear un nuevo dataframe con la misma estructura (también los nombres de las columnas) que el original y con los datos que queremos concatenar. Luego debemos usar la función rbind tal como vemos en el siguiente ejemplo:

> iris2 = data.frame(sl=c(1,2), sw=c(1,2), pl=c(1,2), pw=c(1,2), s=c("setosa", "setosa"))
> colnames(iris2) = colnames(iris) # Asignamos los nombres de las columnas originales
> iris.new = rbind(iris, iris2) # realizamos la concatenacion

Añadir columnas

Podemos concatenar columnas a la derecha de un dataframe mediante el uso de la función cbind que tiene un uso muy similar a rbind. Esta función puede añadir cualquier objeto a nuestro dataframe, ya sea provenientes de un vector o de otro dataframe. Si el objeto que concatenamos no tiene las mismas filas que el dataframe original, estas se repiten hasta completar las necesarias.

Funciones

Debido a que las columnas de un dataframe son vectores, les podemos aplicar las mismas funciones que a cualquier vector. Por otra parte, muchas veces nos puede interesar aplicar la misma función a diversas columnas de un mismo conjunto de datos, en este caso para no tener que realizar la operación de manera individualizada a cada columna usaremos la función sapply que nos ayudará en esta tarea.

sapply recibe como primer parámetro un dataframe y como segundo parámetro una función. Para calcular las medias de las columnas numéricas del dataset Iris sería el siguiente:

> sapply(iris[1:4], FUN=mean)

Hay que tener en cuenta que no hemos seleccionado la columna de Species ya que no es posible calcular la media de un factor.

Es conveniente usar el parámetro na.rm=TRUE de la función sapply , sin el cual el valor que devolverá la función para las columnas que contengan algún NA será NA.

Definición de funciones

Normalmente, usaremos las funciones que R o sus librerías nos proporcionan, pero en algunas ocasiones será necesario o nos convendrá poder definir nuestras propias funciones para luego aplicar a nuestros datos. Recordaremos que la sintaxis para la definición de una función en R es la siguiente:

nombre_de_la_función=function(paramètros){
    código
    return(valor)
}

En uno de los ejercicios anteriores se pedía normalizar una columna entre los valores 0 y 1. Si quisiéramos realizar esta operación a todas las columnas numéricas del dataset Iris podríamos definir la siguiente función:

> rango_01 = function(x){(x-min(x))/(max(x)-min(x))}

Entonces ahora la podríamos aplicar tanto a un vector como a un dataframe. Vamos a aplicarla al conjunto de datos Iris tal como hemos hecho en la explicación de la función sapply, esta vez guardamos el resultado en una variable nueva, de esta manera podemos comprobar que el resultado de la operación es el esperado:

> iris2 = sapply(iris[1:4], FUN = rango_01)

Ejercicios

  1. Construye un nuevo dataframe que solamente contenga muestras del tipo Setosa y su Petal.Width sea menor a 0.4. A continuación concatena aquellas muestras del tipo Versicolor con el Petal.Width menor a 1..

  2. Actualizar el dataframe original con una nueva columna llamada sepal.dif con el vector de las diferencias absolutas entre la longitud del sépalo y la del pétalo.

  3. Añadir las columnas con los datos en el rango de \(0\) a \(1\) que hemos obtenido en el ejemplo de la sección Definición de funciones. El nombre de las columnas debe ser el mismo que las originales, pero debemos añadirle .01 al final.

  4. Crea una nueva versión del dataframe iris, llamada siri que tenga las columnas en orden inverso al original.

Conclusiones

Las operaciones sobre un dataframe siempre devuelven un nuevo dataframe, este diseño está pensado para evitar que al realizar una operación errónea o no deseada perdamos el trabajo hecho hasta el momento. Por lo tanto, la manera correcta (recomendada) de trabajar es la de usar una nueva variable cada vez que llevamos a cabo una acción sobre nuestro dataframe.

Antes de sobreescribir valores en un dataframe se recomienda hacer una copia que nos permita mantener los datos en el estado anterior por si nos equivocamos.

Mi primer proyecto

Vamos a trabajar con un conjunto de datos totalmente nuevo llamado Palmer Penguins podemos encontrar información en el siguiente enlace. Estos datos no se encuentran instalados en R, pero existe una libreria que podemos instalar para tener acceso a ellos:

> install.packages("palmerpenguins")

A continuación es suficiente con llamar a la libreria que hemos instalado y ya tendremos acceso al dataframe mediante la variable penguins:

> library(palmerpenguins)
> View(penguins)

La idea de este primer proyecto es repasar conceptos trabajados hasta ahora y explorar estos datos totalmente nuevos para nosotros además de dejarlos listos para poder analizarlos en un futuro. Como este código es interesante para nosotros y quizá nos interesará volver a usarlo en un futuro, guardaremos todas las instrucciones en un documento (script) para poder guardarlo, iremos ejecutando las instrucciones desde el documento y no desde la consola.

Vamos a empezar inspeccionando el conjunto de datos con la función str y observaremos que tipo de datos vamos a manejar. Nuestra primera misión consistirá en eliminar todas aquellas filas en las que exista algún valor no definido (NA), para este objetivo nos puede servir la función na.omit. En segundo lugar, vamos a añadir una nueva columna llamada bill_r que nos permita conocer la relación entre la longitud y la anchura del pico.

Añadiremos una columna llamada sex_weight en la que guardaremos la relación entre el peso de cada individuo y el individuo que pesa más de su mismo sexo. En referencia al peso y para poder trabajar con medidas que nos sean más familiares, pasaremos el peso de los individuos de gramos a kilogramos, sería conveniente cambiar el nombre de la columna para no producir equívocos.

Vamos a realizar algunas selecciones:

  • Selecciona la columna “species” del dataframe “penguins”. ¿Cuántas especies diferentes de pingüinos hay en el conjunto de datos?

  • Selecciona las columnas “species”, “island” y “bill_length_mm” del dataframe “penguins”. ¿Cuál es la especie de pingüino más común en la isla de “Torgersen”?

  • Encuentra todos los pingüinos que tienen un “bill_length_mm” mayor a 10. ¿Cuántos pingüinos cumplen con esta condición?

  • Encuentra todos los pingüinos de la especie “Gentoo” que tienen un “bill_length_mm” mayor a 5. ¿Cuál es la isla en la que se encuentran?

  • Selecciona las filas desde la 20 hasta la 30 en el dataframe “penguins”. ¿Cuál es el promedio de “bill_length_mm” en este rango?

En futuros análisis no necesitaremos el año en el que se tomó la medición, así que eliminaremos esta columna.

Finalmente, vamos a guardar este dataframe con el nombre de mis_pinguinos.csv.