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()

Usando o Pi-Hole, um filtro de DNS

Algo bem legal de usar o raspberry pi é usar ele como servidor de DNS, para bloquear algumas propagandas. E existe uma solução perfeita para isso chamada pi hole.

Primeiro temos que saber o que é DNS.

DNS é o Domain Name System, um sistema que responde qual o ip de um determinado nome. Basicamente ele traduz nomes em endereços de ip. Antes de existir o DNS, não existiam páginas como google.com, tudo era um endereço de ip, e você tinha que ter uma listinha de ips que te interessavam, até que alguém fez o dns para tirar essa listinha da sua mão e deixar num servidor, porque é mais prático, e mais simples lembrar de google.com do que um endereço de ip.

Pra entender melhor, no terminal no linux, a gente tem o programa chamado host que diz em qual ip um endereço responde, então por exemplo o google.com

augusto@augusto-xps:~$ host google.com google.com has address 216.58.202.174 google.com has IPv6 address 2800:3f0:4001:815::200e google.com mail is handled by 30 alt2.aspmx.l.google.com. google.com mail is handled by 40 alt3.aspmx.l.google.com. google.com mail is handled by 20 alt1.aspmx.l.google.com. google.com mail is handled by 50 alt4.aspmx.l.google.com. google.com mail is handled by 10 aspmx.l.google.com.

Ele na verdade está no ip 216.58.202.174, o programa host fez uma pergunta para o DNS, e esse respondeu. Agora, veja que o google.com.br está em outro endereço.

augusto@augusto-xps:~$ host google.com.br google.com.br has address 216.58.202.3 google.com.br has IPv6 address 2800:3f0:4001:807::2003 google.com.br mail is handled by 40 alt3.aspmx.l.google.com. google.com.br mail is handled by 10 aspmx.l.google.com. google.com.br mail is handled by 20 alt1.aspmx.l.google.com. google.com.br mail is handled by 50 alt4.aspmx.l.google.com. google.com.br mail is handled by 30 alt2.aspmx.l.google.com.

No caso 216.58.202.3, provavelmente porque deve estar mais perto da gente, para evitar ter que percorrer uma grande distância física, mas isso não vem ao caso agora.

Então o DNS é uma lista que fala qual ip está associado a qual nome. Normalmente a gente usa o dns do nosso provedor, ou o do google, mas ter controle sobre o dns pode ser interessante, para bloquear endereços indesejados de propagandas por exemplo, que é o que o pi hole faz.

Para instalar ele no raspberry é bem fácil, começamos fazendo um update e upgrade.

sudo apt-get update
sudo apt-get upgrade

E para instalar

sudo curl -sSL https://install.pi-hole.net | sudo bash

Veja que eu coloco o sudo, porque precisamos ser root, e eu achei mais fácil assim. E pronto, acabou, eu escolhi todas as opções default na instalação e já era. Meu Raspberry é um servidor de DNS bloqueando links indesejados.

Agora quando todo mundo conecta no meu roteador, eu tenho que avisar a todos dispositivo que é para usar o raspberry como DNS, então precisamos agora alterar no roteador isso, que no meu roteador dlink fica logo na tela inicial que se configura a conexão.

Bem simples, agora meu raspberry, que está no endereço de rede interno 192.168.0.3 fixo funciona como DNS server, todo mundo pergunta para ele qual o IP das páginas, claro que não está tudo nele, num grande arquivo, existe uma hierarquia de procura, mas depois vemos isso, o que interessa é que ele da a resposta final, você fala um nome e ele da o ip.

Como tudo não são flores, eu tive um problema que o pi hole começou a bloquear meus javascript, temos que consertar alguns problemas que tive. Lembra que deixo o raspberry em kiosk mode? Então, meu kiosk mode pifou sem javascript, na página, o javascript vinha assim:

x = "Pi-hole: A black hole for Internet advertisements."

Mas com algumas buscas eu vi que ele altera alguns arquivos, nesse caso esse arquivo /etc/lighttpd/lighttpd.conf, então precisamos fazer uma pequena modificação nele.

sudo emacs /etc/lighttpd/lighttpd.conf

O arquivo é grande, mas o problema são essas duas linhas, que basta comentar, e inserir um comentário acima para lembrar que fomos nos que comentamos.

# Impede o uso de javascript # Rewite js requests, must be out of $HTTP block due to bug #2526 # url.rewrite = ( "^(?!/admin/).*\.js$" => "pihole/index.js" )

Feito isso tudo volta a funcionar. Agora para entender o que está sendo feito, como o pi hole está funcionando, veja como uma página como o pirate bay fica sem bloquear o DNS de propaganda.

Agora bloquando, a gente ve o seguinte.

Não abre as propagandas, ou seja, quando o navegador pergunta, ei DNS, qual o ip dessa propaganda para que eu possa baixar as imagens, o DNS fala que não achou e pimba, a gente não baixa a imagem, e fica desse jeito a página. Ai você pode dizer, a, mas eu uso algum bloqueador já, mas de qualquer forma você baixou a imagem e depois bloqueou, dessa forma você nem baixa propaganda, o que pode diminuir até o seu tráfego. Além de que, você pode bloquear páginas maliciosas também de quebra.

O pi hole tem um menu para administração.

Ele traz uma série de informações, e ao fim da instalação, ele te da uma senha gerada automática para acessar esse menu de administração.

Nele podemos ligar e desligar, mexer em várias opções, podemos curiosar inclusive sobre o tráfego, o que está mais sendo bloqueado.

So de olhar os endereços bloqueados, a gente ve o tanto de informação que deve estar mandando para caras como o google, sobre nossa atividade na internet, além das propagandas.

Veja que quem está fazendo mais requisições de DNS, lembrando que 192.168.0.1 é o meu roteador, ou seja meu notebook pergunta o DNS pro roteador, que pergunta pro raspberry, que resolve, e devolve pro roteador que devolve pro notebook, por isso as requisições vem todas do 192.168.0.1.

E podemos resolver nossos próprios endereços também. No arquivo /etc/dnsmasq.d/01-pihole.conf temos algumas configurações interessantes.

pi@rasp_levins:~ $ cat /etc/dnsmasq.d/01-pihole.conf # Pi-hole: A black hole for Internet advertisements # (c) 2015, 2016 by Jacob Salmela # Network-wide ad blocking via your Raspberry Pi # http://pi-hole.net # dnsmasq config for Pi-hole # # Pi-hole is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. ############################################################################### # FILE AUTOMATICALLY POPULATED BY PI-HOLE INSTALL/UPDATE PROCEDURE. # # ANY CHANGES MADE TO THIS FILE AFTER INSTALL WILL BE LOST ON THE NEXT UPDATE # # # # IF YOU WISH TO CHANGE THE UPSTREAM SERVERS, CHANGE THEM IN: # # /etc/pihole/setupVars.conf # # # # ANY OTHER CHANGES SHOULD BE MADE IN A SEPERATE CONFIG FILE # # OR IN /etc/dnsmasq.conf # ############################################################################### addn-hosts=/etc/pihole/gravity.list addn-hosts=/etc/pihole/local.list addn-hosts=/etc/pihole/black.list domain-needed localise-queries bogus-priv no-resolv server=8.8.8.8 server=8.8.4.4 interface=eth0 cache-size=10000 log-queries log-facility=/var/log/pihole.log local-ttl=300 log-async

Veja que no começo, ele chama 3 arquivos, que são listas de nomes com ips para resolver, no caso a lista local.list, da nossa rede interna aqui em casa tem.

pi@rasp_levins:~ $ cat /etc/pihole/local.list 192.168.0.3 rasp_levins 192.168.0.3 pi.hole

Agora quando eu digito http://pi.hole eu sou mandando para o ip 192.168.0.3, ou seja para acessar o pi.hole, eu posso usar http://pi.hole ao invés de http://192.168.0.3. Lembra do comando host la que usamos la em cima, veja so

Agora por exemplo podemos fazer um atalho, ou simplificar para usar o ssh, que também usa o DNS, podemos adicionar rasp_levins.local naquele aquivo com o ip 192.168.0.3, assim:

192.168.0.3 rasp_levins 192.168.0.3 pi.hole 192.168.0.3 rasp_levins.local

E agora basta usar ssh pi@rasp_levins.local ao inves de ssh pi@192.168.0.3.

Bem é isso ai, algo que não mexemos aqui é na interface que o pi hole tem, mas eu vou colocar alguns gráficos no kiosk mode ainda, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Criando um gráfico com bandeiras dos países.

Esses tempos, um colega me pediu para fazer alguns gráficos tipo os que tem no google analytics, mais especificamente nesse estilo aqui.

É de uma versão mais antiga dessa figura, mas a ideia é essa, basicamente um gráfico de barras na vertical. A única parte difícil, foi as bandeiras, primeiro foi achar essas bandeiras, depois inserir na figura.

Para achar as figuras, a primeira coisa foi procurar algum pacote que tivesse isso pronto, achei que encontraria, mas não achei nenhum, então ao procurar pelas bandeiras, uma fonte seria baixar diretamente do wikipedia, que tem bandeiras para todos os paises, mas eu achei esse conjunto aqui, que inclusive está em um repositorio do github que podemos clonar, o que facilita bastante o trabalho, ali temos as bandeiras em formato png em vários tamanhos, eu preferi usar o de maior resolução.

então clonado o repositório com as bandeiras, eu fiz a figura com o seguinte código:

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
library(png)
paises<-list.files("64")
 
##Figura country.png
 
country<-c("Brazil","United States","Portugal","Mozambique","Mexico","Germany","Peru","Australia","Cape Verde","Angola")
country_br<-c("Brasil","Estados Unidos","Portugal","Mozambique","México","Alemanha","Perú","Austrália","Cabo Verde","Angola")
sessions<-c(2113,259,85,63,54,49,38,37,36,36)
sessions_percentage<-c(60.39,7.4,2.43,1.8,1.54,1.4,1.09,1.06,1.03,1.03)
 
ampliacao <- 2
jpeg("country.jpg",width = ampliacao*480,height = ampliacao*480,pointsize = ampliacao*12)
escala<-barplot(rev(sessions_percentage),horiz=T,xlim=c(-150,100),xaxt="n",col="#76A7FA")
text(rep(0,10),escala,rev(sessions),pos=2)
text(rev(sessions_percentage)+14,escala,paste(rev(format(round(sessions_percentage, 2), nsmall = 2)),"%",sep=""))
text(rep(-130,10),escala,10:1,pos=2)
text(rep(-120,10),escala,rev(country_br),pos=4)
mtext(c("País","Sessões","% Sessões"),3,0,at=c(-125,-15,35))
 
list.files()
 
for(i in 1:10){
    country[i]
    posicao<-agrep(country[i],paises)
    paises[posicao]
    bandeira<-readPNG(paste("64/",paises[posicao],sep=""))
    rasterImage(bandeira,-133,rev(escala)[i]-0.5,-117,rev(escala)[i]+0.5)
}
dev.off()

Que gera a seguinte figura.

Bem legal eu achei, agora vou fazer alguns comentários do meu código.

Primeiro:

1
2
country<-c("Brazil","United States","Portugal","Mozambique","Mexico","Germany","Peru","Australia","Cape Verde","Angola")
country_br<-c("Brasil","Estados Unidos","Portugal","Mozambique","México","Alemanha","Perú","Austrália","Cabo Verde","Angola")

Bem como são poucos dados, eu digitei o nomes do países em inglês e em português, isso porque o nome em inglês eu uso para achar o nome do arquivo da bandeira, que é o nome do pais, e em português para usar na figura em si.

Depois disso para facilitar a mudança do tamanho, eu costumo guardar uma variável para o tamanho, que multiplica a largura, a altura e o tamanho do ponto, assim só altero ela para mudar o tamanho da figura, a sua resolução.

1
2
3
4
5
6
ampliacao <- 2
jpeg("country.jpg",width = ampliacao*480,height = ampliacao*480,pointsize = ampliacao*12)
 
##Figura
 
dev.off()

A figura em si é um barplot na vertical, a única coisa é que mudo bastante as margens, o tamanho do eixos de forma a caber as outras informações e bandeiras, que são colocadas com a função text para dentro da figura e mtext para a parte de fora. Depois disso, talvez essa parte ainda tenha algo de interesse.

1
2
3
4
5
6
7
for(i in 1:10){
    country[i]
    posicao<-agrep(country[i],paises)
    paises[posicao]
    bandeira<-readPNG(paste("64/",paises[posicao],sep=""))
    rasterImage(bandeira,-133,rev(escala)[i]-0.5,-117,rev(escala)[i]+0.5)
}

Então eu pego o nome do pais em inglês, e uso agrep na lista de nomes de arquivos, agrep, porque é um grep, ou seja acha a palavra na lista, mas permite alguns erros de caracteres, que da para regular com argumentos, mas então com o agrep a gente vai achar o nome do arquivo, com ele a gente le a figura png com o readPNG, que le o raster da figura, então com rasterImage a gente adiciona ela a figura, aqui a gente tem que colocar os quatro pontos da imagem para adicionar ela, veja se da até para colocar a imagem espelhada, feito isso ta pronto. Para economizar, eu fiz um loop para todas as bandeiras.

Bem é isso ai, 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. Um post bem simples, mas é o tipo de código que é legal guardar, pois pode ser útil copiar e colar um dia.

Iniciando uma pequena Aplicação para controlar atividades no Raspberry

Depois de concluir a faculdade, uma área que eu achei bem legal foi o desenvolvimento web, e resolvi tentar fazer um projeto pessoal na área.

Normalmente eu controlo coisas como áudio e vídeo no raspberry acessando ele remotamente via ssh, mas eu gostaria de controlar do celular ou de qualquer outro dispositivo, e uma forma de fazer isso é com uma aplicaçãozinha web. Eu ja fiz algumas coisas simples como ligar e desligar a música, videos, e apesar de só ser acessível dentro da rede interna aqui que o roteador de casa faz, eu gostaria de fazer algo mais organizado, usando senha.

O que eu quero é fazer algo assim:

Um banner superior com um menu lateral, e algumas informações acima do banner, no centro da tela então eu uma tela com os controles, de som por exemplo.

Então mais especificamente, eu quero fazer algo assim:

Se você entrar na página deslogado, eu peço uma senha, e uma vez que você tenha se autenticado:

A gente começa a aplicação, e deixa algumas informações de login, como quem está logado.

Bem, como eu não conheço muitas tecnologias, o que está mais a mão no raspberry é o php, então eu resolvi usar o php no lado do servidor e html, css e javascript para fazer algumas páginas e formulários. Além disso, vou tentar organizar tudo usando o MVC, model, view e controller.

Eu instalei o apache e o php no raspberry segundo os tutoriais da própria página do raspberry, então dentro da pasta /var/www/html eu criei uma pasta chamada rasp_apps para colocar meu código. O conteúdo dessa pasta está no repositório de mesmo nome aqui, de forma que código fica público, e é fácil administrar mudanças, backup, etc. Acho que não estou adicionando muita coisa aqui, essas são mais notas mentais do que uma explicação do que estou fazendo de verdade.

Mas tudo bem, vamos ao código em si.

Dentro da pasta inicial da minha aplicação, temos um index.php, e nele temos:

1
2
3
4
5
6
7
<?php
require_once 'Controller/controller.php';
ob_start();
session_start(['cookie_lifetime' => 86400,]);
$controller = new Controller();
$controller->init();
?>

Bem, aqui a gente inicia um cookie, que é a forma de manter alguém conectado, de outra forma, não teríamos como manter as variáveis, depois vou tentar escrever sobre a comunicação web, mas depois disso a gente cria um objeto da classe controller que criamos e iniciamos ele, com a função init.

Eu não fiz uma pasta model ainda, pois ainda não comecei a usar banco de dados, até aqui eu só fiz um formulário para entrar com usuário e senha e validar. E como eu faço isso? Para isso server o controller, ele controla tudo, e chama as visualizações.

Quando a gente roda

1
$controller = new Controller();

nos construímos o objeto, que nesse caso, tudo que a gente faz é

1
2
3
4
public function __construct() {
        ini_set('error_reporting', E_ALL);
        ini_set('display_errors', 1);
    }

Habilitar a apresentação dos erros do php, que ajudam a entender porque tudo não funciona, e depois nos rodamos o init

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
 
 public function init() {       
 
        if (isset($_SESSION['valid'])){
 
            if (isset($_GET['op'])) {
                $op = $_GET['op'];
            } else {
                $op = "";
            }
 
            switch ($op) {
            case 'cfg':
                $this->cfg();
                break;
            case 'logout':
                $this->logout();
                break;
            default:
                $this->index();
                break;
            }
 
        }else{
            $this->login();
        }
 
    }

Esse método, é basicamente um ifelse para saber se a pessoa está autenticada,

1
2
3
4
5
6
7
8
9
10
 
if (isset($_SESSION['valid'])){
 
 
             ###Código
            
 
        }else{
            $this->login();
        }

Senão nos vamos logar, e quanto estivermos logados, vamos cair num switch, que vai administrar as possibilidades da aplicação. Mas então para logar, chamamos o método login.

1
2
3
4
5
6
7
8
9
10
public function login() {
        if (isset($_POST['login']) && !empty($_POST['usuario']) && !empty($_POST['senha'])) {
                $this->valida_login();
        } else{
            if(isset($_SESSION['mensagem'])==false){
                $_SESSION['mensagem']='';
            }            
            require 'View/login.php';
        }
    }

O login, vai olhar se foi enviado algo pelo método post, se o usuário enviou algum dado, como é a primeira entrada no método, nenhuma dessas variáveis existem, então vamos ao else, que olhar se tem alguma mensagem, para apaga-la, e como não tem nada, ele só chama a tela de login.

O que de fato interessa na tela de login é o seguinte:

1
2
3
4
5
6
7
8
9
<?php echo $_SESSION['mensagem']?>
 
	<form action="?op=''" method="post" ?>
 
	    <input type = "text"  name = "usuario" placeholder = "Usuário = teste" required autofocus></br>
	    <input type = "password"  name = "senha" placeholder = "Senha = 1234" required>
	    <button type = "submit" name = "login">Login</button>
 
	</form>

Aquela mensagem, ela vai ser exibida, se tiver algo nela, e temos um formulário, que preenche exatamente aquelas informações de usuário e senha e tem um botão que envia essas informações ao controller, veja que a gente preenche duas coisas, o usuário e a senha, enviando via post.

Agora, vamos denovo passar pelo init, não estamos autenticados ainda, o que nos leva para o método login, mas agora temos um usuário e senha então vamos para o método valida_login

1
2
3
4
5
6
7
8
9
10
11
public function valida_login() {
            if ($_POST['usuario'] == 'teste' && $_POST['senha'] == '1234') {
                $_SESSION['valid'] = true;
                $_SESSION['timeout'] = time();
                $_SESSION['usuario'] = 'Rasp_levins';
            }else {
                unset($_POST);
                $_SESSION['mensagem'] = 'Usuário ou senha incorreto!';
            }
            $this->init();
    }

Agora a gente pode finalmente testar se o usuário e senha conferem, e nesse caso, validar a sessão, ou se o usuário digitou o conjunto usuário e senha errados, nós geramos uma mensagem de erro, e não validamos a sessão.

A princípio, é tudo bem simples, e temos somente um usuário e senha no método de avaliação. Agora o próximo passo é criar uma base de dados com usuário e senha para fazer essa consulta, de forma a não ficar os usuários e senhas ali no meio do código, bem existem muitas coisas a serem feitas ainda, mas fazer a conexão com um banco de dados já possibilita várias outras coisas.

Bem é isso ai, o script vai estar la no repositório rasp_aaps, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.