Como organizar grandes programas de R?

161

Quando empreendo um projeto R de qualquer complexidade, meus scripts rapidamente ficam longos e confusos.

Quais são algumas práticas que posso adotar para que meu código sempre seja um prazer trabalhar? Estou pensando em coisas como

  • Colocação de funções nos arquivos de origem
  • Quando dividir algo em outro arquivo de origem
  • O que deve estar no arquivo mestre
  • Usando funções como unidades organizacionais (se vale a pena, pois R dificulta o acesso ao estado global)
  • Práticas de recuo / quebra de linha.
    • Tratar (como {?
    • Coloque coisas como)} em 1 ou 2 linhas?

Basicamente, quais são suas regras práticas para organizar grandes scripts R?

Dan Goldstein
fonte
11
wiki da comunidade deve ser
SilentGhost 12/08/2009
Você também pode querer olhar para o ProjectTemplatepacote.
Ctbrown

Respostas:

71

A resposta padrão é usar pacotes - consulte o manual Writing R Extensions , bem como diferentes tutoriais na Web.

Te dá

  • uma maneira quase automática de organizar seu código por tópico
  • o encoraja fortemente a escrever um arquivo de ajuda, fazendo você pensar na interface
  • um monte de verificações de sanidade via R CMD check
  • a chance de adicionar testes de regressão
  • bem como um meio para namespaces.

Apenas atropelar source()o código funciona para trechos muito curtos. Todo o resto deve estar em um pacote - mesmo que você não planeje publicá-lo, pois você pode escrever pacotes internos para repositórios internos.

Quanto à parte 'como editar', o manual R Internals possui excelentes padrões de codificação R na Seção 6. Caso contrário, eu costumo usar padrões no modo ESS do Emacs .

Atualização 13 de agosto de 2008: David Smith acabou de publicar um blog sobre o Google R Style Guide .

Dirk Eddelbuettel
fonte
8
Se você está cultivando sua árvore-fonte / análise "organicamente", não acha isso difícil / complicado? Se você perceber um erro no seu código (comum ao explorar um novo espaço problemático), precisará (i) corrigir a fonte; (ii) reinstalar o pacote; (iii) recarregá-lo em seu espaço de trabalho? Existe uma maneira de chamar a biblioteca (...) para recarregar um pacote que já está carregado (etapa iii acima)? Você não precisa matar seu espaço de trabalho, reiniciar o R ​​e recarregar sua biblioteca / pacote para ver se está correto?
Steve Lianoglou
1
Tentando pesquisar no estilo de codificação R.
Hadley
3
@SteveLianoglou Eu sei que isso é bastante antigo, mas o pacote devtools do Hadley facilita muito a recarga de todo o seu código.
Dason
1
Esse post dá uma (minha opinião) realmente bom tutorial rápido para construir um esqueleto primeiro pacote: hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch
panterasBox
1
Aqui está trabalhando link para Guia R Estilo Google
Denis Rasulev
51

Eu gosto de colocar funcionalidades diferentes em seus próprios arquivos.

Mas eu não gosto do sistema de pacotes de R. É bastante difícil de usar.

Eu prefiro uma alternativa leve, colocar as funções de um arquivo dentro de um ambiente (o que qualquer outro idioma chama de "espaço para nome") e anexá-lo. Por exemplo, eu criei um grupo de funções 'util' da seguinte forma:

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

while("util" %in% search())
  detach("util")
attach(util)

Isto é tudo em um arquivo util.R . Quando você o origina, você obtém o ambiente 'util' para que você possa ligar util$bgrep()e assim; além disso, a attach()ligação o torna tão justo bgrep()e funciona diretamente. Se você não colocasse todas essas funções em seu próprio ambiente, elas poluiriam o namespace de nível superior do intérprete (aquele que é ls()exibido).

Eu estava tentando simular o sistema do Python, onde cada arquivo é um módulo. Seria melhor ter isso, mas isso parece bom.

Brendan OConnor
fonte
Obrigado, Brendan. Isso é muito útil. O que há com o loop while? O que está errado com se anexar (util) (( "util" % em busca% ())!)
Dan Goldstein
2
para que você possa fazer o código-fonte ("util.R") repetidamente, se quiser ajustá-lo e tal.
Brendan OConnor 28/08/09
você realmente não precisa de um loop while - tudo o que precisa é desanexar (util). Não me lembro se ele dá um erro ou não, se já não está carregado, mas isso é mais seguro e funciona. sugestões bem-vindas.
Brendan OConnor 28/08/09
1
Criar ambientes específicos de funções e anexá-los é o caminho a percorrer para mim. Outro método para conseguir a mesma coisa de uma maneira diferente (com mais modularidade) é usar sys.source: MyEnv <- attach(NULL, name=s_env); sys.source(file, MyEnv). Eu até declaro (em seu próprio ambiente!) Na inicialização, uma função sys.source2que procurará se um ambiente com o mesmo nome já estiver aqui e o alimenta em vez de criar um novo. Torna a adição de funções pessoais rápida, fácil e meio organizada :-)
Antoine Lizée
5
Acabei de ver isso hoje e se aplica a uma alternativa de pacote: github.com/klmr/modules
ctbrown
34

Isso pode parecer um pouco óbvio, especialmente se você é um programador, mas eis como eu penso sobre unidades de código lógicas e físicas.

Não sei se esse é o seu caso, mas quando estou trabalhando no R, raramente começo com um grande programa complexo em mente. Normalmente começo em um script e separo o código em unidades logicamente separáveis, geralmente usando funções. O código de manipulação e visualização de dados é colocado em suas próprias funções, etc. E essas funções são agrupadas em uma seção do arquivo (manipulação de dados na parte superior, visualização, etc.). Por fim, você deseja pensar em como facilitar a manutenção do script e diminuir a taxa de defeitos.

O quão fino / grosso você faz suas funções variará e existem várias regras básicas: por exemplo, 15 linhas de código, ou "uma função deve ser responsável por executar uma tarefa identificada por seu nome", etc. Sua milhagem varia . Como o R não suporta chamada por referência, geralmente sou capaz de tornar minhas funções muito refinadas quando envolve a passagem de quadros de dados ou estruturas similares. Mas isso pode ser uma compensação excessiva por alguns erros de desempenho tolos quando eu comecei com R.

Quando extrair unidades lógicas em suas próprias unidades físicas (como arquivos de origem e agrupamentos maiores, como pacotes)? Eu tenho dois casos Primeiro, se o arquivo ficar muito grande e rolar entre unidades logicamente não relacionadas é um aborrecimento. Segundo, se eu tiver funções que possam ser reutilizadas por outros programas. Geralmente começo colocando algumas unidades agrupadas, como funções de manipulação de dados, em um arquivo separado. Eu posso então originar este arquivo de qualquer outro script.

Se você estiver implantando suas funções, precisará começar a pensar em pacotes. Eu não implanto código R na produção ou para reutilização por outras pessoas por várias razões (brevemente: a cultura organizacional prefere outros idiomas, preocupações com desempenho, GPL, etc.). Além disso, costumo refinar e adicionar constantemente às minhas coleções de arquivos de origem, e prefiro não lidar com pacotes quando faço uma alteração. Portanto, você deve verificar as outras respostas relacionadas ao pacote, como Dirk, para obter mais detalhes nesta frente.

Por fim, acho que sua pergunta não é necessariamente particular para R. Eu realmente recomendaria a leitura de Code Complete, de Steve McConnell, que contém muita sabedoria sobre esses problemas e práticas de codificação em geral.

ars
fonte
3
Comentário muito útil, ars, obrigado. Eu sou um programador, mas é bom verificar com outras pessoas. Quando você diz "Como o R não suporta chamada por referência, geralmente desconfio de tornar minhas funções muito refinadas", eu ouço você. Estou acostumado a escrever funções como ReadData (); CleanData (); Analisar dados(); GraphData (); e R torna isso complicado. Estou acordando com a ideia de que preciso usar o "source" da maneira como uso as funções em outros idiomas.
11119 Dan Goldstein
2
Você está certo, Dan. Eu me pego usando "source" dessa maneira para tarefas de preparação de conjuntos de dados, para que eu possa usar apenas um data.frame preparado em outros scripts em que a análise real é feita. Eu nunca tinha certeza se essa era uma boa prática, porque parece estranha em relação a outras línguas - mais como scripts de shell. É bom comparar anotações. )
Ars
Boa resposta e comentários. Acho que isso é particularmente irritante no R, porque muitas vezes você trabalha com dados em um formato específico, para os quais é realmente difícil escrever funções reutilizáveis. Então, você escreve um bom código funcional, mesmo sabendo que nunca será reutilizado, ou apenas escreve um código processual rápido e desagradável apenas para obter um pouco de eficiência extra com objetos grandes? Um pouco de um dilema .. R seria absolutamente incrível com chamada por referência ..
naught101 20/07/12
Bem, isso pode ser feito, mais ou menos , mas não há nenhum ganho de eficiência ...
naught101
19

Eu concordo com o conselho de Dirk! IMHO, organizar seus programas de scripts simples a pacotes documentados é, para Programação em R, como alternar do Word para o TeX / LaTeX para escrever. Eu recomendo dar uma olhada nos muito úteis Criando Pacotes R: um tutorial de Friedrich Leisch.

Paolo
fonte
6
Os pacotes parecem atraentes. No entanto, eu estava preocupado que eles pudessem ser um exagero. Não estou escrevendo código de uso geral. Muito do que estou fazendo é testar essa hipótese, testar essa hipótese, plotar isso, ajustar parâmetros de plotagem, plotar isso, remodelar os dados, plotar isso. Estou fazendo coisas que, uma vez terminadas, provavelmente nunca serão executadas novamente.
11119 Dan Goldstein
1
Nesse caso, você deve dar uma olhada no Sweave. Combina o código R com o LaTeX. Então você tem a análise e a fonte do relatório juntas.
Thierry
15

Minha resposta concisa:

  1. Escreva suas funções cuidadosamente, identificando saídas e entradas gerais suficientes;
  2. Limitar o uso de variáveis ​​globais;
  3. Use objetos S3 e, quando apropriado, objetos S4;
  4. Coloque as funções em pacotes, especialmente quando suas funções estiverem chamando C / Fortran.

Acredito que R seja cada vez mais utilizado na produção, portanto, a necessidade de código reutilizável é maior do que antes. Acho o intérprete muito mais robusto do que antes. Não há dúvida de que R é 100-300x mais lento que C, mas geralmente o gargalo está concentrado em algumas linhas de código, que podem ser delegadas ao C / C ++. Eu acho que seria um erro delegar os pontos fortes de R na manipulação de dados e análise estatística para outro idioma. Nesses casos, a penalidade de desempenho é baixa e, em qualquer caso, vale a pena a economia no esforço de desenvolvimento. Se apenas o tempo de execução fosse o problema, estaríamos todos escrevendo assembler.

gappy
fonte
11

Eu pretendo descobrir como escrever pacotes, mas não investi tempo. Para cada um dos meus miniprojetos, mantenho todas as minhas funções de baixo nível em uma pasta chamada 'functions /' e as origino em um espaço para nome separado que eu criei explicitamente.

As seguintes linhas de código criarão um ambiente chamado "myfuncs" no caminho de pesquisa, se ele ainda não existir (usando o anexo), e o preencherão com as funções contidas nos arquivos .r no meu diretório 'functions /' (usando sys.source). Eu costumo colocar essas linhas na parte superior do meu script principal destinado à "interface do usuário" a partir da qual as funções de alto nível (invocando as funções de baixo nível) são chamadas.

if( length(grep("^myfuncs$",search()))==0 )
  attach("myfuncs",pos=2)
for( f in list.files("functions","\\.r$",full=TRUE) )
  sys.source(f,pos.to.env(grep("^myfuncs$",search())))

Quando você faz alterações, sempre pode recolocá-las nas mesmas linhas ou usar algo como

evalq(f <- function(x) x * 2, pos.to.env(grep("^myfuncs$",search())))

para avaliar adições / modificações no ambiente que você criou.

É kludgey eu sei, mas evita ter que ser formal demais sobre isso (mas se você tiver a chance, eu incentivo o sistema de pacotes - espero que eu migre dessa maneira no futuro).

Quanto às convenções de codificação, essa é a única coisa que eu tenho visto em relação à estética (eu gosto e sigo livremente, mas não uso muitas chaves no R):

http://www1.maths.lth.se/help/R/RCC/

Existem outras "convenções" relacionadas ao uso de [, drop = FALSE] e <- como o operador de atribuição sugeriu em várias apresentações (geralmente na palestra) no useR! conferências, mas não acho que nenhuma delas seja rigorosa (embora o [, drop = FALSE] seja útil para programas em que você não tem certeza da contribuição esperada).

hatmatrix
fonte
6

Conte-me como outra pessoa a favor dos pacotes. Eu admito que sou muito pobre em escrever páginas de manual e vinhetas até se / quando eu precisar (isto é, ser lançado), mas isso é uma maneira realmente útil de agrupar a fonte doe. Além disso, se você levar a sério a manutenção do seu código, os pontos que Dirk menciona todos entram em plya.

geoffjentry
fonte
4

Eu também concordo. Use a função package.skeleton () para começar. Mesmo que você ache que seu código nunca poderá ser executado novamente, isso pode ajudar a motivá-lo a criar um código mais geral que pode economizar seu tempo mais tarde.

Quanto ao acesso ao ambiente global, isso é fácil com o operador << -, embora seja desencorajado.

cameron.bracken
fonte
3

Como ainda não aprendi a escrever pacotes, sempre me organizei fornecendo sub-scripts. É semelhante a escrever aulas, mas não tão envolvido. Não é programaticamente elegante, mas acho que desenvolvo análises ao longo do tempo. Depois de ter uma seção grande que funciona, geralmente a movo para um script diferente e apenas a origino, pois ela usará os objetos da área de trabalho. Talvez eu precise importar dados de várias fontes, classificar todas elas e encontrar as interseções. Eu poderia colocar essa seção em um script adicional. No entanto, se você deseja distribuir seu "aplicativo" para outras pessoas, ou ele usa alguma entrada interativa, um pacote provavelmente é uma boa rota. Como pesquisador, raramente preciso distribuir meu código de análise, mas muitas vezes preciso aumentá-lo ou ajustá-lo.

kpierce8
fonte
Eu usei esse método, mas desde então percebi que funções e pacotes são melhores que o source ("next_script.R"). Eu escrevi sobre isso aqui: stackoverflow.com/questions/25273166/…
Arthur Yip
1

Também tenho procurado o Santo Graal do fluxo de trabalho certo para montar um projeto de grande porte. Eu encontrei no ano passado este pacote chamado rsuite e, certamente, era o que eu procurava. Este pacote R foi desenvolvido explicitamente para a implantação de grandes projetos de R, mas descobri que ele pode ser usado para projetos R menores, médios e grandes. Vou dar links para exemplos do mundo real em um minuto (abaixo), mas primeiro eu quero explicar o novo paradigma de construção de projectos de I com rsuite.

Nota. Não sou o criador ou desenvolvedor do rsuite.

  1. Temos feito projetos errados com o RStudio; o objetivo não deve ser a criação de um projeto ou pacote, mas de um escopo maior. No rsuite, você cria um superprojeto ou projeto mestre, que mantém os projetos e pacotes R padrão, em todas as combinações possíveis.

  2. Por ter um superprojeto R, você não precisa mais do Unix makepara gerenciar os níveis mais baixos dos projetos R; você usa scripts R na parte superior. Deixe-me te mostrar. Ao criar um projeto mestre do rsuite, você obtém esta estrutura de pastas:

insira a descrição da imagem aqui

  1. A pasta Ré onde você coloca seus scripts de gerenciamento de projetos, os que serão substituídos make.

  2. A pasta packagesé a pasta onde rsuitecontém todos os pacotes que compõem o superprojeto. Você também pode copiar e colar um pacote que não está acessível na Internet, e o rsuite também o compilará.

  3. a pasta deploymenté onde rsuitegravará todos os binários do pacote que foram indicados nos DESCRIPTIONarquivos dos pacotes . Assim, isso faz, por si só, você projetar um tempo acumulado totalmente reproduzível.

  4. rsuitevem com um cliente para todos os sistemas operacionais. Eu testei todos eles. Mas você também pode instalá-lo como um addinpara o RStudio.

  5. rsuitetambém permite criar uma condainstalação isolada em sua própria pasta conda. Este não é um ambiente, mas uma instalação física do Python derivada do Anaconda em sua máquina. Isso funciona em conjunto com os R's SystemRequirements, dos quais você pode instalar todos os pacotes Python desejados, a partir de qualquer canal conda que desejar.

  6. Você também pode criar repositórios locais para extrair pacotes R quando estiver offline ou desejar criar a coisa toda mais rapidamente.

  7. Se desejar, também é possível criar o projeto R como um arquivo zip e compartilhá-lo com os colegas. Ele será executado, desde que seus colegas tenham a mesma versão R instalada.

  8. Outra opção é construir um contêiner de todo o projeto no Ubuntu, Debian ou CentOS. Portanto, em vez de compartilhar um arquivo zip com a construção do seu projeto, você compartilha todo o Dockercontêiner com o seu projeto pronto para execução.

Eu tenho experimentado muito rsuiteprocurando a reprodutibilidade total e evito depender dos pacotes que se instala no ambiente global. Isso está errado porque, assim que você instala uma atualização de pacote, o projeto, mais frequentemente, para de funcionar, especialmente aqueles pacotes com chamadas muito específicas para uma função com determinados parâmetros.

A primeira coisa que comecei a experimentar foi com e- bookdownbooks. Eu nunca tive a sorte de ter um livro para sobreviver ao teste do tempo por mais de seis meses. Então, o que fiz foi converter o projeto original de bookdown para seguir a rsuiteestrutura. Agora, não preciso me preocupar em atualizar meu ambiente R global, porque o projeto possui seu próprio conjunto de pacotes na deploymentpasta.

A próxima coisa que fiz foi criar projetos de aprendizado de máquina, mas no rsuitecaminho. Um mestre, orquestrando o projeto na parte superior e todos os subprojetos e pacotes para estar sob o controle do mestre. Realmente muda a maneira como você codifica com R, tornando-o mais produtivo.

Depois disso, comecei a trabalhar em um novo pacote chamado rTorch. Isso foi possível, em grande parte, por causa de rsuite; permite que você pense e se torne grande.

Um conselho embora. Aprender rsuitenão é fácil. Por apresentar uma nova maneira de criar projetos de R, é difícil. Não desanime nas primeiras tentativas, continue subindo a ladeira até você conseguir. Requer conhecimento avançado do seu sistema operacional e do seu sistema de arquivos.

Espero que um dia RStudionos permita gerar projetos de orquestração, como rsuiteacontece no menu. Seria maravilhoso.

Ligações:

Repo RSuite GitHUb

bookdown r4ds

keras e tutorial brilhante

moderndive-book-rsuite

interpretable_ml-rsuite

IntroMachineLearningWithR-rsuite

clark-intro_ml-rsuite

hyndman-bookdown-rsuite

statistics_rethinking-rsuite

fread-benchmarks-rsuite

dataviz-rsuite

tutorial-segmentação-h2o-varejo

telco-customer-churn-tutorial

sclerotinia_rsuite

f0nzie
fonte
-7

R é bom para uso interativo e pequenos scripts, mas eu não o usaria para um programa grande. Eu usaria uma linguagem mainstream para a maior parte da programação e a envolvia em uma interface R.

John D. Cook
fonte
1
Existem pacotes seriamente grandes (ou seja, programas) por aí. Você está sugerindo seriamente que eles sejam reescritos em outro idioma? Por quê???
11339 Eduardo Leoni
4
Uma consideração é a eficiência. Eu sempre reescrevi o código R como código C ++ e o tornei 100x mais rápido. Outro é o suporte de ferramentas. R não tem nada comparável a IDEs como Eclipse ou Visual Studio. Finalmente, se um programa é muito grande, é provável que esteja executando tarefas não estatísticas às quais R não é adequado.
3111 John D. Cook
2
Existe um plug-in (Stat-ET) disponível que permite que o Eclipse interaja com R. Concordo que o C ++ possa funcionar muito mais rápido que o R. Mas quanto tempo você precisa para recodificar o material do R no C ++? A menos que você possa reutilizar o código com frequência, o benefício do código mais rápido não vale muito em comparação com o esforço para recodificá-lo em C ++.
Thierry
2
Sim, há uma troca (produtividade x desempenho). E para análise puramente de dados / trabalho estatístico, R geralmente vence. Mas, para escrever outras tarefas, por exemplo, GUI, web, etc, não tenho certeza de que seja esse o caso. Costumamos criar protótipos e trabalhar em R, mas implantamos o código de produção em Python / C ++. Com o último, você obtém desempenho e bibliotecas / estruturas muito maduras e reutilizáveis ​​para várias tarefas. Mas, esta é uma situação fluida e o ecossistema R está em constante evolução.
ars
O uso do Rcpppacote, incluindo o código C ++ nos programas R, torna-se bastante simples. Portanto, a reescrita de certas partes do código R pode ser integrada ao R com bastante facilidade. Além disso, o advento do RStudio introduziu um IDE para R, embora talvez ainda não seja tão poderoso quanto o Visual Studio.
Paul Hiemstra