Maestría en Hidrología
Universidad de Cuenca
http://www.ucuenca.edu.ec/maestria-ecohidrologia/

Johanna Orellana-Alvear (MSc, PhD candidate)
johanna.orellana@ucuenca.edu.ec

Curso completo en: http://rpubs.com/Johanna_Orellana_Alvear/MHidro_indice_2018



En esta lección aprenderás a:
- Crear funciones sin argumentos
- Crear funciones con argumentos
- Crear fechas a partir de una cadena de caracteres
- Interfaz con S.O (listado de archivos y hora del sistema)
- Manipulación de fechas

Temario

A- Creación de Funciones
B- Componentes de una función
C- Funciones de S.O
D- Fechas

Funciones implementadas en R

Hemos utilizado en sesiones previas algunas funciones como mean, min o max. Por ejemplo la función round sirve para redondear un número, la función factorial para calcular el factorial, etc. En general se hace uso de una función escribiendo (invocando) el nombre de la función y entre paréntesis () el dato o datos sobre los cuales se desea aplicar la función. Ejm:

round(3.1415)
## [1] 3
factorial(3)
## [1] 6

El valor o valores que se “pasan” dentro de la función se denomina argumento de la función. El argumento puede ser cualquier objeto R o incluso el resultado de otra función R. En el último caso la evaluación de las funciones se hace secuencialmente desde la más interna hasta la llamada más externa. Así en el ejemplo siguiente, R evalúa inicialmente la creación del vector c(1,2,3,4), luego evalúa la media a través de mean(c(1,2,3,4)) y finalmente redondea el resultado que se ha obtenido de esta última operación.

round(mean(c(1,2,3,4)))
## [1] 2

Una función puede recibir tantos argumentos como se desee, siempre y cuando estos se encuentren separados con una coma ,. Normalmente no se conoce a priori cuales son los argumentos que acepta una función en particular, sin embargo es posible obtener esta información a través del comando args(), donde el nombre de la función de interés se coloca entre paréntesis, así: args(min). Nótese, que no todos los argumentos se visualizan, pero sí los más frecuentes. En tanto que para las funciones básicas como args(round) o args(rnorm) presentan todos sus argumentos.

args(round)
## function (x, digits = 0) 
## NULL
args(rnorm)
## function (n, mean = 0, sd = 1) 
## NULL
args(min)
## function (..., na.rm = FALSE) 
## NULL

En el caso de la función de redondeo round(3.5), el argumento digits no se ha “seteado”, es decir no se ha incluido en la llamada de la función. En este caso, R mantiene el valor por defecto que se incluye en la función, es decir redondeo a 0 dígitos decimales. A estos argumentos se les conoce como opcionales ya que tienen un valor predefinido.

Importante: es siempre recomendable incluir en la llamada de la función,el nombre del argumento por dos razones principales. Primero, a medida que se incrementa el número de argumentos es más difícil recordar (o interpretar en el código de alguien más) a que hace referencia cada valor. Segundo, el orden en el que colocan los argumentos es definido y R interpretará los valores asignados al argumento en dicho orden. ¿Cuál de los dos ejemplos es más fácil de interpretar?

rnorm(5, 3, 1)
## [1] 2.727029 3.903482 1.595341 3.574659 2.682911
rnorm(5, mean = 3, sd = 1) 
## [1] 3.797598 3.544420 3.598480 3.941327 3.765731

Funciones creadas por el usuario

¿Para qué crear una función?

  • Para reutilizar código fuente en R, es decir cuando vamos a necesitar ejecutar la misma función en distintos programas o en diversos lugares de un mismo programa.
  • Para organizar el código fuente. Es más sencillo inspeccionar una sección de código y asegurarse que el funcionamiento es el correcto en comparación con inspeccionar todo mi código fuente.

Primero, vamos a crear una función para simular el proceso de lanzar dos dados y obtener el resultado de la suma resultante. Vamos a guardar la codificación de esta función en un archivo llamado funcion_roll.R.

roll <- function() {
die <- 1:6
dice <- sample(die, size = 2, replace = TRUE) 
sum(dice)
}

roll()
## [1] 5
roll
## function() {
## die <- 1:6
## dice <- sample(die, size = 2, replace = TRUE) 
## sum(dice)
## }

Observe que la función definida roll no tiene argumentos de entrada, ahora modificaremos el código para incluir un argumento con valor por “default” que simula el número de caras del dado; a esta función la llamaremos roll2. Se omite la línea die <- 1:6, pero ahora el número de caras del dado está dado por el argumento bones.

roll2 <- function(bones = 1:6) {
dice <- sample(bones, size = 2, replace = TRUE) 
sum(dice)
}

roll2()
## [1] 6
roll2
## function(bones = 1:6) {
## dice <- sample(bones, size = 2, replace = TRUE) 
## sum(dice)
## }

Componentes de una función

Generalizando la estructura para la creación de una función (ilustración tomada del libro “Hands on Programming with R”).

Esquema de una función

Esquema de una función

Ya que sabemos como definir una función, vamos a crear nuestra propia función. Considerando que la función scan(n=1) se utiliza para leer un (1) valor por teclado, vamos a escribir el código en R para una función que, ingresado un valor por teclado, indique al usuario a través de un mensaje si este número es mayor, menor o igual a 0.

valor <- scan(n=1)

En los ejemplos ilustrados hasta ahora hemos creado la función y la hemos utilizado en un mismo script (programa) R. Sin embargo, es posible que las funciones creadas por un usuario se encuentren en distintos archivos. En este caso, al igual que la invocación de una librería library(rgdal), es necesario cargar en memoria de R el código de creación y ejecución de estas funciones. Para ello, basta con usar el comando source("nombre_archivo_funcion.R"). Según el ejemplo anterior de la función roll(), la llamada se efectúa como source("funcion_roll.R").

Funciones de Sistema Operativo

Las funciones más frecuentes de sistema operativo consisten en obtener la fecha-hora del sistema y la manipulación de directorios.

Para contabilizar el tiempo de ejecución de un script (programa) R, es posible tomar la hora del reloj al momento de inicio del programa y al final de dicha tarea calcular la diferencia con la hora de fin del proceso, así:

start_task <- Sys.time ()
end_task <- Sys.time () - start_task
end_task
## Time difference of 0.001222134 secs

Sobre la manipulación de archivos y directorios: consideremos que en una carpeta del sistema se tiene almacenado archivos de distinta extensión (.jpg, .pdf, .csv, .nc, etc) y se desea recuperar únicamente el conjunto de archivos de un tipo específico. Utilizaremos la función list.files (Véase los posibles argumentos de esta función y su efecto: args(list.files))

files.csv <- list.files(path = ".",pattern = ".csv")
files.R <- list.files(path = ".",pattern = ".R")

Esta función es muy útil a nivel de implementación. Imaginemos que tenemos una carpeta con todos los archivos mensuales de descarga de una estación de precipitación donde cada uno de ellos corresponde a enero, febrero, marzo, etc. Podríamos efectuar el mismo proceso sobre cada archivo a través de un bucle (estructura repetitiva) utilizando el comando read.table(archivo) donde la variable archivo tomaría el valor de cada uno de los elementos de la lista de los archivos.

Por otra parte, es práctica común que el nombre del archivo como tal encierre (represente) algún tipo de información sobre los datos contenidos. Por ejemplo el mes al que corresponde, el nombre de la estación, etc. Entonces, a partir del nombre del archivo podríamos “recortar” los fragmentos de información útiles.

Actividad Individual: Asuma que una carpeta contiene los siguientes archivos:

  • 2005_12_01_1_jp.txt
  • 2005_11_01_1_jp.txt
  • 2005_10_01_1_jp.txt
  • 2006_12_01_1_jp.txt
  • 2006_11_01_1_jp.txt
  • 2006_10_01_1_jp.txt
  • 2005_12_01_2_jt.txt
  • 2005_11_01_2_jt.txt
  • 2005_10_01_2_jt.txt
  • 2006_12_01_2_jt.txt
  • 2006_11_01_2_jt.txt
  • 2006_10_01_2_jt.txt

Vamos a hacer uso del comando “substring” para extraer la información del nombre del archivo (de la variable tipo character), así:

archivo <- "2005_12_01_1_jp.txt"
anio <- substr(archivo,start = 1,stop = 4)
anio
## [1] "2005"

Obtener un data frame que tenga la siguiente estructura

##   anio mes tipo responsable
## 1 2005  12    1          jp
## 2 2005  11    1          jp
## 3 2005  10    1          jp
## 4 2006  12    1          jp
## 5 2006  11    1          jp
## 6 2006  10    1          jp

Y luego vamos a modificar el campo “tipo”" para expresarlo como un texto. El data frame final debe visualizarse así:

##   anio mes          tipo responsable
## 1 2005  12 Precipitacion          jp
## 2 2005  11 Precipitacion          jp
## 3 2005  10 Precipitacion          jp
## 4 2006  12 Precipitacion          jp
## 5 2006  11 Precipitacion          jp
## 6 2006  10 Precipitacion          jp

Manejo de Fechas

Normalmente, al importar un archivo .txt o .csv a través de la función read.table el campo asociado a la fecha se almacena en el data frame resultante como un texto (caracter). Sin embargo es conveniente tener este campo en un formato fecha, de tal manera que permita recuperar fácilmente el año, mes, diferencia entre fechas, etc.

setwd("~/Documents/R_WORKSPACE/Maestria_Hidrologia_2018")
df.observaciones <- read.table("observaciones_2003.csv", header = TRUE, stringsAsFactors = FALSE)

Para transformar un character a un objeto tipo fecha o viceversa.

fechas <- strptime(df.observaciones$FECHA, format="%d/%m/%Y")
df.observaciones$FECHA <- as.POSIXct(fechas)  

Para obtener la diferencia entre dos fechas se utiliza la función difftime.

time_series_duration <- difftime(df.observaciones$FECHA[length(df.observaciones$FECHA)], df.observaciones$FECHA[1], units="mins")
time_series_duration
## Time difference of 524160 mins
time_series_duration <- difftime(df.observaciones$FECHA[length(df.observaciones$FECHA)], df.observaciones$FECHA[1], units="hours")
time_series_duration
## Time difference of 8736 hours

Evaluemos la ventaja de tener el campo “FECHA” para extraer el año y el mes de la observación y que posteriormente agregamos a nuestro data frame. En este ejemplo, el archivo original ya incluía el campo month y year, pero esto no es lo común.

df_events_month <- as.numeric(format(df.observaciones$FECHA,"%m"))
df_events_year <- as.numeric(format(df.observaciones$FECHA,"%Y"))

df.observaciones = cbind(df.observaciones,df_events_month)
df.observaciones = cbind(df.observaciones,df_events_year)

¿Cómo funciona el comando colnames?

colnames(df.observaciones)
## [1] "FECHA"           "VALOR"           "STATUS"          "ESTACION"       
## [5] "year"            "day"             "month"           "df_events_month"
## [9] "df_events_year"

Para modificar únicamente ciertos nombres de columnas (variables) del data frame:

colnames(df.observaciones)[8] <- "month_extract"
colnames(df.observaciones)[9] <- "year_extract"
head(df.observaciones)
##           FECHA VALOR STATUS ESTACION year day month month_extract
## 7671 2003-01-01     0            M012 2003   1     1             1
## 7672 2003-01-02     0            M012 2003   2     1             1
## 7673 2003-01-03     0            M012 2003   3     1             1
## 7674 2003-01-04     0            M012 2003   4     1             1
## 7675 2003-01-05     0            M012 2003   5     1             1
## 7676 2003-01-06     0            M012 2003   6     1             1
##      year_extract
## 7671         2003
## 7672         2003
## 7673         2003
## 7674         2003
## 7675         2003
## 7676         2003