Datanalytics

Archivo

Entradas Etiquetadas ‘programación’

Herramientas de depuración en R

Miércoles, 26 de octubre de 2011 Sin comentarios

R dispone de un conjunto de herramientas para depurar (debug) programas. Yo suelo usar la función debug de manera casi exclusiva y sistemática, pero leyendo The Art of R Programming he dado con una discusión sistemática sobre el proceso de depuración así como algunas herramientas adicionales.

Una de las primeras que menciona el libro es la función stopifnot, que puede ser intercalada en el código para verificar condiciones necesarias (y lanzar un error en caso de que no se cumplan):

mi.error <- function( x ){
  res <- 1 / x
  stopifnot( ! is.infinite( res ) )
  2 * res
}
 
mi.error( 2 )
mi.error( 0 )

Puede ser usado para anticiparse activamente a los errores.

Son, creo yo, conocidas de todos las funciones debug y undebug, que permiten ejecutar código línea a línea. Una adición interesante a la familia es debugonce, que llama a debug una única vez y evita tener que eliminar explícitamente a la función undebug en situaciones similares a

f <- function( n, x ){
  for( i in 1:n)
    g(x)
}

La función browser permite inspeccionar el estado de la función sin tener que llamar a debug sobre toda ella. Se le puede añadir, además, una condición para que sólo interrumpa la ejecución del programa bajo ciertas condiciones.

mi.error <- function( x ){
  res <- 1 / x
  browser( expr = x == 0 )
  2 * res
}
 
mi.error( 2 )
mi.error( 0 )

Este resultado también puede obtenerse usando las funciones setBreakpoint o trace.

Finalmente, existe la posibilidad de saber qué ha pasado después del fallo de una función de R usando

options( error = recover )

Con esa opción, después de un fallo, R te deja elegir el contexto que se quiere analizar. Por ejemplo:

> options( error = recover )
> myFit <- lm(y ~ x, data = xy, weights = w)
Error in inherits(x, "data.frame") : object 'xy' not found

Enter a frame number, or 0 to exit   

1: lm(y ~ x, data = xy, weights = w)
2: eval(mf, parent.frame())
3: eval(expr, envir, enclos)
4: model.frame(formula = y ~ x, data = xy, weights = w, drop.unused.levels = TRUE)
5: model.frame.default(formula = y ~ x, data = xy, weights = w, drop.unused.levels = TRU
6: is.data.frame(data)
7: inherits(x, "data.frame")

Selection: 2
Called from: model.frame.default(formula = y ~ x, data = xy, weights = w,
    drop.unused.levels = TRUE)
Browse[1]> ls()
[1] "enclos" "envir"  "expr"
Browse[1]> Q
>

No son este tipo de herramientas aquellas a las que los programadores están más acomodaticiamente acostumbrados. Aparentemente, existen herramientas de depuración análogas a las que dispone Eclipse (para Java) o Eric (para Python) en desarrollo para RStudio. ¿Llegarán pronto?

Categories: r Tags: ,

Códigos de caracteres en R

Jueves, 8 de septiembre de 2011 2 comentarios

Esta entrada acompaña y remata para los usuarios de R la que escribí en general sobre los códigos de caracteres. Es un pequeño experimento en el que comparo lo que pasa al leer un fichero de texto codificado de dos maneras distintas en dos plataformas, Linux y Windows, que usan códigos de caracteres distintos.

Primero creo dos ficheros (en Linux) con el mismo contenido pero codificados de dos maneras distintas, utf-8 y latin1:

$ echo "hóla;adiós" > hola_utf8.txt
$ file hola_utf8.txt
hola_utf8.txt: UTF-8 Unicode text
$ iconv -f utf-8 -t latin1 hola_utf8.txt > hola_latin1.txt
$ file hola_latin1.txt
hola_latin1.txt: ISO-8859 text

Los ficheros pueden descargarse de este enlace.

La codificación interna de caracteres en Linux (al menos, en el mío) es utf-8. En Windows no lo tengo tan claro, pero parece que es algo similar a latin1.

Mi experimento consiste en ejecutar tanto en Linux como en Windows el siguiente código:

foo <- function( file, encoding ){
    a <- scan( file, what = "character",
               fileEncoding = encoding )
    strsplit( a, ";" )
}
 
foo( "hola_latin1.txt", "latin1" )
foo( "hola_latin1.txt", "utf-8" )
foo( "hola_latin1.txt", "" )
 
foo( "hola_utf8.txt", "latin1" )
foo( "hola_utf8.txt", "utf-8" )
foo( "hola_utf8.txt", "" )

El motivo de usar strsplit es que es una función que puede fallar catastróficamente cuando encuentra caracteres codificados incorrectamente (desde el punto de vista de la codificación de la plataforma en la que se ejecuta).

La primera y la quinta llamada funcionan correctamente tanto en Linux como en Windows: a la función scan se le ha especificado la codificación correcta del fichero y lo ha transformado adecuadamente.

Las llamadas segunda y cuarta fallan en ambos sistemas: equivale a engañar a R y es natural que los resultados sean… subóptimos.

Las llamadas tercera y sexta son más interesantes: en Linux falla tercera y funciona la sexta; en Windows ocurre lo contrario. El motivo es que si no se especifica la codificación, R asume que es la natural del sistema (aunque supongo que se podrá configurar de alguna manera, algo en lo que no entro). Es decir, en Linux no especificar la codificación equivale a asumir utf-8 y en Windows, latin1.

Este es un detalle a tener en cuenta por quienes aspiran a desarrollar código portable, es decir, que puede ser usado por cualquiera y en cualquier plataforma para leer archivos de texto.

Nota: este experimento tiene que ver con el desarrollo del paquete pxR, que debería ser capaz de leer ficheros (y ejecutar strsplit sobre sus cadenas, entre otras funciones) en la plataforma de elección de sus usuarios (y sobre la que los autores no tenemos control). Aparentemente, los ficheros PC-Axis que queremos leer en R, según la documentación oficial, están en codificados en formato Windows.

Nuestra actual implementación está basada en ideas extraidas del experimento anterior. Si alguien ve un error en las conclusiones (o conoce una manera más adecuada para garantizar la portabilidad), le rogaría que se pusiese en contacto conmigo para trasladarla al código.

Categories: r Tags: ,

Códigos de caracteres, unicode y UTF-8

Martes, 6 de septiembre de 2011 1 comentario

Unos quebraderos de cabeza en el desarrollo del paquete pxR concernientes a los distintos códigos de caracteres en que hay que transfomar los datos me han obligado a profundizar en este enojoso asunto.

En el principio, todo era felicidad. Existía el código ASCII que establecía una correspondencia entre caracteres, números y su representación binaria. Así, a la letra b le correspondía el número 98 cuya codificación binaria es el byte 01100010.

Felicidad que se interrumpió cuando algún ortógrafo quiso utilizar eñes y acentos. Y algún tailandés a escribir su nombre como había aprendido en la escuela. Etc. De ahí surgió el caos de los múltiples sistemas y codificaciones: que si el ISO-8859-1, que si el KOI 8, que si el de IBM, que si el de Microsoft,… todos ellos intentos de codificar distintos codificar distintos caracteres usando las 256 combinaciones que permiten los 8 bits de un byte mediante funciones biyectivas

f_i: C_i \longleftrightarrow B_1

entre B_1, el conjunto de los distintos bytes (números del 0 al 255), y diversos conjuntos de caracteres (C_1 pueden ser los latinos, C_2 los rusos, etc.).

Y del caos surgió la luz y el orden, una función biyectiva

C = \bigcup_i C_i \longleftrightarrow N = \{0, \dots, 1114112\}

llamada Unicode entre todos los caracteres del mundo mundial y los números hasta el 1.114.112. Esta función hace corresponder el número 88 a “X” y 241 a “ñ”, por ejemplo.

Para tener una representación binaria de un caracter, sin embargo, es necesaria otra función. Y no una función trivial: si este tinglado lo hubiese montado un gañán, harían falta tres bytes por caracter, con lo que el tamaño de los ficheros de texto se multiplicaría automáticamente.

La solución pasa por seleccionar inteligentemente la función entre el conjunto N y determinadas sucesiones de bytes. Una de las funciones propuestas se llama UTF-8, que tiene la peculiaridad de que asigna los números correspondientes a los caracteres más usados a secuencias de ocho bits (un byte) y a los menos usados, secuencias de dos, tres y hasta cuatro de ellos.

Así, usando dicha función, la “X” ocupa un único byte, pero “ñ” ocupa dos. Y supongo que los caracteres chinos ocuparán dos o tres.

Este es el motivo por el que cuando alguien intenta leer un fichero codificado con UTF-8 como si tuviese una codificación que asume una relación directa entre caracteres y bytes, obtiene los famosos caracteres raros en lugar de sus acentos, eñes y demás.

Han sido muchos los años en que nos hemos dejado arrastrar por la inercia un byte, un caracter (aunque no tenemos ni idea de cuál). Ahora la hemos sustituido por la composición de dos funciones, Unicode y UTF-8 que, gracias a Dios, están bien pensadas.

Categories: r Tags: ,