From 31eb957c5bcda8a14a2a6b13e8f4be53addd458d Mon Sep 17 00:00:00 2001 From: williamorim Date: Fri, 30 Sep 2022 16:36:36 -0300 Subject: [PATCH] colocando exercicios --- add-interatividade.qmd | 41 ++++++----- debug.qmd | 54 +++++++++++++++ deploy.qmd | 6 +- htmlwidgets.qmd | 23 +++++-- index.qmd | 6 +- layouts.qmd | 32 +++++++++ primeiro-app.qmd | 52 ++++++++++++-- reatividade-conceitos.qmd | 136 ++++++++++++++++++++++++++++++++----- reatividade-mais-pecas.qmd | 37 +++++++++- shiny-na-pratica-1.qmd | 13 +++- shiny-na-pratica-2.qmd | 4 +- sobre.qmd | 2 +- 12 files changed, 344 insertions(+), 62 deletions(-) diff --git a/add-interatividade.qmd b/add-interatividade.qmd index c30d6c7..b66a5df 100644 --- a/add-interatividade.qmd +++ b/add-interatividade.qmd @@ -138,7 +138,7 @@ input$variavel <- 10 #> Error in : Can't modify read-only reactive value 'variavel ``` -O widget `selectInput()` envia ao servidor uma string com o valor escolhido na caixa de seleção. Por isso utilizamos o operador `[[` para fazer a seleção da variável. Quando o app é inicado, por exemplo, `input$variavel` recebe o valor `"mpg"` e, por consequência, `mtcars[[input$variavel]]` será igual a `mtcars[["mpg"]]`, que retorna um vetor com os valores da coluna `mpg` e será utilizado pela função `hist()` para gerar o gráfico. +O widget `selectInput()` envia ao servidor uma string com o valor escolhido na caixa de seleção. Por isso utilizamos o operador `[[` para fazer a seleção da variável. Quando o app é iniciado, por exemplo, `input$variavel` recebe o valor `"mpg"` e, por consequência, `mtcars[[input$variavel]]` será igual a `mtcars[["mpg"]]`, que retorna um vetor com os valores da coluna `mpg` e será utilizado pela função `hist()` para gerar o gráfico. ```{r} mtcars[["mpg"]] @@ -192,46 +192,51 @@ Repare pelas mensagens no Console^[Essas mensagens são geradas pelas funções 1 - No contexto do Shiny, o que são inputs? E outputs? +___ + 2 - Como fazemos para acessar os valores dos inptus dentro da função `server`? +___ + 3 - Qual a função da família de funções `*Output()`? Onde essas funções são utilizadas? +___ + 4 - Qual a função da família de funções `render*()`? Onde essas funções são utilizadas? +___ + 5 - Faça um shiny app para visualizar histogramas da base `ggplot2::diamonds` e o coloque no shinyapps.io. Seu shiny deve ter um input e um output. -- Input: variáveis numéricas da base diamonds. +- Input: variáveis numéricas da base `diamonds`. - Output: histograma da variável selecionada. -Para acessar a base diamonds, carrrege o pacote ggplot2 +Para acessar a base `diamonds`, carregue o pacote `ggplot2` ```{r, eval = FALSE} library(ggplto2) +diamonds -ou rode +# ou rode ggplot2::diamonds ``` -6 - Faça um Shiny app para explorar a base de filmes da Pixar. +___ + +6 - Reproduza [este Shiny app](https://cursodashboards.shinyapps.io/select-date/). + +Para acessar a base utilizada, rode o código abaixo: ```{r, eval = FALSE} -# Instale o pacote com a versão orinal da base (em inglês) -install.packages("pixarfilms") +# Pacote com versão original da base (em inglês) +install.packages("nycflights13") +nycflights13::flights -# Instale pacote com versão em português da base +# Pacote com versão em português da base install.packages("dados") +dados::voos ``` - -Sugestões: - -- **Input**: data de lançamento. **Output**: tabela de filmes. - -- **Input**: filme. **Outputs**: orçamento, bilheteria, gênero, prêmios e notas. - -- **Input**: caixa de texto para escrever um nome. **Outputs**: tabela com filmes que essa pessoa participou e qual papel ela exerceu no filme (direção, roteiro, produção etc) - -**Dica**: você precisará fazer join das bases do pacote para juntar as informações. diff --git a/debug.qmd b/debug.qmd index 4824316..0114d67 100644 --- a/debug.qmd +++ b/debug.qmd @@ -120,3 +120,57 @@ output$plot <- renderPlot({ ``` Para sair do `browser` e voltar à execução do app, basta clicar no botão `Continue`, no topo do Console. Clicando no botão `Stop`, tanto o `browser` quanto a execução do app serão finalizadas. Após encontrar e solucionar o problema, não se esqueça de remover a função `browser()` do seu código. + +## Exercícios + +1 - Por que, em geral, não conseguimos rodar diretamente o código escrito na função `server`? + +___ + +2 - Para que serve a função `browser()`? + +___ + +3 - Debug e arrume o código do app a seguir: + +```{r, eval = FALSE} +library(shiny) + +ui <- fluidPage( + "Sorteio de números de 1 a 10", + sliderInput( + inputId = "tamanho", + label = "Selecione o tamanho da amostra", + min = 1, + max = 1000, + value = 5 + ), + actionButton("sortear", "Sortear"), + plotOutput(outputId = "grafico"), + "Tabela de frequências" + tableOutput(outputId = "tabela") +) + +server <- function(input, output, session) { + + amostra <- reactive({ + sample(1:10, input$tamanho) + }) + + output$plot <- renderPlot({ + amostra |> + table() |> + barplot() + }) + + output$tabela <- renderPlot({ + data.frame( + numeros = amostra() + ) |> + dplyr::count(numeros) + }) + +} + +shinyApp(ui, server) +``` diff --git a/deploy.qmd b/deploy.qmd index c6ac382..b196acf 100644 --- a/deploy.qmd +++ b/deploy.qmd @@ -14,7 +14,7 @@ Não falaremos neste livro sobre a utilização de computação em nuvem pois ex O [ShinyApps.io](https://www.shinyapps.io/) é um serviço da [RStudio](https://rstudio.com/) para hospedagem de aplicativos Shiny. -A conta gratuita permite até 5 aplicações simultâmneas e 25 horas mensais de uso (um aplicativo utilizado por 1 hora consome 1 hora do seu plano, 2 aplicativos utilizados simultaneamente por 1 hora consomem 2 horas do seu plano). As contas pagas, além de mais horas, fornecem outras vantagens, como suporte personalizado e autenticação. +A conta gratuita permite até 5 aplicações simultâneas e 25 horas mensais de uso (um aplicativo utilizado por 1 hora consome 1 hora do seu plano, 2 aplicativos utilizados simultaneamente por 1 hora consomem 2 horas do seu plano). As contas pagas, além de mais horas, fornecem outras vantagens, como suporte personalizado e autenticação. Criada uma conta, você deve conectá-la com o RStudio. Feito isso, você conseguirá fazer o deploy do seu app com alguns cliques. A seguir, mostramos o passo a passo para conectar a sua conta e para fazer o deploy do seu app. @@ -22,7 +22,7 @@ Criada uma conta, você deve conectá-la com o RStudio. Feito isso, você conseg Primeiro, crie uma conta no serviço site [shinyapps.io](https://www.shinyapps.io/). -Faço o login na sua conta e, no menu lateral, acesseo Account > Tokens. +Faço o login na sua conta e, no menu lateral, acesse Account > Tokens. ```{r, echo = FALSE, out.width="30%"} knitr::include_graphics("img/shinyapps-sidebar.png") @@ -69,7 +69,7 @@ knitr::include_graphics("img/shinyapps-publishing.png") O processo de deploy pode levar alguns minutos, a depender das dependências do seu app e tamanho dos arquivos que você subir. Quando o processo terminar, você receberá uma URL para acessar o aplicativo. -Caso o seu app não abra devido a algum erro, você pode acessar a os logs de utilização para ter uma dica do que aconteceu. Para isso, no dashboard do ShinyApps.io, acesse Applications > All, no meu lateral. Em seguida, clique no app que você gostaria de investidar. Por fim, acesse a opção "Logs". +Caso o seu app não abra devido a algum erro, você pode acessar a os logs de utilização para ter uma dica do que aconteceu. Para isso, no dashboard do ShinyApps.io, acesse Applications > All, no meu lateral. Em seguida, clique no app que você gostaria de investigar. Por fim, acesse a opção "Logs". ```{r, echo = FALSE, out.width="100%"} knitr::include_graphics("img/shinyapps-log.png") diff --git a/htmlwidgets.qmd b/htmlwidgets.qmd index 69f6bfd..bb4e361 100644 --- a/htmlwidgets.qmd +++ b/htmlwidgets.qmd @@ -14,11 +14,11 @@ Usando htmlwidgets, conseguimos construir tabelas, gráficos, mapas e muito outr - plotly, ECharts e Highcharts para gráficos - Leaflet, para mapas -Além de diversos recursos interativos nativos, esssas bibliotecas permitem a captura de certos **eventos**, que são transformados em inputs dentro Shiny e podem ser utilizados para gerar ou modificar resultados. Veremos também como acessar e utilizar esses eventos dentro do servidor. +Além de diversos recursos interativos nativos, essas bibliotecas permitem a captura de certos **eventos**, que são transformados em inputs dentro Shiny e podem ser utilizados para gerar ou modificar resultados. Veremos também como acessar e utilizar esses eventos dentro do servidor. ## Tabelas -Falaremos a seguir como utilziar as bibliotecas React Table e DT para construir tabelas interativas em seu Shiny app. +Falaremos a seguir como utilizar as bibliotecas React Table e DT para construir tabelas interativas em seu Shiny app. ### React Table {#sec-reactable} @@ -80,7 +80,7 @@ Para saber mais sobre `reactable`, [clique aqui](https://glin.github.io/reactabl ### DT -O pacote `DT` embruha a biblioteca JavaScript [DataTables](http://datatables.net/) e é uma alternativa ao `reactable` para a criação de tabelas interativas. Embora seja mais burocrático na customização, essa biblioteca possui um recurso muito útil para a aplicativos Shiny: a edição de tabelas. +O pacote `DT` embrulha a biblioteca JavaScript [DataTables](http://datatables.net/) e é uma alternativa ao `reactable` para a criação de tabelas interativas. Embora seja mais burocrático na customização, essa biblioteca possui um recurso muito útil para a aplicativos Shiny: a edição de tabelas. Antes de mais nada, instale o pacote `DT`: @@ -205,7 +205,7 @@ Na [Seção -@sec-ggplot2], falamos como colocar gráficos estáticos no Shiny u ### plotly -A biblioteca plotly, além de permitir a criação de gráficos interativos, possibilita fazermos isso diretamente de um gráfico feito em `ggplot2`. Para utilizar essa biblioteca a patir do R, utilizamos o pacote `plotly`. +A biblioteca plotly, além de permitir a criação de gráficos interativos, possibilita fazermos isso diretamente de um gráfico feito em `ggplot2`. Para utilizar essa biblioteca a partir do R, utilizamos o pacote `plotly`. ```{r, eval = FALSE} install.packages("plotly") @@ -520,7 +520,7 @@ install.packages("highcharter") O `highcharter` também **não** possui uma função tradutora de ggplot, equivalente à `ggplotly` do pacote `plotly`. Para criar um Highchart, podemos utilizar a função `highcharter::hchart()` ou `highcharter::highchart()`. -A função `highcharter::hchart()` tem uma sintaxe parecida com a do pacote `ggplot2`. No entanto, nem todo gráfico será possível ser contruído a partir dessa função. +A função `highcharter::hchart()` tem uma sintaxe parecida com a do pacote `ggplot2`. No entanto, nem todo gráfico será possível ser construído a partir dessa função. ```{r} mtcars |> @@ -685,7 +685,7 @@ O `leaflet` envia automaticamente valores provenientes de eventos para a lista ` Se um mapa com `outputId = "mapa"` tiver um círculo, sempre que o círculo for clicado o valor reativo `input$mapa_shape_click` será atualizado. Antes do primeiro clique, o valor de `input$mapa_shape_click` é `NULL`. -O leaflet possui os seguintes tipos de objeto (`tipoObjeto`): `marker`, `shape`, `geojson` e `topojson`; e os seguintes tipos de eventos (`nomeEvento`): `click`, `mouseover` and `mouseout`. +O leaflet possui os seguintes tipos de objeto (`tipoObjeto`): `marker`, `shape`, `geojson` e `topojson`; e os seguintes tipos de eventos (`nomeEvento`): `click`, `mouseover` e `mouseout`. Ao realizar uma ação, o valor do `input` correspondente passa a ser uma lista com os seguintes valores: @@ -707,5 +707,16 @@ Além dos eventos associados a um elemento do mapa, o `leaflet` também disponib +## Exercícios + +1 - O que são HTML Widgets? + +___ + +2 - Reproduza [este app](https://acursor.shinyapps.io/htmlwidgets-1/). + +A base utilizada foi a `cetesb`. [Clique aqui](https://github.com/curso-r/programando-em-shiny/raw/main/exemplos/data/cetesb.rds) para fazer o download dela. + + diff --git a/index.qmd b/index.qmd index 2db36bc..afe8426 100644 --- a/index.qmd +++ b/index.qmd @@ -4,7 +4,7 @@ Este é um livro em desenvolvimento. Toda contribuição, seja correção de err O conteúdo aqui presente se destina a: -- pessoas que programam em linguagem R e gostaríam de começar a programar em Shiny; +- pessoas que programam em linguagem R e gostariam de começar a programar em Shiny; - pessoas que programam em Shiny e gostariam de se aprofundar no conceito de reatividade; @@ -32,7 +32,7 @@ Nas universidades, o Shiny já é uma poderosa ferramenta de ensino, que substit O Shiny fornece uma estrutura para gerarmos código HTML a partir de funções em R. Também possui uma base de JavaScript e CSS para deixar os aplicativos funcionais e com um visual satisfatório. Além disso, podemos utilizar por trás todo o poderio de análise de dados que o R e seus pacotes fornecem. Com esses elementos, já conseguimos construir qualquer tipo de layout e lógica interna. -Contudo, o grande poder do Shiny está em não limitar as posssibilidades apenas ao que foi feito pelos desenvolvedores do pacote^[O que já é muita coisa! Veja mais em https://shiny.rstudio.com/.]. Existem vários outros pacotes criados pela comunidade que trazem mais elementos visuais e funcionais para o Shiny, diminuindo ainda mais a necessidade de conhecermos HTML, CSS e JavaScript. E, se você tem conhecimento dessas linguagens, o céu é o limite! É muito simples incluir *tags* HTML, folhas de estilo CSS e suas próprias funções JavaScript em um aplicativo Shiny. +Contudo, o grande poder do Shiny está em não limitar as possibilidades apenas ao que foi feito pelos desenvolvedores do pacote^[O que já é muita coisa! Veja mais em https://shiny.rstudio.com/.]. Existem vários outros pacotes criados pela comunidade que trazem mais elementos visuais e funcionais para o Shiny, diminuindo ainda mais a necessidade de conhecermos HTML, CSS e JavaScript. E, se você tem conhecimento dessas linguagens, o céu é o limite! É muito simples incluir *tags* HTML, folhas de estilo CSS e suas próprias funções JavaScript em um aplicativo Shiny. ## O que você vai aprender neste livro? {-} @@ -67,7 +67,7 @@ Falar sobre Shiny em texto um grande desafio. A maior parte dos resultados que v Deixamos alguns poucos aplicativos utilizados como exemplo neste livro online^[Infelizmente seria muito trabalhoso e caro fazer isso com todos.]. Sempre que disponível, recomendamos acessar o link e interagir com o app. -No restante dos casos, deixaros disponível junto ao texto o código completo do app, que você pode copiar e colar no R para rodar o aplicativo. Recomendamos que você faça isso para todos os exemplos deste tipo, sempre fazendo o exercício de analisar qual parte do código gera cada parte do app. +No restante dos casos, deixamos disponível junto ao texto o código completo do app, que você pode copiar e colar no R para rodar o aplicativo. Recomendamos que você faça isso para todos os exemplos deste tipo, sempre fazendo o exercício de analisar qual parte do código gera cada parte do app. ## O que eu preciso para começar a aprender Shiny? {-} diff --git a/layouts.qmd b/layouts.qmd index 6f3c128..878ff29 100644 --- a/layouts.qmd +++ b/layouts.qmd @@ -668,3 +668,35 @@ p { } ``` +## Exercícios + +1 - O que é uma linguagem de marcação? + +___ + +2 - Como criar as tags HTML usando o pacote `shiny`? + +___ + +3 - O que é o framework Bootstrap? O que é o sistema de grade (*grid system*)? + +___ + +4 - Refaça os apps dos exercícios dos capítulos anteriores utilizando o `sidebarLayout`. + +___ + +5 - Utilizando a base `dados::clima` e o layout `navbarPage`, faça um shiny app que tenha duas páginas: + +- a primeira com uma série temporal da média diária da `temperatura`, permitindo a escolha do intervalo de dias em que o gráfico é gerado + +- a segunda com uma caixa de seleção permitindo escolher as opções `umidade`, `velocidade_vento` e `precipitacao` e um gráfico de dispersão da `temperatura` contra a variável escolhida. + +___ + +6 - Transforme o aplicativo construído no exercício anterior em um `shinydasbhoard`. + +___ + +7 - Transforme o aplicativo construído no exercício anterior em um `bs4Dash`. + diff --git a/primeiro-app.qmd b/primeiro-app.qmd index b445513..c2cb569 100644 --- a/primeiro-app.qmd +++ b/primeiro-app.qmd @@ -18,9 +18,9 @@ install.packages("shiny") A versão do pacote que estamos utilizando neste livro é a `r packageVersion("shiny")`. -É uma boa prática criarmos novos repositórios (ou projetos, no RStudio) para cada nova análise de dados que começamos, mantendo todos os arquivos da análise dentro desse repositório. Veremos que, para aplicavios Shiny, isso será especialmente importante. Para reproduzir os exemplos deste livro, recomendamos criar uma nova pasta para cada aplicativo. Por mais que em alguns casos isso não seja particularmente útil, será uma boa maneira de praticar essa maneira de organização. +É uma boa prática criarmos novos repositórios (ou projetos, no RStudio) para cada nova análise de dados que começamos, mantendo todos os arquivos da análise dentro desse repositório. Veremos que, para aplicativos Shiny, isso será especialmente importante. Para reproduzir os exemplos deste livro, recomendamos criar uma nova pasta para cada aplicativo. Por mais que em alguns casos isso não seja particularmente útil, será uma boa maneira de praticar essa maneira de organização. -Para mais dicas de organização de projetos de análide de dados, em um contexto geral, recomendamos a leitura do [Zen do R](https://curso-r.github.io/zen-do-r/), especialmente o [Capítulo 3](https://curso-r.github.io/zen-do-r/rproj-dir.html). +Para mais dicas de organização de projetos de análise de dados, em um contexto geral, recomendamos a leitura do [Zen do R](https://curso-r.github.io/zen-do-r/), especialmente o [Capítulo 3](https://curso-r.github.io/zen-do-r/rproj-dir.html). ## O que é um aplicativo Shiny? @@ -146,11 +146,11 @@ server <- function(input, output, session) { shinyApp(ui, server) ``` -Repare que algumas opções novas aparecerão no RStudio quando salvamos o arquivo. Isso acontece porque o RStudio reconhece a sintaxe do código como a de um aplacativo Shiny, habilitando para nós algumas ferramentas que são úteis apenas para trabalhar com apps. A mais importante por enquanto será o botão **Run App**. +Repare que algumas opções novas aparecerão no RStudio quando salvamos o arquivo. Isso acontece porque o RStudio reconhece a sintaxe do código como a de um aplicativo Shiny, habilitando para nós algumas ferramentas que são úteis apenas para trabalhar com apps. A mais importante por enquanto será o botão **Run App**. Como discutimos anteriormente, um aplicativo Shiny em funcionamento sempre terá um computador rodando uma sessão de R por trás. Esse computador, chamado genericamente de *servidor*, pode ser uma máquina virtual em um serviço de nuvem, uma máquina virtual em um serviço de hospedagem, um servidor dentro da sua empresa, um servidor na sua casa ou mesmo o seu próprio computador pessoal. -Normalmente, enquanto estamos desenvolvendo um aplicativo Shiny, queremos testá-lo localmente^[E locamente também.] para verificar se tudo funciona corretamente, se está ficando bonito ou simplesmente para gastar alguns minutos apreciando a nossa obra de arte. Testar localmente significa que **o seu próprio computador fará as vezes de servidor**, embora isso não signifique que seu app ficará disponível na internet. +Normalmente, enquanto estamos desenvolvendo um aplicativo Shiny, queremos testá-lo localmente^[E loucamente também.] para verificar se tudo funciona corretamente, se está ficando bonito ou simplesmente para gastar alguns minutos apreciando a nossa obra de arte. Testar localmente significa que **o seu próprio computador fará as vezes de servidor**, embora isso não signifique que seu app ficará disponível na internet. Quando servimos um app localmente, isto é, quando *rodamos um app*, ganhamos um endereço que será acessível apenas do nosso computador. A partir desse endereço, podemos testar nosso app no navegador, como se ele já estivesse em produção. No RStudio, para rodar nossos apps, utilizamos justamente o botão **Run App**. @@ -158,7 +158,7 @@ Quando servimos um app localmente, isto é, quando *rodamos um app*, ganhamos um knitr::include_graphics("img/botao_run_app.png") ``` -Ao clicar nesse botão, o seu navegador padrão será aberto^[Se o app foi aberto na aba Viewer ou em uma janela do próprio RStudio, clique na setinha para baixo ao lado do botão **Run App** e selecione a opção `Run External`.] e você verá a UI do nosso modesto app com apenas a frase "Olá, mundo!". +Ao clicar nesse botão, o seu navegador padrão será aberto^[Se o app foi aberto na aba Viewer ou em uma janela do próprio RStudio, clique na seta para baixo ao lado do botão **Run App** e selecione a opção `Run External`.] e você verá a UI do nosso modesto app com apenas a frase "Olá, mundo!". Se você voltar ao RStudio, eventualmente vai notar algo muito importante: a sua sessão de R estará ocupada! Mas isso não deveria ser uma surpresa, pois já discutimos que todo Shiny app tem uma sessão de R rodando por trás. @@ -170,7 +170,47 @@ Para liberar a sessão, basta clicar no botão "*stop*", na parte de cima do Con knitr::include_graphics("img/console_sessao_ocupada.png") ``` -No console, após rodarmos o app, também aparecerá uma mensagem idenficicando o *endereço* do nosso aplicativo. Nesse caso, será um IP (`http://127.0.0.1`)^[Esse número de IP é padrão e significa "o seu computador".] com alguma porta que esteja disponível escolhida aleatoriamente (`:4028`). Esse endereço aparecerá no nosso navegador e poderemos copiá-lo e colá-lo em qualquer outra aba ou navegador que quisermos rodar o app. +No console, após rodarmos o app, também aparecerá uma mensagem identificando o *endereço* do nosso aplicativo. Nesse caso, será um IP (`http://127.0.0.1`)^[Esse número de IP é padrão e significa "o seu computador".] com alguma porta que esteja disponível escolhida aleatoriamente (`:4028`). Esse endereço aparecerá no nosso navegador e poderemos copiá-lo e colá-lo em qualquer outra aba ou navegador que quisermos rodar o app. +## Exercícios +1 - O que é um aplicativo web? + +___ + +2 - Quais são os componentes básicos de um aplicativo Shiny? + +___ + +3 - Por que a sessão do R fica ocupada quando rodamos um app localmente? + +--- + +4 - Sobre o código de um aplicativo Shiny, escolha a alternativa **falsa**. + +a. As funções do R utilizadas na construção da UI são funções que retornam código HMTL. + +b. Não precisamos criar a função `server` se o app não tiver interatividade. + +c. A função `server` pode ser escrita sem o parâmetro `session`. + +d. Embora seja uma boa prática usar os nomes `ui` e `server`, podemos adotar qualquer nome para esses elementos. + +___ + +5 - Por que o app criado pelo código abaixo retorna erro quando o rodamos? + +```{r, eval = FALSE} +library(shiny) + +ui <- fluidPage( + +) + +server <- function() { + +} + +shinyApp(ui, server) +``` diff --git a/reatividade-conceitos.qmd b/reatividade-conceitos.qmd index 2d4c814..28c054a 100644 --- a/reatividade-conceitos.qmd +++ b/reatividade-conceitos.qmd @@ -7,7 +7,7 @@ knitr::opts_chunk$set( # Reatividade: conceitos básicos {#sec-reatividade} -Imagine que gostaríamos de criar um app que geresse um gráfico com o resultado do sorteio de uma amostra de números entre 1 e 10 e que o tamanho dessa amostra fosse definido por um input. Além disso, que esse app também indicasse em texto qual foi o número mais sorteado. +Imagine que gostaríamos de criar um app que gerasse um gráfico com o resultado do sorteio de uma amostra de números entre 1 e 10 e que o tamanho dessa amostra fosse definido por um input. Além disso, que esse app também indicasse em texto qual foi o número mais sorteado. Pensando na construção desse app, teríamos um input, o tamanho da amostra, e dois outputs, um gráfico (de barras) indicando a frequência de cada número e um texto dizendo qual o número mais sorteado. Pelo que aprendemos no capítulo anterior, podemos usar um `sliderInput()` para receber o tamanho da amostra e as funções `renderPlot()` e `renderText()` para construir os textos. No entanto, uma dúvida emerge com relação ao sorteio da amostra: onde ele deve ser feito? @@ -57,9 +57,9 @@ server <- function(input, output, session) { #> Error in : Can't access reactive value 'tamanho' outside of reactive consumer. ``` -A construção desse app traz à tona a necessidade de uma nova peça do Shiny, que não mencionamos ainda. Sem ela não consiguiríamos criar esse app de maneira eficiente. +A construção desse app traz à tona a necessidade de uma nova peça do Shiny, que não mencionamos ainda. Sem ela não conseguiríamos criar esse app de maneira eficiente. -Antes de falar dela, no entanto, como podemos ver pela mensagem de erro da tentiva anterior, precisamos falar de um dos temas centrais do desenvolvimento de aplicativos Shiny: a reatividade. +Antes de falar dela, no entanto, como podemos ver pela mensagem de erro da tentativa anterior, precisamos falar de um dos temas centrais do desenvolvimento de aplicativos Shiny: a reatividade. Embora dê para fazer muita coisa no Shiny na tentativa e erro, entender a reatividade nos desvia de vários problemas explícitos --- como o erro acima --- e, principalmente, de problemas invisíveis --- quando o app funciona, mas de maneira ineficiente ou incorreta. Economizamos muito tempo (e sanidade) não cometendo esses erros durante o desenvolvimento de um app e, por isso, vale a pena estudar reatividade desde o início da sua jornada pelo Shiny. @@ -179,13 +179,13 @@ Para programar em Shiny, precisamos aprender um conjunto de conceitos e ferramen Vimos na seção anterior que um aplicativo Shiny depende da especificação **adequada** do diagrama de reatividade. Isso quer dizer que devemos seguir alguns princípios na construção da função `server` para que o Shiny consiga montar esse diagrama e para que a reatividade aconteça. -O primeiro desses princípios é o seguinte: todo diagrama de reatividade deve começar com um **valor reativo** e terminar com uma **função observadora**. Nós já utilizamos valores reativos e funções observadoras antes. Vamos definir o que são estrutruras. +O primeiro desses princípios é o seguinte: todo diagrama de reatividade deve começar com um **valor reativo** e terminar com uma **função observadora**. Nós já utilizamos valores reativos e funções observadoras antes. Vamos definir o que são estruturas. -Valores reativos são objetos que **disparam** a reatividade do app a partir de mudanças nos seus valores. Os objetos da `input` são o exemplo mais comum de valores reativos. Funções observadoras são funções que "observam" mudanças nos valores reativos e, quando elas acontecem, recalculam o seu código. As funções da famílias `render*()` são os exemplos mais comuns de funções obervadoras. +Valores reativos são objetos que **disparam** a reatividade do app a partir de mudanças nos seus valores. Os objetos da `input` são o exemplo mais comum de valores reativos. Funções observadoras são funções que "observam" mudanças nos valores reativos e, quando elas acontecem, recalculam o seu código. As funções da famílias `render*()` são os exemplos mais comuns de funções observadoras. -Voltando app dado como exemplo na seçao anterior, quando a "Variável A" é alterada na UI, o seguinte processo acontece: +Voltando app dado como exemplo na seção anterior, quando a "Variável A" é alterada na UI, o seguinte processo acontece: -- o valor reativo `input$variavel_A` emite um sinal de alerta a todas as funções obervadoras que dependem dele dizendo que seu valor mudou; +- o valor reativo `input$variavel_A` emite um sinal de alerta a todas as funções observadoras que dependem dele dizendo que seu valor mudou; - a função observadora `renderPlot()`, que depende de `input$variavel_A`, ao saber que esse valor reativo está desatualizado, desatualiza o seu próprio resultado, isto é, o output `histograma_A`; @@ -199,7 +199,7 @@ Os valores da lista `input` não são o único tipo de valores reativos, assim c ## Expressões reativas -Muitas vezes, precisamos criar objetos dentro do Shiny que dependem de valores reativos e que serão utilizados dentro de um ou mais funções observadoras. Tal objeto precisaria se comportar tanto como uma função observadora, pois opois observaria o valor reatido do qualpois observaria o valor reatido do qual, quanto como uma valor reativo, para disparar a reatividade dentro das funções observadoras onde ele será usado. +Muitas vezes, precisamos criar objetos dentro do Shiny que dependem de valores reativos e que serão utilizados dentro de uma ou mais funções observadoras. Tal objeto precisaria se comportar tanto como uma função observadora, pois observaria o valor reativo do qual depende, quanto como uma valor reativo, para disparar a reatividade dentro das funções observadoras onde ele será usado. Esses objetos existem e são chamados de **expressões reativas**, a peça faltante no problema apresentado na introdução deste capítulo. Para criar expressões reativas, utilizamos as funções `reactive()` e `eventReactive()`. @@ -214,7 +214,7 @@ amostra <- reactive({ }) ``` -Já neste próximo exemplo, a expressão reativa `soma` reacalcula o seu valor sempre que qualquer um dos valores reativos `input$valor1`, `input$valor2` e `input$valor3` mudar. +Já neste próximo exemplo, a expressão reativa `soma` recalcula o seu valor sempre que qualquer um dos valores reativos `input$valor1`, `input$valor2` e `input$valor3` mudar. ```{r, eval = FALSE} soma <- reactive({ @@ -224,7 +224,7 @@ soma <- reactive({ Para acessar o valor de uma expressão reativa, devemos usar parênteses após o seu nome, como se estivéssemos chamando uma função sem argumentos. Podemos utilizar esse valor dentro de qualquer outra expressão reativa ou função observadora, quantas vezes for preciso. -No código a seguir, criamos a expressão reativa `amostra` e utilizamos o seu valor chamabndo `amostra()` dentro das funções observadoras `renderPlot()` e `renderText()`. Essa é a solução para o problema apresentado no início do capítulo. +No código a seguir, criamos a expressão reativa `amostra` e utilizamos o seu valor chamando `amostra()` dentro das funções observadoras `renderPlot()` e `renderText()`. Essa é a solução para o problema apresentado no início do capítulo. ```{r, eval = FALSE} # server @@ -269,7 +269,7 @@ knitr::include_graphics("img/diagrama_reatividade_2.svg") ### A função `eventReactive()` -Enquanto a função `reactive()` observa mudanças em todos os valores reativos presentes em seu código, a função `eventReactive()` observa mudanças em apenas um valor reativo, especificado na chamada da própria função. No código a seguir, a soma só reacalculada quando o valor `input$valor1` mudar. Alterar os valores de `input$valor2` ou `input$valor3` não vai fazer a expressão reativ `soma` disparar reatividade. +Enquanto a função `reactive()` observa mudanças em todos os valores reativos presentes em seu código, a função `eventReactive()` observa mudanças em apenas um valor reativo, especificado na chamada da própria função. No código a seguir, a soma só recalculada quando o valor `input$valor1` mudar. Alterar os valores de `input$valor2` ou `input$valor3` não vai fazer a expressão reativa `soma` disparar reatividade. ```{r, eval = FALSE} soma <- eventReactive(input$valor1, { @@ -319,7 +319,7 @@ server <- function(input, output, session) { shinyApp(ui, server) ``` -Veja que o valor reativo `input$botao`, especificado no primeiro argumento da funcão `eventReactive()`, funciona como o gatilho do fluxo de reatividade. Isso significa que a expressão reativa `amostra` será recalculada apenas quando apertarmos o botão *Gerar gráfico*. Mudanças no slider não resultarão em qualquer mudança no app (estamos impedindo que `input$tamanho` inicie o fluxo de reatividade). Repare também que o código que gera a amostra é passado no segundo argumento da funcão `eventReactive()`. +Veja que o valor reativo `input$botao`, especificado no primeiro argumento da função `eventReactive()`, funciona como o gatilho do fluxo de reatividade. Isso significa que a expressão reativa `amostra` será recalculada apenas quando apertarmos o botão *Gerar gráfico*. Mudanças no slider não resultarão em qualquer mudança no app (estamos impedindo que `input$tamanho` inicie o fluxo de reatividade). Repare também que o código que gera a amostra é passado no segundo argumento da função `eventReactive()`. O diagrama reativo desse app pode ser representado pelo esquema a seguir. A linha pontilhada indica que o `input$tamanho` não dispara reatividade. @@ -332,9 +332,9 @@ knitr::include_graphics("img/diagrama_reatividade_3.svg") ## Contexto reativo -Vimos na introdução do capítulo que a nossa segunda tentativa --- colocar a criação da amostra diretamente na função `server` --- retorna um erro. Isso acontece porque **valores e expressões reativas só podem ser lidas dentro de um contexto reativo**. Essa é outra regra do Shiny, e precisamos segui-la para que ele consiga montar o diagra de reatividade. +Vimos na introdução do capítulo que a nossa segunda tentativa --- colocar a criação da amostra diretamente na função `server` --- retorna um erro. Isso acontece porque **valores e expressões reativas só podem ser lidas dentro de um contexto reativo**. Essa é outra regra do Shiny, e precisamos segui-la para que ele consiga montar o diagrama de reatividade. -*Contexto reativo* remete a qualquer função que observe mudanças em valores reativos, como expressões reativas ou funções observadoras. Basicamente, o processo do Shiny que interpreta a função `server` e cria o driagrama de reatividade não sabe o que fazer quando valores e expressões reativas estão fora de contexto reativo. Então recebemos um erro. +*Contexto reativo* remete a qualquer função que observe mudanças em valores reativos, como expressões reativas ou funções observadoras. Basicamente, o processo do Shiny que interpreta a função `server` e cria o diagrama de reatividade não sabe o que fazer quando valores e expressões reativas estão fora de contexto reativo. Então recebemos um erro. No exemplo abaixo, a função `renderPlot()` cria um contexto reativo e, por isso, podemos utilizar o valor reativo `input$variavel` dentro dela. @@ -369,7 +369,7 @@ Os dois problemas indicam um diagrama de reatividade mal construído. A diferen Para evitar esses problemas, que discutiremos com mais detalhes no [Capítulo -@sec-reatividade2] e no [Capítulo -sec-reatividade3], podemos imaginar ou desenhar o diagrama de reatividade para investigar onde está a falha. Essa é uma tarefa simples em apps com poucos inputs e outputs, mas extremamente difícil ou inviável em apps complexos. -Nesses casos (ou mesmo nos casos simples), podemos utilizar o pacote `reactlog`. Com ele, conseguimos visualizar facilmente o diagrama de dependências reativas de qualquer Shiny app e olhar o que acontece por trás das curtinas da reatividade quando executamos o aplicativo. +Nesses casos (ou mesmo nos casos simples), podemos utilizar o pacote `reactlog`. Com ele, conseguimos visualizar facilmente o diagrama de dependências reativas de qualquer Shiny app e olhar o que acontece por trás das cortinas da reatividade quando executamos o aplicativo. Antes de mais nada, instale o pacote `reactlog`. @@ -385,7 +385,7 @@ options(shiny.reactlog = TRUE) Por fim, rode o seu app e utilize o comando `CTRL + F3` (no Mac, `command + F3`). O seu navegador abrirá uma nova aba com o diagrama de reatividade. -No exemplo a seguir, temos a UI de um aplicativo que gera o histograma de uma amostra com distribuição normal. O tamanho da amostra é determinado pelo sliderInput. Sempre qua o tamanho da amostra muda, o gráfico é recalculado. +No exemplo a seguir, temos a UI de um aplicativo que gera o histograma de uma amostra com distribuição normal. O tamanho da amostra é determinado pelo sliderInput. Sempre que o tamanho da amostra muda, o gráfico é recalculado. ```{r, echo = FALSE} #| fig-cap: > @@ -428,9 +428,9 @@ Em resumo, com um diagrama de reatividade em mãos, podemos: - investigar por que o código de um output não é rodado ou roda duas vezes quando acionamos um input do qual ele deveria depender; -- ter uma visão menos abstrada do fluxo de reatividade e entender melhor o que acontece quando executamos cada parte do nosso app. +- ter uma visão menos abstrata do fluxo de reatividade e entender melhor o que acontece quando executamos cada parte do nosso app. -Utilize o código a seguir para reproduzir o app mostrado nesta seção. Mexa algumas vezes no *slider* e gere o driagrama de reatividade. Use os controles de navegação para visualizar cada etapa do ciclo reativo. +Utilize o código a seguir para reproduzir o app mostrado nesta seção. Mexa algumas vezes no *slider* e gere o diagrama de reatividade. Use os controles de navegação para visualizar cada etapa do ciclo reativo. ```{r, eval = FALSE} library(shiny) @@ -466,3 +466,103 @@ Você pode aprender mais sobre o funcionamento do `reactlog` [clicando aqui](htt Na sequência deste livro, sugerimos utilizar o `reactlog` sempre que ficar com dúvida sobre o diagrama de reatividade de um app. +## Exercícios + +1 - Qual a diferença entre programação imperativa e programação declarativa? + +___ + +2 - O que é reatividade? + +___ + +3 - Qual o papel da função `server` no contexto da reatividade de um aplicativo Shiny? + +___ + +4 - O que são valores reativos? E funções observadoras? + +___ + +5 - Para que servem as expressões reativas? + +___ + +6 - Qual a diferença entre as funções `reactive()` e `eventReactive()`? + +___ + +7 - Selecione a opção **verdadeira**. + +a. Sempre que mudarmos o valor de um `input` na UI de um app, todo o código na função `server` será rodado novamente. + +b. Expressões reativas são o ponto de partida do diagrama de reatividade. + +c. Podemos criar valores reativos com a função `reactive()`. + +d. As funções da família `render*` são um exemplo de função observadora. + +___ + +8 - Faça um shiny app para visualizarmos boxplots da base `ggplot2::diamonds`. + +O seu app deve ter dois inputs e um output: + +- o primeiro input deve ser uma caixa de seleção das variáveis numéricas da base (será o eixo y do gráfico). + +- o segundo input deve ser uma caixa de seleção das variáveis categóricas da base (será o eixo x do gráfico). + +- o output deve ser um gráfico com os boxplots da variável escolhida em (1) para cada categoria da variável escolhida em (2). + +Para acessar a base `diamonds`, carregue o pacote ggplot2: + +```{r, eval = FALSE} +library(ggplto2) +diamonds + +# ou rode + +ggplot2::diamonds +``` + +___ + +9 - Selecionando várias opções em um `selectInput`. + +a. Reproduza o seguinte Shiny app: +https://cursodashboards.shinyapps.io/select-multiple/ + +b. Troque o selectInput pelo checkboxGroupInput(). + +Para acessar a base utilizada, rode o código abaixo: + +```{r, eval = FALSE} +install.packages("nycflights13") +nycflights13::flights + +# Pacote com versão em português da base +install.packages("dados") +dados::voos +``` + +___ + +10 - Faça um Shiny app para explorar a base de filmes da Pixar. + +```{r, eval = FALSE} +# Instale o pacote com a versão original da base (em inglês) +install.packages("pixarfilms") + +# Instale pacote com versão em português da base +install.packages("dados") +``` + +Sugestões: + +- **Input**: data de lançamento (`dateRangeInput`). **Output**: tabela de filmes. + +- **Input**: filme. **Outputs**: orçamento, bilheteria, gênero, prêmios e notas. + +- **Input**: caixa de texto para escrever um nome. **Outputs**: tabela com filmes que essa pessoa participou e qual papel ela exerceu no filme (direção, roteiro, produção etc) + +**Dica**: você precisará fazer join das bases do pacote para juntar as informações. diff --git a/reatividade-mais-pecas.qmd b/reatividade-mais-pecas.qmd index 0abda36..c8a7ddc 100644 --- a/reatividade-mais-pecas.qmd +++ b/reatividade-mais-pecas.qmd @@ -14,7 +14,7 @@ A depender do tipo de interação que queremos construir no app, vamos precisar As funções observadoras são o ponto final do diagrama de reatividade e sem eles o fluxo reativo não acontece. As funções `render*()`, que geram os nossos outputs, são o tipo mais comum de funções observadoras, mas não são o único. -Muitas vezes queremos usar a reatividade para disparar ações que não estão ligadas à geração de outputs, como o registro de informações em bases de dados, o envio de e-mails ou a atualização de informações nos inputs^[Sem a necessidade de recriá-los do zero com `uiOutput()` e `renderUI()`.]. Nesses casos, precisamos utilizar as funções `observe()` e `oberveEvent()`. +Muitas vezes queremos usar a reatividade para disparar ações que não estão ligadas à geração de outputs, como o registro de informações em bases de dados, o envio de e-mails ou a atualização de informações nos inputs^[Sem a necessidade de recriá-los do zero com `uiOutput()` e `renderUI()`.]. Nesses casos, precisamos utilizar as funções `observe()` e `observeEvent()`. A função `observe()` monitora os valores e expressões reativas que estão dentro dela e roda seu código quando algum desses valores são modificados. Ao contrário da função `reactive()`, ela não cria um novo valor reativo. O código atribuído a ela é o ponto chegada de um fluxo reativo, isto é, a ação que a função `observe()` executa é o objetivo final do fluxo. @@ -74,7 +74,7 @@ server <- function(input, output, session) { } ``` -As funções `observe()` e `oberveEvent()` aumentam bastante o leque de opções dos nossos aplicativos. Agora conseguimos criar fluxos reativos que não estão associados necessariamente a um output. +As funções `observe()` e `observeEvent()` aumentam bastante o leque de opções dos nossos aplicativos. Agora conseguimos criar fluxos reativos que não estão associados necessariamente a um output. ## Mais valores reativos @@ -121,7 +121,7 @@ rv$a <- 3 rv$b <- 4 ``` -Um caso em que criar valores reativos no servidor se torna útil aparece quando precisamos modificar a base de dados que alimenta os outputs a patir de alguma ação na UI, como a possibilidade de adicionar ou remover uma linha. Veja o exemplo abaixo. +Um caso em que criar valores reativos no servidor se torna útil aparece quando precisamos modificar a base de dados que alimenta os outputs a partir de alguma ação na UI, como a possibilidade de adicionar ou remover uma linha. Veja o exemplo abaixo. ```{r, eval = FALSE} library(shiny) @@ -251,3 +251,34 @@ output$orcamento <- renderInfoBox({ }) ``` + +## Exercícios + +1 - Por que precisamos das funções `observe()` e `observeEvent()`? Qual a diferença entre elas? + +___ + +2 - Para que serve a função `reactiveVal()`? + +___ + +3 - Qual a diferença entre as funções `reactiveVal()` e `reactiveValues()`? + +___ + +4 - Quais valores retornam `FALSE` na função `isTruth()`? + +___ + +5 - Para que serve a função `req()`? + +___ + +6 - Utilizando a base `dados::dados_gapminder`, construa um app que tenha um filtro de `continente` e outro de `pais`. Escolhido um `continente`, apenas países do continente escolhido devem permanecer no filtro de `pais`. Como output, seu app deve apresentar as séries de `populacao`, `expectativa_de_vida` e `pib_per_capita` ao longo dos anos disponíveis. + +___ + +7 - Faça um app que contenha um formulário de cadastro com os campos "nome", "e-mail", "idade" e "cidade" e um botão de salvar dados que faça o app salvar as informações em uma planilha no computador. O app também deve mostrar a tabela mais atualizada de pessoas cadastradas. + + + diff --git a/shiny-na-pratica-1.qmd b/shiny-na-pratica-1.qmd index 0f57505..53d57a3 100644 --- a/shiny-na-pratica-1.qmd +++ b/shiny-na-pratica-1.qmd @@ -1,5 +1,7 @@ # Shiny na prática I {#sec-shiny-pratica-1} +Neste capítulo, vamos fazer uma pequena pausa na apresentação de conceitos e abordar alguns temas práticos muito úteis no desenvolvimento de aplicativos Shiny. + ## Importando bases de dados A tarefa de importação de dados dentro do Shiny não possui nenhum desafio particular. Podemos seguir lendo nossos dados, seja de arquivos locais, da internet ou de banco de dados, da mesma maneira que fazemos em scripts R habituais. @@ -312,7 +314,7 @@ shinyApp(ui, server) Além várias opções de animações diferentes, que você pode trocar no argumento `type`, também é possível ajustar o tamanho, a cor, a cor de fundo e até usar uma imagem própria como animação^[Pode ser uma imagem estática ou GIF.]. -O pacote `shinyWidgets` é mantido pela equipe da [dreamRs](https://github.com/dreamRs). Além de diversos widgets muito úteis, ele possui a função `shinyWidgets::addSpinner()`. Assm como a função `shinycssloards::withSpinner()`, basta embrulhar suas funções `*Output()` com a função `shinyWidgets::addSpinner()` para adicionar a animação às suas visualizações. +O pacote `shinyWidgets` é mantido pela equipe da [dreamRs](https://github.com/dreamRs). Além de diversos widgets muito úteis, ele possui a função `shinyWidgets::addSpinner()`. Assim como a função `shinycssloards::withSpinner()`, basta embrulhar suas funções `*Output()` com a função `shinyWidgets::addSpinner()` para adicionar a animação às suas visualizações. São diversas opções de animação, escolhidas por meio do argumento `spin`. Aqui você pode customizar apenas a cor delas. Rode o app a seguir para ver um exemplo. @@ -417,7 +419,7 @@ fluidPage( ) ``` -Mesmo um exemplo simples já começa a deixar claro o problema: produzir muitos elementos HTML na UI rapidamente transforma seu código em um emaranhado de funções aninhadas. O mesmo vale para textos muito grandes. Embora nesse caso nem sempre tenhámos muitas tags HTML para criar, scripts R não foram feitos para atender os cuiddos que textos carecem. +Mesmo um exemplo simples já começa a deixar claro o problema: produzir muitos elementos HTML na UI rapidamente transforma seu código em um emaranhado de funções aninhadas. O mesmo vale para textos muito grandes. Embora nesse caso nem sempre tenhamos muitas tags HTML para criar, scripts R não foram feitos para atender os cuidados que textos carecem. A melhor prática nessas situações é justamente transferir esses elementos para um arquivo Markdown e pedir que o Shiny o transforme em HTML e o inclua no lugar adequado apenas na hora do `runApp()`. Para isso usamos a função `shiny::includeMarkdown()`. @@ -435,3 +437,10 @@ ui <- fluidPage( ``` Vale ressaltar que essa função compila arquivos Markdown (`.md`) e não R Markdown (`.Rmd`). Se quisermos rodar códigos R para gerar saídas HTML, devemos fazer isso dentro do próprio Shiny. + + + + + + + diff --git a/shiny-na-pratica-2.qmd b/shiny-na-pratica-2.qmd index 85d13ad..d4ae383 100644 --- a/shiny-na-pratica-2.qmd +++ b/shiny-na-pratica-2.qmd @@ -4,7 +4,7 @@ ## Tooltips -*Tooltips* são uma ótima maneira de se comunicar com a pessoa utilizando o seu app. Elas permitem passar todo tipo de informação extra e não oculpam espaço da UI. +*Tooltips* são uma ótima maneira de se comunicar com a pessoa utilizando o seu app. Elas permitem passar todo tipo de informação extra e não ocupam espaço da UI. Essencialmente, elas são textos que só aparecem quando passamos o ponteiro do mouse em algum elemento da tela. Por exemplo, passe o mouse em cima da frase a seguir: @@ -216,7 +216,7 @@ shinyWidgets::radioGroupButtons( ``` -Agora, precisamos construir a lógina do Output, tanto na UI como no server. Como a nossa visualização pode gerar gráficos ou uma tabela, precisaremos de funções `*Output()` e `render*()` diferentes. Dessa forma, vamos utilizar na nossa UI um `OutputUI()`. +Agora, precisamos construir a lógica do Output, tanto na UI como no server. Como a nossa visualização pode gerar gráficos ou uma tabela, precisaremos de funções `*Output()` e `render*()` diferentes. Dessa forma, vamos utilizar na nossa UI um `OutputUI()`. ```{r, eval = FALSE} # A nossa UI ficará assim diff --git a/sobre.qmd b/sobre.qmd index 2f981a4..2d33806 100644 --- a/sobre.qmd +++ b/sobre.qmd @@ -2,7 +2,7 @@ Este livro foi escrito em [R Markdown](https://rmarkdown.rstudio.com/), com o pacote [bookdown](https://bookdown.org/), e está em construção e em revisão aberta. Fique à vontade para corrigir qualquer tipo de erro que encontrar criando *issues* [neste repositório](https://github.com/curso-r/livro-shiny). -O texto deste livro foi eleborado por [William Amorim](https://wamorim.com/) com colaboração do time da [Curso-R](https://www.curso-r.com/). Ele está disponível gratuitamente no Github sob a licença [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). +O texto deste livro foi elaborado por [William Amorim](https://wamorim.com/) com colaboração do time da [Curso-R](https://www.curso-r.com/). Ele está disponível gratuitamente no Github sob a licença [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). A Curso-R é o braço de Educação da R6 Consultoria, uma empresa de treinamentos e consultoria em Ciência de Dados e linguagem de programação R.