argendatafundar / argendataR

Librería de R para ETL de argendata
Creative Commons Attribution 4.0 International
0 stars 1 forks source link

Autocompletar parámetro `script` en las funciones `agregar_fuente...` o `actualizar_fuente...` #19

Open Ignacio-Ibarra opened 5 days ago

Ignacio-Ibarra commented 5 days ago

Todos los códigos que crean o actualizan fuentes, raw o clean, utilizand funciones del paquete argendataR que han sido creadas para cada caso.

Estas son:

En todos los casos, las funciones posee un parámetro script el cual es la ruta dentro del repositorio /etl/ donde se ubica el script que ejecuta dicha acción. Ejemplo de codigo donde se hace una limpieza de una fuente de datos de AFIP y se actualiza en la base de datos de fuentes limpias.

#limpio la memoria
rm( list=ls() )  #Borro todos los objetos
gc()   #Garbage Collection

get_raw_path <- function(codigo){
  prefix <- glue::glue("{Sys.getenv('RUTA_FUENTES')}raw/")
  df_fuentes_raw <- fuentes_raw() 
  path_raw <- df_fuentes_raw[df_fuentes_raw$codigo == codigo,c("path_raw")]
  return(paste0(prefix, path_raw))
}

id_fuente <- 252
fuente_raw <- sprintf("R%sC0",id_fuente)

cleaning_func <- function(name_cols, sheet_name, cell_range){

  # Hago un cleaning 
return(df_clean)
}

name_cols <- c("actividad_economica", 
               "Total#Presentaciones", 
               "Total#Ventas totales", 
               "Mercado interno#Ventas totales",
               "Mercado interno#Ventas gravadas",
               "Mercado interno#No gravadas y exentas",
               "Exportaciones#Ventas totales")

sheet_name <- "2.1.1.4_2"
cell_range <- "C12:I254"

df_clean <- cleaning_func(name_cols = name_cols, sheet_name = sheet_name, cell_range = cell_range)

# Guardado de archivo
nombre_archivo_raw <- sub("\\.[^.]*$", "", fuentes_raw() %>% 
                            filter(codigo == fuente_raw) %>% 
                            select(path_raw) %>% 
                            pull())

clean_filename <- glue::glue("{nombre_archivo_raw}_CLEAN.parquet")

path_clean <- glue::glue("{tempdir()}/{clean_filename}")

df_clean %>% arrow::write_parquet(., sink = path_clean)

code_name <- str_split_1(rstudioapi::getSourceEditorContext()$path, pattern = "/") %>% tail(., 1)

titulo.raw <- fuentes_raw() %>% 
  filter(codigo == fuente_raw) %>% 
  select(nombre) %>% pull()

clean_title <- glue::glue("{titulo.raw}")

id_fuente_clean <- 121
codigo_fuente_clean <- sprintf("R%sC%s", id_fuente, id_fuente_clean)

df_clean_anterior <- arrow::read_parquet(get_clean_path(codigo = codigo_fuente_clean ))

comparacion <- comparar_fuente_clean(df_clean,
                                     df_clean_anterior,
                                     pk = c("cod_act", "destino_venta", "detalle")
)

actualizar_fuente_clean(id_fuente_clean = id_fuente_clean,
                        path_clean = clean_filename,
                        nombre = clean_title, 
                        script = code_name,
                        comparacion = comparacion)

Como se puede observar, en la anteúltima linea se setea el parámetro script, pasándole la variable code_name, la cual se desprende de:

code_name <- str_split_1(rstudioapi::getSourceEditorContext()$path, pattern = "/") %>% tail(., 1)

El problema de esta linea es que sólo se puede ejecutar cuando el script se está ejecutando en un entorno de RStudio, en cambio si quisiéramos hacer

source("scripts/limpieza_fuentes/limpieza_afip_anuario_estadisticas_tributarias_archivo_2114_anio2009.R")
# Error in `str_split_1()`:
#   ! `string` must be a single string, not `NULL`.
# Run `rlang::last_trace()` to see where the error occurred.
# > rlang::last_trace()
# <error/rlang_error>
#   Error in `str_split_1()`:
#   ! `string` must be a single string, not `NULL`.
# ---
#   Backtrace:
#   ▆
# 1. ├─base::source("scripts/limpieza_fuentes/limpieza_afip_anuario_estadisticas_tributarias_archivo_2114_anio2009.R")
# 2. │ ├─base::withVisible(eval(ei, envir))
# 3. │ └─base::eval(ei, envir)
# 4. │   └─base::eval(ei, envir)
# 5. ├─... %>% tail(., 1) at scripts/limpieza_fuentes/limpieza_afip_anuario_estadisticas_tributarias_archivo_2114_anio2009.R:77:1
# 6. ├─utils::tail(., 1)
# 7. └─stringr::str_split_1(string = rstudioapi::getSourceEditorContext()$path)

Esto se podría resolver con una función que pueda manejar el entorno en el que está corriendo el script, desde consola o dentro de RStudio. Un ejemplo

# Función para obtener la ruta del archivo, compatible tanto en RStudio como en la consola
get_file_location <- function() {
  # Intenta obtener la ruta del archivo en RStudio
  if (interactive() && "rstudioapi" %in% rownames(installed.packages())) {
    return(rstudioapi::getSourceEditorContext()$path)
  }

  # Alternativa para obtener la ruta si se usa source()
  this_file <- (function() { attr(body(sys.function(1)), "srcfile") })()

  # Si no se obtiene el path (e.g., en consola sin RStudio), asigna un valor por defecto
  if (!is.null(this_file)) {
    return(this_file$filename)
  } else {
    return("Archivo no especificado o ruta predeterminada")
  }
}

code_name <- get_file_location()

Incluso dicha función podría estar incorporada dentro de las mismas funciones para agregar o actualizar fuentes de modo tal que se automatice el parámetro script

Ignacio-Ibarra commented 11 hours ago

Agrego un comentario acerca de la implementación de la función get_file_location() dentro del contexto de nuestro paquete argendataR, específicamente para automatizar el llenado del parámetro script en la función write_output(), así como en otras funciones relevantes como agregar_fuente_raw() y actualizar_fuente_raw() o las otras mencionadas arriba.

  1. Objetivo: Queremos que get_file_location() devuelva automáticamente el nombre del script actual (ej. "sarasa.R") cuando se llama dentro de write_output(), agregar_fuente_raw(), actualizar_fuente_raw(), y otras funciones del paquete, independientemente de si el script se ejecuta en RStudio, a través de source() desde la consola o línea por línea.

  2. Funcionalidad: La función get_file_location() se ha diseñado para detectar el contexto de ejecución:

    • RStudio: Utiliza rstudioapi::getSourceEditorContext() para obtener el nombre del archivo.
    • Consola: Analiza la pila de llamadas (sys.calls()) para identificar si el script fue ejecutado a través de source(), extrayendo el nombre del archivo de los argumentos de la llamada.
    • Ejecución Interactiva: Devuelve un nombre predeterminado si se está ejecutando línea por línea en la consola.
  3. Inclusión en Funciones: La función write_output() se modificará para llamar a get_file_location() internamente, eliminando la necesidad de que el usuario especifique manualmente el parámetro script. Asimismo, se sugiere incorporar esta lógica en otras funciones del paquete, como agregar_fuente_raw() y actualizar_fuente_raw(), para mantener la coherencia y facilitar el manejo de metadatos.

  4. Pruebas: Hemos realizado pruebas utilizando un script de ejemplo ("sarasa.R") y se confirmó que get_file_location() devuelve correctamente el nombre del archivo en diferentes contextos de ejecución.

Así podría ser la implementación.

# argendataR.R

get_file_location <- function() {
  # 1. Si estamos en RStudio, intenta obtener el nombre del archivo actual.
  if (interactive() && "rstudioapi" %in% rownames(installed.packages())) {
    path <- tryCatch({
      rstudioapi::getSourceEditorContext()$path
    }, error = function(e) NULL)

    # Si se obtiene un path, devolver solo el nombre del archivo
    if (!is.null(path) && nzchar(path)) {
      print("Obteniendo script_name desde RStudio (getSourceEditorContext)")
      return(basename(path))
    }
  }

  # 2. Si se ejecuta con source() desde la consola, intenta rastrear la pila de llamadas.
  calls <- sys.calls()
  for (call in rev(calls)) {  # Recorre la pila en orden inverso
    if (is.call(call) && identical(call[[1]], as.name("source"))) {
      # Extrae el nombre del archivo del argumento de `source`
      print("Obteniendo script_name desde llamada a source() en la consola")
      return(basename(as.character(call[[2]])))
    }
  }

  # 3. Si se ejecuta línea por línea en la consola, retorna un nombre predeterminado
  print("Obteniendo script_name desde ejecución interactiva (consola o línea por línea)")
  return("Consola o ejecución interactiva")
}

# La función write_output utiliza automáticamente get_file_location
write_output <- function(df, output_name, fuentes, subtopico, analista, pk,
                         es_serie_tiempo, columna_indice_tiempo, nivel_agregacion,
                         etiquetas_indicadores, unidades) {
  # Obtiene el nombre del script llamador automáticamente
  script_name <- get_file_location()

  # Simula el guardado del dataset y metadatos, incluyendo el nombre del script
  print(glue::glue("Guardando output '{output_name}' generado por '{script_name}'"))
  print(glue::glue("Metadatos:\nFuentes: {paste(fuentes, collapse = ', ')}\n",
                   "Subtópico: {subtopico}\nAnalista: {analista}\n",
                   "Llave primaria: {paste(pk, collapse = ', ')}\n",
                   "Es serie de tiempo: {es_serie_tiempo}\n",
                   "Columna índice de tiempo: {columna_indice_tiempo}\n",
                   "Nivel de agregación: {nivel_agregacion}\n",
                   "Etiquetas de indicadores: {paste(names(etiquetas_indicadores), collapse = ', ')}\n",
                   "Unidades: {paste(names(unidades), collapse = ', ')}"))
}

Ejemplo de uso.

# sarasa.R
require(dplyr)
source("argendataR.R")

# Simulación de data wrangling
df_output <- data.frame(
  anio = c(2020, 2021, 2022),
  sector = c("Agricultura", "Industria", "Servicios"),
  valor = c(10.5, 15.2, 12.3)
)

output_name <- "example_output"
fuentes <- c("R36C13", "R36C9", "R38C7")
subtopico <- "ACECON"
analista <- ""
pk <- c("anio", "sector")
es_serie_tiempo <- TRUE
columna_indice_tiempo <- "anio"
nivel_agregacion <- "pais"
etiquetas_indicadores <- list("sector" = "Sector")
unidades <- list("valor" = "Porcentaje del PIB a precios básicos")

df_output %>% write_output(
  output_name = output_name,
  fuentes = fuentes,
  subtopico = subtopico,
  analista = analista,
  pk = pk,
  es_serie_tiempo = es_serie_tiempo,
  columna_indice_tiempo = columna_indice_tiempo,
  nivel_agregacion = nivel_agregacion,
  etiquetas_indicadores = etiquetas_indicadores,
  unidades = unidades
)

Ejecutando desde la consola de RStudio

> source("sarasa.R")
[1] "Obteniendo script_name desde RStudio (getSourceEditorContext)"
Guardando output 'example_output' generado por 'sarasa.R'
Metadatos:
Fuentes: R36C13, R36C9, R38C7
Subtópico: ACECON
Analista: 
Llave primaria: anio, sector
Es serie de tiempo: TRUE
Columna índice de tiempo: anio
Nivel de agregación: pais
Etiquetas de indicadores: sector
Unidades: valor

Ejecutando desde una sesión de R en terminal bash

$ R

R version 4.4.0 (2024-04-24 ucrt) -- "Puppy Cup"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-w64-mingw32/x64

R es un software libre y viene sin GARANTIA ALGUNA.
Usted puede redistribuirlo bajo ciertas circunstancias.
Escriba 'license()' o 'licence()' para detalles de distribucion.

R es un proyecto colaborativo con muchos contribuyentes.
Escriba 'contributors()' para obtener más información y
'citation()' para saber cómo citar R o paquetes de R en publicaciones.

Escriba 'demo()' para demostraciones, 'help()' para el sistema on-line de ayuda,
o 'help.start()' para abrir el sistema de ayuda HTML con su navegador.
Escriba 'q()' para salir de R.

> source("sarasa.R")
Cargando paquete requerido: dplyr

Adjuntando el paquete: 'dplyr'

The following objects are masked from 'package:stats':

    filter, lag

The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union

[1] "Obteniendo script_name desde llamada a source() en la consola"
Guardando output 'example_output' generado por 'sarasa.R'
Metadatos:
Fuentes: R36C13, R36C9, R38C7
Subtópico: ACECON
Analista:
Llave primaria: anio, sector
Es serie de tiempo: TRUE
Columna índice de tiempo: anio
Nivel de agregación: pais
Etiquetas de indicadores: sector
Unidades: valor