Shiny Rmd variable "globale"

Postez ici vos questions, réponses, commentaires ou suggestions - Les sujets seront ultérieurement répartis dans les archives par les modérateurs

Modérateur : Groupe des modérateurs

Jérémy Jachacz
Messages : 153
Enregistré le : 15 Avr 2014, 12:56

Shiny Rmd variable "globale"

Messagepar Jérémy Jachacz » 20 Aoû 2020, 07:30

Bonjour,

J'utilise une interface shiny simple :
un département ou région à sélectionner et un bouton pour lancer l’édition de rapport conditionnellement à la sélection.

Dans le lancement de l’édition de rapport il y un rmd qui est appelé, celui-ci fait appelle à des fonctions qui ont été sourcées plus tôt dans le processus.
Quand dans une fonction (appelée via le rmd) doit utiliser une variable créée dans le rmd (sans l'avoir passée en paramètre) le shiny indique une erreur comme quoi il ne la trouve pas. Cependant quand je test le rmd tout seul (via render) cela fonctionne très bien.

Je pense qu'un petit exemple sera le bienvenu : (j'ai pris un shiny de base et idem pour le rmd)

PARTIE SHINY :

Code : Tout sélectionner

library(shiny)

ui <- fluidPage(
  tags$head(tags$script(src = "message-handler.js")),
  actionButton("do", "Click Me")
)

server <- function(input, output, session) {
  observeEvent(input$do, {
   
    rmarkdown::render("xxxxxxxxxxxxxxxxxxxxxxxxxx / simple_xpl.Rmd", # remplacer les xx par votre repertoire de test
                      output_file = paste0('report_example',
                                           '.docx'))
  })
}

shinyApp(ui, server)



PARTIE RMD :

Code : Tout sélectionner

---
title: "simple_xpl"
author: "Jérémy Jachacz"
date: "20/08/2020"
output: word_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

```{r cars}

## variable créée dans le rmarkdown
max_speed<-max(cars$speed,na.rm = T)

summary(cars)
```

## Including Plots

You can also embed plots, for example:

```{r pressure, echo=FALSE}
print(max_speed)
plot(pressure)
```


```{r max_speed_transformation, echo=FALSE}

res<-export_max_speed()

max_speed_by_fct<-res[1]
max_speed_tr_by_fct<-res[2]

```

la vitesse max obtenue par la fonction est : `r max_speed_by_fct` 
la vitesse max transformée par la fonction est : `r max_speed_tr_by_fct`


ANNEXE :

Code : Tout sélectionner


export_max_speed<-function(){
 
  # exemple en parallele du pb
  max_speed_exp<-max(cars$speed,na.rm = T)
 
  # variable créee dans le rmd mais pas passée en paramètre
  max_speed_tr<-max_speed+10
 
  list(max_speed_exp,max_speed_tr)
 
}

rmarkdown::render("xxxxxxxxxxxxxxxxxxxxxxxxxx / simple_xpl.Rmd", # remplacer les xx par votre repertoire de test
  output_file = paste0('report_example',
                       '.docx'))



Les deux test à faire sont :

Test 1 avec shiny :
Dans l'environnement seulement ui, server et export_speed_max doivent être présent.
lancer l'app shiny..

Test 2 sans shiny :
Environnement identique au test 1, lancer le rmd via render (code annexe)

Une solution est dans le 2eme chunk quand je declare max_speed, d'utiliser "<<-" et cela fonctionne mais je comprends pas bien pourquoi ?

Une autre solution est de mettre ces petites variables indépendante dans la fonction, de les exporter mais le problème c'est que je les ai laissé en dehors car j'en ai besoin à plusieurs endroit dans le rmd, il faudrait que je les passe en paramètre de chaque fonction qui en a besoin.. ça me parait pas "optimal"

En espérant avoir été clair.
Merci
Statisticien (69)

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Shiny Rmd variable "gobale"

Messagepar Mickael Canouil » 20 Aoû 2020, 07:53

Bonjour,

il y a plusieurs choses qui ne vont pas.
Pourquoi votre render() s'effectue dans un observeEvent ?
En bout de ligne, j'imagine que le contenu doit être téléchargé ... https://shiny.rstudio.com/gallery/gener ... ports.html https://shiny.rstudio.com/gallery/downl ... ports.html

Votre fonction export_max_speed() contient une variable globale, c'est mal et c'est très probablement votre problème ici.
Un document Rmarkdown est sensé être autonome et ne dépendre que de paramètres. Il devrait pas utiliser de variable globale (comme les fonctions).

SI vous ne comprenait pas le problème des variables globales en programmation, trois appels de la même fonction avec les mêmes paramètres et pourtant trois résultats différents.

Code : Tout sélectionner

<- function(x) x + 1 + y

Code : Tout sélectionner

f(1)
#> Error in f(1): object 'y' not found    

Code : Tout sélectionner

<- 1
f
(1)
#> [1] 3    

Code : Tout sélectionner

<- 2
f
(1)
#> [1] 4    


En conclusion, retirez toutes les définitions de fonctions et variables globales.
Vous pouvez passer des fonctions dans les paramètres de RMarkdown, si vous ne voulez pas redéfinir la fonction à l'intérieur du dit document.

Cordialement,
Mickaël
mickael.canouil.fr | rlille.fr

Jérémy Jachacz
Messages : 153
Enregistré le : 15 Avr 2014, 12:56

Re: Shiny Rmd variable "gobale"

Messagepar Jérémy Jachacz » 20 Aoû 2020, 09:32

Merci pour votre réponse,

vous pensez qu'il devrait être dans un eventReactive ?
Dans l'exemple, j'ai reproduis mon code en ultra simplifié,
En réalité dans mon observeEvent() il y a plusieurs choses qui sont faites avant l'appel de render et dans cet appel j'y passe un paramètre (liste de code départements), j'avoue que là je ne sais pas justifier le choix de observeEvent() plutôt qu'un autre mais je suis sur que ce n'est pas par hasard.

Encore une fois pour ici c'est simplifié mais en réalité j'utilise un fichier global.R dans lequel (entre autre) je source un fichier qui contient les fonctions, appelons-le "Fct_Rmd.R" (dans l'exemple la fonction export_max_speed() serait dans ce fichier), c'est toujours mal comme procédé ?

L'idée était d’alléger mon Rmarkdown, je vais aller plus loin pour préciser, j'ai 1 fichiers Rmarkdown qui appel 3 autres fichiers rmd dépendant via un chunk et l'option child. Les fonctions du fichier Fct_Rmd.R sont appelée dans les ces 3 rmd child..
Avant d'avoir le fichier de fonction, j'avais une répétition du code 3 fois... 1 fois dans chaque rmd child. Avec les fonctions, j'allège les 3 fichiers rmd child..

EDIT :

En conclusion, retirez toutes les définitions de fonctions et variables globales.
Vous pouvez passer des fonctions dans les paramètres de RMarkdown, si vous ne voulez pas redéfinir la fonction à l'intérieur du dit document.


Je comprends qu'il y a mieux à faire sur les variables globales (les passer en paramètre..) mais ce que j'ai du mal à comprendre c'est le problème avec les fonctions ?
Il faudrait que je les definisse dans le rmd ?
par exemple sourcer le fichier de fonction dans le chunk qui charge les package par exemple ?

EDIT 2 :

Au delà du fait que ce soit une mauvaise pratique, dans votre exemple quand "y" est défini cela fonctionne,
Ce que je voudrais savoir c'est pourquoi dans mon cas (avec shiny) cela ne fonctionne pas (variable not found) alors que max_speed est bien initialisé dans le premier chunk et sans shiny mais avec render en ligne de commande cela fonctionne bien..
Statisticien (69)

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Shiny Rmd variable "globale"

Messagepar Mickael Canouil » 21 Aoû 2020, 08:25

Je vous suggère de vous documenter sur Shiny (et les structures que vous exploitez) : https://mastering-shiny.org/

Vous pouvez tout à fait inclure un source dans un Rmarkdown.
Les diapositives sur les documents "child" peut clairement vous intéresser : https://twitter.com/thomas_mock/status/ ... 45408?s=11

La notion d'environnement dans R est une notion importante, qui à défaut d'être connu doit a minima être connu.
https://adv-r.hadley.nz/

Un autre exemple où ' "y" est défini cela fonctionne'.
Et oui, le code ci-dessous "fonctionne", mais ne fait très clairement pas ce qu'on aurait pu croire :

Code : Tout sélectionner

<- function(x) x + y
<- function(x) {<- 0; f(x)} 

Code : Tout sélectionner

f(1)
#> Error in f(1): object 'y' not found
g(1)
#> Error in f(x): object 'y' not found 

Code : Tout sélectionner

<- 1
f
(2)
#> [1] 3
g(2)
#> [1] 3 

Code : Tout sélectionner

<- 2
f
(2)
#> [1] 4
g(2)
#> [1] 4  

Cordialement,
Mickaël
mickael.canouil.fr | rlille.fr

Jérémy Jachacz
Messages : 153
Enregistré le : 15 Avr 2014, 12:56

Re: Shiny Rmd variable "globale"

Messagepar Jérémy Jachacz » 21 Aoû 2020, 10:17

Merci pour ces diverses sources d'informations que je vais lire attentivement.

J'ai bien compris l'utilisation des environnements et de l'utilisation des variables globales.
Ici avec Shiny et Rmarkdown peut-être il y a un environnement que je ne saisie pas..

Dans ces exemples je comprends bien ce qu'il se passe, mais cela pose problème seulement si on utilise "mal" une variable globale,
comme dans votre fonction g, ce que je n'ai pas fait.. mon souhait d'utiliser une variable globale était fait sciemment.

En revanche la fonction f c'est exactement ce que je cherchais à faire, utiliser la variable globale dans ma fonction sans la passer en paramètre et sans la modifier dans cette fonction.
La raison (qui n'est pas bonne, j'en conviens) est que j'ai besoin de cette variable dans le rmd et qu'elle est fixe/constante.
Je l'ai donc créer hors de la fonction, et son appel dans la fonction devait fonctionner comme avec votre fonction f.
Au final j'ai intégré à la fonction un paramètre et la fonction retourne cette variable en sortie.

La vraie question c'est si f renvoie ce que je souhaite (malgré que ce soit une mauvaise pratique), pourquoi dans mon exemple initial (premier post)
avec Shiny j'ai le message d'erreur de variable introuvable :
max_speed not found
Alors qu'en exécutant le render 'manuellement' cela fonctionne bien.

Merci du temps que vous prenez.
Jérémy
Statisticien (69)

Serge Rapenne
Messages : 1426
Enregistré le : 20 Aoû 2007, 15:17
Contact :

Re: Shiny Rmd variable "globale"

Messagepar Serge Rapenne » 21 Aoû 2020, 11:24

Bonjour,

Je suis plutôt de l'avis de Mickael, utiliser une variable globale c'est "mal", dans le cas d'une appli shiny pourquoi n'utilises tu pas un reactiveValues ?

mes 2 centimes

Serge

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Shiny Rmd variable "globale"

Messagepar Mickael Canouil » 21 Aoû 2020, 13:58

Ici, "variable globale" veut dire dans l'environnement de travail et celui-ci n'a rien de global.
Shiny exploite tout un spectre d'environnement particulier et des interactions spéciales entre ces mêmes environnements.

Une session interactive n'est pas la même chose qu'un serveur shiny, ou que l'execution d'une application shiny via Rstudio.
Ne connaissant pas votre configuration, il n'y a absolument aucune garantie que ce soit comparable.
Par exemple, si vous sauvegarder et charger automatique votre espace de travail (par défaut dans R et Rstudio), quand vous êtes en interactif votre session n'est pas vierge, quand vous êtes en non-interactif, elle l'est.
En conclusion votre variable existe dans un cas et pas dans l'autre.

Il n'y pas de de bonne façon d'utiliser une "variable globale" et je ne vois absolument pas le problème à définir un paramètre (si ce n'est la "flemme").
Au pire, vous pouvez toujours utiliser options() (accessible à tous les niveaux d'environnements).
Pour enfoncer le clou, en dehors de générer des situations à la limite d'être insolvable (quand identifiable), quel est l'interêt d'une variable globale (qui n'est pas définie comme une constante, p.ex., letters ou LETTERS) ?

AU passage, la fonction render() dispose d'un argument pour définir l'environnement (envir) dans lequel le render s'execute et ainsi espérer les variables globales puissent être trouver.
Et oui, utiliser des variables globales, c'est espérer que R fasse ce qu'on veut ...
Mickaël
mickael.canouil.fr | rlille.fr

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Shiny Rmd variable "globale"

Messagepar Mickael Canouil » 21 Aoû 2020, 14:31

Parce-que les exemples reproductibles, c'est plus efficace:

app.R

Code : Tout sélectionner

myvar <- "hello"

shinyApp(
  ui = fluidPage(
    sliderInput("slider", "Slider", 1, 100, 50),
    downloadButton("report", "Generate report")
  ),
  server = function(input, output) {
    output$report <- downloadHandler(
      filename = "report.html",
      content = function(file) {
        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)
        rmarkdown::render(
          input = tempReport, 
          output_file 
= file,
          params = list(= input$slider),
          envir = new.env(parent = globalenv())
        )
      }
    )
  }
)


report.Rmd

Code : Tout sélectionner

---
title: "Dynamic report"
output: html_document
params
:
  n: NA
---

```
{r eval = FALSE, echo = FALSE}
# For PDF output, change the header to have "output: pdf_document".
#
# Note that due to an issue in rmarkdown, the default value of a parameter in
# the header cannot be 
`NULL`, so I used a default of `NA` for the default value
# of 
`n`.
```

My global variable: `r myvar`

```
{r}
# The 
`params` object is available in the document.
params
$n
```

A plot of `params$n` random points.

```
{r}
plot(rnorm(params
$n), rnorm(params$n))
```


Execution de l'application shiny
Image

Code : Tout sélectionner

> ls()
#> character(0)    

Aucune trace de de myvar ...
Normal, il est défini dans shiny::runApp() et non dans l'environnement global.

Si j'ajoute la variable dans l'environnement global, le render fonctionne "manuellement"
Image
C'est aussi maintenant le cas de l'application shiny:
Image


Si maintenant, on fait ça mieux:
app.R

Code : Tout sélectionner

myvar <- "hello"

shinyApp(
  ui = fluidPage(
    sliderInput("slider", "Slider", 1, 100, 50),
    downloadButton("report", "Generate report")
  ),
  server = function(input, output) {
    output$report <- downloadHandler(
      filename = "report.html",
      content = function(file) {
        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)
        rmarkdown::render(
          input = tempReport, 
          output_file 
= file,
          params = list(= input$slider, myvar = myvar),
          envir = new.env(parent = globalenv())
        )
      }
    )
  }
)

report.Rmd

Code : Tout sélectionner

---
title: "Dynamic report"
output: html_document
params
:
  n: NA
  myvar
: NA
---

```
{r eval = FALSE, echo = FALSE}
# For PDF output, change the header to have "output: pdf_document".
#
# Note that due to an issue in rmarkdown, the default value of a parameter in
# the header cannot be 
`NULL`, so I used a default of `NA` for the default value
# of 
`n`.
```

My global variable: `r params$myvar`

```
{r}
# The 
`params` object is available in the document.
params
$n
```

A plot of `params$n` random points.

```
{r}
plot(rnorm(params
$n), rnorm(params$n))
```

Absolument aucun problème
Image

CQFD :D
Mickaël
mickael.canouil.fr | rlille.fr

Jérémy Jachacz
Messages : 153
Enregistré le : 15 Avr 2014, 12:56

Re: Shiny Rmd variable "globale"

Messagepar Jérémy Jachacz » 21 Aoû 2020, 15:31

Merci pour l’exemple c’est plus clair,
C’est justement toutes ces interactions d’environnement que je ne connais pas.. j’apprends en marchant.

Ce n’est pas par flemme, je reprends du code existant, j’avais besoin de la variable dans la partie rmd (pour le texte) et dans la fonction. Elle était déclarée directement dans le chunk (juste avant l'appel de la fonction) et cela fonctionnait bien (et je savais que çela devait bien fonctionner, jusqu’à l’utilisation avec shiny).
Sur le coup, je ne voyais pas l’utilité d’ajouter un paramètre à ma fonction pour créer une variable "constante" dans la fonction puis la retourner en sortie autant la laisser en dehors... maintenant je comprends l’interêt de fonctionner comme ça.

Pour enfoncer le clou, en dehors de générer des situations à la limite d'être insolvable (quand identifiable), quel est l'interêt d'une variable globale (qui n'est pas définie comme une constante, p.ex., letters ou LETTERS) ?


Dans ma tête la variable en question est une constante à l'échelle du rmd generé, elle n’est pas modifiée au court de l'execution du rmd, c’est le nb de ligne de donnée du département en question.
Evidemment rien à voir avec une vraie constante comme "letters", j’en ai conscience.

Pour aller plus loin :
L’interface shiny sert à sélectionner un ou plusieurs départements/régions uniquement.

Le seul paramètre qui est passé dans le render (qui est dans server.R) c’est le numéro de département..
Ensuite dans les premiers chunk du rmd j’importe les données brutes et filtre à partir du département passé en paramètre et en sortie j’aurais un rapport spécifique à ce département/région.

Avec cette structure la variable dont on parle (nb de ligne pour un département/région donné) ne peut qu’être créée une fois dans le rmd car c'est ici qu'on a les données filtrées spécifiquement sur le département en question..
OU
Je pourrais (devrais ?) faire ses opérations (import, filtre) dans la partie shiny avant l’appel avec render et passer les variables (diverses jeux de données filtrés) en paramètre ?
Cela remets en question la structure du code, mais cela ne me pose pas de problème, au contraire l’objectif est de l’optimiser ; en terme de bonnes pratique autant qu’en terme de performance..

La raison de cette construction est que la partie shiny est arrivée après la partie rmd et que cela me permettais de pouvoir exécuter la partie rmd indépendement de la partie shiny.

Bonne fin de journée
Jérémy
Statisticien (69)

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: Shiny Rmd variable "globale"

Messagepar Mickael Canouil » 25 Aoû 2020, 07:58

L'ajout d'un paramètre à RMarkdown n'empêche en rien son exécution, puisque vous pouvez définir les valeurs par défaut et les changer ou non à la volée lors de l'execution du render().

Avec cette structure la variable dont on parle (nb de ligne pour un département/région donné) ne peut qu’être créée une fois dans le rmd car c'est ici qu'on a les données filtrées spécifiquement sur le département en question..
OU
Je pourrais (devrais ?) faire ses opérations (import, filtre) dans la partie shiny avant l’appel avec render et passer les variables (diverses jeux de données filtrés) en paramètre ?
Cela remets en question la structure du code, mais cela ne me pose pas de problème, au contraire l’objectif est de l’optimiser ; en terme de bonnes pratique autant qu’en terme de performance..

J'avoue être un peu perdu, faute de code, donc difficile d'aller vraiment plus loin de façon précise.
Mickaël
mickael.canouil.fr | rlille.fr


Retourner vers « Questions en cours »

Qui est en ligne

Utilisateurs parcourant ce forum : Aucun utilisateur enregistré et 1 invité