Cuando trabajamos en R, usualmente vamos a analizar y procesar datos obtenidos desde fuentes externas. La cantidad de fuentes desde donde podemos obtener datos es enorme, y a su vez estos pueden venir en una cantidad muy variada de formatos. De igual modo, una vez que hayamos trabajado con los datos, vamos a querer salvarlos.
Afortunadamente, para la mayoría de los casos que se nos presenten, ya existen paquetes y funciones de R que se ocupan del trabajo duro. En este post veremos los casos más comunes, junto a múltiples ejemplos que nos servirán de guía.
Tabla de Contenidos
CSV
El formato CSV (del inglés comma-separated values) es uno de los más utilizados para intercambiar información entre aplicaciones. Básicamente, consiste en representar la información en forma de tabla donde las columnas se delimitan con un caracter (por defecto, con coma) y las filas con saltos de linea. Un beneficio de este tipo de representación es que los datos se almacenan en texto plano, por lo que se los puede visualizar/editar con un editor de texto.
Ejemplo de un CSV:
Para leer estos archivos CSV desde R tenemos varias opciones. La primera que veremos es mediante la función read.table
(del paquete base).
datos <- read.table(file = "C:/Users/Mauricio/Desktop/datos_ejemplos.csv", header = TRUE, col.names = c("fecha", "cliente", "producto", "precio"), stringsAsFactors = FALSE, sep = ",", dec = ".")
La función read.table
tiene muchas opciones al momento de leer archivos. Las más importantes son:
- file: ubicación del archivo.
- header: si posee o no una fila con los nombres de las columnas.
- col.names: indicamos manualmente el nombre de las columnas de nuestro data frame. Dado que en este caso el archivo ya lo incluye, resulta redundante.
- stringsAsFactors: por defecto, los campos de texto se los trata como factor. Si queremos que se los trate como cadenas ponemos este argumento a FALSE.
- sep: seleccionamos el símbolo que se utiliza para delimitar las columnas.
- dec: indicamos el símbolo que se utiliza para la representación decimal.
Luego, para ver el resto de opciones disponibles debemos ir a la documentación → ?read.table
.
Una alternativa que se puede emplear es la función fread
(del paquete data.table). Esta función es mucho más rápida para cargar los datos que la primera, por lo que se recomienda su utilización si el tamaño del archivo de datos es grande, mayor a 1Gb. Para archivos más pequeños, la diferencia de velocidad es despreciable.
library(data.table) datos <- fread(input = "C:/Users/Mauricio/Desktop/datos_ejemplos.csv", header = TRUE, col.names = c("fecha", "cliente", "producto", "precio"), data.table = FALSE, sep = ",", dec = ".", showProgress = TRUE)
La opción showProgress
muestra en pantalla muestra el avance en la tarea de lectura del archivo. Por otro lado, la opción data.table
la ponemos a FALSE
para que el archivos sea tratado como un data frame. De otro modo, se carga en una estructura llamada data.table que no hemos visto hasta el momento. Esta estructura es similar a un data frame, pero con varias mejoras mejoras. En algún post futuro hablaré más de ella.
Calculo de memoria
Ya dijimos que R almacena todos los objetos del espacio de trabajo en memoria RAM. Esto hace que sea importante tener una idea de cuanto espacio ocupará un objeto que carguemos. Al no ser completamente lineal la cantidad de memoria reservada, lo más fácil es leer solo unas miles de filas de archivo que se va a cargar y visualizar la pendiente de la siguiente curva:
# Grafico de cantidad de memoria utilizada según # tamaño del data frame obtenido para el ejemplo previo x <- c() y <- c() for(n in c(1,1e1,1e2,1e3,1e4,1e5,1e6)) { print(format(object.size(datos[1:n,]), units = "Kb")) x <- c(x,n) y <- c(y,object.size(datos[1:n,])/1000) } library(ggplot2) ggplot(data = data.frame(x,as.integer(y))) + geom_line(aes(x,y)) + geom_point(aes(x,y), size = 2) + scale_x_log10() + xlab("filas") + scale_y_log10() + ylab("memoria (Kb)")
Y obtenemos esto:
Vemos que nuestro data frame crece a una taza de ~ 140Kb / 1000 filas, para un tamaño superior a 1000 filas.
El proceso por el cual R toma memoria del sistema operativo lo veremos con más detalle en otro post.
Planilla Excel
Para trabajar con planillas de Excel tenemos el paquete xlsx. Este nos provee la función read.xlsm
para leer una planilla, y una seria de funciones para crear una nueva o modificar una existente. Veamos un ejemplo.
Tenemos la planilla:
y la leemos con read.xlsx
:
> read.xlsx(file = "C:/Users/Mauricio2/Desktop/planilla_excel_ejemplo.xlsx", sheetIndex = 1) Var1 Var2 Var3 Var4 Var5 1 0.8705654 0.83348162 0.4540418 0.41439640 0.24024426 2 0.9573109 0.54124017 0.6109131 0.75302748 0.14769723 3 0.1339557 0.95043721 0.9766552 0.71620849 0.53619736 4 0.4701480 0.28027422 0.2205766 0.36401530 0.85311430 5 0.8469980 0.84726859 0.8676150 0.58073516 0.17010552 6 0.4860212 0.30600439 0.9854242 0.36343243 0.35851265 7 0.4674635 0.80492235 0.0995812 0.38022557 0.89336350 8 0.8646877 0.43842488 0.8124651 0.38403866 0.39427993 9 0.9670325 0.04256131 0.8184683 0.91178739 0.07093257 10 0.2586586 0.43123579 0.6310787 0.34236825 0.22223150 11 0.9961069 0.03055265 0.9109668 0.80274758 0.87528744 12 0.6429137 0.03379375 0.3649987 0.03154035 0.89482130 13 0.7645558 0.21064568 0.3622043 0.51543462 0.24218604 14 0.1710769 0.34454229 0.3928599 0.98844874 0.28224392
Ahora realizamos unas modificaciones y la guardamos el data frame modificado en la misma planilla (workbook), pero en una nueva hoja (sheet):
# Modificamos el data frame con el contenido original datos <- datos * 5.5 # Creamos un objeto workbook wb <- xlsx::createWorkbook() # Creamos una planilla de calculo sheet <- xlsx::createSheet(wb, sheetName="test") # Pintamos el data frame a la planilla xlsx::addDataFrame(x = datos, sheet = sheet) # Pintamos la planilla en el archivo excel xlsx::saveWorkbook(wb, file = "C:/Users/Mauricio2/Desktop/planilla_excel_ejemplo.xlsx")
Y obtenemos la nueva planilla en nuestro archivo Excel:
Para más detalle de esta función → ?xlsx
.
Otro paquete similar es XLConnect
. Este último posee algunas opciones más, pero para usos básicos permite lo mismo realizar lo mismo.
Archivos de texto
Para leer y escribir un archivo de texto tenemos las funciones readLines
y writeLines
. Pero antes de leer un archivo tenemos que crear una conexión hacia este con la función file
, y luego cerrarla con close
. Ejemplo:
archivo <- "C:/Users/Mauricio2/Desktop/texto_ejemplo.txt" con <- file(archivo, open="r") # Abrimos la conexión documento <- readLines(con) # Leemos el contenido del archivo close(con) # Cerramos la conexión str(documento)
Y para escribir:
texto <- "Hola mundo" con <- file("C:/Users/Mauricio2/Desktop/texto_ejemplo.txt", open="w") documento <- writeLines(text = texto, con = con) close(con)
Si en cambio nuestro archivo está en un servidor remoto, entonces no podemos abrir la conexión a este mediante la función file
. Lo que debemos utilizar es la función url
. Con esta función, a su vez, podemos crear una conexión a cualquier página web y descargar el HTML. Vemos un ejemplo.
con2wp <- url("http://mauricioanderson.com/curso-r-subsetting/", "r")
x <- readLines(con2wp)
close(con2wp)
head(x)
[1] "<!DOCTYPE html>"
[2] "<!--[if IE 6]>"
[3] "<html id=\"ie6\" lang=\"es-ES\" prefix=\"og: http://ogp.me/ns#\">"
[4] "<![endif]-->"
[5] "<!--[if IE 7]>"
[6] "<html id=\"ie7\" lang=\"es-ES\" prefix=\"og: http://ogp.me/ns#\">"
XML
XML (del inglés eXtensible Markup Language) es un formato que permite almacenar datos con jerarquía en texto plano. Es ampliamente utilizado para intercambiar información en Internet y en bases de datos, entre otras aplicaciones. Para más información sobre el formato XML pasen por acá.
Para acceder y procesar datos en formato XML contamos con la biblioteca XML
. Veamos un ejemplo tomando el contenido ubicado en http://www.w3schools.com/xml/plant_catalog.xml:
<CATALOG> <PLANT> <COMMON>Bloodroot</COMMON> <BOTANICAL>Sanguinaria canadensis</BOTANICAL> <ZONE>4</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE>$2.44</PRICE> <AVAILABILITY>031599</AVAILABILITY> </PLANT> <PLANT> <COMMON>Columbine</COMMON> <BOTANICAL>Aquilegia canadensis</BOTANICAL> <ZONE>3</ZONE> <LIGHT>Mostly Shady</LIGHT> <PRICE>$9.37</PRICE> <AVAILABILITY>030699</AVAILABILITY> </PLANT> ... <PLANT> <COMMON>Cardinal Flower</COMMON> <BOTANICAL>Lobelia cardinalis</BOTANICAL> <ZONE>2</ZONE> <LIGHT>Shade</LIGHT> <PRICE>$3.02</PRICE> <AVAILABILITY>022299</AVAILABILITY> </PLANT> </CATALOG>
Entonces:
library(XML)
xml.url <- "http://www.w3schools.com/xml/plant_catalog.xml" # Ubicación del archivo XML
xmlfile <- xmlTreeParse(xml.url) # Tomanmos el archivo y lo convertimos en un objeto XMLDocument
xmltop <- xmlRoot(xmlfile) # Nos quedamos con el contenido del documento (descartamos la metainformación)
print(xmltop[5]) # Miramos el quinto nodos de primer nivel del documento
$PLANT
<PLANT>
<COMMON>Dutchman's-Breeches</COMMON>
<BOTANICAL>Dicentra cucullaria</BOTANICAL>
<ZONE>3</ZONE>
<LIGHT>Mostly Shady</LIGHT>
<PRICE>$6.44</PRICE>
<AVAILABILITY>012099</AVAILABILITY>
</PLANT>
attr(,"class")
[1] "XMLNodeList"
Para poder transformar un documento XML en un data frame necesitamos que este tenga una estructura acorde. Luego:
datos <- xmlSApply(xmltop, function(x) xmlSApply(x, xmlValue)) # Extraemos los valores de la estructura
XML del documento usando xmlSApply
df <- data.frame(t(datos), row.names=NULL) # Convertimos la estructura a un data frame
df[1:5,] # Visualizamos las 5 primeras filas
COMMON BOTANICAL ZONE LIGHT PRICE AVAILABILITY
1 Bloodroot Sanguinaria canadensis 4 Mostly Shady $2.44 031599
2 Columbine Aquilegia canadensis 3 Mostly Shady $9.37 030699
3 Marsh Marigold Caltha palustris 4 Mostly Sunny $6.81 051799
4 Cowslip Caltha palustris 4 Mostly Shady $9.90 030699
5 Dutchman's-Breeches Dicentra cucullaria 3 Mostly Shady $6.44 012099
JSON
JSON (del inglés JavaScript Object Notation) es un formato para intercambio de información que desde hace unos años está ganando bastante popularidad. Destaca por ser más ligero que XML y fácil de interpretar. Además, todos los lenguaje de programación modernos implementan diversas bibliotecas para trabajar con este. Para quienes no conozcan como es la estructura de un registro en formato JSON, pueden pasar por acá.
Entonces, tengo un archivo en formato JSON que posee el siguiente contenido:
[{"name":"Doe, John","group":"Red","age (y)":24,"height (cm)":182,"wieght (kg)":74.8,"score":null}, {"name":"Doe, Jane","group":"Green","age (y)":30,"height (cm)":170,"wieght (kg)":70.1,"score":500}, {"name":"Smith, Joan","group":"Yellow","age (y)":41,"height (cm)":169,"wieght (kg)":60,"score":null}, {"name":"Brown, Sam","group":"Green","age (y)":22,"height (cm)":183,"wieght (kg)":75,"score":865}, {"name":"Jones, Larry","group":"Green","age (y)":31,"height (cm)":178,"wieght (kg)":83.9,"score":221}, {"name":"Murray, Seth","group":"Red","age (y)":35,"height (cm)":172,"wieght (kg)":76.2,"score":413}, {"name":"Doe, Jane","group":"Yellow","age (y)":22,"height (cm)":164,"wieght (kg)":68,"score":902}]
Para trabajar con JSON en R tenemos la biblioteca jsonlite
, la cual cuanta con las funciones fromJSON
y toJSON
. Estas funciones permiten tomar un JSON y convertirlo a un data frame, y realizar lo opuesto, tomar un data frame y convertirlo en JSON. Veamos como convertir el este archivo en un data frame:
library(jsonlite) archivo <- "C:/Users/Mauricio2/Desktop/r_ejemplo_json.json" con <- file(archivo, open="r") ejemplo_json <- readLines(con) close(con) datos <- fromJSON(ejemplo_json)
y obtenemos:
fromJSON(ejemplo_json)
name group age(y) height(cm) wieght(kg) score
1 Doe,John Red 24 182 74.8 NA
2 Doe,Jane Green 30 170 70.1 500
3 Smith,Joan Yellow 41 169 60.0 NA
4 Brown,Sam Green 22 183 75.0 865
5 Jones,Larry Green 31 178 83.9 221
6 Murray,Seth Red 35 172 76.2 413
7 Doe,Jane Yellow 22 164 68.0 902
RData (formato binario)
Para salvar objetos que hayamos creado en R contamos con las funciones save
y load
. Son de las más importantes con que contamos en R dado que permite la persistencia de objeto que no son representables como texto. La extensión de los archivos que generan es RData.
Veamos un ejemplo. Creamos un modelo de regresión lineal y lo guardamos como un RData.
# CREAMOS UN MODELO LINEAS UTILIZANDO EL DATASET mtcars lmfit = lm( mpg ~ ., data = mtcars) lmfit Call: lm(formula = mpg ~ ., data = mtcars) Coefficients: (Intercept) cyl disp hp drat wt ... 12.30337 -0.11144 0.01334 -0.02148 0.78711 -3.71530 ... gear carb 0.65541 -0.19942 # SALVAMOS EL OBJETO MODELO EN UN ARCHIVO RDATA save(lmfit, file = "C:/Users/Mauricio2/Desktop/modelo_lineal_ejemplo.RData") # ELIMINAMOS EL OBJETO DEL ESPACIO DE TRABAJO rm(lmfit) # CARGAMOS EL OBJETO PREVIAMENTE GUARDADO load(file = "C:/Users/Mauricio2/Desktop/modelo_lineal_ejemplo.RData") # VEMOS COMO HEMOS RECUPERADO EL OBJETO lmfit Call: lm(formula = mpg ~ ., data = mtcars) Coefficients: (Intercept) cyl disp hp drat wt ... 12.30337 -0.11144 0.01334 -0.02148 0.78711 -3.71530 ... gear carb 0.65541 -0.19942
Para ejemplificar el uso de esta función, supongamos que estamos ejecutando un código cuyo tiempo de ejecución son varias horas. Una buena practica es ir guardando durante el proceso los resultados parciales que se vayan creando, de modo que antes problemas no tengamos que volver a empezar de cero.
Por otra parte, contamos con la función save.image
, la cual guarda en un RData todo el espacio de trabajo que se tenga en el momento de invocarla. Luego, para recuperar el espacio de trabajo simplemente empleamos al función load de la misma forma que la utilizamos para recuperar cualquier otro RData.
Bases de datos
Desde R podemos trabajar con todas las bases de datos, o al menos las más populares (MySQL, Oracle, Postgresql, etc). Lo que hay que tener en cuenta es que cada una de ellas requiere de cargar y trabajar con la biblioteca correspondiente. Voy a tomar el caso de trabajar con una base de datos en Postgresql, en la que contamos con la biblioteca RPostgreSQL
.
Primero veamos una tabla que tengo en Postgresql:
Sí queremos trabajar con el resultado de esta consulta a un data frame en R, debemos crear una conexión a la base de datos y realizar la consulta desde R:
library(RPostgreSQL) drv <- dbDriver("PostgreSQL") # Cargamos el driver pw <- "*" # Top Secret ;) con <- dbConnect(drv, dbname = "datos_exploracion", # nombre de la base de datos host = "localhost", # dirección port = 5432, # puerto user = "postgres", # usuario password = pw) # contraseña
Ahora la consultamos la base de datos:
consulta <- dbGetQuery(con, "select * from traceroutes limit 10")
Y vemos lo que hemos obtenido:
consulta
analysis_id trace_id src dst sport dport hop_count firsthop stop_reason stop_data ts ts_epoch path
1 53 27 218.94.106.114 212.123.229.163 36394 33435 14 1 GAPLIMIT 0 2016-04-12 00:34:03 1460432043
2 53 38 218.94.106.114 193.84.68.148 36394 33435 7 1 GAPLIMIT 0 2016-04-12 00:34:04 1460432044
3 53 92 218.94.106.114 91.195.153.251 36394 33435 20 1 GAPLIMIT 0 2016-04-12 00:34:03 1460432043
4 53 24 218.94.106.114 200.10.213.184 36394 33435 8 1 GAPLIMIT 0 2016-04-12 00:34:03 1460432043
5 53 14 218.94.106.114 92.42.203.56 36394 33435 10 1 UNREACH 1 2016-04-12 00:34:04 1460432044
6 53 40 218.94.106.114 192.150.218.204 36394 33435 7 1 GAPLIMIT 0 2016-04-12 00:34:04 1460432044
7 53 46 218.94.106.114 192.124.155.19 36394 33435 8 1 GAPLIMIT 0 2016-04-12 00:34:04 1460432044
8 53 35 218.94.106.114 37.234.141.138 36394 33435 16 1 GAPLIMIT 0 2016-04-12 00:34:02 1460432042
9 53 56 218.94.106.114 111.90.160.110 36394 33435 19 1 GAPLIMIT 0 2016-04-12 00:34:04 1460432044
10 53 43 218.94.106.114 203.21.45.100 36394 33435 7 1 GAPLIMIT 0 2016-04-12 00:34:04 1460432044
Por último, debemos cerrar la conexión a la base de datos:
dbDisconnect(con) dbUnloadDriver(drv)
Si en cambio lo que queremos es pintar un data frame como una tabla en la base de datos, tenemos la función dbWriteTable
. Voy a pintar el dataset mtcars
en una tabla en Postgresql para ejemplificar:
dbWriteTable(con, c("mtcars"), value=mtcars, row.names=FALSE)
Luego, en la base de datos vemos:
Otras funciones que resultan útiles son:
dbExistsTable
: devuelveTRUE
oFALSE
según la tabla por la que consultamos exista o no.dbReadTable
: traer a R una tabla completa desde la base de datos.
También podemos hacer modificaciones a la base de datos mediante la función dbGetQuery
. De hecho, con esta función podemos ejecutar cualquier comando en como create table, drop table, create index, etc.
Web Scraping
Web Scraping es el nombre que se utiliza para agrupar a las técnicas y programas que se utilizan para extraer información de sitios web. Con este tema se podría hacer un post entero (en un futuro lo haré), pero de momento veamos lo esencial.
La forma más básica de esto ya la hemos visto. Con a función url
podemos crear una conexión a una página web y descargar el HTML para luego analizarlo linea por linea.
con2wp <- url("http://mauricioanderson.com/curso-r-subsetting/", "r") pagina <- readLines(con2wp) close(con2wp)
Para facilitar la tarea de análisis sintáctico del HTML podemos utilizar dos bibliotecas: XML
y httr
.
Con la biblioteca XML
:
library(XML)
direccion <- "http://mauricioanderson.com/curso-r-subsetting/"
html <- htmlTreeParse(direccion, useInternalNodes = T)
title <- xpathSApply(html, "//title", xmlValue) # Extraemos el valor del nodo 'title'
title
[1] "Curso de R para principiantes | Subsetting - Mauricio Anderson"
Mientras que con httr
:
library(httr)
direccion <- "http://mauricioanderson.com/curso-r-subsetting/"
html <- GET(direccion)
html
Response [http://mauricioanderson.com/curso-r-subsetting/]
Date: 2016-11-09 16:04
Status: 403
Content-Type: text/html; charset=UTF-8
Size: 69.1 kB
<!DOCTYPE html>
<!--[if IE 6]>
<html id="ie6" lang="es-ES" prefix="og: http://ogp.me/ns#">
<![endif]-->
<!--[if IE 7]>
<html id="ie7" lang="es-ES" prefix="og: http://ogp.me/ns#">
<![endif]-->
<!--[if IE 8]>
<html id="ie8" lang="es-ES" prefix="og: http://ogp.me/ns#">
<![endif]-->
...
contenido <- content(html, as = "text") # html como texto plano
parsedHtml <- htmlParse(contenido, asText = TRUE) # equivalente a htmlTreeParse de la biblioteca XML
Para ver como acceder a los distintos elementos de la estructura HTML basta con leer la documentación de cualquiera de estos dos bibliotecas que hemos visto.
0 Comments