Capítulo 8 Funções

Nesse capítulo explicamos o que são as funções, como e porque usar. Também damos algumas dicas de como criar as funções.

A primeira coisa que deve ser observada é que funções não se limitam a funções matemáticas: uma função recebe alguns inputs, faz algumas operações e devolve um output. Isso pode ser tão geral quanto necessário: de funções que recebem um valor de \(x\) e retornam \(x^2+x+4\) até funções que recebem matrizes e fazem operações complicadíssimas. Por exemplo, o comando lm, que usamos para estimar uma regressão linear, é uma função que recebe a variável dependente e a variável independente e faz uma série de operações com elas e devolve os coeficientes e seus erros padrões.

Em geral, funções são criadas para tarefas que serão repetidas várias vezes. Não faz nenhum sentido escrever uma função que só será usada uma única vez.

8.1 Um exemplo simples: uma função matemática

Suponha que queremos uma função \(x^2+2x+4\), que iremos chamar de funcao. Nesse caso:

funcao <- function(x){x^2+2*x+4}

Veja que, depois de function, entre parêntese, temos o nome da variável. Entre chaves o que a função de fato faz. Podemos criar uma função $ G = x^2 + y^2$. Nesse caso, o código é:

G <- function(x,y){x^2+y^2}

Veja que podemos precisar de definir funções como essas quando queremos integrar ou derivar usando o R. Mas em geral, um exemplo mais interessante de como (e porque) usar funções são exemplos não numéricos.

8.2 Caso geral e exemplos

Em geral, funções são feitas da seguinte forma:

nome_da_funcao <- function(variaveis){
comandos
}

Veja que podemos ter muitas variáveis - e em geral as funções do R tem dezenas de variáveis. Os comandos podem ser absolutamente qualquer coisa que o R faz: de rodar uma regressão até operações com arquivos. O importante ao escrever funções - e provavelmente o mais difícil - é estruturar as coisas de maneira geral: os inputs são as variáveis da função e não coisas que estão neste momento no ambiente do R.

Existem duas coisas importantes sobre funções que devem ser explicitadas antes de seguirmos para o próximo exemplo:

  1. Variáveis podem ter defaults quando a função é construída. Por exemplo, na função \(G(x,y) = x^2+y^2\), podia ser o caso de que, exceto se o usuários especificasse ao contrário, o padrão fosse \(y = 0\). Para estabelecer essa mudança, bastaria alterar o código para:
G <- function(x,y=0){x^2+y^2}

Assim:

G(1)
## [1] 1
G(1,0)
## [1] 1
  1. Os exemplos de funções matemáticas foram muito simples e o R sabe exatamente qual resultado deve ser exibido. Isso nem sempre é verdade, e em funções mais complicadas usamos o comando return. O comando só funciona dentro de funções e diz o que a função deve retornar como resposta. No exemplo da função numérica, poderíamos ter escrito:
g <- function(x,y){return(x^2+y^2)}

Vamos aplicar essas duas ideias. Suponha que queremos gerar duas amostras de normais independentes que podem ter médias e variâncias diferentes. Também queremos poder mudar o tamanho da amostra. No fim, essa função deve retornar uma matriz \(n \times 2\), onde n é o tamanho da amostra. E ainda queremos que, por padrão, as duas variáveis sejam uma normal padrão (com média zero e variância 1). Podemos escrever:

amostrador <- function(n,media_x = 0,media_y = 0,variancia_x = 1,variancia_y = 1){
sd_x <- sqrt(variancia_x)
sd_y <- sqrt(variancia_y)
amostra.x <- rnorm(n,media_x,sd_x)
amostra.y <- rnorm(n,media_y,sd_y)
return(cbind(amostra.x,amostra.y))
}
amostrador(100)
##           amostra.x     amostra.y
##   [1,]  0.718293633  0.8600844047
##   [2,]  0.335490839 -0.6635724390
##   [3,]  0.075343566 -0.0347763790
##   [4,] -0.176943345 -0.6273394950
##   [5,] -1.399367025  0.0685600449
##   [6,]  0.824329709  2.3781543355
##   [7,] -0.825091214 -2.1556493943
##   [8,]  1.127722302 -1.1029162042
##   [9,] -0.554740916 -0.4661599986
##  [10,] -1.244439780 -1.0006704603
##  [11,] -0.138147415  0.5064026291
##  [12,]  1.479458744 -0.5979431848
##  [13,] -0.399006457 -0.5678099122
##  [14,] -0.298111446  0.5977321127
##  [15,] -0.460745195 -1.0826113473
##  [16,]  0.213153398 -0.8901032945
##  [17,]  0.421186752  0.1060644426
##  [18,]  0.123760415 -1.2325415929
##  [19,] -0.414138818  0.1624707549
##  [20,] -0.005033633 -0.0092806873
##  [21,] -0.897773702  1.2750918238
##  [22,] -0.447641572  0.7263910524
##  [23,] -0.789351119  0.8028114959
##  [24,]  0.840028977 -1.0008340604
##  [25,]  0.333345310  0.8115257895
##  [26,]  1.155416496  1.0275551784
##  [27,] -1.558740363  0.2122541549
##  [28,]  0.402093862  0.3342644371
##  [29,] -0.199965083  1.3114180333
##  [30,]  0.611341071 -1.4002469437
##  [31,] -0.199792406 -1.3343064007
##  [32,] -1.240993238  0.3327949851
##  [33,] -0.260099772 -2.0509916193
##  [34,]  0.502905052  0.2436862171
##  [35,]  0.513583267  0.8256682003
##  [36,]  0.002368356  1.1068085957
##  [37,]  0.081781460  0.9155825468
##  [38,]  0.893233840  2.0988221508
##  [39,] -0.299933476 -0.3790194478
##  [40,]  1.584847797  1.7640485807
##  [41,] -0.323163550  0.4117237831
##  [42,]  0.387489985 -0.7852152744
##  [43,]  0.593746646 -0.0253192137
##  [44,] -1.143210475 -1.4664721334
##  [45,] -0.699134922  0.8255766770
##  [46,] -0.563157663 -0.3015198950
##  [47,] -0.361755194  0.1428712879
##  [48,] -0.653561737  0.1107993982
##  [49,] -1.401316927 -1.1725290692
##  [50,]  0.060215438  0.1113076746
##  [51,]  0.269842008  0.1253386577
##  [52,]  1.082226553  0.6139166447
##  [53,]  1.739625645  0.6517269830
##  [54,] -1.195713070  1.8574078587
##  [55,]  0.017556296 -0.2771270834
##  [56,]  0.348467439 -1.1719939039
##  [57,] -0.442743053 -0.6028888749
##  [58,]  3.514987716 -1.9474615939
##  [59,]  0.268012018  0.0804247766
##  [60,] -0.533351024  1.0777988315
##  [61,]  0.100036351 -1.2256136083
##  [62,]  0.566710062  0.6093026921
##  [63,]  1.244379105  0.2619515119
##  [64,] -0.486077795 -2.1120820058
##  [65,] -0.882803939 -1.4624106153
##  [66,] -0.044538809  1.0629601542
##  [67,]  0.217141987 -0.9362078957
##  [68,]  0.082830345 -0.0009904007
##  [69,]  0.569970979  1.3052634445
##  [70,]  1.132305312  0.3289079820
##  [71,] -1.002768739 -0.3067718459
##  [72,]  1.787110564  1.6125236822
##  [73,]  0.114321186 -0.1428698182
##  [74,] -0.339733905  0.3457300380
##  [75,]  1.405884945  0.4963447043
##  [76,]  0.862362952  0.6118068059
##  [77,]  0.289956879 -0.7188252473
##  [78,]  0.372402586 -0.5769010422
##  [79,]  1.379126387  1.2464906871
##  [80,]  0.480186944  2.4390593987
##  [81,] -1.135746773 -0.1380849325
##  [82,]  0.866156568 -1.2708188670
##  [83,] -0.253460661  0.7887372997
##  [84,]  1.458718311 -0.3444853735
##  [85,] -1.218927165  0.2389165249
##  [86,]  1.075248398  1.5627028348
##  [87,] -1.441709195  1.7651224708
##  [88,] -1.063758528  0.2834147918
##  [89,] -1.168627898 -0.9832919370
##  [90,]  2.581455827 -0.6917566099
##  [91,] -0.162559882 -0.4689404239
##  [92,]  1.752839346  0.7502401100
##  [93,]  0.279718930  0.0669117622
##  [94,]  0.893623404 -0.3089511493
##  [95,]  0.647039934 -0.4952645078
##  [96,]  0.114351976 -1.7746561672
##  [97,]  0.574657439  0.4421593193
##  [98,]  1.469561653 -2.0933514087
##  [99,] -1.348346843 -0.0415687559
## [100,] -0.426303663  0.7376097284

Veja que, como o rnorm recebe o desvio padrão da distribuição e a minha função tem como parâmetro a variância da distribuição, criei uma variável na função que tira a raiz quadrada.

Atenção: checando argumentos É importante notar que em todos os casos, os argumentos de uma função são restritos de alguma forma: no exemplo acima, \(n\) tem que ser um número inteiro; a variância não pode ser negativa. Aqui, isso não é tão importante por dois motivos: (1) eu estou assumindo que só você vai usar a função e você sabe o que cada argumento recebe, (2) o rnorm daria um erro caso você violasse essas duas restrições. Isso nem sempre é verdade e pode gerar muitos problemas, então em geral desenvolvedores sérios criam ifs que checam o tipo do argumento passado. Isso vai além do escopo do manual.

8.3 Por que escrever funções?

Escrever funções nem sempre é fácil, especialmente para iniciantes. Uma função te força a escrever tudo em termos gerais: um n que não existe, um vetor que ainda não foi criado, etc. Como dito no inicio deste capítulo, funções são criadas para tarefas que serão repetidas várias vezes. Em geral, há uma tentação de copiar e colar o código várias vezes fazendo as alterações necessárias ``no braço’’.

Não caia nesta tentação! Há dois motivos básicos para isso:

  • O seu código vai ser ilegível em pouco tempo, com milhares de linhas repetidas que você é incapaz de ver qual a diferença
  • É muito fácil esquecer de mudar uma parte do código e a coisa dar erro, ou ainda pior, rodar e te dar um resultado errado.

Este autor já precisou rodar de novo várias partes de código que demoravam horas por esquecer de mudar alguma pequena coisa no código copiado e colado. Usar funções é uma maneira muito mais razoável de resolver o problema e, uma vez criada e debbugada, a dor de cabeça é muito menor. Quando o autor aprendeu R, ele foi informado de que escrever tudo em funções era o ideal e ignorou. Esta parece ser uma das muitas coisas que só se aprende cometendo o erro.

8.4 Como escrever funções

Um dos principais motivos para iniciantes no R ignorarem a sugestão da seção anterior é que escrever funções pode ser desafiador. Escrever a função exige, de antemão, que você saiba o tamanho dos vetores, matrizes, como o código irá se comportar etc. Existe uma maneira simples de mitigar este desafio: comece escrevendo o caso específico e depois generalize. No exemplo das duas amostras normais, nós poderiamos ter começado escrevendo apenas:

amostra.x <- rnorm(100,0,1)
amostra.y <- rnorm(100,2,2)

E depois ter generalizado: sabemos que 100 é o tamanho da amostra, então troque por n. Para permitir médias diferentes (como o código acima fez), colocamos duas variáveis no lugar - com nomes que evidenciam o que elas fazem. Etc. No fim, só precisamos colocar, no começo, o nome.da.funcao <- function(argumentos){e fechar com o return de maneira a amarrar as duas amostras (cbind, talvez).

Outro desafio é que o return só aceita um único objeto, ou seja, funções no R só retornam uma única coisa. Em alguns casos isso é um desafio. Voltando ao exemplo das duas amostras, return(amostra.x,amostra.y) daria um erro. A solução aqui é simples: cole os dois vetores em uma matriz. Mas nem sempre isso é possível: e se os vetores tiverem dimensão diferente? E se a função gera várias matrizes?

Nesses casos, lembre que list() recebe qualquer coisa. Você pode ter um elemento na lista que é só um número, outro que é um vetor e até uma lista! O comando lm retorna, de fato, uma lista. Dentro da lista há um vetor com os coeficientes, uma matriz de variância covariância e outras coisas.

Uma terceira dica é quebrar uma função grande em funções menores. O R permite que uma função chame outra, o que facilita em muito o processo de criação de funções complicadas. Em um exemplo extremo, poderíamos imaginar que você quer fazer uma função que faz OLS para você. Poderiamos quebrar isso em vários pedaços: uma que estima os coeficientes, outra que calcula o erro padrão, uma terceira que faz a conta do \(R^2\)…e finalmente uma última que amarra todas essas funções e devolve uma lista com os coeficientes, erros padrões e tudo mais.