15 RECUPERACIÓN DE SALIDAS

Las funcionalidades incorporadas en Quarto permiten formatear elegante y eficientemente las salidas generadas por R para incorporarlas en un informe. Esta es nuestra herramienta recomendada. En el Anexo F se presenta una introducción a Quarto. No obstante, algunos usuarios que no estuvieran preparados o motivados para hacer uso de tales herramientas podrían requerir una herramienta que les permita satisfacer un interés más prosaico: llevar las salidas, tal y como se generan por defecto, a un archivo editable, es decir a un archivo de Word o de Excel, sin que se desbaraten en dicho proceso. Esto puede resultar útil para estudiar dichas salidas, para imprimirlas o para tomar y editar lo que de allí se estime conveniente y llevarlo a un informe.

15.1 Salidas de texto

En muchas ocasiones, el objetivo de llevar las salidas a un archivo de texto puede satisfacerse mediante el simple copiado y pegado. No obstante, cuando estas son muy voluminosas, se requieren otras estrategias.

Considérense las siguientes instrucciones en las que se usa la base de datos iris, la cual forma parte del paquete datasets que viene preinstalado en R:

data(iris) # Carga la base de datos en memoria

Puede solicitarse un resumen de la base datos:

summary(iris)
#>   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
#>  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
#>  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
#>  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
#>  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
#>  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
#>  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
#>        Species  
#>  setosa    :50  
#>  versicolor:50  
#>  virginica :50  
#>                 
#>                 
#> 

Para visualizar las primeras observaciones de este conjunto de datos, así como las últimas, se usan las funciones head y tail, respectivamente, así:

head(iris)
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa
tail(iris)
#>     Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
#> 145          6.7         3.3          5.7         2.5 virginica
#> 146          6.7         3.0          5.2         2.3 virginica
#> 147          6.3         2.5          5.0         1.9 virginica
#> 148          6.5         3.0          5.2         2.0 virginica
#> 149          6.2         3.4          5.4         2.3 virginica
#> 150          5.9         3.0          5.1         1.8 virginica

Considérese ahora la obtención de un análisis de varianza para la variable Petal.Width, en función del factor Species.

anova <- aov(Petal.Width ~ Species, data = iris)
summary(anova)
#>              Df Sum Sq Mean Sq F value Pr(>F)    
#> Species       2  80.41   40.21     960 <2e-16 ***
#> Residuals   147   6.16    0.04                   
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Finalmente, supóngase que se desea llevar toda la información obtenida a un archivo de Word, sin que se desconfigure. Para el efecto, pueden usarse las funciones sink y capture.output.

La función sink es las más expedita, siendo la recomendada cuando se tienen muchas salidas y/o se quiere enviar absolutamente todo al archivo, incluyendo instrucciones y resultados. Dicha función, en su forma básica, tiene un único argumento obligatorio, que es el nombre del archivo, el cual debe escribirse entrecomillado, incluyendo su correspondiente extensión (admite “txt”, “rtf” y “doc”). Es importante aclarar, sin embargo, que, aunque el archivo de salida tenga extensión “rtf” o “doc”, se trata en realidad de un archivo de texto sin formato; por tal razón, si se desea tomar dicho archivo como punto de partida para construir un informe en el que se manejen las diferentes herramientas de formato que proporciona Word y se incluyan gráficos, deberá usarse la opción “guardar como” de Word para guardarlo como un verdadero archivo de Word. Asimismo, podrán incorporarse trozos de dicho documento en cualquier documento Word, mediante el recurso del copiado y pegado, respetando el formato.

Una vez se ejecuta la función sink con un nombre de archivo como argumento, todo lo que se genera en adelante, tanto instrucciones como resultados, se envía a dicho archivo. Al final del script, debe usarse la función sink sin ningún argumento para cerrar la conexión con el archivo, permitiendo su manipulación.

El script para el presente ejemplo tendría la siguiente forma.

sink("Salidas.doc")
options(max.print = 10000)
data(iris)
summary(iris)
head(iris)
tail(iris)
anova <- aov(Petal.Width ~ Species, data = iris)
summary(anova)
sink()

El anterior script genera un archivo llamado “Salidas.doc”, que contiene tanto las instrucciones como los correspondientes resultados de todo lo incluido entre sink("Salidas.doc") y sink(). Por defecto, se incluye un máximo de 1000 líneas por salida; cualquier información adicional queda truncada. Cuando se tienen salidas muy extensas (que no es el presente caso), debe incluirse una instrucción que permita superar el límite de las 1000 líneas (p. e., options(max.print = 10000)), asegurando que el archivo de salidas contenga la información completa.

Para ejercer un mayor control sobre lo que se escribe en el archivo, seleccionando exactamente lo que se quiera registrar, sin incluir las correspondientes instrucciones e incorporando saltos de línea entre los diferentes resultados27, puede usarse la función capture.output, así:

capture.output(summary(iris), cat("\n", "\n"), head(iris),
cat("\n", "\n"), tail(iris), cat("\n", "\n"),
summary(anova), file = "Salidas.doc")

La instrucción anterior genera un archivo de texto llamado “Salidas.doc”, que contiene las cuatro salidas solicitadas. En la sección 15.1.1 se detalla lo relativo a la función cat.

Con cualquiera de las dos funciones ilustradas (sink y capture.output), es posible añadirle información al archivo creado, incluyendo el argumento append = T. Supóngase que se desea adicionar el resultado de una prueba de Shapiro-Wilk realizada sobre los residuales del modelo. Para ello se ejecutan las siguientes instrucciones, asegurándose de que el archivo “Salidas.doc” esté cerrado.

Este sería el conjunto de instrucciones, usando la función sink:

sink("Salidas.doc", append = T)
shapiro.test(resid(anova))
sink()

O, usando la función capture.output:

capture.output(cat("\n", "\n"), shapiro.test(resid(anova)),
               file = "Salidas.doc", append = T)

En ocasiones, la apariencia del archivo resultante puede hacer pensar que no se satisfizo el objetivo de capturar el texto, sin que se desconfigurara el formato. Esto es debido a que el contenido capturado es más amplio que el ancho de hoja, lo que hace que la(s) última(s) columna(s) pase(n) a la siguiente línea. Esto se soluciona fácilmente, bien sea disminuyendo el tamaño de la fuente, disminuyendo las márgenes o mediante una combinación de ambas estrategias.

Es importante resaltar que el hecho de que las funciones anteriores (sink y capture.output) estén dirigidas exclusivamente a la captura de texto no representa un problema, dado que la exportación de contenido gráfico puede realizarse acorde con lo expuesto en la sección 19.1 o incluso mediante la funcionalidad del botón EXPORT en el panel “plots”.

15.1.1 Funciones para impresión de texto

Entre las funciones que permiten imprimir cadenas de caracteres o texto, se destacan print, cat, paste, c, strwrap y WriteLines. A continuación, se presentan sus principales características.

La función print despliega en consola la información de su argumento, cualquiera sea su clase. Esta es la función que el sistema usa internamente cuando se invoca un objeto por su nombre y se muestra su contenido en consola.

Esta función no altera el contenido de su argumento para mostrarlo, sino que envía a consola una copia exacta del mismo. Esto puede verificarse, evaluando la clase de cualquier salida generada con print o, mejor aun, evaluando si la copia que se envía a consola es idéntica a la de su argumento.

class(print(df))
#>    id  v1     v2
#> 1 a23 2.4 4+3.0i
#> 2 f31 7.9 2-0.8i
#> 3 j33 1.1 1+1.1i
#> 4 m54 8.5 3-5.0i
#> [1] "data.frame"
identical(print(df), df)
#>    id  v1     v2
#> 1 a23 2.4 4+3.0i
#> 2 f31 7.9 2-0.8i
#> 3 j33 1.1 1+1.1i
#> 4 m54 8.5 3-5.0i
#> [1] TRUE

Para muchos objetos, esta es la única función de impresión posible. No obstante, puede resultar poco práctica para enviar texto a la consola como parte de las salidas generadas por una función y, de hecho, es poco usada para tal fin.

La función cat (nemotécnicamente: concatenación) realiza un trabajo similar al de la función print, esto es, imprimir el resultado en pantalla, pero brinda mayores posibilidades de personalización del formato, por lo que suele preferirse en los scripts para presentación de avisos. Esta función únicamente es aplicable a objetos atómicos. Si tales objetos tienen un atributo de dimensionalidad, como es el caso de matrices y arreglos, este se elimina, y se despliega el contenido del vector resultante.

print(m1)
#>      a  b c
#> [1,] 2  5 9
#> [2,] 0 -1 1
cat(m1)
#> 2 0 5 -1 9 1

Nótese que, además de eliminar el atributo de dimensionalidad, cat elimina cualquier nombre que pudiera estar asociado con las dimensiones o con los elementos del contenedor. Asimismo, cat deja de generar los, a veces molestos, indicadores de posición que aparecen cuando se usa print. La función cat tampoco encierra entre comillas los objetos tipo carácter, como sí lo hace print (aunque podría evitarse que print entrecomille, usando el argumento quote = F).

cat(id)
#> a23 f31 j33 m54

El principal uso de cat es la concatenación de diferentes cadenas de caracteres, combinando a menudo información contenida en los argumentos de una función con texto personalizado.

conf.level <- 0.95
confianza  <- 100 * conf.level
method     <- "Tukey"
cat("Intervalo de confianza del", confianza, "%", "\n",
    "usando el método", method)
#> Intervalo de confianza del 95 % 
#>  usando el método Tukey

Se habrá notado que los saltos de línea están representados por la etiqueta "\n". Pueden usarse tantos como se requieran. Si se desea imprimir un texto al final de la línea, antes del salto, puede introducirse antes de la barra invertida. Así, para introducir, por ejemplo, una coma al final de la línea, se usaría ",\n".

Nótese que la función cat separa por defecto todos sus argumentos con un espacio. Para evitar este comportamiento, forzando, por ejemplo, que el símbolo de porcentaje quede pegado de su valor, se usa el argumento sep = "". En este caso, al declarar los argumentos, se hace necesario introducir los espacios, donde sean requeridos (después de del y después de método, en el ejemplo). Igualmente, podría usarse el argumento sep para introducir cualquier otro separador.

cat("Intervalo de confianza del ", confianza, "%", ",\n",
    "usando el método ", method, sep = "")
#> Intervalo de confianza del 95%,
#> usando el método Tukey

La función paste, en algunos casos realiza un trabajo similar al de cat. Sin embargo, a diferencia de cat, paste sí admite objetos recursivos como argumentos, aunque su alcance no es tan amplio como el de print, que admite cualquier tipo de objeto (funciones, por ejemplo). La función paste está diseñada para trabajar con texto y lo que hace es concatenar todos sus argumentos como una cadena de caracteres. A diferencia de cat, paste permite asignar su resultado a un objeto que será, en todos los casos, una cadena de texto, es decir, un vector tipo character. La función paste, al igual que cat, separa por defecto sus argumentos con un espacio, lo cual puede modificarse mediante el argumento sep. Asimismo, puede usarse la función paste0, que no deja espacio por defecto entre sus argumentos.

El hecho de que los resultados de paste puedan asignarse a un objeto hace que esta función resulte muy versátil para guardar cadenas de texto que luego se traspasen como argumentos de alguna otra función. Así, por ejemplo, para usar el texto generado en el ejemplo anterior como título de un gráfico, bastaría con asignarlo a un objeto que luego se utilice como argumento del gráfico en cuestión (cf. capítulo 19).

 main <- paste("Intervalo de confianza del", confianza, "%",
               "usando el método", method)

Si la cadena de caracteres resultante fuera a mostrarse en consola, esta se adaptaría automáticamente al ancho de la ventana, ocupando las líneas que fueran necesarias. Esto podría dar lugar a textos poco estéticos, por cuanto la partición de la cadena de caracteres no tendría en cuenta la separación natural entre palabras.

Por otra parte, si se quisiera usar esta cadena de caracteres como título de un gráfico, su longitud podría llegar a ser problemática, puesto que esta se mostraría en una sola línea. Para imprimir en un gráfico un título de gran longitud, distribuyéndolo en varias líneas, es más conveniente usar la función c, para concatenar lo que se desea imprimir en cada línea.

Considérese el título del gráfico que se genera mediante la función que se presenta en el capítulo 23.

nombre.g <- 'Species'
main <- c('Medias y desviaciones estándar por nivel de',
          nombre.g,
          'usando la varianza individual dentro de cada grupo')

En este caso, cada uno de los tres elementos del vector main se muestra centrado en una línea, tal y como se aprecia en el gráfico de el capítulo 23.

Para manejar cadenas de texto muy largas, que incluso puedan estar conformadas por varios párrafos, deben combinarse las funciones strwrap y WriteLines. La función strwrap sí tiene en cuenta los límites naturales entre palabras, pudiendo establecerse el ancho máximo de cada línea, mediante el argumento width. La impresión de las diferentes líneas resultantes se realiza mediante la función WriteLines.

A continuación, se presenta de manera resumida y comentada el ejemplo que aparece en las ayudas de la función strwrap.

Inicialmente se lee el archivo 'THANKS', que está ubicado en la carpeta “doc”, dentro de la ruta de instalación de R.

x <- paste(readLines(file.path(R.home("doc"), "THANKS")),
           collapse = "\n")

El objeto x es un vector de la clase character, de longitud 1, es decir que está conformado por una única cadena de caracteres (otra cosa es el número de caracteres contenidos en dicha cadena, lo cual puede verificarse con la función nchar).

A continuación, se fracciona la información en párrafos (este trabajo lo hace strsplit) y se eliminan los 10 primeros, dando por resultado un vector de la clase character con 4 elementos (cuatro párrafos).

x <- unlist(strsplit(x, "\n[ \t\n]*\n"))[-(1:10)]

Seguidamente, usando paste, se unen estos 4 párrafos en una única cadena de caracteres, manteniendo el marcador \n\n para representar los saltos de párrafo.

x <- paste(x, collapse = "\n\n")

Hasta este punto todo lo que se ha hecho es leer y acondicionar una cadena de caracteres muy larga (864 caracteres), manteniendo un marcador para representar los saltos de párrafo (). Esta cadena de caracteres puede imprimirse, adecuadamente formateada, combinando las funciones strwrap y WriteLines.

Para imprimir los once párrafos en cuestión, sin que se parta ninguna palabra y sin que ninguna línea de impresión exceda los 50 caracteres, se usa la siguiente instrucción.

writeLines(strwrap(x, width = 50))
#> The Windows port was originally developed by
#> Guido Masarotto (for a while a member of R Core)
#> and Brian Ripley, then further by Duncan Murdoch
#> (a former member of R Core) and then Jeroen Ooms
#> (base) and Uwe Ligges (packages).  Tomas Kalibera
#> is the current main developer of the Windows port
#> and provides assistance with package porting.
#> 
#> Tomas Kalibera's work has been sponsored by Jan
#> Vitek and funded by his European Research Council
#> grant "Evolving Language Ecosystems (ELE)".
#> 
#> Computing support (including hardware, hosting
#> and infrastructure) has been provided/funded by
#> the R Foundation, employers of R-Core members
#> (notably WU Wien, ETH Zurich, U Oxford and U
#> Iowa) and by Northeastern University and the
#> University of Kent.
#> 
#> Distributions of R contain the recommended
#> packages, whose authors/contributors are listed
#> in their DESCRIPTION files.

En resumen, debe usarse la función print para imprimir el resultado de un objeto no atómico, respetando su estructura; en particular, si se trata de listas o data frames. La función cat es la más adecuada para formatear texto que se envíe a la consola, siempre que no se requiera guardar dicho contenido. La función paste (o paste0) es la más adecuada para formatear cadenas de caracteres, cuyo contenido deba guardarse para uso posterior. La función c permite controlar los saltos de línea en el título de un gráfico. Las funciones strwrap y WriteLines permiten formatear adecuadamente cadenas de caracteres de gran longitud.

15.2 Escritura de datos en una hoja de cálculo

La función writexl::write_xlsx permite copiar data frames a una hoja de cálculo de Excel. Para tal efecto se usa la siguiente sintaxis:

dfiris <- as.data.frame(iris)
writexl::write_xlsx(dfiris, "Iris.xlsx")

Para este ejemplo, mediante la primera línea se asigna el contenido de iris al data frame dfiris28. El data frame que se desee exportar (en este caso, dfiris) se usa como primer argumento de la función write_xlsx para ser exportado a la hoja de cálculo “Iris.xlsx”.