rlabuonora.com

Flujo de trabajo en Git

Muchas veces usamos software sin entender totalmente como funciona. Cuando algo (inevitablemente) sale mal, creemos que nos merecemos los tormentos que sufrimos Read the Fucking Manual!

En este post uso una de las killer features de Git para seguir el ejemplo del capítulo de flujos de trabajo de Mastering Shiny. Esta feature son las branches (si bien otros SVNs tienen branches, Git las hace muy fáciles de usar).

Las branches permiten tener versiones independientes de el programa que estamos desarrollando. Esto me permite moverme fácilmente entre una versión y otra y experimentar interferir con el trabajo de otros miembros del equipo.

En este caso, trato de arreglar un bug, pero como mi idea original del problema se basa en un modelo mental incorrecto de la función que estoy usando, introduzco un montón de cambios innecesarios y/o contraproducentes en el proceso de aclarar ese malentendido en mi mente.

Trabajar en una branch de Git me permite experimentar y una vez que llego a la solución correcta quedarme solo con los cambios que necesito, y descartar todos los cambios que no eran necesarios para la solución final.

El ejemplo es una app con un selector para elegir la región y listar los registros del archivo de csv que pertenecen a esa región.

El ejemplo.

library(shiny)
library(readr)

sales <- readr::read_csv("https://raw.githubusercontent.com/hadley/mastering-shiny/master/sales-dashboard/sales_data_sample.csv")
sales <- sales[c(
    "TERRITORY", "ORDERDATE", "ORDERNUMBER", "PRODUCTCODE",
    "QUANTITYORDERED", "PRICEEACH"
)]

ui <- fluidPage(
    selectInput("territory", "territory", choices = unique(sales$TERRITORY)),
    tableOutput("selected")
)
server <- function(input, output, session) {
    selected <- reactive(sales[sales$TERRITORY == input$territory, ])
    output$selected <- renderTable(head(selected(), 10))
}

# Run the application 
shinyApp(ui = ui, server = server)

Si pegás el código en RStudio y corrés la app, ves que hay un montón de NAs. El problema está muy bien explicado en el capítulo de Hadley que mencioné antes.

Entramos al directorio de la app y chequeamos el estado de Git:

# Bash
cd app
git status
git log --oneline
## On branch master
## nothing to commit, working tree clean
## bff90c4 Initial commit

Solo está el commit inicial.

El problema está en esta expresión:

sales[sales$TERRITORY == input$territory, ]

Cuando sales$TERRITORY es NA, sales$TERRITORY == input$territory es NA y sales[NA] da una fila de NAs.

Vamos a usar subset para arreglarlo, pero primero hago un branch.

cd app
git checkout -b demasiados_nas
## Switched to a new branch 'demasiados_nas'
cd app
cp app_1.R app.R
cd app
git status
git diff
## On branch demasiados_nas
## Changes not staged for commit:
##   (use "git add <file>..." to update what will be committed)
##   (use "git restore <file>..." to discard changes in working directory)
##  modified:   app.R
## 
## no changes added to commit (use "git add" and/or "git commit -a")
## diff --git a/app.R b/app.R
## index 7ef3837..27c80bb 100644
## --- a/app.R
## +++ b/app.R
## @@ -12,7 +12,7 @@ ui <- fluidPage(
##      tableOutput("selected")
##  )
##  server <- function(input, output, session) {
## -    selected <- reactive(sales[sales$TERRITORY == input$territory, ])
## +    selected <- reactive(subset(sales, sales$TERRITORY == input$territory))
##      output$selected <- renderTable(head(selected(), 10))
##  }
## 

Acá hay más información sobre como leer esta salida. Muestra 7 líneas a partir de la línea 12. Las que empiezan con - son la versión anterior, y la que empieza con + la nueva. Commiteo el cambio y sigo.

cd app
git add app.R
git commit -m "Usa subset en vez de =="
## [demasiados_nas 5a2006c] Usa subset en vez de ==
##  1 file changed, 1 insertion(+), 1 deletion(-)

Pero ahora me encuentro con otro problema. Como dice Hadley, los NA son infecciosos. Eso implica que sales$TERRITORY == NA es siempre NA, por lo si elegimo NA en el dropdown vamos a subsetear por un vector de NA:

subset(sales, TERRITORY == NA)
## # A tibble: 0 x 6
## # … with 6 variables: TERRITORY <chr>, ORDERDATE <chr>, ORDERNUMBER <dbl>,
## #   PRODUCTCODE <chr>, QUANTITYORDERED <dbl>, PRICEEACH <dbl>

Para solucionar eso, podemos usar %in%:

subset(sales, TERRITORY %in% c("EMEA"))
## # A tibble: 1,407 x 6
##    TERRITORY ORDERDATE       ORDERNUMBER PRODUCTCODE QUANTITYORDERED PRICEEACH
##    <chr>     <chr>                 <dbl> <chr>                 <dbl>     <dbl>
##  1 EMEA      5/7/2003 0:00         10121 S10_1678                 34      81.4
##  2 EMEA      7/1/2003 0:00         10134 S10_1678                 41      94.7
##  3 EMEA      11/11/2003 0:00       10180 S10_1678                 29      86.1
##  4 EMEA      11/18/2003 0:00       10188 S10_1678                 48     100  
##  5 EMEA      1/15/2004 0:00        10211 S10_1678                 41     100  
##  6 EMEA      7/23/2004 0:00        10275 S10_1678                 45      92.8
##  7 EMEA      9/30/2004 0:00        10299 S10_1678                 23     100  
##  8 EMEA      10/15/2004 0:00       10309 S10_1678                 41     100  
##  9 EMEA      11/24/2004 0:00       10341 S10_1678                 41     100  
## 10 EMEA      2/3/2005 0:00         10375 S10_1678                 21      34.9
## # … with 1,397 more rows

Commiteamos el resultado.

cd app
git add app.R
git commit -m "Usa %in% en vez de == en subset"
## [demasiados_nas 41b2365] Usa %in% en vez de == en subset
##  1 file changed, 1 insertion(+), 1 deletion(-)

Y mergear a master

cd app
git checkout master
git merge demasiados_nas
git log --all --decorate --oneline --graph
## Switched to branch 'master'
## Updating bff90c4..41b2365
## Fast-forward
##  app.R | 2 +-
##  1 file changed, 1 insertion(+), 1 deletion(-)
## * 41b2365 (HEAD -> master, demasiados_nas) Usa %in% en vez de == en subset
## * 5a2006c Usa subset en vez de ==
## * bff90c4 Initial commit

Plot Twist

Bueno acá viene lo mejor, en el archivo original, NA no es NA de R, sino “NA” de North America. 🤦. Estuvimos todo el tiempo atrás de la pista incorrecta. En realidad sales no tiene NA en la variable TERRITORY.

La solución correcta es especificar los NAs en la llamada a read_csv, para que no confunda “NA” con NA.

sales <- readr::read_csv("https://raw.githubusercontent.com/hadley/mastering-shiny/master/sales-dashboard/sales_data_sample.csv", na = "")

Pero ahora tenemos toda nuestra app plagada de cambios que hicimos cuando no entendíamos el problema!

Vamos a usar Git para arreglar este problema. Usammos git reset para volver master dos commits para atrás. Así, master apunta al commit donde empezó el problema.

cd app
git reset --hard HEAD~2
git checkout -b no_hay_nas_en_territory
## HEAD is now at bff90c4 Initial commit
## Switched to a new branch 'no_hay_nas_en_territory'

Hacer los cambios

cd app
git status
git diff
## On branch no_hay_nas_en_territory
## Changes not staged for commit:
##   (use "git add <file>..." to update what will be committed)
##   (use "git restore <file>..." to discard changes in working directory)
##  modified:   app.R
## 
## no changes added to commit (use "git add" and/or "git commit -a")
## diff --git a/app.R b/app.R
## index 7ef3837..387e0c9 100644
## --- a/app.R
## +++ b/app.R
## @@ -1,7 +1,7 @@
##  library(shiny)
##  library(readr)
##  
## -sales <- readr::read_csv("https://raw.githubusercontent.com/hadley/mastering-shiny/master/sales-dashboard/sales_data_sample.csv")
## +sales <- readr::read_csv("https://raw.githubusercontent.com/hadley/mastering-shiny/master/sales-dashboard/sales_data_sample.csv", na = "")
##  sales <- sales[c(
##      "TERRITORY", "ORDERDATE", "ORDERNUMBER", "PRODUCTCODE",
##      "QUANTITYORDERED", "PRICEEACH"

La línea que había que cambiar era la 7!

cd app
git add app.R
git commit -m "Agrega argumento na a la llamada a read_csv"
git log --all --decorate --oneline --graph
## [no_hay_nas_en_territory fbc196e] Agrega argumento na a la llamada a read_csv
##  1 file changed, 1 insertion(+), 1 deletion(-)
## * fbc196e (HEAD -> no_hay_nas_en_territory) Agrega argumento na a la llamada a read_csv
## | * 41b2365 (demasiados_nas) Usa %in% en vez de == en subset
## | * 5a2006c Usa subset en vez de ==
## |/  
## * bff90c4 (master) Initial commit

Ahora tengo que mergeamos estos estos cambios con master.

cd app
git checkout master
git merge no_hay_nas_en_territory
git log --all --decorate --oneline --graph
## Switched to branch 'master'
## Updating bff90c4..fbc196e
## Fast-forward
##  app.R | 2 +-
##  1 file changed, 1 insertion(+), 1 deletion(-)
## * fbc196e (HEAD -> master, no_hay_nas_en_territory) Agrega argumento na a la llamada a read_csv
## | * 41b2365 (demasiados_nas) Usa %in% en vez de == en subset
## | * 5a2006c Usa subset en vez de ==
## |/  
## * bff90c4 Initial commit