La importancia de la divulgación

(Esta anotación se publica simultáneamente en Naukas)

¿Para qué sirve la divulgación? ¿Es efectiva? ¿Cómo debe hacerse? Son las preguntas que alimentan el eterno debate instalado en el seno de foros de divulgación como este, Naukas, que persiguen el fomento de la ciencia y la racionalidad frente a la superchería y el pensamiento mágico. Un debate sempiterno que resurge periódicamente cada vez que la irracionalidad se hace notar a través de una noticia triste, una acción política desafortunada… Pero es lo que tienen las carreras de fondo, que carecen de referencias a corto plazo a las que agarrarse. Y aunque algunos puedan estar hastiados, es importante darse cuenta de que el debate es necesario, porque el andamiaje metodológico que se construye a partir de la duda sistemática es el único mecanismo de revisión y control que tenemos, imprescindible para seguir mejorando y avanzando.

Se hace camino al andar, decía el poema, y a veces es importante pararse y darse la vuelta a contemplar lo andado. Creo que nadie a día de hoy cuestionaría que la educación es efectiva, a pesar de las evidencias en contra que constantemente salpican la realidad cotidiana. No en vano, la educación ha traído el progreso científico y tecnológico, y estos a su vez hasta donde estamos. Para mí, la divulgación es un complemento que llena aquellos huecos adonde la educación no puede llegar. Y como la educación, la divulgación es un proceso lento, pero inexorable, de transformación de la sociedad.

Hay muchas formas de divulgar, como hay muchas formas de comunicar en general, mejores y peores, más o menos adecuadas a según qué formatos y audiencias. A menudo, se habla del papel del humor en la divulgación: si haces reír, el mensaje entra mejor, se suele decir, pero mi opinión es que el humor está un paso más allá. El humor es un mecanismo de cohesión social que refuerza la estructura de grupo a través de sobreentendidos. Esto es muy importante: no se puede hacer humor, un grupo de personas no puede reír con una broma, si no existe un sobreentendido previo, un conocimiento compartido, un poso común. Y cerramos el círculo: para esto sirve la divulgación, para dejar ese poso que después el humor es capaz de amalgamar y compactar como ninguna otra herramienta humana.

La educación hace el camino, la divulgación lo asfalta y el humor lo apisona para que todos avancemos más libres como sociedad. Y he aquí a continuación uno de estos maestros de la apisonadora, caminando a hombros de gigantes. Seguid divulgando.

#Naukas17: El sonido del viento

Ya estamos de vuelta de #Naukas17, la séptima edición —que se dice pronto— de Naukas Bilbao. Un nuevo año, un nuevo reto repleto de nuevas incorporaciones, ausencias notables y 2000 localidades por llenar con la receta de siempre: ciencia, escepticismo y humor. Este fue el resultado:

Imagen de Xurxo Mariño.

Además, Almudena, que ha divulgado ciencia no solo en este blog, en Naukas y dando charlas en muchos otros eventos, sino también desde la Expedición Malaspina, el Ártico y Radio Clásica, recibió el Premio Tesla 2017 a la divulgación, junto con Jose Cervera y Daniel Torregrosa.

Imagen de Xurxo Mariño.

Finalmente, esta fue nuestra contribución: El sonido del viento (pulsad sobre la imagen para ir al vídeo).

Imagen de Xurxo Mariño.

simmer 3.6.3

The third update of the 3.6.x release of simmer, the Discrete-Event Simulator for R, is on CRAN. First of all and once again, I must thank Duncan Garmonsway (@nacnudus) for writing a new vignette: “The Bank Tutorial: Part II”.

Among various fixes and performance improvements, this release provides a way of knowing the progress of a simulation. This feature is embed into the run() function as a parameter called progress, which accepts a function with a single argument and will be called for each one of the steps (10 by default) with the current progress ratio (a number between 0 and 1). A very naive example using a simple print():

library(simmer)

wait_and_go <- trajectory() %>%
  timeout(1)

simmer() %>%
  add_generator("dummy", wait_and_go, function() 1) %>%
  run(until=10, progress=print, steps=5)
## [1] 0
## [1] 0.2
## [1] 0.4
## [1] 0.6
## [1] 0.8
## [1] 1
## simmer environment: anonymous | now: 10 | next: 10
## { Generator: dummy | monitored: 1 | n_generated: 10 }

Or we can get a nice progress bar with the assistance of the progress package:

simmer() %>%
  add_generator("dummy", wait_and_go, function() 1) %>%
  run(until=1e5, progress=progress::progress_bar$new()$update)
#> [==============---------------------------------------------------------]  20%

But more importantly, this release implements a new way of retrieving attributes (thus deprecating the old approach, which will be still available throughout the 3.6.x series and will be removed in version 3.7). Since v3.1.x, arrival attributes were retrieved by providing a function with one argument. A very simple example:

trajectory() %>%
  set_attribute("delay", 3) %>%
  timeout(function(attr) attr["delay"])
## Warning: Attribute retrieval through function arguments is deprecated.
## Use 'get_attribute' instead.
## trajectory: anonymous, 2 activities
## { Activity: SetAttribute | key: delay, value: 3, global: 0 }
## { Activity: Timeout      | delay: 0x5569098b9228 }

Later on, v3.5.1 added support for global attributes, making it necessary to add a second argument to retrieve this new set of attributes:

trajectory() %>%
  set_attribute("delay", 3, global=TRUE) %>%
  timeout(function(attr, glb) glb["delay"])
## Warning: Attribute retrieval through function arguments is deprecated.
## Use 'get_attribute' instead.
## trajectory: anonymous, 2 activities
## { Activity: SetAttribute | key: delay, value: 3, global: 1 }
## { Activity: Timeout      | delay: 0x556908730320 }

This method is a kind of rarity in simmer. It’s clunky, as it is not easy to document (and therefore to discover and learn), and non-scalable, because new features would require more and more additional arguments. Thus, it is now deprecated, and the get_attribute() function becomes the new method for retrieving attributes. It works in the same way as now() for the simulation time:

env <- simmer()

trajectory() %>%
  set_attribute("delay_1", 3) %>%
  # shortcut equivalent to set_attribute(..., global=TRUE)
  set_global("delay_2", 2) %>% 
  timeout(function() get_attribute(env, "delay_1")) %>%
  # shortcut equivalent to get_attribute(..., global=TRUE)
  timeout(function() get_global(env, "delay_2"))
## trajectory: anonymous, 4 activities
## { Activity: SetAttribute | key: delay_1, value: 3, global: 0 }
## { Activity: SetAttribute | key: delay_2, value: 2, global: 1 }
## { Activity: Timeout      | delay: 0x55690829f550 }
## { Activity: Timeout      | delay: 0x55690830c310 }

This is a little bit more verbose, but I believe it is more consistent and intuitive. Moreover, it allows us to easily implement new features for extracting arrival information. In fact, get_attribute() will come hand in hand with two more verbs: get_name() and get_prioritization(), to retrieve the arrival name and prioritization values respectively.

New features:

  • Show simulation progress via an optional progress callback in run() (#103).
  • New “The Bank Tutorial: Part II” vignette, by Duncan Garmonsway @nacnudus (#106).
  • New getters for running arrivals (#109), meant to be used inside trajectories:
    • get_name() retrieves the arrival name.
    • get_attribute() retrieves an attribute by name. The old method of retrieving them by providing a function with one argument is deprecated in favour of get_attribute(), and will be removed in version 3.7.x.
    • get_prioritization() retrieves the three prioritization values (prioritypreemptiblerestart) of the active arrival.
  • New shortcuts for global attributes (#110): set_global() and get_global(), equivalent to set_attribute(global=TRUE) and get_attribute(global=TRUE) respectively.

Minor changes and fixes:

  • Some code refactoring and performance improvements (2f4b484, ffafe1e, f16912a, fb7941b, 2783cd8).
  • Use Rcpp::DataFrame instead of Rcpp::List (#104).
  • Improve argument parsing and error messages (#107).
  • Improve internal function make_resetable() (c596f73).

Agente Smith forever

No sé si lo han leído, pero dice Chema, AKA Rinzewind, que Las penas del Agente Smith ya no se llama Las penas del Agente Smith. Algo así como que se le salió la sopa por la nariz y las letritas formaron un nombre de mierda (lo dice alguien cuyo blog se llama Enchufa2, con un dos: sé de lo que hablo) que le hizo gracia; no sé, no me he parado en los detalles.

Me da igual: para mí seguirá siendo Las penas del Agente Smith. Podrá cambiarlo en el HTML, pero no en nuestros corazones. De hecho, lo primero tiene arreglo: he aquí una extensión de Chrome para que su blog aparezca con el nombre correcto. Descomprimir, instalar y a disfrutar.

Programming with dplyr by using dplyr

The title may seem tautological, but since the arrival of dplyr 0.7.x, there have been some efforts at using dplyr without actually using it that I can’t quite understand. The tidyverse has raised passions, for and against it, for some time already. There are excellent alternatives out there, and I myself use them when I find it suitable. But when I choose to use dplyr, I find it most versatile, and I see no advantage in adding yet another layer that complicates things and makes problems even harder to debug.

Take the example of seplyr. It stands for standard evaluation dplyr, and enables us to program over dplyr without having “to bring in (or study) any deep-theory or heavy-weight tools such as rlang/tidyeval”. Let’s consider the following interactive pipeline:

library(dplyr)

starwars %>%
  group_by(homeworld) %>%
  summarise(mean_height = mean(height, na.rm = TRUE),
            mean_mass = mean(mass, na.rm = TRUE),
            count = n())
## # A tibble: 49 x 4
##         homeworld mean_height mean_mass count
##             <chr>       <dbl>     <dbl> <int>
##  1       Alderaan    176.3333      64.0     3
##  2    Aleen Minor     79.0000      15.0     1
##  3         Bespin    175.0000      79.0     1
##  4     Bestine IV    180.0000     110.0     1
##  5 Cato Neimoidia    191.0000      90.0     1
##  6          Cerea    198.0000      82.0     1
##  7       Champala    196.0000       NaN     1
##  8      Chandrila    150.0000       NaN     1
##  9   Concord Dawn    183.0000      79.0     1
## 10       Corellia    175.0000      78.5     2
## # ... with 39 more rows

Let’s say we want to parametrise the grouping variable and wrap the code above into a re-usable function. Apparently, this is difficult with dplyr. But is it? Not at all: we just need to add one line and a bang-bang (!!):

starwars_mean <- function(var) {
  var <- enquo(var)
  starwars %>%
    group_by(!!var) %>%
    summarise(mean_height = mean(height, na.rm = TRUE),
            mean_mass = mean(mass, na.rm = TRUE),
            count = n())
}

starwars_mean(homeworld)
## # A tibble: 49 x 4
##         homeworld mean_height mean_mass count
##             <chr>       <dbl>     <dbl> <int>
##  1       Alderaan    176.3333      64.0     3
##  2    Aleen Minor     79.0000      15.0     1
##  3         Bespin    175.0000      79.0     1
##  4     Bestine IV    180.0000     110.0     1
##  5 Cato Neimoidia    191.0000      90.0     1
##  6          Cerea    198.0000      82.0     1
##  7       Champala    196.0000       NaN     1
##  8      Chandrila    150.0000       NaN     1
##  9   Concord Dawn    183.0000      79.0     1
## 10       Corellia    175.0000      78.5     2
## # ... with 39 more rows

The enquo() function quotes the name we put in our function (homeworld), and the bang-bang unquotes and uses that name instead of var. That’s it. What about seplyr? With seplyr, we just have to (and I quote)

  • Change dplyr verbs to their matching seplyr “*_se()” adapters.
  • Add quote marks around names and expressions.
  • Convert sequences of expressions (such as in the summarize()) to explicit vectors by adding the “c()” notation.
  • Replace “=” in expressions with “:=”.

This is the result:

library(seplyr)

starwars_mean <- function(my_var) {
  starwars %>%
    group_by_se(my_var) %>%
    summarize_se(c("mean_height" := "mean(height, na.rm = TRUE)",
                   "mean_mass" := "mean(mass, na.rm = TRUE)",
                   "count" := "n()"))
}

starwars_mean("homeworld")
## # A tibble: 49 x 4
##         homeworld mean_height mean_mass count
##             <chr>       <dbl>     <dbl> <int>
##  1       Alderaan    176.3333      64.0     3
##  2    Aleen Minor     79.0000      15.0     1
##  3         Bespin    175.0000      79.0     1
##  4     Bestine IV    180.0000     110.0     1
##  5 Cato Neimoidia    191.0000      90.0     1
##  6          Cerea    198.0000      82.0     1
##  7       Champala    196.0000       NaN     1
##  8      Chandrila    150.0000       NaN     1
##  9   Concord Dawn    183.0000      79.0     1
## 10       Corellia    175.0000      78.5     2
## # ... with 39 more rows

Basically, we had to change the entire pipeline. If re-usability was the goal, I think we lost some of it here. But, wait, we are still using non-standard evaluation in the first example. What if we really need to provide the grouping variable as a string? Easy enough, we just need to change enquo() with as.name() to convert the string to a name:

starwars_mean <- function(var) {
  var <- as.name(var)
  starwars %>%
    group_by(!!var) %>%
    summarise(mean_height = mean(height, na.rm = TRUE),
            mean_mass = mean(mass, na.rm = TRUE),
            count = n())
}

starwars_mean("homeworld")
## # A tibble: 49 x 4
##         homeworld mean_height mean_mass count
##             <chr>       <dbl>     <dbl> <int>
##  1       Alderaan    176.3333      64.0     3
##  2    Aleen Minor     79.0000      15.0     1
##  3         Bespin    175.0000      79.0     1
##  4     Bestine IV    180.0000     110.0     1
##  5 Cato Neimoidia    191.0000      90.0     1
##  6          Cerea    198.0000      82.0     1
##  7       Champala    196.0000       NaN     1
##  8      Chandrila    150.0000       NaN     1
##  9   Concord Dawn    183.0000      79.0     1
## 10       Corellia    175.0000      78.5     2
## # ... with 39 more rows

But we can do even better if we remember that dplyr provides scoped variants (see ?dplyr::scoped) for most of the verbs. In this case, group_by_at() comes in handy:

starwars_mean <- function(var) {
  starwars %>%
    group_by_at(var) %>%
    summarise(mean_height = mean(height, na.rm = TRUE),
            mean_mass = mean(mass, na.rm = TRUE),
            count = n())
}

starwars_mean("homeworld")
## # A tibble: 49 x 4
##         homeworld mean_height mean_mass count
##             <chr>       <dbl>     <dbl> <int>
##  1       Alderaan    176.3333      64.0     3
##  2    Aleen Minor     79.0000      15.0     1
##  3         Bespin    175.0000      79.0     1
##  4     Bestine IV    180.0000     110.0     1
##  5 Cato Neimoidia    191.0000      90.0     1
##  6          Cerea    198.0000      82.0     1
##  7       Champala    196.0000       NaN     1
##  8      Chandrila    150.0000       NaN     1
##  9   Concord Dawn    183.0000      79.0     1
## 10       Corellia    175.0000      78.5     2
## # ... with 39 more rows

That’s it: no bang-bang, just strings and only one change to the original code. Let’s dwell on the potential of the scoped variants with a final example. We can make a completely generic re-usable “grouped mean” function using seplyr and R’s paste0() function to build up expressions:

grouped_mean <- function(data, grouping_variables, value_variables) {
  result_names <- paste0("mean_", value_variables)
  expressions <- paste0("mean(", value_variables, ", na.rm = TRUE)")
  data %>%
    group_by_se(grouping_variables) %>%
    summarize_se(c(result_names := expressions,
                   "count" := "n()"))
}

starwars %>% 
  grouped_mean("eye_color", c("mass", "birth_year"))
## # A tibble: 15 x 4
##        eye_color mean_mass mean_birth_year count
##            <chr>     <dbl>           <dbl> <int>
##  1         black  76.28571        33.00000    10
##  2          blue  86.51667        67.06923    19
##  3     blue-gray  77.00000        57.00000     1
##  4         brown  66.09231       108.96429    21
##  5          dark       NaN             NaN     1
##  6          gold       NaN             NaN     1
##  7 green, yellow 159.00000             NaN     1
##  8         hazel  66.00000        34.50000     3
##  9        orange 282.33333       231.00000     8
## 10          pink       NaN             NaN     1
## 11           red  81.40000        33.66667     5
## 12     red, blue       NaN             NaN     1
## 13       unknown  31.50000             NaN     3
## 14         white  48.00000             NaN     1
## 15        yellow  81.11111        76.38000    11

And the same with dplyr’s scoped verbs (note that I’ve added the last rename_at() on a whim, just to get exactly the same output as before, but it is not really necessary):

grouped_mean <- function(data, grouping_variables, value_variables) {
  data %>%
    group_by_at(grouping_variables) %>%
    mutate(count = n()) %>%
    summarise_at(c(value_variables, "count"), mean, na.rm = TRUE) %>%
    rename_at(value_variables, funs(paste0("mean_", .)))
}

starwars %>% 
  grouped_mean("eye_color", c("mass", "birth_year"))
## # A tibble: 15 x 4
##        eye_color mean_mass mean_birth_year count
##            <chr>     <dbl>           <dbl> <dbl>
##  1         black  76.28571        33.00000    10
##  2          blue  86.51667        67.06923    19
##  3     blue-gray  77.00000        57.00000     1
##  4         brown  66.09231       108.96429    21
##  5          dark       NaN             NaN     1
##  6          gold       NaN             NaN     1
##  7 green, yellow 159.00000             NaN     1
##  8         hazel  66.00000        34.50000     3
##  9        orange 282.33333       231.00000     8
## 10          pink       NaN             NaN     1
## 11           red  81.40000        33.66667     5
## 12     red, blue       NaN             NaN     1
## 13       unknown  31.50000             NaN     3
## 14         white  48.00000             NaN     1
## 15        yellow  81.11111        76.38000    11

Wrapping up, the tidyeval paradigm may seem difficult at a first glance, but don’t miss the wood for the trees: the new version of dplyr is full of tools that will make your life easier, not harder.