Javascript episódio 2

Continuando no Javascript, vamos olhar como funciona as coordenadas, o sistema de coordenadas no canvas. Lembro que vi isso pela primeira vez no livro de java do Robert Sedgewick, porque ele fazia o próprio sistema de gráficos dele, bem simples, para fazer histogramas e gráficos de pontos, mas la a gente ve que diferente de como a gente pensa, o monitor desenha da parte superior esquerda, para baixo e para direta, então é como se fosse tudo uma mega matriz de pixels, e o (0,0) esta no canso superior esquerdo, pra quem esta olhando a tela. Com arduino, tem um projetinho que você projeta a hora na tv, via cabo coaxial, e você tem que fazer umas contas nesses sentido, mas uma coisa é pintar meia duzia de pixel para fazer um número, controlar uma matriz de led, outra é fazer uma imagem, mas chega de divagar, vamos continuar aquele exemplo, vamos olhar algumas partes agora.

No post anterior, a gente colocou tudo no arquivo html, e ele ficou grande, colocamos o elemento style e os scripts de javascript tudo junto, mas isso começa a complicar, então o que vamos fazer é separar tudo em arquivos diferentes, para web, a gente pode fazer mais de um arquivo na mesma pasta, para facilitar, e usamos arquivos .css para o código do estilo da página e os arquivos .js para scripts de javascript.

Ai como você faz para separar, eu vou ter dois arquivos, o java02.css e o java02.html, o tudo que estava dentro da tag style, eu vou colocar dentro do java02.css, que fica assim:

$ cat java02.css body { text-align: center; font-family: sans-serif; } canvas { background-color: black; }

E no arquivo java02.html, vamos adicionar uma linha no para chamar esse arquivo assim:

1
<link rel="stylesheet" href="java02.css">

E pronto, ja diminuímos um pouco o tamanho de tudo. E agora, vamos continuar com aquela mesma estrutura de html, com um canvas. Então o java02.html vai ser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Desenhando no canvas html</title>
    <link rel="stylesheet" href="java02.css">
 
  </head>
  <body>
 
    <h1>Desenhando no canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
    //Meu script
    </script>
 
 
  </body>
</html>

E vamos fazer um script, primeiro para entender como funciona esse grid.

Precisamos sempre a referência do elemento para trabalhar

1
2
var canvas = document.getElementById("asteroids");
var context = canvas.getContext("2d");

Mas para desenhar, vamos fazer uma função, colocando em outro arquivo, para depois utilizar, então vamos criar um arquivo chamado biblioteca.js, vamos referenciar ele, parecido com o que fizemos com o arquivo css, na linha depois dela dentro do

1
<script src="biblioteca.js"></script>

Agora dentro desse aquivos, vamos criar uma função de javascript da seguinte forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function desenha_grid(ctx, minor, major, stroke, fill) {
 
    minor = minor || 10;
    major = major || minor * 5;
    stroke = stroke || "#00FF00";
    fill = fill || "#009900";
    ctx.save();
    ctx.strokeStyle = stroke;
    ctx.fillStyle = fill;
 
    let width = ctx.canvas.width, height = ctx.canvas.height
 
    for(var x = 0; x < width; x += minor) {
	ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, height);
        if(x % 50 == 0){
            ctx.lineWidth = (0.5);
	}else{
           ctx.lineWidth = (0.25);
        }
        ctx.stroke();
        if(x % major == 0 ) {ctx.fillText(x, x, 10);}
    }
    for(var y = 0; y < height; y += minor) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(width, y);
        ctx.lineWidth = (y % major == 0) ? 0.5 : 0.25;
        ctx.stroke();
        if(y % major == 0 ) {ctx.fillText(y, 0, y + 10);}
    }
    ctx.restore();
}

Então essa é uma função, chamada desenha_grid, que aceita cinco argumentos, o primeiro é o contexto, o canvas que a gente quer desenhar, e o outros são de quanto em quanto pixels a gente quer desenhar linhas, assim como num papel milimetrados, temos linhas pequenas e linhas mais grossas, esses são os tamanhos em pixel, e o stroke e fill são as cores de preenchimento.

O jeito mais simples de usar é apenas falar qual o contexto, que temos argumentos de tamanhos e cores pré-definidos, então o seguinte arquivo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Desenhando no canvas html</title>
    <link rel="stylesheet" href="java02.css">
    <script src="biblioteca.js"></script>
 
  </head>
  <body>
 
    <h1>Desenhando no canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
      var context = canvas.getContext("2d");
      // Desenhando no Canvas
 
      desenha_grid(context);
 
    </script>
 
  </body>
</html>

Vai produzir isso:

Legal, veja que podemos chamar a função com outras cores para produzir outras coisas. Por exemplo

1
desenha_grid(context, 15, 45, 'red', 'yellow');

Vai produzir

Ok, agora que a gente já viu o que a função faz vamos entender como ela funciona.

A estrutura geral para definir uma função no javascript é assim:

1
2
3
function desenha_grid(ctx, minor, major, stroke, fill) {
Seu código
}

Não precisamos definir o tipo dos argumentos, so dar nomes, ai o código da função é o seguinte

1
2
3
4
    minor = minor || 10;
    major = major || minor * 5;
    stroke = stroke || "#00FF00";
    fill = fill || "#009900";

Com exceção do contexto, de onde desenhar, que não tem como ser padrão, precisamos de um canvas para desenhar, todos os outros argumentos, se não forem definidos na função, definimos eles aqui, veja que eles são pegos na ordem que são fornecidos na função.

Depois, antes de alterar o contexto, salvamos todos os parâmetros deles

1
    ctx.save();

Depois que temos salvo os parâmetros, começamos a mexer neles,

1
2
    ctx.strokeStyle = stroke;
    ctx.fillStyle = fill;

Definimos duas váriaveis, altura e largura, que vamos usar para fazer o grid

1
    let width = ctx.canvas.width, height = ctx.canvas.height

E então vamos fazer dois loops

1
2
3
4
5
6
7
8
9
10
11
12
    for(var x = 0; x < width; x += minor) {
	ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, height);
        if(x % major == 0){
            ctx.lineWidth = (0.5);
	}else{
           ctx.lineWidth = (0.25);
        }
        ctx.stroke();
        if(x % major == 0 ) {ctx.fillText(x, x, 10);}
    }

Aqui para a altura, vamos fazer linhas, então começamos a linhas no (0,0), vamos com ela até la em baixo, já que height foi definido ali em cima como o tamanho da tela, e definimos o lineWidth fina para cada linha minor, e mais grossa para o major, que são argumentos para o grid, usando um if, depois o ctx.stroke() desenha a linha e para cada vez que chegamos no major, escrevemos em que pixel estamos com o fillText. Depois repetimos o mesmo esquema para as linhas horizontais

1
2
3
4
5
6
7
8
    for(var y = 0; y < height; y += minor) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(width, y);
        ctx.lineWidth = (y % major == 0) ? 0.5 : 0.25;
        ctx.stroke();
        if(y % major == 0 ) {ctx.fillText(y, 0, y + 10);}
    }

So dando um destaque aqui, para essa forma mais resumida de usar if e else, acho que é o mesmo esquema que da pra usar no c++ e no c, mas que a gente não vê em R e python, se bem que python tem maneiras bem sintéticas de escrever as coisas, mas a primeira vez que a gente vê pode ser confuso, mas tem a mesma coisa usando if else ali em cima só para facilitar o entendimento.

Bem, feito isso, como mudamos os parâmetros do contexto, e la em cima salvamos como estava antes, voltamos eles com o restore()

1
    ctx.restore();

E agora é como se nem tivéssemos mexidos. Esse grid vai ajudar muito a entender melhor as coordenadas, da para entender melhor como são feitas as retas e o preenchimento agora.

Vamos fazer algumas retas

1
2
3
4
5
6
7
8
9
10
11
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);

Começamos um caminho, fomos para o ponto (50, 50) dai fazemos uma reta para (150, 250) e depois (250, 170), e vamos escrever esses pontos, um pouco deslocado do local sendo desenhado com fillText, para se ligar no que estamos fazendo, lembrando que só o lineTo não desenha nada, so depois do stroke o desenho aparece na tela.

Agora diferente do texto, Stroke desenha a linha, mas podemos preencher essa linha, que como não é uma forma fechada, fica assim.

1
2
3
4
5
6
7
8
9
10
11
12
13
      //Reta inicial
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);
      context.fill();

Que fica assim:

Então o que acontece, é que a forma sempre é fechada na origem e preenchida, caso ela não esteja fechada, por exemplo se expandirmos as linhas assim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      //Reta inicial
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.lineTo(320, 280);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);
      //context.fill();

quando preenchemos com o fill temos

Mas para fecharmos nossas forma, e fazer um triângulo por exemplo, podemos usar o closePath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Triângulos
      context.beginPath()
      context.moveTo(50, 250);
      context.lineTo(50, 350);
      context.lineTo(150, 350);
      context.closePath();
      context.moveTo(230, 360);
      context.lineTo(270, 360);
      context.lineTo(270, 310);
      context.closePath();
      context.moveTo(250, 50);
      context.lineTo(370, 50);
      context.lineTo(370, 100);
      context.closePath();
      context.strokeStyle = "#FFFF00";
      context.fillStyle = "#000000";
      context.fill();
      context.stroke();

Que da.

Mas estrada sem curvas da sono não é verdade, existe algumas formar de curvarmos um poucos as formas, uma é usando o quadraticCurveTo, que é uma equação de segundo grau, basicamente, então o ponto inicial e final são as duas raizes e temos os outros termos da equação.

Nesse caso, a gente fornece mais dois pontos,

context.quadraticCurveTo(cpx,cpy,x,y);

cpx e cpy são um ponto de controle, e o x e y são a outra raiz, dessa forma

É equivalente a

1
2
moveTo(20,20)
quadraticCurveTo(20,100,200,20)

Então esse código

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      //Curvas Quadráticas
      context.beginPath()
      context.moveTo(50, 250);
      context.quadraticCurveTo(25, 300, 50, 350);
      context.quadraticCurveTo(100, 375, 150, 350);
      context.closePath();
      context.moveTo(230, 360);
      context.quadraticCurveTo(255, 340, 270, 360);
      context.quadraticCurveTo(255, 340, 270, 310);
      context.closePath();
      context.moveTo(250, 50);
      context.quadraticCurveTo(310, 60, 370, 50);
      context.quadraticCurveTo(400, 75, 370, 100);
      context.closePath();
      context.strokeStyle = "#FFFF00";
      context.fillStyle = "#000000";
      context.fill();
      context.stroke();

da isso.

Legal, mas no manual do w3schools para quadraticCurveTo a gente já vê sobre o bezierCurveTo(), que é parecido, mas usamos dois pontos de referencia, para entortar diferente as curvas.

a sintaxe vai pedir dois pontos de controle

context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);

E o codigo

1
2
moveTo(20,20)
bezierCurveTo(20,100,200,100,200,20)

Vai produzir o seguinte

Então podemos modificar aquelas primeiras retas e usar o closePath

1
2
3
4
5
6
7
8
9
10
11
//Curvas Bezier
      context.beginPath();
      context.strokeStyle = "red";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.bezierCurveTo(0, 0, 80, 250, 150, 250);
      context.bezierCurveTo(250, 250, 250, 250, 250, 170);
      context.bezierCurveTo(250, 50, 400, 350, 320, 280);
      context.closePath();
      context.stroke();

Para produzir

Sendo que o closePath continua sendo uma reta.

Bem é isso ai, já da para entender mais alguma coisinha de Javascript agora, tudo ficando mais organizado, o script vai estar la no repositório de javascript, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Referência:
Graeme Stuart 2017 – Introducing JavaScript Game Development Build a 2D Game from the Ground Up. Apress 221pp

Javascript episódio 1

Como quase tudo, eu tenho tentado aprender javascript na tentativa e erro, mas acho que já esta na hora de começar a ler e entender melhor o que estou fazendo, melhor que tentativa e erro para progredir mais um pouco, e acabei que achei um livro que achei tanto simples como legal. Essa foi a lição do capítulo 1.

Bem aqui a gente vai apenas fazer um canvas com javascript do zero, para começar, vamos fazer somente um arquivo html, e ele vai ter um elemento no corpo que vai ser um canvas.

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Isto é um canvas html</title>
  </head>
  <body>
    <h1>Isto é um canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
  </body>
</html>

Isso gera o seguinte no navegador.

Então temos um canvas de 400 por 400, mas na verdade a gente não vê nada, porque tudo, tanto a página quanto o canvas tem tudo a mesma cor, mas ele está ali, abaixo do texto. Agora vamos desenhar algo nele, com um script de javascript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!doctype html>                                                                                                                                                                                             
<html>                                                                                                                                                                                                      
  <head>                                                                                                                                                                                                    
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">                                                                                                                                
    <title>Isto é um canvas html</title>                                                                                                                                                                    
  </head>                                                                                                                                                                                                   
  <body>                                                                                                                                                                                                    
 
    <h1>Isto é um canvas html</h1>                                                                                                                                                                          
    <canvas id="asteroids" width="400" height="400"></canvas>                                                                                                                                               
 
    <script>                                                                                                                                                                                                
      var canvas = document.getElementById("asteroids");                                                                                                                                                    
      var context = canvas.getContext("2d");                                                                                                                                                                
      context.strokeStyle = 'dimgrey';                                                                                                                                                                      
      context.lineWidth = 5;                                                                                                                                                                                
      context.rect(75, 75, 250, 250);                                                                                                                                                                       
      context.stroke();                                                                                                                                                                                     
      // Comentário                                                                                                                                                                                         
    </script>                                                                                                                                                                                               
 
  </body>                                                                                                                                                                                                   
</html>

E vemos o seguinte.

Bem, primeiro a gente tem que se ligar que javascript é uma linguagem scriptada, então assim como o R, ele vai rodando linha por linha, e so para se a gente fizer algo errado, até la ele roda, e ele roda na ordem, por isso o script esta depois do elemento canvas no html, porque se tiver antes, ele vai tentar rodar num elemento de html que ainda não existe.
Outra coisa, é que ele é orientado a objetos, então a primeira linha

1
      var canvas = document.getElementById("asteroids");

Apenas instancia um objeto, que esta ligado ao elemento asteroids, que é o id que demos no nosso canvas ali no html

1
<canvas id="asteroids" width="400" height="400"></canvas>

Depois disso criamos um contexto. Apesar do elemento canvas ser usado para desenhar, plotar, etc, através de scripts, ele não tem métodos de desenho, então quando a gente usa o método getContext(), ele retorna o objeto que tem os métodos para desenho, no caso o “2d” é para desenhos em 2d, como retas, formas, texto entre outras coisas.

Agora podemos mexer no contexto, que é o que realmente desenha, o strokeStyle seta a cor, lineWidth qual a espessura da linha a ser desenhada em pixels, rect faz um retangulo, que no nosso caso é um quadrado, os dois primeiros valores são o ponto onde ele vai começar, onde vai ser ancorado essa forma, e depois temos o tamanho dele, 250 de largura e altura.

Até essa linha, nada foi desenhado, so depois que rodamos o método stroke, que tudo realmente é desenhado. Mas não da para ver onde é o canvas, porque precisamos mexer no estilo da página, para colocar outra cor no canvas, no nosso elemento, para diferencias ele, então precisamos de um pouco de CSS, que podemos colocar direto aqui.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Isto é um canvas html</title>
 
    <style media="screen">
      body {
      text-align: center;
      font-family: sans-serif;
      }
      canvas {
      background-color: black;
      }
    </style>
 
  </head>
  <body>
 
    <h1>Isto é um canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
      var context = canvas.getContext("2d");
      context.strokeStyle = 'dimgrey';
      context.lineWidth = 5;
      context.rect(75, 75, 250, 250);
      context.stroke();
      // Comentário
    </script>
 
  </body>
</html>

Essa modificação no estilo, também direto dentro do html, colocamos o elemento style la no cabeçalho, no head, e mudamos o plano de fundo dos elementos, e centralizamos na página também.

Agora da pra ver melhor o canvas, vamos mexer no nosso desenho melhor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Isto é um canvas html</title>
 
    <style media="screen">
      body {
      text-align: center;
      font-family: sans-serif;
      }
      canvas {
      background-color: black;
      }
    </style>
 
  </head>
  <body>
 
    <h1>Isto é um canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
      var context = canvas.getContext("2d");
      context.strokeStyle = 'lightgrey';
      context.fillStyle = 'dimgrey';
      context.lineWidth = 5;
      context.rect(75, 50,canvas.width - 150, canvas.height - 100);
      context.stroke();
      context.fill();
      // Comentário
    </script>
 
  </body>
</html>

Isso vira o seguinte.

O que a gente fez aqui, primeiro veja que temos agora uma cor para a borda do elemento e uma cor de preenchimento, que é o fillStyle, outra coisa, veja que mudamos agora as medidas e o ponto de referencia do retângulo que fazemos com rect, estamos fazendo ele baseado nas medidas do canvas, veja que podemos acessar esses valores a partir do objeto canvas que criamos, ele tem esses atributos, e agora usamos o stroke para desenhar o e fill para preencher, antes de fazer isso, nada acontece, podemos comentar essas linhas para ir testando o comportamento dos métodos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Isto é um canvas html</title>
 
    <style media="screen">
      body {
      text-align: center;
      font-family: sans-serif;
      }
      canvas {
      background-color: black;
      }
    </style>
 
  </head>
  <body>
 
    <h1>Isto é um canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
 
      //Retângulo
      var context = canvas.getContext("2d");
      context.strokeStyle = 'lightgrey';
      context.fillStyle = 'dimgrey';
      context.lineWidth = 5;
      context.rect(75, 50,canvas.width - 150, canvas.height - 100);
      context.stroke();
      context.fill();
 
      // Texto
      context.font = "34px Arial";
      context.strokeStyle = '#FF2222';
      context.fillStyle = '#FFAAAA';
      context.lineWidth = 0.75;
      context.textAlign="center";
      let msg = "Desenhando 2d"
      context.fillText(msg, canvas.width / 2, 100);
      context.strokeText(msg, canvas.width / 2, 100);
    </script>
 
  </body>
</html>

Agora adicionando o elemento de texto.

Bem, os comentários são como no c++, com duas barras, podemos usar eles para dar uma organizada no texto, bem agora vamos mexer no contexto, veja que a cor e o preenchimento são uma propriedade, no caso nos mudamos ela de novo depois que fizemos o retângulo, pegamos cores novas, mudamos a grossura da linha de desenho com lineWidth e vamos desenhar texto, alinhando ele centralizado a partir do ponto onde desejamos escrever, declaramos um string com let, a diferença de let e var é sutil, os dois declaram variáveis, mas tem diferentes escopo e eu não sei explicar isso direito ainda, mas tudo bem. Ai assim como o retângulo, usamos o fillText para preencher as letras, e o strokeText para o contorno, veja que mandamos para ele o texto e o ponto onde ele deve ser ancorado, sendo o meio do canvas, na altura 100 pixel, lembrando que é um desenho numa tela, então não contamos os eixos como num gráfico, mas ao contrario, o canto superior esquerdo é onde começa a tela.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Isto é um canvas html</title>
 
    <style media="screen">
      body {
      text-align: center;
      font-family: sans-serif;
      }
      canvas {
      background-color: black;
      }
    </style>
 
  </head>
  <body>
 
    <h1>Isto é um canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
 
      //Retângulo
      var context = canvas.getContext("2d");
      context.strokeStyle = 'lightgrey';
      context.fillStyle = 'dimgrey';
      context.lineWidth = 5;
      context.rect(75, 50,canvas.width - 150, canvas.height - 100);
      context.stroke();
      context.fill();
 
      // Texto
      context.font = "34px Arial";
      context.strokeStyle = '#FF2222';
      context.fillStyle = '#FFAAAA';
      context.lineWidth = 0.75;
      context.textAlign="center";
      let msg = "Desenhando 2d"
      context.fillText(msg, canvas.width / 2, 100);
      context.strokeText(msg, canvas.width / 2, 100);
 
      // Bonequinho
      context.strokeStyle = '#FFFFFF';
      context.lineWidth = 2;
      context.beginPath();
      context.arc(200, 140, 20, 0, Math.PI * 2);
      context.moveTo(200, 160);
      context.lineTo(200, 220);
      context.moveTo(180, 300);
      context.lineTo(185, 260);
      context.lineTo(200, 220);
      context.lineTo(215, 260);
      context.lineTo(220, 300);
      context.moveTo(240, 130);
      context.lineTo(225, 170);
      context.lineTo(200, 170);
      context.lineTo(175, 180);
      context.lineTo(170, 220);
      context.stroke();
 
      // Mais texto
      let msg2 = "bem simples!!!";
      context.font = "24px Arial";
      context.strokeStyle = '#FF2222';
      context.lineWidth = 0.75;
      context.fillText(msg2, canvas.width / 2, 330);
      context.strokeText(msg2, canvas.width / 2, 330);
    </script>
 
  </body>
</html>

Agora basicamente é isso, com mais algumas funções, fazemos um bonequinho usando path, funciona assim, a gente começa um path com beginPath, veja que ele usa os parâmetros de cor, tamanho ja determinados, ai usamos outra função que é fazer um arco, para fazer a cabeça, um arco é um circulo, então temos que ter um ponto onde ele começa, onde ele ancora, depois o tamanho do raio e onde ele começa e onde termina, no caso 2 \pi é uma volta completa, dai temos um circulo, o moveTo move para onde queremos começar a desenhar, e lineTo para da onde está o último moveTo até o ponto que indicamos, e antes de terminar, temos que desenhar o que queremos, bem vamos fazer mais uma mensagem, e sempre lembrando que precisamos tomar cuidado pois mudamos as cores, as propriedades do texto.

Bem é isso ai, estava olhando javascript hoje e resolvi posta o que fiz, o script vai estar la no repositório de javascript, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Referência:
Graeme Stuart 2017 – Introducing JavaScript Game Development Build a 2D Game from the Ground Up. Apress 221pp

Método de Monte Carlo e o experimento da Agulha de Buffon

Desde que eu comecei a ler sobre estatística bayesiana (nossa já faz alguns anos), eu comecei a sempre olhar como funcionava os algorítimos dela, que sempre caia no MCMC. Um MC desses é do Monte Carlo, que é um método de amostragem bem legal, já falamos dele aqui quando estimamos o valor de pi, mas agora vamos ver outro experimento com ele bem legal, o experimento das Agulhas de Buffon.

Bem biólogos sempre estão enfiados no meio da análise de dados, desde o Fisher fazendo anova a muitos outros nomes, e não é que no Monte Carlo a gente vê isso também. O Georges-Louis Leclerc, conde de Buffon era um botânico que propôs e estudou esse experimento.

O experimento é o seguinte, ele jogava várias agulhas no chão de madeira.

Então, depois contava quantas dessas agulhas encostavam nas divisões das madeiras.
Como ninguém tem muitas agulhas e as vezes não tem piso de madeira, podemos simular o experimento no R. Fazemos nosso piso.

E agora é so jogar Agulhas nele.

Jogamos uma agulhinha ali, e ela caiu no meio do piso, ou seja, ela não acertou a junção do piso. Mas se jogarmos várias.

Algumas vão acertar, e outras não. E agora é só contar. As agulhas ficaram legais né, são uma reta com um pontinho na ponta hehehe. Mas como eu fiz as agulhas? Foi com o seguinte código.

1
2
b=1
a=0.3

Bem primeiro definimos duas coisas, o tamanho das tábuas e o tamanho das agulhas, as tábuas aqui são de 1 unidade de tamanho e a agulha de 0.3, chamamos de unidade porque se for metro ou centímetro não tem real interesse aqui na simulação. Depois jogamos a agulha.

1
2
3
4
5
6
7
8
9
10
11
    xcenter <- runif(1,0,4*b)
    ycenter <- runif(1,0,4*b)
 
    phi <- runif(1,0,2*pi)
    x0 <- xcenter-(a/2)*cos(phi)
    y0 <- ycenter-(a/2)*sin(phi)
    x1 <- xcenter+(a/2)*cos(phi)
    y1 <- ycenter+(a/2)*sin(phi)
 
    points(x=c(x0,x1),y=c(y0,y1),type="l")
    points(x=x0,y=y0,type="p",pch=19,cex=0.6)

Eu sorteio onde é o meio da agulha, depois calculo onde ela começa no x0 e y0 e onde ela termina, o x1 e y1, que é bem fácil usando seno e cosseno, dai no gráfico eu ploto uma linha e uma bolinha na ponto, para dar um charme. Agora para testar se a agulha encosta borda da do piso, podemos começar a pensar de forma mais simplificada o problema. Veja que temos três variáveis que realmente interessam.

Uma variável é onde estão as ranhuras da madeira, que como a tábuas tem tamanho b, será de b em b na nossa área de amostragem, e onde as agulhas caem, que pode ser representado pelo ponto central onde ela caiu e o ângulo \phi dela em relação ao chão, e o tamanho da agulha que é a.

As agulhas não interagem umas com as outras, nem rolam para dentro das junções do piso, além que de as tábuas são simétricas, então x_{center} \leftrightarrow b-x_{center} e \phi \leftrightarrow -\phi.

Então de forma geral, podemos considerar um plano reduzido de amostragem, simplificado na verdade que é:

 0  \textless \phi   \textless \frac{\pi}{2}

e,

 0  \textless x_{center}   \textless \frac{b}{2}

Dessa forma, podemos testar se a agulha encosta no canto da madeira se x_{tip} \textless 0 onde x_{tip} = x_{center} - \frac{a}{2} cos (\phi).

Confesso que fiquei meio perdido quanto a isso no começo, mas veja que o centro da agulha sempre vai estar entre  0  \textless x_{center}   \textless \frac{b}{2} e temos a fração de b porque passou de \frac{b}{2} temos o outro lado da madeira, lembre-se da simetria x_{center} \leftrightarrow b-x_{center}. O cosseno vem da inclinação da agulha, estamos tipo projetando ela no eixo x, porque o eixo y não interessa aqui. Assim como temos sempre algo entre 0 e \frac{b}{2}, a “sombra” da agulha no eixo x vai ser negativo quando tivermos passado o canto do piso.

No caso das agulhas que estamos fazendo no meu código, podemos sempre avaliar se o tamanho dela ta certinho com:

1
sqrt((x1-x0)^2+(y1-y0)^2)

E para fazermos o teste dessa forma, usamos o modulo em b e em pi para ficarmos nos intervalos acima, e usar essa simetria

1
xcenter%%(b/2)-(a/2)*cos(phi%%(pi/2))

Agora vamos fazer um experimento dessa forma mais simples, duas mil vezes, para ver algo legal, e contar quantas vezes acertamos os cantos do piso

1
2
3
4
5
6
7
8
9
10
11
12
n <- 2000
b <- 1
a <- 0.7
nhits <- 0
for(i in 1:n){
    xcenter <- runif(1,0,b/2)
    phi <- runif(1,0,pi/2)
    xtip <- xcenter-(a/2)*cos(phi)
    if(xtip<0){
        nhits <- nhits+1
        }
}

Que vai resultar em:

> nhits [1] 877

Nesse caso acertamos 877 vezes, que da

> nhits/n [1] 0.4385

Mais ou menos 44 porcento, que é mais ou menos

> ((a/b)*(2/pi)) [1] 0.445633

Hã? O que está acontecendo? Bem, não é de se admirar que o valor encontrado está relacionado a pi, mas veja que a é o tamanho da agulha, e intuitivamente sabemos que quanto mair agulha, mais chance ela tem de encostar no canto do piso certo? Mas quanto maior o piso, menor a chance da agulha encostar no canto, pois tem mais piso para ela cair sem encostar em nada. Veja que podemos olhar, a partir da nossa fórmula para testar se caiu ou no canto, o valor de N_{hit} o que ta acontecendo quando variamos essas quantidades.

Primeiro da agulha em relação a N_{hit} para vários tamanhos de pisos.

E depois o contrário

Veja que dependemos dessas duas variáveis, na verdade, podemos plotar todos os casos juntos

Então ((a/b)*(2/pi)) nada mais é que essa área azul, a integral disso, infelizmente eu não sei cálculo muito bem, ainda mais com duas variáveis, então nem vou tentar, mas a intuição é essa do resultado do experimento, se essa figura é o total das possibilidades, a área azul é a porcentagem desse total que são quando a agulha encosta no canto. Da até um pouco de vergonha que em 1700 o cara já dava conta de calcular isso, sem computador, e hoje eu apanho para entender. Pior ainda o fato do cosseno, que devia ser bem complicado sem computador.

Bem é isso ai, MCMC, como quase tudo em estatística, tem sempre uns biólogos perdidos no meio, acho que principalmente porque precisamos de problemas para resolver, e biólogo sempre observa mais problema na natureza para resolver o script vai estar la no repositório recologia, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail e bom natal a todos 🙂

Referência:
Werner Krauth 2006 – Statistical Mechanics: Algorithms and Computations Oxford University Press 342pp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
##Ilustrando o problema de buffon
b=1
a=0.3
 
 
plot(0,0,type="n",xlim=c(0,4*b),ylim=c(0,4*b),frame=F,xlab="",ylab="",main="Piso",xaxt="n",yaxt="n")
abline(v=seq(0,3*b,b))
polygon(x=c(0,1,1,0),y=c(-1,-1,5,5),col="brown3")
polygon(x=c(2,3,3,2),y=c(-1,-1,5,5),col="brown3")
 
 
for(i in 1:100){
    xcenter <- runif(1,0,4*b)
    ycenter <- runif(1,0,4*b)
 
    phi <- runif(1,0,2*pi)
    x0 <- xcenter-(a/2)*cos(phi)
    y0 <- ycenter-(a/2)*sin(phi)
    x1 <- xcenter+(a/2)*cos(phi)
    y1 <- ycenter+(a/2)*sin(phi)
 
    points(x=c(x0,x1),y=c(y0,y1),type="l")
    points(x=x0,y=y0,type="p",pch=19,cex=0.6)
}
 
##Tamanho das agulhas
sqrt((x1-x0)^2+(y1-y0)^2)
 
##Testando se ela encostou no canto
xcenter%%(b/2)-(a/2)*cos(phi%%(pi/2))
 
 
##Experimento simplificado
n <- 2000
b <- 1
a <- 0.7
nhits <- 0
for(i in 1:n){
    xcenter <- runif(1,0,b/2)
    phi <- runif(1,0,pi/2)
    xtip <- xcenter-(a/2)*cos(phi)
    if(xtip<0){
        nhits <- nhits+1
        }
}
 
##Porcentagem de hits
nhits/n
 
##Estimativa de quanto deve ser a porcentagem de hits
((a/b)*(2/pi))
 
 
##Avaliando a relação entre tamanho do piso, tamanho da agulha e hits
b_vetor<- seq(0.1,b,length.out = 6)
a_vetor<- seq(0,1,0.001)
 
par(mfrow=c(3,2))
for(i in 1:length(b_vetor)){
    plot(a_vetor, ((a_vetor/b_vetor[i])*(2/pi)),type="l",xlab="Valores de a",ylab="N hits",main=paste("b=",round(b_vetor[i],2),sep=""),ylim=c(0,5))
}
 
b_vetor<- seq(0.1,b,0.001)
a_vetor<- seq(0.1,1,length.out = 6)
par(mfrow=c(3,2))
for(i in 1:length(a_vetor)){
    plot(b_vetor, ((a_vetor[i]/b_vetor)*(2/pi)),type="l",xlab="Valores de b",ylab="N hits",main=paste("a=",round(a_vetor[i],2),sep=""),ylim=c(0,5))
}
 
 
##Plotando as duas variáveis juntas
angulo <- seq(0,pi/2,length.out = 500)
vetor <- seq(0,b/2,length.out = 500)
casos <- expand.grid(angulo=angulo,b=vetor)
casos$cor <- ifelse(casos$b<a/2 & casos$angulo < acos(casos$b/(a/2)),"lightblue","brown1")
 
 
plot(casos$b,casos$angulo,col=casos$cor,pch=19,cex=0.4,xaxt="n",yaxt="n",xlab="",ylab="",frame=F)
axis(1,at=c(0,a/2,b/2),labels=c(0,"a/2","b/2"))
axis(2,at=c(0,pi/4,pi/2),labels=c(0,expression(phi),expression(frac(pi,2))),las=2)
text(a/4,pi/8,expression('N'['hits']*'=1'))
text(a/2,pi/3.5,expression('N'['hits']*'=0'))

Random Web Surfer, como o google classifica suas páginas

Eu fiz algumas aulas no coursera, e em computação, as que mais me chamaram atenção foram as do Robert Sedgewick, muito famoso acho que pela tese dele sobre a análise de algorítimos do Quicksort, que ele mostra que é em média mais rápido que muitos outros, e acho que o mais comum em uso hoje. Bem também depois da aula eu comecei a olhar os livros deles, que são bem legais, entre eles o Introduction to Programming in Java, que é legal principalmente pelos exercícios que ele propõem.

Um que eu achei muito legal foi o Random Web Surfer, que eu vou colocar aqui, adaptando o código para python.

Nos primórdios da internet, a gente tinha os listão de página, para conseguir acessar, veja o post do pi-hole para mais alguma informação, mas, então, como a gente tinha uma lista, chegou a hora de ordenar ela, ordem alfabética não era muito útil, talvez classificar por tipo de páginas, mas ainda sim fica a pergunta, qual seria a melhor, qual consultar primeiro? Os primeiros web search engines usaram o critérios de quanto mais links apontavam para uma página, melhor ela era, porque todo mundo apontava para ela, logo a gente sempre ia parar nele, logo ela era muito boa. Porém todo mundo começou a burlar isso, se alguém queria ficar pra cima nesse critério, era só pagar para outras páginas colocarem um link para sua página, e rapidamente você estava no topo, ou ainda você poderia criar mil páginas mequetrefes, e colocar o link que quiser nelas, e assim controlar quem está no topo da lista de uma busca da internet, e logo esse artificio começou dificultar a vida dos buscadores de página. Mas o google usou uma forma diferente para classificar páginas.

Bem como funciona a internet, temos uma conjunto de páginas, que tem coneções umas para as outras.

O primeiro passo é como representar essa estrutura, que para nossa sorte pode ser representado facilmente usando um grafo, onde as páginas são vértices e os link entre elas são arestas, um grafo direcional.

Então a entrada pode ser um arquivo onde na primeira linha a gente tem o número de páginas que vamos trabalhar, e depois todos as arestas existentes, qual página tem um link que leva para qual página.

5 0 1 1 2 1 2 1 3 1 3 1 4 2 3 3 0 4 0 4 2

Bem então temos uma representação da internet, o que fazer com ela? De forma a não cair na armadilha anterior, que é contar o quantos links chegam a você?
Uma forma é simular como uma pessoa navega, uma pessoa entra numa página, ai pode clicar num link para outra página, ou digitar o endereço de uma nova página. Mas esses eventos tendem a acontecer com uma distribuição de pareto, a maior parte do tempo, você segue links para as próximas páginas que vai visitar, mas de vez em quanto você simplismente entra numa página por vontade própria, isso numa proporção mais ou menos de 90% do tempo seguindo links e 10% do tempo entrando em novas páginas. Seguindo essa lógica, podemos fazer uma matriz de transição entre páginas, para realizar uma simulação de como o navegador se comporta, e contabilizar quais páginas ele visita mais frequentemente.

Diferente do que uso aqui, numa programação mais em um script continuo realizando as coisas passo a passo, vamos tentar usar um pouco de orientação a objetos, que nada mais é um paradigma de programação para deixar as coisas mais organizadas.

Bem vamos começar criando uma classe em python, e vamos ter um script que vai usar essa classe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
#!/usr/bin/env python
 
import sys
import random
 
class Randomwebsurfer(object):
    #Aqui vai métodos(funções da minha classe)
 
#####################################
## Random Surfer
#####################################
 
if __name__ == '__main__':
    #Aqui eu faço o script, como normalmente fazemos.

Alguns detalhes é que essa linha aqui:

1
#!/usr/bin/env python

Avisa o sistema operacional, quem é o interpretador desse programa, ou seja, se a gente já avisou quem é o interpretador, ao inves de digitar python programa.py, a gente so precisa digitar ./programa.py no terminal.

Bem toda classe tem seus atributos, ou seja a informação dela, nesse caso tem que ser o grafo que representa a relação entre as páginas, que podemos representar como uma matriz, que em python vai ser uma lista de listas, e para não ficar olhando toda hora a matriz para saber quantas páginas temos, vamos anotar o numero de páginas também, como python não precisa realmente declarar os atributos da classe, eu coloquei apenas para não me perder do que tem na classe.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Randomwebsurfer(object):
    n=None
    matriz=None
 
Além disso, toda classe tem que ter um construtor, uma função que vai rodar toda vez que um objeto dessa classe for instanciado, que em classes de python a gente define o __init__
 
class Randomwebsurfer(object):
    n=None
    matriz=None
 
    def __init__(self,n):
        self.n=n
        self.matriz=self.inicia_matriz(n)

Agora toda vez que eu instancio um objeto dessa classe, por exemplo

1
objeto=Randomsurfer(5)

Eu tenho que rodar esse método (para classes a gente chama funções de métodos, mas é a mesma coisa).

Mas o que eu quero fazer, eu quero que alguém me diga quantas páginas existem, para fazer uma matriz quadrada de zeros, para anotar nela os links.

1
2
3
4
5
6
7
8
9
10
11
class Randomwebsurfer(object):
    n=None
    matriz=None
 
    def __init__(self,n):
        self.n=n
        self.matriz=self.inicia_matriz(n)
 
    def inicia_matriz(self,n):
        matriz = [[0 for i in xrange(n)] for j in xrange(n)]
        return matriz

Para tanto eu fiz esse método chamado inicia_matriz, que inicia a matriz de zeros, então dentro da classe eu chamo um método que so existe nela mesma, por isso o “self.”, assim o python sabe onde procurar, nesse caso nela mesma, na classe.

Para ver o que fizemos, podemos fazer um método para imprimir essa matriz, para ver como ela é.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Randomwebsurfer(object):
    n=None
    matriz=None
 
    def __init__(self,n):
        self.n=n
        self.matriz=self.inicia_matriz(n)
 
    def inicia_matriz(self,n):
        matriz = [[0 for i in xrange(n)] for j in xrange(n)]
        return matriz
 
    def imprime_matriz(self):
        for linha in self.matriz:
            print ''
            for item in linha:
                print item,
        print ''

Agora temos que ter um método para adiconar links também nessa matriz, porque ela não pode ser zerada. Então fazemos mais um método.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Randomwebsurfer(object):
    n=None
    matriz=None
 
    def __init__(self,n):
        self.n=n
        self.matriz=self.inicia_matriz(n)
 
    def inicia_matriz(self,n):
        matriz = [[0 for i in xrange(n)] for j in xrange(n)]
        return matriz
 
    def adiciona_link(self,linha,coluna):
        self.matriz[linha][coluna]+=1
 
    def imprime_matriz(self):
        for linha in self.matriz:
            print ''
            for item in linha:
                print item,
        print ''

Assim adicionamos o método adiciona_link, que recebe o link, da onde ele vem, que é a linha e para onde ele vai, que é uma coluna, ai adicionamos a esse item 1, que é um link, uma aresta, uma conexão entre páginas.

Agora nos vamos precisar calcular a matriz de transição, para conseguir realizar a simulação, a matriz de transição vai ter que incorporar aquelas porcentagens de 10% de ir parar por acaso em qualquer página e 90% de seguir um link na página.

As porcentagens ao acaso é fácil, basta dividir esse 10% pelo número de páginas, que fica uma pouquinho de porcentagem para cada um, se temos 5 páginas, 2% para cara uma. Já os 90% temos que dividir de forma ponderada, pelo número de vezes que outra página é linkada a ela. Para isso podemos calcular o grau de saída da página, dividimos 90% por esse número, que é dividir 90% por quantos links tem nessa página, e ai multiplicamos pela linha da matriz, o valor que esta na linha, se é zero, não há chance, se tem 2 links a chance é maior que uma que tem 1 link.

Então no código fazemos assim, um método para calcular o grau:

1
2
3
4
5
6
    def calcula_grau(self):
        self.grau=[0]*self.n
 
        for i in range(self.n):
            for j in range(self.n):
                self.grau[i]+=self.matriz[i][j]

E um método para iniciar a matriz de transição, onde calculamos o grau, fazemos uma matriz de pulo ao acaso, que é dividir o 10% entre todas as páginas, uma de link que faz a conta que eu disse, divide o 90% pelo grau da página, todos os links que ela tem, e dai multiplica pela quantidade de link para cada local, feito essas duas, a gente simplesmente soma essa duas matrizes que temos a matriz de transição.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    def inicia_matriz_transisao(self):
        self.calcula_grau()        
        pulo=[[0 for i in xrange(n)] for j in xrange(n)]
        link=[[0 for i in xrange(n)] for j in xrange(n)]
        self.transisao=[[0 for i in xrange(n)] for j in xrange(n)]
 
        for i in range(self.n):
            for j in range(self.n):
                pulo[i][j]=0.1/float(self.n)
 
        for i in range(self.n):
            for j in range(self.n):
                link[i][j]=self.matriz[i][j] * (0.9/float(self.grau[i]))
 
        for i in range(self.n):
            for j in range(self.n):
                self.transisao[i][j]=pulo[i][j]+link[i][j]

Agora veja que ela é a essência da simulação, então ela tem que ser um atributo também, além é claro, do resultado da simulação, então já vamos deixar um frequencia, para guardar o resultado da simulação.

1
2
3
4
5
6
class Randomwebsurfer(object):
    n=None
    matriz=None
    grau=None
    transisao=None
    frequencia=None

Agora vamos finalmente fazer a simulação, que vai ser assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 def simulacao(self,passos):
        self.inicia_matriz_transisao()
 
        if self.frequencia==None:
            self.frequencia=[0]*self.n
 
        pagina=0
        i=0
        while i < passos:
            sorteio=random.random()
            soma=0.0
 
            for j in xrange(self.n):
                soma+=self.transisao[pagina][j]
                if sorteio < soma:
                    pagina=j
                    break
 
            self.frequencia[pagina]+=1
            i+=1

Primeiro, a gente não vai navegar para sempre, então número de passos é o número de páginas que vamos simular, precisamos desse valor para realizar a simulação.
Nesse momento o modelo da internet, as páginas já tem que estar inserido, acho que poderíamos colocar um erro aqui, caso esse não seja o caso, mas depois eu faço as exceções.

A gente usa nosso proprio método de iniciar a matriz de transisão, depois, se não temos um local para guardar resultado, nos o iniciamos, eu fiz dessa forma testando se o resultado ja existia porque imaginei que poderiamos continuar a simulação depois, mas ok.

E temos que ter um ponto de partida, que nesse caso é a página zero, que sempre será a inicial.

Certo então começamos a simulação no while, nele a gente sorteia um numero de zero a um, depois ve em que página caiu. Que é essa parte em espeficio

1
2
3
4
5
            for j in xrange(self.n):
                soma+=self.transisao[pagina][j]
                if sorteio < soma:
                    pagina=j
                    break

suponha que temos três páginas com as porcentagens [20% 30% 50%], eu sorteio um número ao acaso entre zero e um, se caiu 0.97, qual página ele é? A porcentagem pode ser dividida assim:

20% 30% 50% [----------0.2------------0.5------------------------------------1] Página 1 Página 2 Página 3

Então temos intervalos entre zero e um, cada um de tamanho correspondente a porcentagem dada para cada página, e dependendo onde o ponto cai, é qual página a pessoa foi parar.

Por isso o for vai somando as porcentagens e comparando com o ponto, até encontrar a página.

Feito isso a gente faz um update de qual página estamos e somamos 1 a frequência com que a página é visitada, ou seja, é essa simulação se comporta como uma cadeia de markov, é um random walk sem memoria das páginas de internet.

Agora que temos nossa classe arrumadinha, podemos utilizá-la da seguinte forma

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if __name__ == '__main__':
 
    print "Lendo entrada..."
    arquivo = open(sys.argv[1], 'r')
    n=int(arquivo.readline())
    print "Numero de paginas: " +  str(n)
 
    minharede=Randomwebsurfer(n)
 
    for linha in arquivo:
        vetor=linha.strip('\n')
        vetor=vetor.split(' ')        
        while '' in vetor: vetor.remove('')
 
        for i in range(0,len(vetor),2):
            minharede.adiciona_link(int(vetor[i]),int(vetor[i+1]))
 
 
    print "Matriz de entrada:",
    minharede.imprime_matriz()
 
    print ''
    print 'Rodando simulacao...'
    minharede.simulacao(100)
 
    print ''
    print 'Resultado:'
    minharede.imprime_resultado_simulacao()

A gente faz uma método main, que é o que o python realmente vai rodar, é o nosso script. Bem a entrada vem de um arquivo, naquel formato que falamos, usandos o sys.argv para ler o nome do arquivo que a pessoa fornece ao utilizar o programa.

1
arquivo = open(sys.argv[1], 'r')

Na primeira linha temos o número de páginas

1
2
n=int(arquivo.readline())
print "Numero de paginas: " +  str(n)

A gente le e imprime ele, para ver se ta fazendo correto, lembre-se que lemos tudo como string, então temos que converter para números.
Iniciamos um objeto da nossa classe recem criada

1
minharede=Randomwebsurfer(n)

E vamos depois lendo os pares de inteiro que são os links das páginas e adicionando ao modelo com o método adiciona_link

1
2
3
4
5
6
7
for linha in arquivo:
        vetor=linha.strip('\n')
        vetor=vetor.split(' ')        
        while '' in vetor: vetor.remove('')
 
        for i in range(0,len(vetor),2):
            minharede.adiciona_link(int(vetor[i]),int(vetor[i+1]))

Com isso pronto, a gente pode imprimir a matriz que representa o modelo, pra ver se está correta

1
2
print "Matriz de entrada:",
minharede.imprime_matriz()

Realizamos a simulação, com 100 passos para experimentar

1
2
print 'Rodando simulacao...'
minharede.simulacao(100)

E imprimimos o resultado para olhar

1
2
print 'Resultado:'
minharede.imprime_resultado_simulacao()

Então utilizando nosso programinha, vemos o seguinte:

augusto@augusto-xps:~/git/recologia$ ./randomwebsurfer.py tiny.txt Lendo entrada... Numero de paginas: 5 Matriz de entrada: 0 1 0 0 0 0 0 2 2 1 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 Rodando simulacao... Resultado: 25 30 14 24 7

E pronto, temos uma pequena implementação do Random Surfer.

Acho que depois eu faço um post discutindo esse resultado, porque esse post ta ficando muito grande já, mas é legal programar um pouquinho, eu ando muito preguiçoso e a gente vai esquecendo como fazer as coisas se não treina, mas depois eu continuo esse post. Bem é isso ai, o script vai estar la no repositório recologia, vejam o exercicio orignal aqui , provavelmente com uma explicação melhor que a minha, mas eu queria apensar programar alguma coisa aqui para passar algum tempo, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Referência:

Robert Sedgewick e Kevin Wayne 2007 An Introduction to Programming in Java: An Interdisciplinary Approach – Pearson Addison Wesley 736pp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python
 
import sys
import random
 
class Randomwebsurfer(object):
    n=None
    matriz=None
    grau=None
    transisao=None
    frequencia=None
 
    def __init__(self,n):
        self.n=n
        self.matriz=self.inicia_matriz(n)
 
 
    def simulacao(self,passos):
        '''
        Esse método realiza a simulacao de navegação por um usuário.
 
        '''
        self.inicia_matriz_transisao()
 
        if self.frequencia==None:
            self.frequencia=[0]*self.n
 
        pagina=0
        i=0
        while i < passos:
            sorteio=random.random()
            soma=0.0
 
            for j in xrange(self.n):
                soma+=self.transisao[pagina][j]
                if sorteio < soma:
                    pagina=j
                    break
 
            self.frequencia[pagina]+=1
            i+=1
 
 
    def inicia_matriz(self,n):
        '''
        Esse método inicia a matriz que representa a rede, mas sem nenhuma conecao entre paginas,
        recebe um argumento que é o numero de paginas do sistema
 
        '''
        matriz = [[0 for i in xrange(n)] for j in xrange(n)]
        return matriz
 
    def adiciona_link(self,linha,coluna):
        '''
        Esse método adiciona uma conecao entre duas paginas, sendo o primeiro argumento a origem e o segundo o destino
 
        '''
        self.matriz[linha][coluna]+=1
 
    def calcula_grau(self):
        '''
        Esse método calcula o grau de cada pagina.
 
        '''
        self.grau=[0]*self.n
 
        for i in range(self.n):
            for j in range(self.n):
                self.grau[i]+=self.matriz[i][j]
 
    def inicia_matriz_transisao(self):
        '''
        Esse método calcula a matriz de transicao, a matriz e links precisam estar todos adicionados
 
        '''
        self.calcula_grau()        
        pulo=[[0 for i in xrange(n)] for j in xrange(n)]
        link=[[0 for i in xrange(n)] for j in xrange(n)]
        self.transisao=[[0 for i in xrange(n)] for j in xrange(n)]
 
        for i in range(self.n):
            for j in range(self.n):
                pulo[i][j]=0.1/float(self.n)
 
        for i in range(self.n):
            for j in range(self.n):
                link[i][j]=self.matriz[i][j] * (0.9/float(self.grau[i]))
 
        for i in range(self.n):
            for j in range(self.n):
                self.transisao[i][j]=pulo[i][j]+link[i][j]       
 
    def imprime_matriz(self):
        '''
        Esse método imprime a matriz de dados que presenta o sistema
 
        '''
        for linha in self.matriz:
            print ''
            for item in linha:
                print item,
                print ''
 
    def imprime_resultado_simulacao(self):
        '''
        Esse método imprime o resultado da simulação
 
        '''
        for i in self.frequencia:
            print i, 
 
 
 
 
#####################################
## Random Surfer
#####################################
 
if __name__ == '__main__':
 
    print "Lendo entrada..."
    arquivo = open(sys.argv[1], 'r')
    n=int(arquivo.readline())
    print "Numero de paginas: " +  str(n)
 
    minharede=Randomwebsurfer(n)
 
    for linha in arquivo:
        vetor=linha.strip('\n')
        vetor=vetor.split(' ')        
        while '' in vetor: vetor.remove('')
 
        for i in range(0,len(vetor),2):
            minharede.adiciona_link(int(vetor[i]),int(vetor[i+1]))
 
 
    print "Matriz de entrada:",
    minharede.imprime_matriz()
 
    print ''
    print 'Rodando simulacao...'
    minharede.simulacao(100)
 
    print ''
    print 'Resultado:'
    minharede.imprime_resultado_simulacao()

Generalized additive model, quando não sabemos que modelo usar.

Vamos olhar algo que um amigo meu me perguntou sempre sobre, a algum tempo atrás, o GAM, ou Generalized additive model.

Primeiro, vamos ver qual o interesse em outro método estatístico. Sabemos que existem muitos modelos matemáticos, que explicam partes determinísticas de processos biológicos, como crescimento populacional com o modelo do Lotka-Volterra ou o comportamento de populações no espaço com o modelo de metapopulações do Levins.

Normalmente a gente não coleta dados e ajusta um modelo de crescimento populacional neles. A gente usa coisas como análise de variância e regressão linear entrou outros modelos, porque são modelos mais simples, mais robustos que acabamos por detectar padrões nos dados, e depois vamos mais afundo nesses padrões.

Vamos pensar num exemplo aqui. Vamos gerar dados de um preditor, e duas respostas diferentes.

1
2
3
4
5
###Simulando Dados
set.seed(123)
preditor <- runif(30,0,4*pi)
resposta_pot<-5+2*(preditor)^2+rnorm(30,0,10)
resposta_seno<-5+2*sin(preditor)+rnorm(30)

Seja o preditor a quantidade de açúcar no néctar e a resposta a territorialidade na fonte desse néctar. Suponha que temos algo assim:

Bem a gente pode ajustar uma reta ali, usando uma regressão linear e vai dar um resultado legal.

E teremos uma relação significativa.

Call: lm(formula = resposta_pot ~ preditor) Residuals: Min 1Q Median 3Q Max -43.001 -24.431 -2.866 23.330 53.420 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) -52.67 11.82 -4.456 0.000123 *** preditor 26.04 1.47 17.718 < 2e-16 *** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Residual standard error: 28.98 on 28 degrees of freedom Multiple R-squared: 0.9181, Adjusted R-squared: 0.9152 F-statistic: 313.9 on 1 and 28 DF, p-value: < 2.2e-16

Com um valor de R quadrado bastante alto, e fim de papo? Pode ser que sim, mas podemos fazer melhor. Veja que nesse caso, temos um modelo exponencial.

Apesar da regressão resolve ali localmente, o primeiro problema é o fato de que não podemos extrapolar baseado nesse modelo de regressão, já que ele vai falhar miseravelmente além do intervalo de dados que temos, é so pensar que a curva vai subir muito mais “rápido” que a reta da regressão para imaginar isso, outro problema é que nosso residuo não tem uma distribuição normal, temos um padrão forte nele, o padrão que vem do determinismo dos dados que não está sendo coberto.

Na figura Residual vs Fitted a gente vê do que estou falando, aqui era para nao ter padrão nenhum, no entanto temos uma curva bem fácil de identificar até no olhômetro.

Mas esse é quando a gente ta com sorte, agora imagine se os dados tem um comportamento todo estranho, um padrão, mas que não é algo com cara de reta.

Ai a regressão não vai dar em nada, no caso anterior a gente poderia resolver com uma transformação, mas nesse caso não.

E aqui a robustez não funciona

Call: lm(formula = resposta_seno ~ preditor) Residuals: Min 1Q Median 3Q Max -2.8625 -1.3596 -0.2053 1.5895 3.9992 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 5.46404 0.77687 7.033 1.19e-07 *** preditor -0.06799 0.09658 -0.704 0.487 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Residual standard error: 1.904 on 28 degrees of freedom Multiple R-squared: 0.01739, Adjusted R-squared: -0.0177 F-statistic: 0.4956 on 1 and 28 DF, p-value: 0.4873

Mas o padrão está la.

Denovo a gente vê que não deveria acreditar na regressão, a primeira figura do Residual vs Fitted, a gente vê que estamos quebrando premissas, não há uma distribuição normal nos resíduos, tem um padrão neles, mas não conseguimos pegar no modelo.

Então é pra esse tipo de coisa que existe o GAM, a gente sabe que existe um padrão determinístico nos dados, mas não sabemos qual é ele, então ou a gente sai chutando vários modelos, ou a gente uma o Generalized additive model. De certa forma é bem simples usar, ele tem uma implementação no R no pacote mgcv, que é bem simples de usar.

a sintaxe é praticamente a mesma de um glm ou lm, so adicionando aquela função s(), então o ajuste fica assim.

> modelo_gam<- gam(resposta_pot~s(preditor,k=5,fx=T),family = gaussian) > summary(modelo_gam) Family: gaussian Link function: identity Formula: resposta_pot ~ s(preditor, k = 5, fx = T) Parametric coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 134.639 1.618 83.22 <2e-16 *** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Approximate significance of smooth terms: edf Ref.df F p-value s(preditor) 4 4 908.1 <2e-16 *** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 R-sq.(adj) = 0.992 Deviance explained = 99.3% GCV = 94.22 Scale est. = 78.516 n = 30

E temos um sumario bem parecido com o da regressão linear, o modelo significativo e tal, e podemos plotar o modelo.

E ele consegue capitar bem o modelo original não? Apesar de o intercepto não ter ficado muito correto, bem, existe um padrão nos dados, nos simulamos ele assim, e ele respondeu que existe esse padrão, e deu a forma mais ou menos correta.

Agora vamos ver para o segundo caso, onde a regressão linear não era de muita serventia.

> modelo_gam<- gam(resposta_seno~s(preditor,k=5,fx=T),family = gaussian) > summary(modelo_gam) Family: gaussian Link function: identity Formula: resposta_seno ~ s(preditor, k = 5, fx = T) Parametric coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 4.9750 0.2667 18.66 3.46e-16 *** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 Approximate significance of smooth terms: edf Ref.df F p-value s(preditor) 4 4 5.861 0.00181 ** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 R-sq.(adj) = 0.401 Deviance explained = 48.4% GCV = 2.5598 Scale est. = 2.1332 n = 30

E de novo conseguimos detectar que algo está acontecendo, esses dados não apenas acaso puro, e vamos ver como é o modelo que ele está propondo gráficamente.

Agora sim não? Mas que magia é essa? Bem o GAM funciona a partir de regressões locais como se medíssemos como os dados mudam ao longo da nossa amostra. Algo como o modelos de Broken-Stick em regressão segmentada ou em piecewise.

Então a é algo como a gente pega uma janelinha nos dados e faz uma regressão.

Depois outra janelinha e faz outra regressão.

E a gente vai ficando com um monte dessas regressões.

Depois é só fazer uma função, tipo piecewise bem grandona e juntar todas elas.

No nosso modelo clássico de regressão, a gente tem

Y_i = \alpha + \beta \cdot X_i + \varepsilon_i

onde o \varepsilon_i=N(0,\sigma^2),

já que falamos que estamos um erro com distrubição normal dos resíduos.

A mudança que o GAM faz é

Y_i = \alpha + f (X_i) + \varepsilon_i

onde o \varepsilon_i=N(0,\sigma^2)

e o  f (X_i) é uma função que descreve os dados, e não mais um coeficiente como era na regressão.

So que você pode pensar, ei, então esse negocio vai ajustar qualquer coisa, qualquer conjunto de dados ele vai achar uma uma função que explica!

Não é tão simples assim, podemos fazer um teste, gerar dados sem padrão nenhum e ver o que ele encontra.

E para um conjunto de dados sem padrão.

Family: gaussian Link function: identity Formula: resposta_aleatoria ~ s(preditor, k = 5, fx = T) Parametric coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) -0.2277 0.1509 -1.509 0.144 Approximate significance of smooth terms: edf Ref.df F p-value s(preditor) 4 4 1.592 0.208 R-sq.(adj) = 0.0754 Deviance explained = 20.3% GCV = 0.81982 Scale est. = 0.68318 n = 30

Ele nos diz, não tem padrão nenhum aqui uai. O modelo simplesmente cobre tudo, não existe previsibilidade nenhuma.

Agora nem tudo são flores, e temos que tomar algumas decisões, sempre existe um preço a se pagar por algo tão legal assim, que é como definir a função que descreve os dados, veja que quando estavamo vendo como é feito ela, que é definida naquela função s(), separamos os dados em quadradinhos, fazendo pequenas regressões dentro deles certo, mas qual o tamanho ideal de quadradinho a usar? Isso é o que esta sendo definido naquele argumento k dentro da função s, e esta ligado ao nossos graus de liberdade, veja que quanto menor o quadradinho, menos dados por quadradinho então é mais facil definir errado a inclinação da reta, se tiver somente um ponto num quadradinho, nem tem como definir a inclinação da reta, agora se o quadradinho for muito grande, bem a regressão linear é como se fosse so um quadrado, então nao tem porque desse método, além de que podemos peder mudanças nos dados, então isso pode ser um problema, veja que no caso dos dados oscilando na função seno, se a gente usar um k muito baixo

modelo_gam<- gam(resposta_seno~s(preditor,k=2,fx=T),family = gaussian)

Avaliando os resíduos ainda vemos que algo não está correto.

Agora se aumentarmos o k
modelo_gam<- gam(resposta_seno~s(preditor,k=5,fx=T),family = gaussian)

E podemos ir aumentando até ficar aceitavel a distribuição dos residuos, a partir do momento que estiver aceitavel, aumentar mais é disperdicio pois estamos diminuindo os graus de confiança sem obter nenhum ganho em ter um modelo com melhor ajuste.

E é claro, alguém automatizou isso, então se você coloca -1 no k, a gente usa um algoritimo que define esse valor automaticamente.

Bem é isso ai, uma pequena introdução ao GAM, veja que aquela similaridade com a ideia de regressão não é so para ser bonitinho, isso permite trabalharmos com mais de um termo, interação entre termos, variáveis aleatória, bem tudo que da pra fazer com regressão da para fazer com GAM, sendo que regressão linear pode ser considerado um caso especial de GAM, que é quando a gente cai na função da reta. Eu acho que ando muito desanimado em casa, mas acho que preciso voltar a fazer posts com análise de dados que é muito legal pra ver se animo. O script vai estar la no repositório recologia, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Referência:

Zuur, AF 2012 Beginner’s Guide to Generalized Additive Models with R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
###Simulando Dados
set.seed(123)
preditor <- runif(30,0,4*pi)
resposta_pot<-5+2*(preditor)^2+rnorm(30,0,10)
resposta_seno<-5+2*sin(preditor)+rnorm(30)
 
##
jpeg("01.jpg")
plot(resposta_pot~preditor,frame=F,pch=19,xlab="Preditor", ylab="Resposta")
dev.off()
 
##
jpeg("02.jpg")
plot(resposta_pot~preditor,frame=F,pch=19,xlab="Preditor", ylab="Resposta")
modelo_linear <- lm(resposta_pot~preditor)
abline(modelo_linear, col="red")
dev.off()
 
##
summary(modelo_linear)
 
##
jpeg("03.jpg")
plot(resposta_pot~preditor,frame=F,pch=19,xlab="Preditor", ylab="Resposta")
curve(5+2*x^2,add=T,lwd=2,col="blue")
legend("topleft",col=c("blue","red"),lwd=c(2,1),legend=c("Realidade","Modelo"),bty="n")
 
modelo_linear <- lm(resposta_pot~preditor)
abline(modelo_linear, col="red")
novos_dados <- seq(0,4*pi,0.25)
pred_interval <- predict(modelo_linear, newdata=data.frame(preditor=novos_dados), interval="prediction",level = 0.95)
lines(novos_dados, pred_interval[,2], col="orange", lty=2)
lines(novos_dados, pred_interval[,3], col="orange", lty=2)
dev.off()
 
##
jpeg("04.jpg")
par(mfrow=c(2,2))
plot(modelo_linear)
dev.off()
 
##
jpeg("05.jpg")
plot(resposta_seno~preditor,frame=F,pch=19,ylim=c(0,12),xlab="Preditor",ylab="Resposta")
curve(5+2*sin(x),add=T,lwd=2,col="blue")
legend("topleft",col=c("blue","red"),lwd=c(2,1),legend=c("Realidade","Modelo"),bty="n")
 
modelo_linear <- lm(resposta_seno~preditor)
abline(modelo_linear, col="red")
novos_dados <- seq(0,4*pi,0.25)
pred_interval <- predict(modelo_linear, newdata=data.frame(preditor=novos_dados), interval="prediction",level = 0.95)
lines(novos_dados, pred_interval[,2], col="orange", lty=2)
lines(novos_dados, pred_interval[,3], col="orange", lty=2)
dev.off()
 
##
summary(modelo_linear)
 
##
jpeg("06.jpg")
par(mfrow=c(2,2))
plot(modelo_linear)
dev.off()
 
##Pacote utilizado  para o GAM
##install.packages("mgcv")
library(mgcv)
 
## Ajuste de modelo
modelo_gam<- gam(resposta_pot~s(preditor,k=5,fx=T),family = gaussian)
summary(modelo_gam)
 
## Visualização do modelo
jpeg("07.jpg")
plot(modelo_gam)
dev.off()
 
##
modelo_gam<- gam(resposta_seno~s(preditor,k=5,fx=T),family = gaussian)
summary(modelo_gam)
 
##
jpeg("08.jpg")
plot(modelo_gam)
dev.off()
 
##
jpeg("09.jpg")
plot(resposta_seno~preditor,frame=F,pch=19,ylim=c(0,12))
 
points(x=c(4,4,6,6,4),y=c(2,4,4,2,2),type="l",lty=2)
selecao <- preditor>=4 & preditor<=6
modelo_linear <- lm(resposta_seno[selecao]~preditor[selecao])
curve(coef(modelo_linear)[1]+x*coef(modelo_linear)[2],4,6,add=T,lwd=2,col="red")
dev.off()
 
##
jpeg("10.jpg")
plot(resposta_seno~preditor,frame=F,pch=19,ylim=c(0,12))
 
points(x=c(4,4,6,6,4)+3,y=c(2,4,4,2,2)*2+1,type="l",lty=2)
selecao <- preditor>=7 & preditor<=9
modelo_linear <- lm(resposta_seno[selecao]~preditor[selecao])
curve(coef(modelo_linear)[1]+x*coef(modelo_linear)[2],7,9,add=T,lwd=2,col="red")
dev.off()
 
##
jpeg("11.jpg")
plot(resposta_seno~preditor,frame=F,pch=19,ylim=c(0,12))
 
points(x=c(4,4,6,6,4),y=c(2,4,4,2,2),type="l",lty=2)
selecao <- preditor>=4 & preditor<=6
modelo_linear <- lm(resposta_seno[selecao]~preditor[selecao])
curve(coef(modelo_linear)[1]+x*coef(modelo_linear)[2],4,6,add=T,lwd=2,col="red")
 
points(x=c(4,4,6,6,4)+3,y=c(2,4,4,2,2)*2+1,type="l",lty=2)
selecao <- preditor>=7 & preditor<=9
modelo_linear <- lm(resposta_seno[selecao]~preditor[selecao])
curve(coef(modelo_linear)[1]+x*coef(modelo_linear)[2],7,9,add=T,lwd=2,col="red")
dev.off()
 
##Testando gam contra dados aleatórios
resposta_aleatoria <- rnorm(30)
 
##
jpeg("12.jpg")
plot(resposta_aleatoria~preditor,pch=19,frame=F)
dev.off()
 
modelo_gam<- gam(resposta_aleatoria~s(preditor,k=5,fx=T),family = gaussian)
summary(modelo_gam)
 
##
jpeg("13.jpg")
plot(modelo_gam)
dev.off()
 
##Definindo um valor de K
modelo_gam<- gam(resposta_seno~s(preditor,k=2,fx=T),family = gaussian)
jpeg("14.jpg")
hist(resid(modelo_gam))
dev.off()
 
##
modelo_gam<- gam(resposta_seno~s(preditor,k=5,fx=T),family = gaussian)
jpeg("15.jpg")
hist(resid(modelo_gam))
dev.off()
 
##Auto ajuste de k
modelo_gam<- gam(resposta_seno~s(preditor,k=-1,fx=T),family = gaussian)
jpeg("16.jpg")
hist(resid(modelo_gam))
dev.off()