Explorando data frames
Hoja de ruta
Leccion: 20 min
Prácticas: 10 minPreguntas
¿Cómo puedo manipular un dataframe?
Objetivos
Poder agregar y quitar filas y columnas.
Poder quitar filas con valores
NA
.Poder anexar dos dataframe.
Poder articular qué es un
factor
y cómo convertir entrefactor
ycharacter
.Poder entender las propiedades básicas de un dataframe, incluyendo tamaño, clase o tipo de columnas, nombres y primeras filas.
A esta altura, ya viste todo - en la última lección, donde recorrimos las estructuras básicas de R. Todo lo que hagas va a ser una manipulación de esas herramientas. Pero la mayoría del tiempo, la estrella del show va a ser el dataframe - la tabla que creamos al cargar información de un archivo csv. En ésta lección, vamos a aprender un par de cosas sobre cómo trabajar con la clase dataframe.
Agregando columnas y filas a un dataframe
Aprendimos que las columnas en un dataframe son vectores. Por lo tanto, sabemos que nuestros datos son consistentes con el tipo de dato dentro de esa columna. Si queremos agregar una nueva columna, podemos empezar por crear un nuevo vector:
age <- c(2,3,5,12)
cats
coat weight likes_string
1 calico 2.1 1
2 black 5.0 0
3 tabby 3.2 1
Podemos entonces agregarlo como una columna via:
cats <- cbind(cats, age)
Error in data.frame(..., check.names = FALSE): arguments imply differing number of rows: 3, 4
¿Por qué no funcionó? Claro, R quiere ver un elemento en nuestra nueva columna para cada fila de la tabla:
cats
coat weight likes_string
1 calico 2.1 1
2 black 5.0 0
3 tabby 3.2 1
age <- c(4,5,8)
cats <- cbind(cats, age)
cats
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
Ahora, qué tal si agregamos filas, en este caso, la última vez vimos que las filas de un dataframe están compuestas por listas:
newRow <- list("tortoiseshell", 3.3, TRUE, 9)
cats <- rbind(cats, newRow)
Warning in `[<-.factor`(`*tmp*`, ri, value = "tortoiseshell"): invalid
factor level, NA generated
Factors
Los objetos de la clase factor son otro tipo de datos que debemos usar con cuidado. Cuando R crea un factor, únicamente permite los valores que originalmente estaba allí cuando cargamos los datos. Por ejemplo, en nuestro caso ‘black’, ‘calico’ y ‘tabby’. Cualquier categoría nueva que no entre en esas categorías será rechazada (y se conviertirá en NA).
La advertencia (Warning) nos está diciendo que agregamos ‘tortoiseshell’ a nuestro factor coat. Pero los otros valores, 3.3 (de tipo numeric), TRUE (de tipo logical), y 9 (de tipo numeric) se añadieron exitosamente a weight, likes_string, y age, respectivamente, dado que esos valores no son de tipo factor. Para añadir una nueva categoría ‘tortoiseshell’ al dataframe cats en la columna coat, debemos agregar explícitamente a ‘tortoiseshell’ como un nuevo nivel (level) en el factor:
levels(cats$coat)
[1] "black" "calico" "tabby"
levels(cats$coat) <- c(levels(cats$coat), 'tortoiseshell')
cats <- rbind(cats, list("tortoiseshell", 3.3, TRUE, 9))
De manera alternativa, podemos cambiar la columna a tipo character. En este caso, perdemos las categorías, pero a partir de ahora podemos incorporar cualquier palabra a la columna, sin problemas con los niveles del factor.
str(cats)
'data.frame': 5 obs. of 4 variables:
$ coat : Factor w/ 4 levels "black","calico",..: 2 1 3 NA 4
$ weight : num 2.1 5 3.2 3.3 3.3
$ likes_string: int 1 0 1 1 1
$ age : num 4 5 8 9 9
cats$coat <- as.character(cats$coat)
str(cats)
'data.frame': 5 obs. of 4 variables:
$ coat : chr "calico" "black" "tabby" NA ...
$ weight : num 2.1 5 3.2 3.3 3.3
$ likes_string: int 1 0 1 1 1
$ age : num 4 5 8 9 9
Desafío 1
Imaginemos que, como los perros, 1 año humano es equivalente a 7 años en los gatos (La compañía Purina usa un algoritmo más sofisticado).
- Crea un vector llamado
human.age
multiplicandocats$age
por 7.- Convierte
human.age
a factor.- Convierte
human.age
de nuevo a un vector numérico usando la funciónas.numeric()
. Ahora, divide por 7 para regresar a las edades originales. Explica lo sucedido.Solución al Desafío 1
human.age <- cats$age * 7
human.age <- factor(human.age)
oas.factor(human.age)
las dos opciones funcionan igual de bien.as.numeric(human.age)
produce1 2 3 4 4
porque los factores se guardan como objetos de tipo entero integer (1:4), cada uno de los cuales tiene asociado una etiqueta label (28, 35, 56, y 63). Convertir un objeto de un tipo de datos a otro, por ejemplo de factor a numeric nos dá los enteros, no las etiquetas labels. Si queremos los números originales, necesitamos un paso intermedio, debemos convertirhuman.age
al tipo character y luego a numeric (¿cómo funciona esto?). Esto aparece en la vida real cuando accidentalmente incluimos un character en alguna columna de nuestro archivo .csv, que se suponía que únicamente contendría números. Tendremos este problema, si al leer el archivo olvidamos incluirstringsAsFactors=FALSE
.
Quitando filas
Ahora sabemos cómo agregar filas y columnas a nuestro dataframe en R, pero en nuestro primer intento para agregar un gato llamado ‘tortoiseshell’ agregamos una fila que no sirve.
cats
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
4 <NA> 3.3 1 9
5 tortoiseshell 3.3 1 9
Podemos pedir el dataframe sin la fila errónea:
cats[-4,]
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
5 tortoiseshell 3.3 1 9
Notar que -4 significa que queremos remover la cuarta fila, la coma sin nada detrás indica que se aplica a todas las columnas. Podríamos remover ambas filas en un llamado usando ambos números dentro de un vector: cats[c(-4,-5),]
Alternativamente, podemos eliminar filas que contengan valores NA
:
na.omit(cats)
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
5 tortoiseshell 3.3 1 9
Volvamos a asignar el nuevo resultado output al dataframe cats
, así nuestros cambios son permanentes:
cats <- na.omit(cats)
Añadiendo a un dataframe
La clave que hay que recordar al añadir datos a un dataframe es que las columnas son vectores o factores, mientras que las filas son listas. Podemos pegar dos dataframes usando rbind
que significa unir las filas (verticalmente):
cats <- rbind(cats, cats)
cats
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
5 tortoiseshell 3.3 1 9
11 calico 2.1 1 4
21 black 5.0 0 5
31 tabby 3.2 1 8
51 tortoiseshell 3.3 1 9
Pero ahora los nombres de las filas rownames son complicados. Podemos removerlos y R los nombrará nuevamente, de manera secuencial:
rownames(cats) <- NULL
cats
coat weight likes_string age
1 calico 2.1 1 4
2 black 5.0 0 5
3 tabby 3.2 1 8
4 tortoiseshell 3.3 1 9
5 calico 2.1 1 4
6 black 5.0 0 5
7 tabby 3.2 1 8
8 tortoiseshell 3.3 1 9
Desafío 2
Puedes crear un nuevo dataframe desde R con la siguiente sintaxis:
df <- data.frame(id = c('a', 'b', 'c'), x = 1:3, y = c(TRUE, TRUE, FALSE), stringsAsFactors = FALSE)
Crear un dataframe que contenga la siguiente información personal:
- Nombre
- Apellido
- Número favorito
Luego usa
rbind
para agregar una entrada para la gente sentada alrededor tuyo. Finalmente, usacbind
para agregar una columna con espacio para que cada persona conteste a la siguiente pregunta: “¿Es hora de una pausa?”Solución al Desafío 2
df <- data.frame(first = c('Grace'), last = c('Hopper'), lucky_number = c(0), stringsAsFactors = FALSE) df <- rbind(df, list('Marie', 'Curie', 238) ) df <- cbind(df, coffeetime = c(TRUE,TRUE))
Ejemplo realista
Hasta ahora, hemos visto las manipulaciones básicas que pueden hacerse en un dataframe. Ahora, vamos a extender esas habilidades con un ejemplo más real. Vamos a importar el gapminder dataset que descargamos previamente:
gapminder <- read.csv("data/gapminder-FiveYearData.csv")
Tips misceláneos
Otro tipo de archivo que puedes encontrar es el separado por tabuladores (.tsv). Para especificar este separador, usa
"\t"
oread.delim()
.Los archivos pueden descargarse de Internet a una carpeta local usando
download.file
. La funciónread.csv
puede ser ejecutada para leer el archivo descargado, por ejemplo:download.file("https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/gh-pages/_episodes_rmd/data/gapminder-FiveYearData.csv", destfile = "data/gapminder-FiveYearData.csv") gapminder <- read.csv("data/gapminder-FiveYearData.csv")
- De manera alternativa, puedes leer los archivos directamente en R, usando una dirección web y
read.csv
. Es importante notar que, si se hace esto último, no habrá una copia local del archivo csv en tu computadora. Por ejemplo,gapminder <- read.csv("https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/gh-pages/_episodes_rmd/data/gapminder-FiveYearData.csv")
- Puedes leer directamente planillas de Excel sin necesidad de convertirlas a texto plano usando el paquete readxl.
Vamos a investigar gapminder un poco; lo primero que hay que hacer siempre es ver cómo se ve el dataset usando str
:
str(gapminder)
'data.frame': 1704 obs. of 6 variables:
$ country : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
$ year : int 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
$ pop : num 8425333 9240934 10267083 11537966 13079460 ...
$ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
$ lifeExp : num 28.8 30.3 32 34 36.1 ...
$ gdpPercap: num 779 821 853 836 740 ...
También podemos examinar columnas individuales del dataframe con la función typeof
:
typeof(gapminder$year)
[1] "integer"
typeof(gapminder$country)
[1] "integer"
str(gapminder$country)
Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
También podemos interrogar al dataframe por la información sobre sus dimensiones;
recordando que str(gapminder)
dijo que había 1704 observaciones de 6 variables en gapminder, ¿Qué piensas que el siguiente código producirá y por qué?
length(gapminder)
[1] 6
Un intento certero hubiera sido decir que el largo (length
) de un dataframe es el número de filas (1704), pero no es el caso; recuerda, un dataframe es una lista de vectores y factors.
typeof(gapminder)
[1] "list"
Cuando length
devuelve 6, es porque gapminder está construida por una lista de 6 columnas. Para conseguir el número de filas, intenta:
nrow(gapminder)
[1] 1704
ncol(gapminder)
[1] 6
O, para obtener ambos de una vez:
dim(gapminder)
[1] 1704 6
Probablemente queremos saber los nombres de las columnas. Para hacerlo, podemos pedir:
colnames(gapminder)
[1] "country" "year" "pop" "continent" "lifeExp" "gdpPercap"
A esta altura, es importante preguntarnos si la estructura de R está en sintonía con nuestra intuición y nuestras expectativas, ¿tienen sentido los tipos de datos reportados para cada columna? Si no lo tienen, necesitamos resolver cualquier problema antes de que se conviertan en sorpresas ingratas luego. Podemos hacerlo usando lo que aprendimos sobre cómo R interpreta los datos y la importancia de la estricta consistencia con la que registramos los datos.
Una vez que estamos contentos con el tipo de datos y que la estructura parece razonable, es tiempo de empezar a investigar nuestros datos. Mira las siguientes líneas:
head(gapminder)
country year pop continent lifeExp gdpPercap
1 Afghanistan 1952 8425333 Asia 28.801 779.4453
2 Afghanistan 1957 9240934 Asia 30.332 820.8530
3 Afghanistan 1962 10267083 Asia 31.997 853.1007
4 Afghanistan 1967 11537966 Asia 34.020 836.1971
5 Afghanistan 1972 13079460 Asia 36.088 739.9811
6 Afghanistan 1977 14880372 Asia 38.438 786.1134
Desafío 3
También es útil revisar algunas líneas en el medio y el final del dataframe ¿Cómo harías eso?
Buscar líneas exactamente en el medio no es tan difícil, pero simplemente revisar algunas lineas al azar es suficiente. ¿cómo harías eso?
Solución al desafío 3
Para revisar las últimas líneas del dataframe R tiene una función para esto:
tail(gapminder)
country year pop continent lifeExp gdpPercap 1699 Zimbabwe 1982 7636524 Africa 60.363 788.8550 1700 Zimbabwe 1987 9216418 Africa 62.351 706.1573 1701 Zimbabwe 1992 10704340 Africa 60.377 693.4208 1702 Zimbabwe 1997 11404948 Africa 46.809 792.4500 1703 Zimbabwe 2002 11926563 Africa 39.989 672.0386 1704 Zimbabwe 2007 12311143 Africa 43.487 469.7093
tail(gapminder, n = 15)
country year pop continent lifeExp gdpPercap 1690 Zambia 1997 9417789 Africa 40.238 1071.3538 1691 Zambia 2002 10595811 Africa 39.193 1071.6139 1692 Zambia 2007 11746035 Africa 42.384 1271.2116 1693 Zimbabwe 1952 3080907 Africa 48.451 406.8841 1694 Zimbabwe 1957 3646340 Africa 50.469 518.7643 1695 Zimbabwe 1962 4277736 Africa 52.358 527.2722 1696 Zimbabwe 1967 4995432 Africa 53.995 569.7951 1697 Zimbabwe 1972 5861135 Africa 55.635 799.3622 1698 Zimbabwe 1977 6642107 Africa 57.674 685.5877 1699 Zimbabwe 1982 7636524 Africa 60.363 788.8550 1700 Zimbabwe 1987 9216418 Africa 62.351 706.1573 1701 Zimbabwe 1992 10704340 Africa 60.377 693.4208 1702 Zimbabwe 1997 11404948 Africa 46.809 792.4500 1703 Zimbabwe 2002 11926563 Africa 39.989 672.0386 1704 Zimbabwe 2007 12311143 Africa 43.487 469.7093
Para revisar algunas lineas al azar?
sugerencia: Hay muchas maneras de hacer esto
La solución que presentamos aquí utiliza funciones anidadas, por ejemplo una función es el argumento de otra función. Esto te puede parecer nuevo, pero ya lo haz usado. Recuerda my_dataframe[rows, cols] imprime el dataframe con la sección de filas y columnas definidas (incluso puedes seleccionar un rando de filas y columnas usando : por ejemplo). Para obtener un número al azar o varios números al azar R tiene una función llamada sample.
gapminder[sample(nrow(gapminder), 5), ]
country year pop continent lifeExp gdpPercap 1039 Mozambique 1982 12587223 Africa 42.795 462.2114 73 Austria 1952 6927772 Europe 66.800 6137.0765 25 Algeria 1952 9279525 Africa 43.077 2449.0082 1028 Morocco 1987 22987397 Africa 62.677 2755.0470 510 Ethiopia 1977 34617799 Africa 44.510 556.8084
Para que nuestro analisis sea reproducible debemos poner el código en un script entonces podemos volver y editar en el futuro.
Desafío 4
Ve a Archivo -> nuevo -> R script, y crea un script de R llamado load-gapminder.R para cargar el dataset gapminder. Ponlo en el directorio
scripts/
y agrégalo al control de versiones.Ejecuta el script usando la función
source
, usando el path como su argumento o apretando el botón de “source” en RStudio.Solución al desafío 4
Los contenidos de
scripts/load-gapminder.R
:download.file("https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/gh-pages/_episodes_rmd/data/gapminder-FiveYearData.csv", destfile = "data/gapminder-FiveYearData.csv") gapminder <- read.csv(file = "data/gapminder-FiveYearData.csv")
Para ejecutar el script y cargar los archivos en la variable
gapminder
:To run the script and load the data into the
gapminder
variable:source(file = "scripts/load-gapminder.R")
Desafío 5
Leer el output de
str(gapminder)
de nuevo; esta vez, usar lo que has aprendido de factores, listas y vectores, las funciones comocolnames
ydim
para explicar qué significa el output destr
. Si hay partes que no puedes entender, discútelo con tus compañeros.Solución desafío 5
El objeto
gapminder
es un dataframe con columnas
country
ycontinent
como factors.year
como integer vector.pop
,lifeExp
, andgdpPercap
como numeric vectors.
Puntos Clave
Usar
cbind()
para agregar una nueva columna a un dataframe.Usar
rbind()
para agregar una nueva fila a un dataframe.Quitar filas de un dataframe.
Usar
na.omit()
para remover filas de un dataframe con valoresNA
.Usar
levels()
yas.character()
para explorar y manipular columnas de clase factorUsar
str()
,nrow()
,ncol()
,dim()
,colnames()
,rownames()
,head()
ytypeof()
para entender la estructura de un dataframeLeer un archivo csv usando
read.csv()
Entender el uso de
length()
en un dataframe