Quando confiar nas suas previsões?


Quando você deve confiar em suas previsões? Como um amigo meu já disse, a resposta para essa questão é fácil: nunca (ou quase nunca).

Mas, brincadeiras à parte, para este post fazer sentido, vou reformular a pergunta: quando você deve desconfiar ainda mais das previsões do seu modelo?

Há várias situações em que isso ocorre, ilustremos aqui uma delas.

***

Imagine que você tenha as seguintes observações de x e y.

unnamed-chunk-1-1

 

Para modelar os dados acima, vamos usar uma técnica de machine learning chamada Suport Vector Machine com um núcleo radial. Se você nunca ouviu falar disso, você pode pensar na técnica, basicamente, como uma forma genérica de aproximar funções.

Será que nosso modelo vai fazer um bom trabalho?

unnamed-chunk-3-1

 

Pelo gráfico, é fácil ver que nossa aproximação ficou bem ajustada! Para ser mais exato, temos um R2 de 0.992 estimado por cross validation (que é uma estimativa do ajuste fora da amostra – e é isso o que importa, você não quer saber o quão bem você fez overfitting dos dados!).

Agora suponha que tenhamos algumas observações novas, isto é, observações nunca vistas antes. Só que essas observações novas serão de dois “tipos”, que aqui criativamente chamaremos de tipo 1 e tipo 2. Enquanto a primeira está dentro de um intervalo de x que observamos ao “treinar” nosso modelo, a segunda está em intervalos muito diferentes.

unnamed-chunk-4-1

Qual tipo de observação você acha que teremos mais dificuldades de prever, a de tipo 1 ou tipo 2? Você já deve ter percebido onde queremos chegar.

Vejamos, portanto, como nosso modelo se sai agora:

unnamed-chunk-5-1

Note que nas observações “similares” (tipo 1) o modelo foi excelente, mas nas observações “diferentes” (tipo 2) nós erramos – e erramos muito. Este é um problema de extrapolação.

Neste caso, unidimensional, foi fácil perceber que uma parte dos dados que gostaríamos de prever era bastante diferente dos dados que usamos para modelar. Mas, na vida real, essa distinção pode se tornar bastante difícil. Uma complicação simples é termos mais variáveis. Imagine um caso com mais de 20 variáveis explicativas – note que já não seria trivial determinar se novas observações são similares ou não às observadas!

Quer aprofundar mais um pouco no assunto? Há uma discussão legal no livro do Max Kuhn, que já mencionamos aqui no blog.

Excel, csv e C++ no R. Livro do Alvin Roth, Nova biografia de Steve Jobs. PCO e liberdade de expressão.


Alguns links interessantes:

R

O pessoal do RStudio não para de trabalhar:

Novo pacote (readr) para ler arquivos de texto (csv e similares) no R;

Novo pacote (readxl) para ler arquivos do Excel no R;

Novo pacote (dygraphs) para fazer gráficos interativos de séries temporais no R usando JavaScript; e

O novo RStudio está ficando cada vez mais poderoso: agora tem uma série de recursos novos para C++ como code completion, diagnóstico de sintaxe e source interativo.

Livros

– O Nobel Alvin Roth irá lançar um novo livro para o público geral: Who Gets What — and Why: The New Economics of Matchmaking and Market Design. O livro está em pré-venda, previsto para sair em junho.

– Nova biografia de Steve Jobs está tendo uma boa repercussão no público e na crítica: Becoming Steve Jobs: The Evolution of a Reckless Upstart into a Visionary Leader.

Para finalizar

– Ainda estou na dúvida se é sério, mas o PCO tem um texto  – aparentemente de verdade – defendendo a liberdade de expressão:

Levy Fidelix é um político de direita e inimigo da luta dos homossexuais, e seu discurso foi um ataque direito aos direitos democráticos, mas a condenação dele não é uma vitória da luta pelas liberdades democráticas (…) A multa de um milhão de reais como penalidade para um candidato expressar a sua opinião política em um debate de campanha eleitoral é uma gravíssimo precedente contra a já limitada possibilidade de livre expressão. A crença de que a justiça está do lado do progresso social e da democracia e, por este motivo, podemos dar a ela poderes discricionários, é não só equivocada, como é uma completa cegueira política (…) Nessas condições, é uma política suicida, já não digamos deixar de denunciar estes abusos, mas principalmente aplaudi-los e confundir a consciência das massas, chamando-as apoiar medidas antidemocráticas apenas porque atingem este ou aquele elemento reacionário. (…) A liberdade de expressão, completa e irrestrita, é uma condição sine qua non para a existência das outras liberdades democráticas, ela é uma liberdade que engloba toda a sociedade e que precede todas as liberdades individuais. (…) Existe uma crescente campanha para solidificar o “crime de opinião” o crime onde você pensa algo que alguns não gostaram e naturalmente você é culpado, onde a sua liberdade está reboque da opinião pública, e se sua opinião tem de ter “selo de aprovação” da opinião pública formada pelos monopólios de comunicação e pela burguesia, a opinião própria já está proibida.

 

Competições de análise de dados: BoE e Kaggle


Quer mostrar suas habilidades de visualização de dados ou previsão? Seguem dois links:

Uma competição de visualização do Bank of England. Na verdade, a primeira competição deste tipo que o BoE lança. O prazo final é primeiro de maio. A final da competição ocorrerá em Londres e o BoE não pagará passagens para os finalistas (mas, se eu fosse você, tentaria chegar na final antes de decidir se isso será um problema). O prêmio é de 5.000 libras (mais de R$ 20.0000).

– Um site sobre o qual sempre quis falar mais detalhadamente por aqui, mas ainda não tive tempo, é o Kaggle. Resumidamente,  o Kaggle é um site de competições de modelagem preditiva em que as empresas colocam os problemas que gostariam de  solucionar (juntamente com um prêmio) e analistas de todo o mundo competem para produzir os melhores modelos. Atualmente há dois grandes prêmios sendo disputados:

  1. US$ 100.000,00 para quem criar o melhor modelo preditivo para sinais de retinopatia diabética com imagens do olho.
  2. US$ 30.000,00 para quem criar o melhor modelo preditivo para faturamento de restaurantes.

Além de outros prêmios de menor montante. Não somente isso, participantes do Kaggle que conseguem boas classificações também conseguem, em geral, bons empregos na área.

 

 

 

Funções: Definição, argumentos e operadores binários


***

Parte do livro Introdução à análise de dados com R. Este trabalho está em andamento, o texto é bastante preliminar e sofrerá muitas alterações. As partes do livro não estão sendo publicadas na ordem. Volte para ver atualizações.

Quer fazer sugestões? Deixe um comentário abaixo ou, se você sabe utilizar o github, acesse aqui. Não copie ou reproduza este material sem autorização.

***

Por que funções?

Uma das grandes vantagens de usar uma linguagem de programação é automatizar o seu trabalho ou análise. Você será capaz de realizar grande parte do trabalho utilizando as funções internas do R ou de pacotes de terceiros em um script. Entretanto, você ganha ainda mais flexibilidade e agilidade criando suas próprias funções.

Vejamos um exemplo de script:

# vetor de precos em formato de texto ao invés de numérico
# e com registros errados (0.1 e 10000000)
# queremos converter para numérico, retirar os dados discrepantes
# dividir por 1000 e arredondar o resultado
precos <- c("0.1", "1250.55", "2346.87", "3467.40", "10000000")
precos <- as.numeric(precos)
precos <- precos[!(precos < 1 | precos > 10000)]
precos <- precos/1000
precos <- round(precos)
precos
## [1] 1 2 3

Nosso script faz o trabalho corretamente. Mas imagine que você queira realizar o mesmo procedimento com um vetor de preços diferente, digamos, precos2. Da forma como o seu código foi feito, você terá que copiar e colar os comandos e substituir os nomes.

# novo vetor de precos
precos2 <- c("0.0074", "5547.85", "2669.98", "8789.45", "150000000")
precos2 <- as.numeric(precos2)
precos2 <- precos2[!(precos2 < 1 | precos2 > 10000)]
precos2 <- precos2/1000
precos2 <- round(precos2)
precos2
## [1] 6 3 9

Note como isto é ineficiente. Além de ter que repetir todo o seu código para cada análise diferente que você desejar fazer, você ainda estará sujeito a diversos erros operacionais, como esquecer de trocar um dos nomes ao copiar e colar.

O ideal, aqui, é criar uma função que realize este trabalho.

Definindo funções

Uma função, no R, é definida da seguinte forma:

nomeDaFuncao <- function(arg1, arg2, arg3 = default3,  ...){
  # corpo da função: uma série de comados válidos.
  return(resultado) # opcional
}
  • o comando function() diz para o R que você está definindo uma função.
  • os valores dentro dos parênteses de function() são os argumentos (ou parâmetros) da função. Argumentos podem ter valores default (padrão), que são definidos com o sinal de igualdade (no caso arg3 tem como default o valor default3). Existe um parâmetro “coringa” muito útil, o ..., que permite passsar argumentos para outras funções. Veremos mais sobre o ... em seguida.
  • dentro das chaves encontra-se o “corpo” da função, isto é, uma série de comandos válidos que serão realizados.
  • o comando return() encerra a função e retorna seu argumento. Como veremos, o return() é opcional. Caso omitido, a função retorna o último objeto calculado.

Criemos nossas primeiras funções:

# retorna o quadrado de um número
quadrado <- function(x){
  x^2
}
quadrado(3)
## [1] 9
## forma mais sucinta
## se o corpo da função for na mesma linha não é necessário colocar chaves
quadrado <- function(x) x^2
quadrado(3)
## [1] 9
## função mais geral, elevar um número x à potência n
elevado_n <- function(x,n) x^n
elevado_n(3, 3)
## [1] 27

Funções criam um ambiente local e, em geral, não alteram o objeto ao qual são aplicadas. Isto é, se você passa um valor x para uma função que eleva x ao quadrado, o valor original de x não muda. Funções tomam objetos como argumentos e criam outro objeto, modificado, como resultado. Na maior parte dos casos, a idéia é que uma função no R não tenha efeitos colaterais, isto é, que ela não modifique objetos fora de seu ambiente.

x <- 10
elevado_n(x, 2) # isso alterou o valor de x?
## [1] 100
# note que não
x
## [1] 10
# se você quer salvar o resultado
# tem que atribuí-lo a outro objeto
y <- elevado_n(x, 2)
y
## [1] 100

Voltando ao exemplo

Montemos uma função que realiza o tratamento dos dados visto anteriormente:

limparDados <- function(dados){
  dados <- as.numeric(dados)
  dados <- dados[!(dados < 1 | dados > 10000)]
  dados <- dados/1000
  dados <- round(dados)
  return(dados)
}
ls() # note que a função foi criada
## [1] "elevado_n"   "limparDados" "precos"      "precos2"     "quadrado"
## [6] "x"           "y"

Vejamos em detalhes:

  • o comando function() diz para o R que você está definindo uma função.
  • os valores dentro dos parênteses de function() são os argumentos da função. No nosso caso definimos um único argumento chamado dados.
  • dentro das chaves encontra-se o “corpo” da função, isto é, as operações que serão realizadas. Neste caso, transformamos dados em numeric, retiramos aqueles valores menores do que 1 e maiores do que 10000, dividimos por 1000 e, finalmente, arredondamos.
  • a função return() encerra a função e retorna o vetor dados modificado.

Pronta a função, sempre que você quiser realizar essas operações em um vetor diferente, basta utilizar limparDados(). Testemos com novos vetores:

precos3 <-  c("0.02", "4560.45", "1234.32", "7894.41", "12000000")
precos4 <- c("0.001", "1500000", "1200.9", "2000.2", "4520.5")
precos5 <- c("0.05", "1500000", "1000000", "7123.4", "9871.5")

# limpando os dados
limparDados(precos3)
## [1] 5 1 8
limparDados(precos4)
## [1] 1 2 5
limparDados(precos5)
## [1]  7 10

Ficou bem melhor do que o script. Note que tínhamos 3 vetores diferentes e bastou chamar a função três vezes, ao invés de ter que copiar e colar três vezes o código. Note, também, que se houver algum erro, temos que corrigir apenas a definicão da função e não três partes distintas do código.

Mais argumentos

Podemos refinar mais a função limparDados(). Por exemplo, perceba que não função ainda não é geral o suficiente. Da forma como está escrita, os valores de corte de mínimo e de máximo serão sempre 1 e 10000; além disso, os resultados sempre serão dividos por 1000. E se quisermos modificar esses valores?

Para isso é necessário definir mais argumentos:

limparDados <- function(dados, min, max, div){
  dados <- as.numeric(dados)
  dados <- dados[!(dados < min | dados > max)]
  dados <- dados/div
  dados <- round(dados)
  return(dados)
}

Agora você pode alterar os valores de min, max e div ao aplicar a função.

precos3 <-  c("0.02", "4560", "1234", "7894", "12000000")

limparDados(precos3, min=0, max=5000, div=2)
## [1]    0 2280  617
limparDados(precos3, min=0, max=4000, div=4)
## [1]   0 308
limparDados(precos3, min=-Inf, max=Inf, div=1)
## [1]        0     4560     1234     7894 12000000

Veja que os argumentos são nomeados. Dessa forma, se você colocar os argumentos com seus respectivos nomes, a ordem dos argumentos não importa. Você também pode omitir os nomes dos argumentos, desde que os coloque na ordem correta.

#  argumentos em ordem diferente
limparDados(max=5000, div=2, min=0, dados=precos3)
## [1]    0 2280  617
# argumentos sem nomes (na ordem correta)
limparDados(precos3, 0, 4000, 4)
## [1]   0 308

Argumentos Default

Colocar mais argumentos em uma função, contudo, causa um certo incômodo. Você passa a ter que especificar todos estes argumentos cada vez que for chamar a função. Ao se esquecer de especificar algum, ocorrerá um erro:

limparDados(precos3, max=5000, div=1)
## Error in limparDados(precos3, max = 5000, div = 1): argumento "min" ausente, sem padrão

Existe uma forma simples de solucionar isto: basta definir valores padrão (default):

# colocando valores padrnao para min, max e div
limparDados <- function(dados, min=1, max=10000, div=1000){
  dados <- as.numeric(dados)
  dados <- dados[!(dados < min | dados > max)]
  dados <- dados/div
  dados <- round(dados)
  return(dados)
}

Agora podemos usar a função omitindo os argumentos que possuem default.

# usa o default para min
limparDados(precos3, max=5000, div=1)
## [1] 4560 1234
# usa o default para min e div
limparDados(precos3, max=Inf)
## [1]     5     1     8 12000
# usa o default para tudo
limparDados(precos3)
## [1] 5 1 8

Funções também podem ser argumentos

Funções também podem ser passadas como argumentos de funções. Por exemplo, suponha que você não queira sempre usar o round() para arredondamento. Você pode colocar a função que é aplicada a dados como um dos argumentos de limparDados().

limparDados <- function(dados, min=1,
                        max=10000, div=1000, funcao=round){
  dados <- as.numeric(dados)
  dados <- dados[!(dados < min | dados > max)]
  dados <- dados/div
  dados <- funcao(dados) # função que será aplicada agora é um argumento
  return(dados)
}

Se quisermos usar a função floor() ao invés de round(), basta trocar o argumento funcao.

# usou os defaults
limparDados(precos3)
## [1] 5 1 8
# usa floor ao invés de round
limparDados(precos3, funcao=floor)
## [1] 4 1 7
# funcao anonima que pega x e retorna x (não faz nada com x)
limparDados(precos3, funcao = function(x) x)
## [1] 4.560 1.234 7.894

Funções anônimas

Como vimos no final do exemplo anterior, você pode definir uma nova função no próprio argumento de limparDados(). Estas funções são chamadas de anônimas.

limparDados(precos3, funcao = function(x) x) # não faz nada com os dados ao final
## [1] 4.560 1.234 7.894
limparDados(precos3, funcao = function(x) x^2) # eleva dados ao quadrado
## [1] 20.793600  1.522756 62.315236
limparDados(precos3, funcao = function(x) log(x+1)) # tira o log de (dados +1 )
## [1] 1.7155981 0.8037937 2.1853769
limparDados(precos3, funcao = function(x) { # reliaza diversas operações
  x <- round(x)
  x <- as.complex(x)
  x <- (-x)^(x/10)
} )
## [1]  0.0000000-2.236068i  0.9510565-0.309017i -4.2700173-3.102349i

O …

O R tem ainda um argumento coringa os “três pontos” ....

O ... permite repassar argumentos para outras funções dentro da sua função, sem necessariamente ter que elencar todas as possibilidades de antemão. Não ficou muito claro? Vejamos um exemplo.

A função round() tem o argumento digits. Ou a nossa função elevado_n() tem o argumento n. Muitas vezes vamos querer mudar esses arguementos, mas como incluir todos em nossa função? Não temos como prever exatamente que funções ou que argumentos vamos precisar. Mas não precisamos fazer isso, podemos repassar argumentos arbitrários para essas funções por meio do ....

limparDados <- function(dados, min=1,
                        max=10000, div=1000, funcao=round, ...){
  dados <- as.numeric(dados)
  dados <- dados[!(dados < min | dados > max)]
  dados <- dados/div
  dados <- funcao(dados, ...) # note os três pontos
  return(dados)
}

Agora, tudo que for colocado em ... é repassado para funcao(dados, ...).

limparDados(precos3)
## [1] 5 1 8
limparDados(precos3, digits=1) # passamos o argumento digits = 1 para a função round (default)
## [1] 4.6 1.2 7.9
limparDados(precos3, funcao=elevado_n, n=2) # passamos o argumento n = 2 para a função elevado_n
## [1] 20.793600  1.522756 62.315236

Operadores binários

Lembra que, nos primeiros capítulos, mostramos que o operador +, no R, na verdade é uma função e pode ser chamado com a seguinte sintaxe: '+'(x,y)? Funções deste tipo são chamadas de operadores binários. No R, você também pode definir seus próprios operadores binários com o auxílio do caracter especial %.

Para ilustrar, façamos um operador binário que cole textos:

`%+%` <- function(x, y) paste(x, y)

Agora podemos colar textos usando %+%:

"colando" %+% "textos" %+% "com nosso" %+% "operador"
## [1] "colando textos com nosso operador"

Vejamos outro exemplo:

`%depois%` <- function(x, fun) fun(x)

Olhe que interessante:

set.seed(10)
x <- rnorm(100)
sqrt(exp(mean(x)))
## [1] 0.9340041
x %depois% mean %depois% exp %depois% sqrt
## [1] 0.9340041

Ou seja, com esse simples operador, você mudou a sintaxe das operações. Ao invés de escrever sqrt(exp(mean(x))), que te força a pensar “de fora para dentro” (tirar a raiz quadrada do exponencial da média de x) você pode escrever na “ordem natural” das operações x %depois% mean %depois% exp %depois% sqrt (pegue x, tire a média, exponencie e tire a raiz quadrada). Na verdade, este caso é uma versão super simplificada da lógica do operador %>% do pacote magrittr, muito usado no dplyr, que veremos na seção de manipulação de data.frames.

A imaginação é o limite.

Exercícios

Sua vez.

  • Defina uma função que retorne o mínimo, a mediana e o máximo de um vetor. Faça com que a função lide com NA’s e que isso seja um argumento com default;
  • Defina uma versão “operador binário” da função rep. Faça com que tenha seguinte sintaxe: x %rep% n retorna o objeto x repetido n vezes.
  • Defina uma função que normalize/padronize um vetor (isto é, subtraia a média e divida pelo desvio-padrão). Faça com que a função tenha a opção de ignorar NA’s. Permita ao usuário escolher outros parâmetros para a média (Dica: use …);
  • Dados um vetor y e uma matriz de variáveis explicativas X, defina uma função que retorne os parâmetros de uma regressão linear de x contra y, juntamente com os dados originais usados na regressão. (Dicas: use álgebra matricial. Use uma lista para retornar o resultado)

Soluções sugeridas (você pode fazer melhor!)

mmm <- function(x, na.rm=TRUE){

  # calcula min, median, e max, guarda em resultado
  resultado <- c(min(x, na.rm = na.rm),
                 median(x, na.rm = na.rm),
                 max(x, na.rm = na.rm))

  # nomeia o vetor para facilitar consulta
  names(resultado) <- c("min", "mediana", "max")

  #retorna vetor
  return(resultado)
}

mmm(c(1,2,3, NA))
##     min mediana     max
##       1       2       3
`%rep%` <- function(x, n) rep(x, n)

7 %rep% 5
## [1] 7 7 7 7 7
padronize <- function(x, na.rm=TRUE,...){

  m <- mean(x,na.rm=na.rm, ...) # calcule a média
  dp <- sd(x, na.rm=na.rm)      # calcule o dp
  pad <- (x-m)/dp               # padronize os dados

  attr(pad, "media") <- m       # guarda a média original p/ consulta
  attr(pad, "dp") <- dp         # guarda o dp original p/ consulta

  return(pad)                   # retorna o vetor pad já com atributos
}

padronize(1:5)
## [1] -1.2649111 -0.6324555  0.0000000  0.6324555  1.2649111
## attr(,"media")
## [1] 3
## attr(,"dp")
## [1] 1.581139
ols <- function(X, y){

  b <- solve(t(X)%*%X)%*%t(X)%*%y  # ols ((X'X)^-1)X'Y

  # guarda resultados em lista nomeada
  resultado <- list(coef = b, X = X, y = y)

  # retorna resultado
  return(resultado)
}

# cria dados simulados
set.seed(10)                       # para reproducibilidade
X <- matrix(rnorm(300), ncol=3)    # vetor X
y <- X%*%c(3,6,9) + rnorm(100)     # y = Xb + e, b=c(3, 6, 9), e~N(0,1)

# vamos testar a formula
resultado <- ols(X, y)
str(resultado)
## List of 3
##  $ coef: num [1:3, 1] 3.01 6.01 9
##  $ X   : num [1:100, 1:3] 0.0187 -0.1843 -1.3713 -0.5992 0.2945 ...
##  $ y   : num [1:100, 1] 7.93 5.53 1.53 11.11 -12.52 ...
resultado$coef
##          [,1]
## [1,] 3.012419
## [2,] 6.011090
## [3,] 8.998595

*** falar sobre escopo e orientação a objetos ***