Nesta série de posts, estamos apresentamos a todos a nossa primeira tentativa de escrever um livro: O Zen do R! Durante as últimas semanas, todas as quartas, trouxemos para o nosso blog os capítulos que já escrevemos do livro e respondemos qualquer pergunta sobre o conteúdo.
Este é o penúltimo capítulo, mas provavelmente é o mais importante! Falamos sobre pacotes: por que e como fazê-los.
Pacotes
Nas palavras do maior guru do R, Hadley Wickham, “pacotes são a unidade
fundamental de código R reprodutível”. Toda vez que você utiliza a função
library()
, algum pacote está sendo carregado na sessão. Muitas vezes criar uma
biblioteca de funções pode parecer uma tarefa árdua e confusa, restrita a
grandes conhecedores da linguagem, mas essa impressão não poderia estar mais
distante da realidade: pacotes para o R são bastante simples e intuitivos de
fazer.
No início deste livro foi abordado o conceito de projeto. Ele não passa de um
arquivo .Rproj
que indica para o RStudio que aquele diretório é um ambiente de
trabalho estruturado. Nesse sentido, pacotes iguais a projetos porque eles
também têm um .Rproj
; pacotes na verdade são projetos.
A diferença entre os dois é que pacotes podem ser documentados e instalados, permitindo toda uma gama de novas possibilidades para o programador. Muitas vezes uma análise de dados pode envolver dezenas de funções e diversas pessoas, fazendo com que o compartilhamento de código seja vital para que a análise não fuja do controle. Pacotes permitem gerenciar dependências, manter documentação, executar testes unitários e muito mais com o objetivo de deixar todos os analistas na mesma página.
Sendo assim, recomenda-se criar um pacote para qualquer análise que envolva pelo
menos meia dúzia de funções complexas e mais de uma pessoa; caso contrário, um
projeto já é suficiente. Outra motivação para criar um pacote é compartilhar
conjuntos úteis de funções com outras pessoas; isso acaba sendo menos comum para
a maioria dos usuários, mas é importante ressaltar que o R não seria a linguagem
popular que é hoje se não fossem pelas famosas bibliotecas ggplot2
e dplyr
.
usethis::create_package("~/Documents/demo")
#> ✔ Setting active project to '~/Documents/demo'
#> ✔ Creating 'R/'
#> ✔ Writing 'DESCRIPTION'
#> Package: demo
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#> * First Last <first.last@example.com> [aut, cre]
#> Description: What the package does (one paragraph).
#> License: What license it uses
#> Encoding: UTF-8
#> LazyData: true
#> ✔ Writing 'NAMESPACE'
#> ✔ Writing 'demo.Rproj'
#> ✔ Adding '.Rproj.user' to '.gitignore'
#> ✔ Adding '^demo\\.Rproj$', '^\\.Rproj\\.user$' to '.Rbuildignore'
#> ✔ Opening '~/Documents/demo/' in new RStudio session
#> ✔ Setting active project to 'demo'
A função executada acima é exatamente análoga à função de criação de projetos.
A principal diferença é que ela cria um arquivo DESCRIPTION
e assume que o
nome do pacote é igual ao nome da pasta onde o mesmo está sendo criado (neste
caso, “demo”). Alguns outros arquivos também são criados (como .Rbuildignore
e
NAMESPACE
), mas eles não vêm ao caso. De resto, o pacote é idêntico a um
projeto e pode ser sincronizado com o Git exatamente da mesma maneira.
O primeiro passo para começar a usar um pacote é atribuir a ele uma licença (caso um dia você resolva compartilhá-lo com o mundo) e preencher a descrição. Abaixo encontra-se uma função simples que adiciona uma licença MIT ao pacote.
usethis::use_mit_license("Seu Nome")
#> ✔ Setting active project to '~/Documents/demo'
#> ✔ Setting License field in DESCRIPTION to 'MIT + file LICENSE'
#> ✔ Writing 'LICENSE.md'
#> ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
#> ✔ Writing 'LICENSE'
O arquivo de descrição, no entanto, é um pouco mais complexo porque ele tem
alguns campos que precisam ser preenchidos manualmente. Quando o pacote for
criado, eles já estarão populados com instruções para facilitar a vida do
programador. Abaixo está um exemplo de como DESCRIPTION
deve ficar depois de
completo:
Package: demo
Title: O Que o Pacote Faz (Uma Linha)
Version: 0.0.0.9000
Authors@R:
person(given = "Seu",
family = "Nome",
role = c("aut", "cre"),
email = "seunome@dominio.com")
Description: O que o pacote faz (um paragrafo curto terminado em ponto final).
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
A partir deste ponto, os metadados do pacote estão essencialmente prontos e não
precisam mais ser modificados. Assim como em um projeto, o que resta é adicionar
arquivos com funções à pasta R/
.
Documentação
Para poder programar pacotes com mais facilidade, é necessário instalar o
devtools
. Assim como o tidyverse
, este é um conjunto de pacotes (que inclui
o usethis
por sinal) que auxiliam no processo de criar e testar um pacote de
R.
install.packages("devtools")
A partir de agora você pode, por exemplo, criar documentações para as funções do
seu pacote. Quando outras pessoas o instalarem, elas poderão consultar esses
manuais da mesma forma que fazem com qualquer outra função: ?funcao()
.
A documentação mais simples (e obrigatória) envolve dar um título para a função
e descrever o que cada parâmetro significa. Para documentar uma função qualquer,
basta adicionar comentários em cima dela com #'
assim como no exemplo abaixo:
#' Função demonstrativa que soma e imprime
#'
#' @param x Um número ou vetor numérico
#' @param y Um número ou vetor numérico
#' @param ... Outros argumentos passados para [print()]
#'
#' @export
funcao_demo <- function(x, y, ...) {
z <- x + y
print(z, ...)
return(z)
}
No RStudio esse tipo de documentação é tratado diferentemente de outros
comentários, então certas palavras-chave ficam coloridas. @param
por exemplo
indica a documentação de um dos parâmetros e @export
indica que aquela função
será exportada pelo pacote, ou seja, ficará disponível ao usuário quando ele
executar library(demo)
.
Para gerar a documentação do pacote, basta chamar uma outra função do
devtools
:
devtools::document()
#> Updating demo documentation
#> Updating roxygen version in ~/Documents/demo/DESCRIPTION
#> Writing NAMESPACE
#> Loading demo
#> Writing NAMESPACE
#> Writing funcao_demo.Rd
?funcao_demo()
#> Rendering development documentation for 'funcao_demo'
Conforme o número de funções no pacote for crescendo, basta iterar nesse ciclo
descrito até aqui. Além disso, é importante lembrar (como destacado na sessão
anterior) que qualquer função utilizada de outro pacote deve ser invocada na
forma pacote::funcao()
; neste momento, o pacote em questão se tornará uma
dependência do seu pacote e deve ser declarado como tal com
usethis::use_package("pacote")
.
Para garantir que o R não encontrará nenhum problema no seu pacote, basta
executar a função de verificação devtools::check()
. Se nenhum defeito for
encontrado, basta compartilhar o pacote com os seus colegas e instalá-lo com
devtools::install_local()
.
devtools::check()
#> Updating demo documentation
#> Writing NAMESPACE
#> Loading demo
#> Writing NAMESPACE
#> ── Building ───────────────────────────────────────────────────────── demo ──
#> Setting env vars:
#> ● CFLAGS : -Wall -pedantic -fdiagnostics-color=always
#> ● CXXFLAGS : -Wall -pedantic -fdiagnostics-color=always
#> ● CXX11FLAGS: -Wall -pedantic -fdiagnostics-color=always
#> ─────────────────────────────────────────────────────────────────────────────
#> ✔ checking for file ‘/home/clente/Documents/demo/DESCRIPTION’ ...
#>
#> [... omitido por brevidade ...]
#>
#> ── R CMD check results ───────────────────────────────── demo 0.0.0.9000 ────
#> Duration: 8.2s
#>
#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔
Testes automatizados
Antes de concluir a sessão sobre pacotes, se faz necessária uma breve menção aos
testes automatizados. Eles são disponibilizados pelo pacote testthat
e
permitem que um programador verifique que seu código está atendendo às
especificações. Testes unitários garantem que uma alteração pontual em algum
ponto do código não vai alterar o comportamento de nenhuma outra parte, já que
as outras funções ainda terão que passar nos seus próprios testes.
Para criar um conjunto de testes é necessário primeiro criar o ambiente para tal dentro do pacote. Depois disso, basta criar conjuntos individuais de testes para cada função.
usethis::use_testthat()
#> ✔ Adding 'testthat' to Suggests field in DESCRIPTION
#> ✔ Creating 'tests/testthat/'
#> ✔ Writing 'tests/testthat.R'
#> ● Call `use_test()` to initialize a basic test file and open it for editing.
usethis::use_test("funcao_demo")
#> ✔ Increasing 'testthat' version to '>= 2.1.0' in DESCRIPTION
#> ✔ Writing 'tests/testthat/test-funcao_demo.R'
#> ● Modify 'tests/testthat/test-funcao_demo.R'
Como é possível notar, o pacote testthat
permite criar um arquivo de testes
para funcao_demo()
(neste caso tests/testthat/test-funcao_demo.R
). Esse
arquivo já vem com um teste padrão a título de demonstração, mas, depois de
reescrito manualmente, um possível conjunto de testes para funcao_demo()
seria
o seguinte:
library(demo)
test_that("funcao_demo funciona", {
expect_equal(funcao_demo(1, 2), 3)
expect_equal(funcao_demo(-1, -2), -3)
expect_equal(funcao_demo(1, -2), -1)
expect_output(funcao_demo(1, 2), "3")
})
E o resultado da execução dos testes é o seguinte:
devtools::test()
#> Loading demo
#> Testing demo
#> ✔ | OK F W S | Context
#> ✔ | 4 | funcao_demo
#>
#> ══ Results ══════════════════════════════════════════════════════════════════
#> OK: 4
#> Failed: 0
#> Warnings: 0
#> Skipped: 0
#>
#> Keep up the good work.