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.

 

R-leer-escribir

 

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:

 

csv_ejemplo

 

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:

r_memoria_utilizada

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:

excel_planilla_ejemplo

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:

excel_planilla_ejemplo2

 

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&apos;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 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:

r_ejemplo_tabla_sql

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:

r_ejemplo_tabla_sql2

Otras funciones que resultan útiles son:

  • dbExistsTable: devuelve TRUE o FALSE 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.

 

Comentarios

0 Comments

Submit a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

This site uses Akismet to reduce spam. Learn how your comment data is processed.