Curso de R | Leer y Escribir datos

Curso de R | Leer y Escribir datos

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
Curso de R | Subsetting

Curso de R | Subsetting

En el post anterior hablamos sobre las estructuras de datos en R. Una vez que estemos familiarizados con estas, toca comenzar a ver cómo podemos extraer subconjuntos de datos (subsetting) y cómo pintar nuevos valores (assignment). Estos dos conceptos es fundamental que los dominemos para continuar con temas más avanzados.

En este post comenzaremos a viendo los operadores de subsetting, y luego como aplicarlos a cada tipo de estructura. Por último, veremos algunas aplicaciones prácticas.

 

R-Subsetting

 

Operadores


Hay tres operadores que pueden ser usados para extraer subconjuntos de objetos en R:

  • El operador [: siempre devuelve un objeto de la misma clase que el original, y puede ser usado para seleccionar múltiples elementos de un objeto.
  • El operador [[: es usado para extraer elementos de una lista o de un data frame, y devuelve elementos simplificados.
  • El operador $: es usado para extraer elementos de una lista o un data frame indicando su nombre.

Para comenzar a ver cómo utilizar estos operadores en la práctica vayamos viendo caso por caso.

 

Vectores

Los vectores son las estructuras más sencillas para comenzar.

x <- c("a.1", "s.2", "d.3", "f.4")
x
"a.1" "s.2" "d.3" "f.4"

Hay tres formas de extraer subconjuntos de un vector utilizando el operadora [.

  1. Indicando con un vector la posición de cada uno de los elementos que deseamos extraer. Recordemos que en R el primer elemento de un vector lleva el índice 1.

x[c(3, 1)] # Extraemos el tercer y primer elemento
"d.3" "a.1"

 

x[c(1, 1)] # Extraemos dos veces el primer elemento
"a.1" "a.1"

 

x[c(3.5, 2.9)] # En caso de indica la posición con un número decimal, este es truncado.
"d.3" "s.2"

  1. Podemos indicar un vector con todos los elementos que no queremos extraer anteponiendo un menos al vector de posiciones.

x[-c(3, 1)]
"s.2" "f.4"

  1. Con un vector booleano podemos indicar para cada posición si queremos recuperar el elemento (TRUE) o no (FALSE).

x[c(TRUE, TRUE, FALSE, FALSE)]
"a.1" "s.2"

Esta forma de recuperar elementos de un vector nos permite hacer cosas muy útiles como extraer solo aquellos elementos que cumplan alguna condición.

a <- 1:10
a
1 2 3 4 5 6 7 8 9 10

 

a > 3
FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

 

a[a > 3]
4 5 6 7 8 9 10

En caso de que alguno de los elementos del vector sea NA, entonces el elemento en tal posición resultará también NA.

x[c(TRUE, TRUE, NA, TRUE)]
"a.1" "s.2" NA "f.4"

Y en caso de indicar un vector con menor cantidad de posiciones, este se reciclará, es decir, se repetirá una y otra vez hasta alcanzar el tamaño del vector del cual buscamos extraer elementos.

x[c(TRUE, FALSE)]
"a.1" "d.3"

 

x[c(TRUE, FALSE, TRUE, FALSE)] # Equivalente a la expresión anterior
"a.1" "d.3"

Por otra parte, si no indicamos un vector de posiciones dentro de las llaves, se recuperará el mismo vector.

x[]
"a.1" "s.2" "d.3" "f.4"

En caso de indicar la posición 0 del vector, no se recuperarán elementos.

x[0]
character(0)

Si intentamos recuperar un elemento que no existe se nos devolverá un NA.

x[c(1,10, 15)]
"a.1" NA NA

En caso de los vectores nombrados, es decir, aquellos vectores en los que sus elementos tengan asignado un nombre, podemos recuperar tales elementos mediante sus nombres. Lo que haremos es indicar en el vector de posiciones el nombre de los elementos que queremos recuperar.

y <- setNames(x, c("primero", "segundo", "tercero", "cuarto"))

y
primero segundo tercero cuarto
"a.1" "s.2" "d.3" "f.4"

 

y[c("segundo", "tercero")]
segundo tercero
"s.2" "d.3"

 

Listas

En cuanto a las listas, se pueden extraer subconjuntos usando los tres operadores que mencionamos, cada uno con un propósito diferente.

x <- list(foo = 1:5, bar = 3.14)
x
$foo
[1] 1 2 3 4 5

$bar
[1] 3.14

 

x[[1]]
[1] 1 2 3 4 5

 

x[["bar"]]
[1] 3.14

 

x$bar
[1] 3.14

Una diferencia importante entre usar el [[ y el $ es que en este último debemos indicar literalmente el nombre del elemento a extraer, mientras que en el caso del [[ el nombre puede estar contenido en una variable.

x <- list(foo = 1:5, bar = 3.14, baz = "hello")

name <- "foo"

 

x[[name]]
1 2 3 4 5

 

x$name
NULL

 

x$foo
1 2 3 4 5

 

Matrices y Arrays

La forma más simple de extraer un subconjunto de una matriz o un array es generalizando lo que hicimos previamente con los vectores, es decir, indicando los indices con la forma (i, j). Lo más simple es que veamos esto con un ejemplo.

# Subsetting en Matrices

a <- matrix(1:9, nrow = 3)
colnames(a) <- c("A", "B", "C")

a
     A B C
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9

a[1:2, ]
     A B C
[1,] 1 4 7
[2,] 2 5 8

a[c(T, F, T), c("B", "A")]
     B A
[1,] 4 1
[2,] 6 3

a[0, -2]
[1] A C
# Subsetting en Arrays

x <- matrix(1:6, 2, 3)
x
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6

x[1, 2]
[1] 3

x[2, 1]
[1] 2

En caso de que deseemos recuperar una fila o una columna de una matriz podemos dejar el índice asociado a estos vació.

x[1, ] # Recuperamos la primer fila de la matriz
[1] 1 3 5

x[, 2] # Recuperamos la segunda columna de la matriz
[1] 3 4

 

Data frames

Los data frames poseen características tanto de listas como de matrices. Esto nos permite extraer subconjuntos de forma similar a como veníamos haciendo. Veamos esto con ejemplos:

head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda_RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda_RX4_Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun_710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet_4_Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet_Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1


mtcars$cyl
 [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4

mtcars$hp
 [1] 110 110 93 110 175 105 245 62 95 123 123 180 180 180 205 215 230 ...

head(mtcars[mtcars$cyl==4,]) # Por fila

                mpg cyl  disp hp drat    wt  qsec vs am gear carb
Datsun 710     22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
Merc 240D      24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
Merc 230       22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
Fiat 128       32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1

 

head(mtcars[c(1, 3)]) # Por columna, por índice, como lista
                   mpg disp
Mazda RX4         21.0  160
Mazda RX4 Wag     21.0  160
Datsun 710        22.8  108
Hornet 4 Drive    21.4  258
Hornet Sportabout 18.7  360
Valiant           18.1  225

head(mtcars[,c("mpg", "disp", "cyl")]) # Por columna, por nombre, como matriz
                   mpg disp cyl
Mazda RX4         21.0  160   6
Mazda RX4 Wag     21.0  160   6
Datsun 710        22.8  108   4
Hornet 4 Drive    21.4  258   6
Hornet Sportabout 18.7  360   8
Valiant           18.1  225   6

En caso de seleccionar una única columna, hay una diferencia importante entre seleccionar como lista o como matriz. En el primer caso, la columna que seleccionamos la obtenemos como un data frame, mientras que en el segundo caso como un vector.

str(mtcars["cyl"]) # Como lista -> obtenemos data frame
'data.frame': 32 obs. of 1 variable:
$ cyl: num 6 6 4 6 8 6 8 4 4 6 ...

 

str(mtcars[, "cyl"]) # Como matriz -> obtenemos vector
num [1:32] 6 6 4 6 8 6 8 4 4 6 ...

Una consideración a tener en cuenta es que mediante el operador $ no se puede acceder a una columna mediante una variable. Si tenemos almacenado en una variable el nombre la columna que deseamos recuperar debemos utilizar el [[.

col_name <- "mpg"
mtcars$col_name
NULL

mtcars[[col_name]]
[1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 ...

Otra diferencia importante entre el $ y el [[, es que el primero acepta coincidencias parciales (partial matching), mientras que el segundo no.

mtcars$m # La única columna cuyo nombre empieza por 'm' es 
           'mpg'. Entonces esta es devuelva
[1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 ...

 

Modificación de estructuras


Los operadores que hemos visto hasta aquí pueden realizar modificaciones en la estructura de los datos devueltos. Es decir, estos operadores los aplicamos sobre cierta estructura de datos, pero no necesariamente la estructura devuelta es la misma que la original. A este concepto en ingles se lo denomina simplifying & preserving subsetting.

Cuando un operador simplifica un resultado, la estructura de este es la más simple posible que pueda representar al resultado. En cambio, los operadores que conservan la estructura de datos, devuelve datos con la misma estructura que el objeto original. Este último caso es el más aconsejable a utilizar cuando se realicen scripts dado que nos aseguramos que la salida de una operación será siempre del mismo tipo.

No hay operadores que conserven las estructuras, y otros que las simplifiquen, sino que esto depende del tipo de objeto sobre el que se aplican. En la siguiente tabla se resumen los comportamientos para los casos vistos.

Simplifica Conserva
vector x[[1]] x[1]
lista x[[1]] x[1]
factor x[1:4, drop = T] x[1:4]
array x[1, ] or x[, 1] x[1, , drop = F] or x[, 1, drop = F]
data frame x[, 1] or x[[1]] x[, 1, drop = F] or x[1]

Como dijimos, en los casos que se conserva la estructura de datos, esta es la misma para la salida que para la entrada. En el caso que se simplifique, esta varia. Veamos unos ejemplos para visualizar esto.

# Vectores
x <- c(a = 1, b = 2)
x[1]
a 
1 
x[[1]]
[1] 1

# Listas
y <- list(a = 1, b = 2)
str(y[1])
List of 1
 $ a: num 1
str(y[[1]])
 num 1

# Factor
z <- factor(c("a", "b"))
z[1]
[1] a
Levels: a b
z[1, drop = TRUE]
[1] a
Levels: a

# Matrix, array
a <- matrix(1:4, nrow = 2)
a[1, , drop = FALSE]
    [,1] [,2]
[1,]  1    3
a[1, ]
[1] 1 3

# Data frame
df <- data.frame(a = 1:2, b = 1:2)
str(df[1])
'data.frame': 2 obs. of 1 variable:
 $ a: int 1 2
str(df[[1]])
 int [1:2] 1 2
str(df[, "a", drop = FALSE])
'data.frame': 2 obs. of 1 variable:
 $ a: int 1 2
str(df[, "a"])
 int [1:2] 1 2

 

Subsetting y asignación de valores


Todas las operaciones que hemos visto hasta el momento nos permiten extraer valores de los objetos que tenemos en el espacio de trabajo en R. Si en cambio, lo que queremos es modificar estos datos, simplemente debemos utilizar los mismos operadores, pero invirtiendo el orden de asignación. Veamos unos ejemplos para que quede claro.

# Para el caso de vectores
x <- 1:5
x[c(1, 2)] <- 8:9
x
[1] 8 9 3 4 5

x[-1] <- 4:1 # La longitud de ambos ambos miembros de la asignación debe ser igual
x
[1] 8 4 3 2 1

x[c(1, 1)] <- 2:3 # En caso de pintar más de una vez en la misma posición, no se 
                    generará una error
x
[1] 3 4 3 2 1

x[c(1, NA)] <- c(1, 2) # Si intentamos pintar indicando la posición con un NA, 
                         nos saldrá un error
Error in x[c(1, NA)] <- c(1, 2) : NAs are not allowed in subscripted assignments

x[c(T, F, NA)] <- 1 # Pero sí podemos combinar un índice lógico con un NA, en 
                      tal caso, se omitirá reemplazar el valor en la posición
x
[1] 1 4 3 1 1

 

Casos de aplicación


En esta sección vamos a ver algunas aplicaciones practicas de los métodos de subsetting.

 

Muestras aleatorias

En muchas situaciones es preferible trabajar con muestras de un conjunto, que con el conjunto entero. Casos de estos iremos viendo en próximos post. De momento, quedémonos con la idea de que tomar muestras de un objeto es algo que realizaremos con frecuencia.

Entonces, existen funciones que se ocupan de simplificar esta tarea, como por ejemplo sample_n del paquete dplyr. No obstante, siempre es bueno saber realizar este procedimiento utilizando los operadores de  subsetting. Vamos a ver como hacer esto para el caso de los data frames, que es lo más habitual.

# Primero, como muestrear un vector
datos <- 1:15
datos
 [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# La función 'sample' toma un vector y extraer una muestra 
  aletoria, de tamaño 'size'. Con el argumento 'replace' se
  elije si se tomar elementos repetido. Para más información
  mirar la documentación, '?sample' 

sample(x = datos, size = 5, replace = F)
[1] 2 9 8 1 7

sample(x = datos, size = 5, replace = F)
[1] 11 3 14 15 13

sample(x = datos, size = 5, replace = F)
[1] 9 2 1 3 13


# Y ahora como muestrear un data frame
df <- data.frame(x = rep(1:3, each = 2), y = 6:1, z = letters[1:6])
df
  x y z
1 1 6 a
2 1 5 b
3 2 4 c
4 2 3 d
5 3 2 e
6 3 1 f

nrow(df) # La función 'nrow' devuelve el número de filas de un data frame
[1] 6

set.seed(88) # Configuramos la semilla para que las muestra aleatoria sea reproducible

df[sample(nrow(df)), ] # Reordenamos el data frame. La función 'sample' por defecto tiene 
                         el argumento 'replace' a FALSE. 
  x y z
3 2 4 c
1 1 6 a
6 3 1 f
2 1 5 b
4 2 3 d
5 3 2 e

df[sample(nrow(df), 3), ] # Tomamos tres aleatorias filas, sin repetición, del data frame.
  x y z
1 1 6 a
4 2 3 d
3 2 4 c

 

Lookup tables

Una lookup table es una estructura, típicamente un vector, que contiene datos precalculados de operaciones. De este modo, permite ahorra tiempo de procesamiento al ya tener guardado los resultados de ciertas operaciones. De momento, con esta idea nos alcanza. Si quieren leer más sobre esto pueden ir acá. Veamos un ejemplo:

x <- c("h", "m", "x", "m", "m", "h", "h")       # Valores a hallar (índices)
lookup <- c(h = "Hombre", m = "Mujer", x = NA)  # Vector con los valores precalculados
unname(lookup[x])                               # Resultado de la búsqueda
[1] "Hombre" "Mujer" NA "Mujer" "Mujer" "Hombre" "Hombre"

 

Ordenando vectores

Para ordenar vectores, nos podemos ayudar mediante la función order de la siguiente forma.

x <- c("mauricio", "carlos", "marcos")
order(x)
[1] 2 3 1

x[order(x)]
[1] "carlos" "marcos" "mauricio"

En caso de que deseemos invertir el orden, podemos indicar el argumento decreasing = TRUE de la función order.

 

Eliminar columnas de un data frame

En caso de que deseemos eliminar una columnas de un data frame, hay dos caminos que podemos seguir.

El primer es mediante el operador $ de la siguiente forma: df$variable <- NULL.

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])

df
  x y z
1 1 3 a
2 2 2 b
3 3 1 c

df$z <- NULL # Eliminamos la columna z del data frame
df
  x y
1 1 3
2 2 2
3 3 1

Y la otra alternativa es con el operador [.

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df
  x y z
1 1 3 a
2 2 2 b
3 3 1 c

df[c("x", "y")] # Seleccionamos las columnas que queremos conservar
  x y
1 1 3
2 2 2
3 3 1

df[setdiff(names(df), "z")] # o seleccionamos las que queremos quitar
  x y
1 1 3
2 2 2
3 3 1

 

Selección de filas en función de condiciones (logical subsetting)

Continuando con la idea anterior, podemos fácilmente combinar condiciones de múltiples columnas para quedarnos con las filas (o registros) que cumplan las condiciones buscadas. Para quienes estén familiarizados con SQL, esto sería similar a poner condiciones en el where para quedarnos con los registros que deseamos. Entonces, veamos con unos ejemplos como hacer esto.

mtcars[mtcars$mpg > 25 & mtcars$disp > 75, ]
               mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Fiat 128      32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic   30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Fiat X1-9     27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa  30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2

mtcars[mtcars$mpg > 25 & mtcars$disp > 80, ]
               mpg cyl  disp  hp drat    wt qsec vs am gear carb
Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
Lotus Europa  30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2

Una alternativa que podemos utilizar y mejora la legibilidad del código es la función subset.

subset(mtcars, mpg > 25)
                mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Fiat X1-9      27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2

subset(mtcars, mpg > 25 & disp > 80)
               mpg cyl  disp  hp drat    wt qsec vs am gear carb
Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
Lotus Europa  30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2

 

Comentarios
Curso de R | Estructura de datos

Curso de R | Estructura de datos

En el post de hoy vamos a continuar donde nos quedamos en Introducción a R. Presentaremos las principales estructuras de datos en R que usaremos de acá en adelante, y a la vez veremos como se declaran variables y se asignan valores a estas. R estructura de datos

Introducción


Las estructuras de datos básicas de R pueden agrupar por su dimensionalidad y según si son homogéneas (todos los elementos son del mismo tipo) o heterogéneas (hay elementos de distintos tipos). En el siguiente cuadro se resumen estas:

 

Homogéneas Heterogéneas
1d Vector Simple Lista
2d Matriz Data Frame
nd Array

 

La mayoría de los objetos que pueden existir en R se construyen sobre las bases de estas estructuras. Como se observa, no existen objetos de dimensionalidad 0, sino que los elementos individuales como enteros o cadenas, son realmente tratados como vectores de longitud 1. Entonces, dado un objeto, la mejor forma de entender su estructura es mediante la función str (de ‘structure’):

str(mtcars)
'data.frame': 32 obs. of 11 variables:
 $ mpg : num 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num 6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num 160 160 108 258 360 ...
 $ hp : num 110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num 3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt : num 2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num 16.5 17 18.6 19.4 17 ...
 $ vs : num 0 0 1 1 0 1 0 1 1 1 ...
 $ am : num 1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num 4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num 4 4 1 1 2 1 4 2 2 4 ...

En este ejemplo observamos como está formado el objeto mtcars. Vemos que es un objeto de tipo data frame que cuenta con 32 observaciones y 11 variables, y a su vez podemos ver las características de cada una de estas variables.

Antes de continuar, les comento sobre el paquete datasets de R, en el cual está mtcars. Este se carga por defecto al iniciar una sesión en RStudio, y simplemente contiene conjuntos de datasets de distintas fuentes de datos. Estos datasets son típicamente utilizados para aprender a usar R, por lo que verán que son utilizados en la mayoría de tutoriales de R que andan dando vueltas por la red. Una descripción de los datasets incluidos la podemos ver con:

library(help = "datasets")

Information on package ‘datasets’

Description:

Package: datasets
Version: 3.2.3
Priority: base
Title: The R Datasets Package
Author: R Core Team and contributors worldwide
Maintainer: R Core Team <R-core@r-project.org>
Description: Base R datasets.
License: Part of R 3.2.3
Built: R 3.2.3; ; 2015-12-10 13:04:33 UTC; windows

Index:

AirPassengers    Monthly Airline Passenger Numbers 1949-1960
BJsales          Sales Data with Leading Indicator
BOD              Biochemical Oxygen Demand
CO2              Carbon Dioxide Uptake in Grass Plants
ChickWeight      Weight versus age of chicks on different diets
DNase            Elisa assay of DNase
EuStockMarkets   Daily Closing Prices of Major European Stock Indices, 1991-1998
Formaldehyde     Determination of Formaldehyde
...
mtcars           Motor Trend Car Road Tests
...

A lo largo del curso haremos uso de varios de estos datasets para ayudarnos a aprender distintos temas.

Ahora sí, comencemos a ver estos los tipos estructuras con más detenimiento.

Vectores


Los vectores son la estructura de datos básica en R, y se los puede clasificar en vectores simples (o atómicos, del ingles atomic vector) y listas. Poseen 3 propiedades:

  • Tipotypeof
  • Longitud (es decir, cuantos elementos contienen) → length
  • Atributos (meta-datos adicionales) → attributes

Los vectores simples y las listas se diferencian respecto del tipo de elementos que pueden contener. Mientras que en los vectores simples todo los elementos deben ser de un mismo tipo, en las listas esto no es una restricción.

De ahora en más mencionaré a los «vectores simples» simplemente como «vectores».

Vectores

Hay cuatro tipos básicos de vectores, definidos según el tipo de elementos que lo componen: logicalintegernumeric (double)character.

Los vectores son creados comúnmente con el comando c (del ingles combine):

v_numeric <- c(1, 2.5, 4.5)
v_numeric
[1] 1.0 2.5 4.5

v_integer <- c(1L, 6L, 10L) # La L se la utiliza para indicar que el elemento sea un entero,
                              ya que por defecto R lo tratará con un numeric
v_integer
[1] 1 6 10

v_boolean <- c(TRUE, FALSE, T, F)
v_boolean
[1] TRUE FALSE TRUE FALSE

v_character <- c("arbol", "casa")
v_character
[1] "arbol" "casa"

Entonces, dado un vector x, se puede determinar su tipo mediante typeof(x), o comprobar si es de un tipo particular mediante la familia de funciones is: is.character, is.double, is.integer, is.logical, o más genéricamente con is.atomic.

typeof(v_integer)
[1] "integer"

is.integer(v_integer)
[1] TRUE

is.atomic(v_integer)
[1] TRUE

typeof(v_numeric)
[1] "double"

is.double(v_numeric)
[1] TRUE

is.atomic(v_numeric)
[1] TRUE

Luego, como todos los elementos de un vector deben ser del mismo tipo, si intentamos combinar distintos tipos de elementos, estos serán transformados al tipo de datos más flexible. Esto se lo llama «coerción», y es un concepto importantísimo. El orden de los tipos de datos desde el menos flexible al más flexible es: logical < integer < double < character.

Por ejemplo, si combinamos una cadena y un entero, el vector resultantes será una cadena:

str(c("Maricio", TRUE, 1)) 
chr [1:3] "Maricio" "TRUE" "1"

A su vez, si intentamos realizar operaciones sobre elementos en los cuales dicha operación no está definida, esta operación se intentará realizar sobre elementos más flexibles, y recién si la operación no es posible con ningún tipo de elemento se nos devuelve un error. Este hecho es especialmente útil en muchos casos que iremos viendo a los largo del curso. Por ejemplo, cuando trabajamos con las funciones sum o mean (las cuales, como se imaginan, calculan la sumatoria y el promedio de los elementos de un vector), la coerción nos permite utilizarlas juntos a los operadores de comparación para obtener cuantos elementos, o que porcentaje, cumplen una condición:

x <- 1:10 # Definimos una secuencia del 1 al 10
x
[1] 1 2 3 4 5 6 7 8 9 10
x > 7 # Los elementos que cumplen la condición se transforman en TRUE, y los que no en FALSE
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE

# Aplicamos la función de suma a este vector de booleanos. Como la operación suma
  no está definida para este tipo de elementos, a estos se los trata como el 
  siguiente elemento las flexible, es decir, como enteros. Al suceder esto, 
  los elementos TRUE se los interpreta como 1, mientras que a los FALSE como 0.

sum(x > 7) # Cantidad de elementos que cumplen la condición
[1] 3
mean(x > 7) # Proporción de elementos que cumplen la condición
[1] 0.3

Listas

Las listas son diferentes de los vectores debido a que sus elementos pueden ser de cualquier tipo, incluyendo nuevas listas. Estas se crean con el comando list:

x <- list(1:5, "Mauricio", c(TRUE, FALSE, TRUE), c(1.5, 2.23), list(1, "a"))
str(x)
List of 5
 $ : int [1:5] 1 2 3 4 5
 $ : chr "Mauricio"
 $ : logi [1:3] TRUE FALSE TRUE
 $ : num [1:2] 1.5 2.23
 $ :List of 2
 ..$ : num 1

En esta última lista, los elementos no poseen nombre. Para acceder a ellos utilizamos su índice. Existen otros tipo llamadas listas nombradas, en las que cada uno de sus elementos posee un nombre con el cual se los puede acceder:

x <- list(elem_1 = 1:5, 
          elem_2 = "Mauricio", 
          elem_3 = c(TRUE, FALSE, TRUE), 
          elem_4 = c(1.5, 2.23), 
          elem_5 = list(1, "a"))
str(x)
List of 5
 $ elem_1: int [1:5] 1 2 3 4 5
 $ elem_2: chr "Mauricio"
 $ elem_3: logi [1:3] TRUE FALSE TRUE
 $ elem_4: num [1:2] 1.5 2.23
 $ elem_5:List of 2
 ..$ : num 1
 ..$ : chr "a"

La forma de acceder a los elementos de una estructura de datos lo veremos en el próximo post.

Las listas son uno de los tipos de datos más importantes que posee R, por lo que las usaremos mucho de acá en adelante.

 

Atributos


Todos los objetos pueden tener una cantidad adicional de atributos, los cuales son usados para almacenar meta-información acerca de estos. Los atributos pueden ser pensados como una lista nombrada, y por lo tanto pueden ser accedidos individualmente con attr, o todo juntos con el comando attributes:

y <- 1:10
attr(y, "pais") <- "Japon" 
attr(y, "pais")
 [1] "Japon"


attr(y, "color") <- "Purpura"
str(attributes(y))
List of 2
 $ pais : chr "Japon"
 $ color: chr "Purpura"

En el caso de las listas, los tres atributos más importantes son:

  • Names: es un vector tipo character en que cada uno de sus elementos es el nombre de un elemento del objeto.
  • Dim: es usado para convertir vectores en matrices y arrays. Lo vemos más adelantes.
  • Class: es usado para implementar objetos de tipo S3. Es un tema avanzado, así que de momento no lo veremos.

Cada uno de estos atributos tiene una función propia para leer su valor o modificarlo: names, dim y class.

 

Names

Tanto en vectores como en listas y demás estructuras, existen tres formas de definir los nombre de los elementos:

x <- c(a = 1, b = 2, c = 3) # Cuando se los crea
  
x <- 1:3; names(x) <- c("a", "b", "c") # Modificando un vector existente con la función names
  
x <- setNames(1:3, c("a", "b", "c")) # Creando una copia modificada del objeto

x
a b c 
1 2 3 

Factores

Un uso importante de los atributos es la creación de variables tipo factor, el cual es un tipo de dato que solo puede contener una cantidad predefinida de elementos, y es usado para almacenar variables categóricas. Los factores están caracterizados por dos atributos: el atributo class, que toma el valor factor, y el atributo levels, que define los valores que puede tomar la variable.

x <- factor(c("argentina", "brasil", "brasil", "argentina"))
x
[1] argentina brasil brasil argentina
Levels: argentina brasil 
class(x)
[1] "factor"
  
levels(x) 
[1] "argentina" "brasil"
  
x[2] <- "chile" # No se puede utilizar valores que no están en el atributo levels
Warning message:
In `[<-.factor`(`*tmp*`, 2, value = "chile") :
 invalid factor level, NA generated

x 
[1] argentina <NA> brasil argentina 
Levels: argentina brasil

La utilidad de las variable factor radica en poder tratar ciertos tipos de datos, de los cuales conocemos previamente que tomarán únicamente una cantidad finita de valores, como si fuesen de alguna manera etiquetas. Ya los iremos usando a los largo del curso y quedará más claro.

 

Matrices and arrays


Los arrays y las matrices son creadas, o bien modificando el atributo dim de vectores, o usando los comandos matrix y array:

a <- c(1,2,3,4,5,6)
a
[1] 1 2 3 4 5 6

dim(a)
NULL
  
dim(a) <- c(2,3)
a
   [,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
  
a <- matrix(1:6, ncol = 3, nrow = 2) 
a
  [,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6

b <- array(1:12, c(2, 3, 2))
b
 , , 1

   [,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6

, , 2

   [,1] [,2] [,3]
[1,] 7 9 11
[2,] 8 10 12

En el caso de estos tipos de datos, se generalizan las funciones length y names que vimos previamente para vectores de la siguiente forma:

  • length → se generaliza con nrow y ncol para matrices, y con dim para arrays.
  • names  se generaliza con rownames y colnames para matrices, y con dimnames, para arrays.
length(a)
[1] 6
nrow(a)
[1] 2
ncol(a)
[1] 3

rownames(a) <- c("A", "B")
colnames(a) <- c("a", "b", "c")
a
  a b c
A 1 3 5
B 2 4 6
 
length(b)
[1] 12
 
dim(b)
[1] 2 3 2
 
dimnames(b) <- list(c("one", "two"), c("a", "b", "c"), c("A", "B"))
b
, , A

    a b c
one 1 3 5
two 2 4 6

, , B

    a  b  c
one 7  9 11
two 8 10 12

Luego, c se generaliza con cbind y rbind para matrices, y con abind para arrays.

 

Data Frames


Los data frame son la estructura de datos más importante en R. Son usados para almacenar información en forma tabular y realizar análisis sobre la misma. Similar a las listas, los data frames pueden almacenar valores de distintos tipos, y al tener dos dimensiones comparten muchas de las propiedades de las matrices. Esto significa que los data frames tienen los atributos names (o colnames), rownames, ncol y nrow. Para crear un data frame se usa la función data.frame:

df <- data.frame(x = 1:3, y = c("a", "b", "c"))
str(df) 
'data.frame': 3 obs. of 2 variables:
 $ x: int 1 2 3
 $ y: Factor w/ 3 levels "a","b","c": 1 2 3

Por defecto, al crear un data frame, las cadenas son tratadas como tipo factor, por lo que si queremos que sean tratadas realmente como cadenas debemos indicar el argumento stringAsFactors = FALSE de la siguiente forma:

df <- data.frame( x = 1:3, y = c("a", "b", "c"), stringsAsFactors = FALSE)
str(df)
'data.frame': 3 obs. of 2 variables:
 $ x: int 1 2 3
 $ y: chr "a" "b" "c"

Otra formas de crear un data frame es obteniendolo desde otra estructura como un vector, una lista o una matriz, mediante la función as.data.frame.

Luego, si tenemos múltiples data frames y queremos unirlos, podemos hacerlo mediante la unión por fila o por columna con las funciones rbind y cbind respectivamente:

cbind(df, data.frame(z = 3:1))
  x y z
1 1 a 3
2 2 b 2
3 3 c 1

rbind(df, data.frame(x = 10, y = "z")) 
   x y
1  1 a
2  2 b
3  3 c
4 10 z

Unos puntos a tener en cuenta a la hora de unir tablas es que en el caso de unirlos por columnas, ambas tablas tengan la misma cantidad de filas, y en el caso de unirlas por filas, ambas tablas tengan las mismas columnas.

 

 

Comentarios

Curso de R | Introducción

Curso de R | Introducción

Bienvenidos al curso de R para principiantes. El objetivo de este curso es que puedan tomar conocimiento de los aspectos básicos para comenzar a trabajar con este lenguaje, sin enfocarnos en un primer momento en ningún tema en particular. Ya una vez tengamos manejo de los temas esenciales de R podremos comenzar a realizar cosas increíbles. Algunos puntos que tengo en mente para realizar tutoriales en el futuro son modelos predictivos (machine learning), generación de clústers (clustering), análisis estadísticos, visualización de datos y mucho más. Pero por el momento comencemos con lo básico.

 

R para principiantes

 

Entonces, ¿Por qué debería comenzar aprender R? Si bien hay otros lenguajes y entornos de programación dedicados a los mismos temas ahí fuera, R le saca ventaja a todos estos. Algunas de las razones por la que afirmo esto son que R es gratuito (se distribuye bajo licencia GNU GPL) y cuenta con una inmensa comunidad detrás que le da soporte y muy buena documentación. A su vez, esto hace que continuamente aparezcan nuevos paquetes que amplían su funcionalidad. Y lo mejor de todo es que, a pesar que se suela decir lo contrario, es simple de aprender y muy entretenido.

Y si aún no te convenzo, pásate por el siguiente enlace sobre las tecnologías mejor pagas del mercado y luego me cuentas.

 

Introducción


R fue desarrollado a principios de los años ’90 por Ross Ihaka y Robert Gentleman (Universidad de Auckland) como una extensión de un lenguaje llamado S, al que se le agregaron capacidades para ser empleado en estadística. De esta forma, siendo que R es un derivado de S (surgido a fines de los ‘70) se puede decir que es un lenguaje bastante viejo, lo cual se plasma en algunos aspecto que veremos a medida que vayamos usándolo.

Actualmente, su desarrollo es responsabilidad del R Development Core Team, siendo la versión actual con la que trabajaré la 3.2.3.

Si quieren informarse más acerca de R y su historia pasense el siguiente enlace, no obstante, para nosotros nos basta con esto.

 

Instalación


Entonces, lo primero es lo primero. Debemos dejar el equipo listo para comenzar a trabajar. Para esto nos vamos a https://www.r-project.org/ y desde ahí a Download / CRAN. Seleccionamos la distribución para nuestro sistema operativo, la descargamos y por último la instalamos. Simple.

 

cap1

 

R traer por defecto un entorno de desarrollo llamado a R-commander, del cual a mi juicio es mejor olvidarse. En cambio, vamos a utilizar RStudio. Para instalarlo nos dirigimos a https://www.rstudio.com/products/RStudio, descargamos la versión para escritorio y la instalamos.

 

cap2

 

Y ya tenemos preparado nuestro entorno de trabajo para comenzar a trabajar. Así de simple.

 

Entorno de trabajo


Una vez abierto RStudio, nos encontramos con la zona de trabajo, compuesta por 4 áreas principales:

  • (1) El editor de texto
  • (2) La consola, donde se irán ingresando comandos
  • (3) El área de trabajo (enviaroment) y el historial de comandos (History)
  • (4) El directorio de trabajo, los plots que realicemos, los paquetes que carguemos, la ayuda y un visor.

 

curso-r_cap3

 

Para comenzar a ver un poco mejor esto, creemos algunas variables. Como R es un lenguaje interpretado, a medida que ingresemos comando en la consola y le demos enter, estos se irán ejecutando. Es decir, no hay necesidad de compilar nada, lo cual hace que manejarse en R sea bastante dinámico al momento de trabajar.

Dicho esto, lo primero es ver como se asignar valores a variables. Para esto existe dos métodos. El primero es utilizando el operador <-, mientras que el segundo es con =. En general las guías de estilo sugieren utilizar el primero para la asignación de valores a variables, mientras que el segundo se reserva para la asignación de valores a argumentos en funciones. En este blog utilizo la guía de estilo propuesta por Google para trabajar. Esta la pueden ver acá: Google’s R Style Guide.

x <- 1
x
[1] 1

s <- "hola"
s
[1] "hola"

y <- c(1,3,2,4)
y
[1] 1 3 2 4

Una vez creadas las variables, estas aparecen en el entorno de trabajo:

 

curso-r_cap4

 

En el siguiente post ya comenzaremos a ver más a fondo los tipos y estructuras de datos y cómo asignarles valores. Por ahora dejemoslo acá.

A su vez, mas allá de la necesidad de poder asignar datos manualmente a variables, lo más usual es obtener los datos con que se va a trabajar desde fuera de R. Veamos un ejemplo. Carguemos un archivo CSV, para lo cual utilizamos la función read.table:

t <- read.table(file = ..., header = TRUE, sep = ",")

Un punto importante a tener en cuanta es que R almacena el entornos de trabajo en memoria activa (memoria RAM), por lo cual tenemos que estar atentos a la disponibilidad de memoria con que contamos. La cantidad de memoria que ocupa cada objeto se puede ver en el área de trabajo, o usar alguna función como object.size:

object.size(s)
# 96 bytes

object.size(x)
# 48 bytes
object.size(y)
# 72 bytes
format(object.size(t), units = "auto")
# "75.8 Mb"

En este último caso, hemos combinado la función object.size con la función format. En este punto quizás se pregunten como es que sé que puedo combinar ambas funciones para modificar la salida de la primera. La respuesta es simple: he mirado la documentación de la función object.size, y dentro de esta se explica lo que he hecho. Leer la documentación de las funciones que utilicemos es algo importantísimo. Cómo hacer esto lo veremos en la siguiente sección.

Continuando, para poder explorar las variables que hayamos cargado tenemos una sería de funciones que presentamos ahora, y que iremos usando a lo largo del curso:

ls() # Muestra las variables cargadas en el entorno de trabajo
[1] "s" "t" "x" "y"

head(t)# Imprime los primeros 6 elementos de la variable que indiquemos. Si 
         agregamos el argumento n = N podemos hacer que se muestren los 
         primeros N elementos de la variable.
#                 fecha                         cliente producto precio
# 1 03/06/2008 00:05:44 8034dd89aa255e076be42ff87665a72b       E      3
# 2 03/06/2008 00:06:47 9a7c801fb29330b3e8d7dbe5ec4805a1       B     12
# 3 03/06/2008 00:09:16 7026f1a04128fb4541aa5bdd409b4d37       B     15
# 4 03/06/2008 00:10:22 def679eb50db52f66ad3b6e84728024c       E      3 
# 5 03/06/2008 00:10:34 1a99ffb6f019163213d269b9b4249a4a       A      3
# 6 03/06/2008 00:10:34 298b548f62ebc5f8f48054d4be78b607       E      3

# tail(t) # Hace lo mismo que head() pero con los últimos elementos.
str(t) # Muestra la estructura de la variable. 
# 'data.frame': 1103615 obs. of 4 variables:
# $ fecha: Factor w/ 457644 levels "01/07/2008 00:01:10",..: 23474 23475 23476 23477 23478 23478 23479 23480 23481 23481 ...
# $ cliente : Factor w/ 262299 levels "000005977a868020fcca9f32e44ad0a2",..: 131439 158597 115022 228722 27224 42565 101363 61585 240792 87661 ...
# $ producto: Factor w/ 6 levels "A","B","C","D",..: 5 2 2 5 1 5 5 2 5 1 ...
# $ precio : int 3 12 15 3 3 3 3 2 3 3 ...

summary(t) # Muestra un resumen de la la variable. En este caso tenemos que 
             la variable es un tabla (dataframe, lo veremos mas adelante) por 
             lo que se muestra un resumen de cada columna
# fecha cliente producto precio 
# 08/06/2008 17:23:48: 19       a1ce93ccad5e4a78a7cf2f00cba7219f: 47      A:473439    Min. : 2.00 
# 08/06/2008 16:58:49: 18       8d8235de8f0bd9119932c43fedd48310: 29      B: 45245    1st Qu.: 9.00 
# 08/06/2008 17:01:49: 18       2c5306a3969cb2dad1ea48896fe17d6f: 27      C: 28552    Median :15.00 
# 08/06/2008 17:06:53: 18       d5c41d6e5b12ce4144e3d1520d527415: 26      D: 83185    Mean :11.83 
# 08/06/2008 17:18:50: 18       f1a80222bbca04d591946e03074e7631: 26      E:473183    3rd Qu.:15.00 
# 08/06/2008 16:19:52: 17       1f30febab5f5c69806120a727a93875b: 25      X: 11       Max. :16.00 
# (Other)              :1103507 (Other)                         : 1103435

 

Documentación de las funciones


Mencionamos en la sección anterior lo importante que es utilizar la documentación presente en R. Para acceder a la documentación de cada función simplemente usamos ?funcion:

?object.size

y luego de ingresar este comando, en la solapa help se nos muestra la documentación:

 

curso-r_cap5

 

El esquema de la documentación sigue un formato estándar:

  • Description: descripción breve.
  • Usage: para una función, proporciona el nombre de la misma con todos sus argumentos y los posibles valores por defecto (opciones); para un operador describe su uso típico.
  • Arguments: para una función, describe en detalle cada uno de sus argumentos.
  • Details: descripción detallada.
  • Value: si se aplica, el tipo de objeto retornado por la función o el operador.
  • See Also: otras páginas de ayuda con funciones u operadores similares.
  • Examples: algunos ejemplos que generalmente pueden ser ejecutados sin abrir la ayuda con la función examples().

 

Instalación y carga de paquetes


Lo último que veremos en este post es como instalar y cargar paquetes. Cuando iniciamos R, este se carga con lo que se llama paquete base, el cual contiene las funciones propias de R. No obstante, R cuenta con una comunidad de colaboradores inmensa, la cual provee de cientos de paquetes con nuevas funciones, tipos de datos, etc. que amplían enormemente sus capacidades. Entonces, veamos como instalar estos paquetes y luego cargarlos.

Para instalar paquetes el comando que se utiliza es:

install.packages("nuevo_paquete")

o podemos ir a Tools -> Install Package y hacer lo propio.

Y luego, para cargar paquete usamos:

library(paquete_a_cargar)

Con esto ya podemos instalar y cargar los paquetes que deseemos. A lo largo de los próximos post iremos explorando una gran cantidad de paquetes, por lo que haremos amplio uso de estos comando.

 

Video


Comentarios