knitr::opts_chunk$set(
  collapse = TRUE, 
  out.width = "60%", out.height = "60%",
  fig.retina = 2
)

O fluxo da ciência de dados se encerra na parte de comunicação. Essa parte é responsável por transferir todo o conhecimento gerado durante a análise de dados. E isso não é nada fácil!!

knitr::include_graphics("figures/data-science-communicate.png")

A forma de construir a comunicação depende muito do interlocutor. Por exemplo, se a pessoa que vai receber a comunicação é um técnico como você, é importante elaborar um documento que deixe claro o que você fez e como você chegou nos resultados. Por outro lado, se a pessoa que vai receber seus trabalhos for um tomador de decisão, o desafio é passar os resultados de uma forma intuitiva e impactante.

Mas, e se o interlocutor for um robô? Qual a melhor forma de se comunicar com uma máquina?

Nesse material trabalhamos o conceito e aplicação de Application Programming Interfaces, ou APIs. Essa é a forma que as máquinas se comunicam nos dias de hoje.

Pré-requisitos

Esse material assume que você sabe usar dplyr e o %>%, que sabe o que é o RStudio e rmarkdown. Se você não sabe usar essas coisas, veja os materiais em http://material.curso-r.com/pipe e http://material.curso-r.com/manip.

Também seria interessante ter uma noção de pacotes em R e a utilização do curl (via terminal).

O que é uma API?

Para explicar APIs, podemos utilizar a analogia do Matrix. Tem aquela cena:

knitr::include_graphics('figures/kungfu.gif')

O que o Neo fez aqui? Ele conectou com uma máquina, setou no computador o que ele queria aprender, e puxou um curso de Kung Fu.

Agora, imagine que ao invés de um curso de Kung Fu, você queira saber o resultado de um modelo preditivo. Você faz o upload de uma base de dados, e recebe um score, por exemplo, um score de crédito. Ou então você recebe um dado de um servidor de banco de dados. Ou, ainda, você quebra um CAPTCHA. Melhor do que aprender Kung Fu r paste(rep(emo::ji("smile"), 3), collapse = '').

Hoje em dia, a maioria das grandes empresas disponibilizam APIs para análise de dados. Twitter, Facebook, Spotify, Uber e Instagram são apenas algumas delas. Num mundo dinâmico como conhecemos hoje, não faz sentido construir do zero um lugar centralizador capaz de fornecer todos os serviços; os serviços precisam ser conectados, e a conexão é feita através de APIs.

Uma vantagem de usar APIs é que ela não depende de linguagens. Na verdade, só depende de uma linguagem: as requisições web. Em particularmente, é importante saber usar as requisições GET e POST.

Colocando numa casca de noz, usamos GET quando pedimos dados para da máquina e POST quando enviamos dados para a máquina. Na prática, é possível enviar dados para a máquina usando GET, e recebemos algo de volta quando usamos POST. A diferença é que o POST é mais especializado, permitindo que você passe os dados para o servidor de diversas maneiras diferentes. Além disso, o POST permite a transferência de dados de forma segura.

Nossa tarefa será montar códigos que abrem um link para uma pessoa poder chamar usando requisições GET ou POST.

No R, existem duas soluções principais para trabalhar com APIs: opencpu e plumber. A diferença entre os dois é que o opencpu é um framework mais geral e apresenta diversas formas de resolver problemas de segurança e escalabilidade. Enquanto isso, o plumber foi montado para ser simples.

Nesse tutorial, vamos primeiro trabalhar com o plumber, depois com o opencpu. No final, voltaremos ao plumber para uma aplicação mais prática.

Para contextualizar nossa aplicação, utilizaremos um preditor de notas dos filmes do IMDb. Para isso, utilizaremos a base de dados movies do pacote ggplot2movies. Essa base contém informações das notas e outras características de 58.788 filmes. Para mais detalhes sobre a base, rode ?movies.

Se não tiver esse pacote, rode install.packages("ggplot2movies").

library(tidyverse)
dados <- ggplot2movies::movies %>% 
  filter(!is.na(budget), budget > 0) %>% 
  select(title, year, budget, rating) %>% 
  arrange(desc(year))

dados

Nosso modelo tentará prever rating utilizando as seguintes variáveis:

O modelo é dado por

modelo <- lm(rating ~ budget + year, data = dados)
summary(modelo)

R^2 de 0.003, nada mal (risos). Agora, vamos montar uma função que prevê a nota de um filme com base no seu orçamento e ano.

funcao_que_preve <- function(orcamento, ano) {
  predict(modelo, newdata = data.frame(budget = orcamento, year = ano))
}

Também faremos uma função que retorna uma amostra aleatória de dez casos da base de dados

solta_10 <- function() {
  dados %>% 
    sample_n(10)
}

Plumber

Para criar uma API com o plumber, você precisa de três coisas:

Por exemplo, para a função solta_10(), bastaria criar um script R/meuscript.R com o conteúdo abaixo

#* @get /solta
solta_10 <- function() {
  dados %>% 
    sample_n(10)
}

Após criar esse R/meuscript.R, basta rodar

p <- plumber::plumb('R/meuscript.R')
p$run(port = 8888)
Starting server to listen on port 8888

No momento que você rodou p$run(), a sua sessão do R ficou ocupada. O que esse código está fazendo é servir a sua função! Ela será acessível pelo seu próprio computador, através de http://localhost:8888. Para testar, abra uma aba de terminal no RStudio e rode

curl "http://localhost:8888/solta"

No lugar do curl (terminal), você poderia abrir uma outra sessão do R e rodar:

httr::GET("http://localhost:8888/solta")

Note que chamamos a função pelo nome que está escrito em #* @get ....

Você também pode testar a solução copiando o IP do seu servidor e substituir no lugar do localhost. Pronto! você fez uma API.

Agora, vamos fazer uma API que aceita uma requisição POST. Basta usar a mesma abordagem:

#* @post /prever
funcao_que_preve <- function(orcamento, ano) {
  d <- data.frame(budget = as.numeric(orcamento), year = as.numeric(ano))
  predict(modelo, newdata = d)
}

Novamente, basta adicionar o código no script R/meuscript.R e rodar com plumb.

p <- plumber::plumb('R/meuscript.R')
p$run(port = 8888)

Agora, você pode prever a nota do IMDb rodando:

curl --data "orcamento=10000&ano=1991" "http://localhost:8888/prever"

Exercícios

Entre na documentação do plumber em https://www.rplumber.io/ e leia.

  1. Lembre que também é possível chamar uma função com parâmetros usando GET! Descubra como.
  2. Crie com o plumber uma função plotar que retorna um gráfico de dispersão de budget (eixo x) e rating (eixo y). Salve a imagem rodando
curl "http://localhost:8888/plotar" > meu_plot.png

OpenCPU

O pacote opencpu usa uma lógica um pouco diferente do plumber. Para o opencpu,

Para utilizar o opencpu, primeiro teremos de criar um pacote em R. Se tiver interesse em como criar novos pacotes muito rápido, leia esse artigo, Aqui, utilizaremos o código

devtools::create('preditorIMDb')

Dentro da pasta R do pacote preditorIMDb, criaremos um arquivo funs.R com as funções definidas anteriormente em R/meuscript.R, mas com uma documentação de pacote no lugar da documentação do plumber:

#' Solta dez observações
#'
#' A partir de uma base de dados, solta 10 observações aleatórias
#'
#' @export
solta_10 <- function() {
  dplyr::sample_n(dados, 10)
}

#' Prevê o score do filme
#'
#' Com base no orçamento e no ano, solta o rating médio de um filme
#' 
#' @param orcamento Orçamento do filme
#' @param ano Ano do filme
#'
#' @export
funcao_que_preve <- function(orcamento, ano) {
  predict(modelo, newdata = data.frame(budget = orcamento, year = ano))
}

Agora precisamos carregar a documentação e instalar o pacote:

# adiciona dependencias de pacotes externos
devtools::use_package('tidyverse', pkg = 'preditorIMDb')
devtools::use_package('ggplot2movies', pkg = 'preditorIMDb')
# dados
devtools::use_data(modelo, pkg = 'preditorIMDb')
devtools::use_data(dados, pkg = 'preditorIMDb')
# documenta o pacote
devtools::document('preditorIMDb')
# instala o pacote na máquina
devtools::install('preditorIMDb')

Agora podemos rodar nossa API com base no pacote, fazendo

opencpu::ocpu_start_app('preditorIMDb')

Para ver o código da função, basta rodar

curl http://localhost:5656/ocpu/library/preditorIMDb/R/solta_10/print

Para de fato rodar o a função, se sua função não tiver parâmetros, rode

curl http://localhost:5656/ocpu/library/preditorIMDb/R/solta_10/json -X POST

Exercícios:

Entre na documentação do OpenCPU em https://www.opencpu.org/api.html

  1. O que acontece quando você roda
curl http://localhost:5656/ocpu/library/preditorIMDb/R/solta_10 -X POST

? Explique cada uma dessas saídas

/ocpu/tmp/x0ed707c4a1/R/solta_10
/ocpu/tmp/x0ed707c4a1/R/.val
/ocpu/tmp/x0ed707c4a1/source
/ocpu/tmp/x0ed707c4a1/console
/ocpu/tmp/x0ed707c4a1/info
/ocpu/tmp/x0ed707c4a1/files/DESCRIPTION
  1. Descubra como rodar uma requisição POST com parâmetros e rode funcao_que_preve(10000, 2004).

OpenCPU gratuito!

O opencpu é mais do que um pacote em R! Se você olhar no site do produto, verá que um esforço enorme foi envidado por seu autor, Jeroen Ooms para desenvolver não só o pacote como também um software para servir vários aplicativos feitos em R. Com o software opencpu, basta você criar vários pacotes com soluções para que elas sejam imediatamente utilizáveis na forma de APIs.

Já pensou se existisse algum ser generoso que criasse um servidor com todos os pacotes do CRAN e disponibilizasse esse recurso online e de graça para todos? Muito bem, essa pessoa existe, e trata-se do próprio Jeroen!

Assim, basta acessar http://cran.ocpu.io para ter todos os pacotes do R em suas mãos. Por exemplo, podemos rodar

r <- httr::POST("http://cran.ocpu.io/praise/R/praise/json")
httr::content(r)

(o pacote praise é um gerador de elogios aleatório)

E tem mais! Com o serviço do ocpu.io você pode vincular sua conta ao GitHub e deixar todos os seus pacotes funcionarem como APIs automaticamente. Veja em https://www.opencpu.org/cloud.html como fazer isso.

Por exemplo, o post da Curso-R chamado Aquele 1% é deep learning foi montado usando OpenCPU como gerador dos textos do Wesley Safadão.

r <- httr::POST("http://jtrecenti.ocpu.io/safadao/R/gen/json")
cat(httr::content(r)[[1]])

Plumber em produção

Agora vamos mostrar um exemplo que utilizamos em produção para quebrar captchas. Vamos estudar o repositório api da página do github https://github.com/decryptr. Trata-se de um código que carrega os modelos dos quebradores de CAPTCHA e cria funções do tipo predict_* que recebem uma imagem de CAPTCHA e retornam um texto contendo o resultado da classificação.

Por exemplo, o código abaixo, salvo em R/captcha.R serve para carregar o modelo para quebrar o CAPTCHA da receita federal.

library(magrittr)
library(decryptr)

reticulate::py_available(TRUE)
suppressMessages(suppressWarnings({
  rfb_model <- decryptrModels::read_model('rfb')
  trt_model <- decryptrModels::read_model('trt')
  tjmg_model <- decryptrModels::read_model('tjmg')
  esaj_model <- decryptrModels::read_model('esaj')
}))

#* @post /predict_tjmg
predict_tjmg <- function(img) {
  img_decoded <- base64enc::base64decode(img)
  predict(tjmg_model, newdata = decryptr::prepare(img_decoded))
}

Como vimos anteriormente, basta rodar

p <- plumber::plumb('R/captcha.R')
p$run(port = 8888)
library(decryptr)
arq <- captcha_download_tjmg()
library(decryptr)
arq <- "./captcha5fdb51a0b41b.jpeg"
arq %>% 
  read_captcha() %>% 
  plot()

```{bash eval=FALSE} (echo -n '{"img": "'; base64 "captcha5fdb51a0b41b.jpeg"; echo '"}') | (curl -s -H "Content-Type: application/json" -d @- http://localhost:8888/predict_tjmg) | sed 's/[^[:alnum:]]//g'


73563

Ou, em outra sessão do R

```r
path <- 'captcha5fdb51a0b41b.jpeg'
base64 <- base64enc::base64encode(readr::read_file_raw(path))
r <- httr::POST('http://localhost:8888/predict_tjmg', 
                body = list(img = base64), 
                encode = 'json')
httr::content(r, 'text')

Fazendo mais com o plumber

O plumber também pode ser utilizado numa solução Docker. O Docker é um serviço de containers, e um container é uma espécie de máquina virtual que roda somente a sua aplicação.

A vantagem de usar Docker é que você seria capaz de instalar sua API em qualquer servidor, sem precisar configurar muitas coisas na máquina.

Para ver como usar plumber com Docker, siga a documentação deste link: https://www.rplumber.io/docs/hosting.html#docker.



curso-r/pu.api documentation built on May 3, 2019, 4:06 p.m.