<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Viniboscoa]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.viniboscoa.dev/</link><image><url>https://blog.viniboscoa.dev/favicon.png</url><title>Viniboscoa</title><link>https://blog.viniboscoa.dev/</link></image><generator>Ghost 5.43</generator><lastBuildDate>Thu, 23 Apr 2026 19:31:50 GMT</lastBuildDate><atom:link href="https://blog.viniboscoa.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Dependency Injection Container - combinando padrões de projeto]]></title><description><![CDATA[Nesse artigo exploramos os padrões de projeto Registry, Singleton, Proxy e Decorator com a implementação de um container de injeção de dependências.]]></description><link>https://blog.viniboscoa.dev/dependency-injection-container-with-design-patterns/</link><guid isPermaLink="false">6599791b74e77c0001419764</guid><category><![CDATA[Desenvolvimento]]></category><category><![CDATA[Design patterns]]></category><category><![CDATA[Development]]></category><category><![CDATA[padrões de projeto]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Sat, 06 Jan 2024 20:02:48 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2024/01/v870-tang-15-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2024/01/v870-tang-15-1.jpg" alt="Dependency Injection Container - combinando padr&#xF5;es de projeto"><p>Inje&#xE7;&#xE3;o de depend&#xEA;ncia (<em>dependency injection - ou DI</em>) &#xE9; uma t&#xE9;cnica de programa&#xE7;&#xE3;o na qual objetos ou fun&#xE7;&#xF5;es recebem outros objetos e fun&#xE7;&#xF5;es que eles necessitam, ao inv&#xE9;s de constru&#xED;-los internamente. Abaixo segue uma forma simples de inje&#xE7;&#xE3;o de depend&#xEA;ncia:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// Definindo as classes
class MinhaClasse {
	constructor(minhaDependencia: MinhaDependencia) {}
}

class MinhaDependencia {
}

// Construindo objetos
const meuObjeto = new MinhaClasse(new MinhaDependencia());</code></pre><figcaption>Exemplo de inje&#xE7;&#xE3;o de depend&#xEA;ncia</figcaption></figure><p>Imagine, no entanto, que <code>meuObjeto</code> dependa de v&#xE1;rios outros objetos, e cada um desses objetos dependa de mais alguns e assim por diante... Com o passar do tempo a constru&#xE7;&#xE3;o de um objeto simples como <code>meuObjeto</code> poderia se tornar muito verbosa e de dif&#xED;cil manuten&#xE7;&#xE3;o, vamos a um exemplo:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// Definindo classes

class MinhaClasse {
	constructor(minhaDependencia1: MinhaDependencia1, minhaDependencia2: MinhaDependencia2) {}
}

class MinhaDependencia1 {
	constructor(outraDependencia: OutraDependencia1) {}
}

class MinhaDependencia2 {
	constructor(outraDependencia: OutraDependencia2) {}
}

class OutraDependencia1 {
}

class OutraDependencia2 {
}

// Construindo objetos
const dependencia1 = new MinhaDependencia1(new OutraDependencia1());
const dependencia2 = new MinhaDependencia2(new OutraDependencia2());
const meuObjeto = new MinhaClasse(dependencia1, dependencia2);
// ...</code></pre><figcaption>Exemplo de inje&#xE7;&#xE3;o de depend&#xEA;ncia com m&#xFA;ltiplas depend&#xEA;ncias</figcaption></figure><p>Para esses tipos de situa&#xE7;&#xE3;o um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia (<em>dependency injection container - </em>ou <em>DI container</em>) pode ser &#xFA;til principalmente para facilitar a leitura e, algumas vezes, ajudando a evitar duplica&#xE7;&#xE3;o de c&#xF3;digo.</p><p>Um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia, de forma resumida, &#xE9; uma classe - normalmente global - capaz de criar e configurar objetos. Sendo assim, ele deve ter pelo menos duas funcionalidades bem simples: registrar e injetar depend&#xEA;ncias.</p><p>Ao decorrer desse artigo iremos construir e utilizar um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia simples utilizando alguns padr&#xF5;es de projeto. O c&#xF3;digo desenvolvido foi baseado em uma implementa&#xE7;&#xE3;o realizada em uma das aulas do curso de <a href="https://branas.io/?ref=blog.viniboscoa.dev">Clean Code e Clean Architecture</a> do Rodrigo Branas e <a href="https://branas.io/blog/qual-e-a-diferenca-entre-dependency-injection-e-dependency-inversion.html?ref=blog.viniboscoa.dev">desse artigo</a> em seu blog.</p><h2 id="conhecendo-o-padr%C3%A3o-registry">Conhecendo o padr&#xE3;o Registry</h2><p>Martin Fowler, em seu livro <em>Patterns of Enterprise Application Architecture,</em> descreve o padr&#xE3;o <em>Registry </em>como &quot;um objeto [global] bem conhecido que outros objetos podem utilizar para encontrar servi&#xE7;os e objetos comuns&quot; (tradu&#xE7;&#xE3;o livre).</p><p>Diante das caracter&#xED;sticas de um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia e levando em conta a fun&#xE7;&#xE3;o de um <em>Registry, </em>precisamos garantir que todos os objetos da aplica&#xE7;&#xE3;o acessem a mesma inst&#xE2;ncia de <em>Registry</em> - e considerando que estamos trabalhando com uma aplica&#xE7;&#xE3;o <em>single-threaded</em> <em>- </em>outro padr&#xE3;o pode nos ajudar com essa tarefa. Estamos falando do...</p><h2 id="singleton">Singleton</h2><p>Conforme descrito no livro Padr&#xF5;es de Projeto (Gang of Four), o padr&#xE3;o <em>singleton </em>&#xE9; um padr&#xE3;o de cria&#xE7;&#xE3;o cujo objetivo &#xE9; &quot;garantir que uma classe tenha somente uma inst&#xE2;ncia e fornecer um ponto global de acesso para a mesma&quot;.</p><h3 id="anatomia-de-um-singleton">Anatomia de um singleton</h3><p>Em geral um singleton possui algumas caracter&#xED;sticas que permitem identific&#xE1;-lo de uma maneira r&#xE1;pida e direta, s&#xE3;o elas:</p><ul><li>Propriedade est&#xE1;tica que armazena a inst&#xE2;ncia da classe</li><li>Construtor privado</li><li>M&#xE9;todo est&#xE1;tico para recuperar a inst&#xE2;ncia do objeto ou instanci&#xE1;-lo caso n&#xE3;o exista.</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// Definindo singleton
class MeuSingleton {
	static instance: MeuSingleton;
    
    private constructor() {
    	// ...
    }
    
    static getInstance() {
    	if (!MeuSingleton.instance) {
			MeuSingleton.instance = new MeuSingleton();
		}
		return MeuSingleton.instance;
    }
}

// utilizando singleton
const singleton = MeuSingleton.getInstance();</code></pre><figcaption>Exemplo de implementa&#xE7;&#xE3;o de singleton</figcaption></figure><p>Dessa forma, sempre que invocarmos o m&#xE9;todo <code>getInstance()</code> estaremos recebendo a mesma inst&#xE2;ncia de <code>MeuSingleton</code> independente de onde o chamarmos (desde que esteja executando no mesmo processo).</p><h2 id="criando-o-container">Criando o Container</h2><p>O container de inje&#xE7;&#xE3;o de depend&#xEA;ncia deve possuir, pelo menos duas funcionalidades: registrar depend&#xEA;ncias e injetar depend&#xEA;ncias.</p><p>Combinando o Registry com o Singleton, conseguimos garantir que todas as depend&#xEA;ncias registradas no container estar&#xE3;o sempre na mesma inst&#xE2;ncia, independente de onde ele for chamado:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// Definindo registry (DI container)
class Registry {
	dependencies: {[name: string]: any}
	static instance: Registry;
    
    private constructor() {
    	this.dependencies = {};
    }
    
    register(name: string, dependency: any) {
    	this.dependencies[name] = dependency
    }
    
    inject(name: string) {
    	if(!this.dependencies[name]) throw new Error(&quot;Dependency not found&quot;);
    	return this.dependencies[name];
    }
    
    static getInstance() {
    	if (!Registry.instance) {
			Registry.instance = new MeuSingleton();
		}
		return Registry.instance;
    }
}</code></pre><figcaption>Implementa&#xE7;&#xE3;o do container</figcaption></figure><p>Com o que temos at&#xE9; aqui j&#xE1; &#xE9; poss&#xED;vel termos os benef&#xED;cios de um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia em nossa aplica&#xE7;&#xE3;o, podemos construir o container e pass&#xE1;-lo por inje&#xE7;&#xE3;o &#xE0;s nossas classes, por exemplo:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// main.ts
const registry = Registry.getInstance();
registry.register(&quot;minhaDependencia&quot;, new MinhaDependencia);
const meuObjeto = new MinhaClasse(registry);

// MinhaClasse.ts
class MinhaClasse() {

	private dependency: MinhaDependencia;

	constructor(registry: Registry) {
    	this.dependency = registry.inject(&quot;minhaDependencia&quot;);
    }
}</code></pre><figcaption>Exemplo de implementa&#xE7;&#xE3;o direta de registry</figcaption></figure><p>Essa forma de implementa&#xE7;&#xE3;o j&#xE1; &#xE9; suficiente para simplificar nosso problema inicial, por&#xE9;m ainda exige que todas as nossas classes recebam uma inst&#xE2;ncia de <em>registry</em> para serem capazes de injetar a depend&#xEA;ncia correspondente para sua &#xA0;correta execu&#xE7;&#xE3;o.</p><p>Por&#xE9;m ainda podemos simplificar. Algumas linguagens, como o <code>typescript</code>, implementam, um recurso chamado <em>decorator (ou annotations) </em>que de maneira bem resumida e superficial, podemos definir como, atalhos (a&#xE7;&#xFA;car sint&#xE1;tico - <em>sugar syntax</em>) de implementa&#xE7;&#xE3;o do padr&#xE3;o de projeto de mesmo nome.</p><h2 id="decorator">Decorator</h2><p>Decorator &#xE9; um padr&#xE3;o estrutural descrito no livro Padr&#xF5;es de Projeto (Gang of Four) cujo objetivo &#xE9; &quot;dinamicamente, agregar responsabilidades adicionais a um objeto&quot;. </p><p>Em ess&#xEA;ncia o <em>decorator</em> define a mesma interface do componente que ser&#xE1; decorado (Component) e mat&#xE9;m uma refer&#xEA;ncia ao mesmo. Quando invocado, ele repassa solicita&#xE7;&#xF5;es para objeto podendo executar opera&#xE7;&#xF5;es adicionais antes e depois de repassar a solicita&#xE7;&#xE3;o.</p><h3 id="anatomia-de-um-decorator">Anatomia de um decorator</h3><p>Vamos ver uma maneira de implementar o padr&#xE3;o decorator:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// definindo o componente que ser&#xE1; decorado
interface Component {
	meuMetodo();
}

// Decorator Base
class Decorator implements Component{
	private _component: Component

	constructor(component: Component){
    	this._component = component;
    }	
    
    meuMetodo() {
    	this._component.meuMetodo();
    }
}

// Decorator espec&#xED;fico
class MeuSuperDecorator extends Decorator {
	private _param: string;

	constructor(component: Component, param: string) {
    	super(component);
        this._param = param;
    }
    
    meuMetodo() {
    	console.log(this.param);
        super.meuMetodo();
    }
}</code></pre><figcaption>Exemplo de classes decorator</figcaption></figure><p>No typescript, de maneira nativa, utilizamos decorators na forma de &quot;fun&#xE7;&#xE3;o que retorna outra fun&#xE7;&#xE3;o&quot; e podemos decorar classes, m&#xE9;todos, propriedades, acessores e par&#xE2;metros. </p><p>Fazendo uma ponte entre a implementa&#xE7;&#xE3;o de <em>decorators </em>do <code>typescript</code> com o que &#xE9; definido pelo padr&#xE3;o Decorator podemos dizer que a fun&#xE7;&#xE3;o mais interna &#xE9; o m&#xE9;todo do componente, enquanto a externa &#xE9; a chamada do decorator.</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// definindo o decorator
function meuDecorator(parametro: string) {
	return (target: any, propertyKey: string) =&gt; {
    	// faz alguma coisa
    }
}

// Utilizando decorator
class MinhaClasse {
	@meuDecorator(&quot;blabla&quot;)
	minhaProp: string
}</code></pre><figcaption>Exemplo de implementa&#xE7;&#xE3;o de decorators no typescript</figcaption></figure><p>Existe, por&#xE9;m, um detalhe quando utilizamos os <em>decorators</em> do <code>typescript</code> no que diz respeito &#xE0; sua inicializa&#xE7;&#xE3;o: os <em>decorators</em> s&#xE3;o executados assim que a classe &#xE9; importada. </p><p>Esse comportamento nos traz alguns problemas pois podemos ter uma classe sendo importada antes do nosso register ser construido e, consequentemente, quando nossa classe for instanciada n&#xE3;o termos depend&#xEA;ncia nenhuma injetada para utilizar. Para contornar esse problema podemos utilizar um outro padr&#xE3;o de projeto que &#xE9; implementado de forma nativa pelo <code>typescript</code>. Estamos falando do Proxy.</p><h2 id="proxy">Proxy</h2><p>Outro padr&#xE3;o estrutural presente no livro Padr&#xF5;es de Projeto (Gang of Four) e seu objetivo &#xE9; &quot;Fornecer um substituto ou marcador da localiza&#xE7;&#xE3;o de outro objeto para controlar o acesso a esse objeto&quot;. A ideia por tr&#xE1;s de controlar o acesso a um objeto &#xE9; adiar sua inicializa&#xE7;&#xE3;o at&#xE9; o momento que realmente precisamos utiliz&#xE1;-lo. </p><p>A implementa&#xE7;&#xE3;o do padr&#xE3;o &#xE9; semelhante &#xE0; implementa&#xE7;&#xE3;o do <em>decorator</em>, o que muda nesse caso &#xE9; a inten&#xE7;&#xE3;o de cada um.</p><h2 id="finalizando-implementa%C3%A7%C3%A3o">Finalizando implementa&#xE7;&#xE3;o</h2><p>Agora que j&#xE1; entendemos um pouco sobre os padr&#xF5;es envolvidos na constru&#xE7;&#xE3;o do nosso container, vamos implementar o decorator que utilizaremos nas nossas propriedades:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// registry.ts

export function inject(name: string) {
	return (target: any, propertyKey: string) =&gt; {
    	target[propertyKey] = new Proxy({}, {
        	get(target: any, propertyKey: string, receiver: any) {
            	const dependency = Registry.getInstance().inject(name);
				return dependency[propertyKey];
            }
        })
    }
}</code></pre><figcaption>Criando decorator</figcaption></figure><p>No c&#xF3;digo acima estamos exportando uma fun&#xE7;&#xE3;o <code>inject</code> que utilizaremos da seguinte forma: <code>@inject(&quot;nomeDaDependencia&quot;)</code> nas classes que ter&#xE3;o a depend&#xEA;ncia injetada.</p><p>Essa fun&#xE7;&#xE3;o retorna uma outra fun&#xE7;&#xE3;o (que respeita a assinatura de decorators de propriedades) onde:</p><ul><li><code>target</code> &#xE9; o objeto e</li><li><code>propertyKey</code> &#xE9; a propriedade que est&#xE1; sendo decorada. </li></ul><p>Quando invocada, essa fun&#xE7;&#xE3;o ir&#xE1; sobrescrever a propriedade da classe com um proxy que, quando chamado retornar&#xE1; a inst&#xE2;ncia da depend&#xEA;ncia que registramos e injetamos.</p><p>Considerando o seguinte cen&#xE1;rio:</p><pre><code class="language-typescript">class MinhaClasse {
	@inject(&quot;minhaDependencia&quot;)
    dep: Dependencia
}</code></pre><p>Quando a classe for importada, <code>dep</code> ser&#xE1; um proxy que, quando chamada resolver&#xE1; a depend&#xEA;ncia que foi previamente injetada.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>A implementa&#xE7;&#xE3;o final do container ficou da seguinte maneira:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// Registry.ts
export class Registry {
	dependencies: {[name: string]: any}
	static instance: Registry;
    
    private constructor() {
    	this.dependencies = {};
    }
    
    register(name: string, dependency: any) {
    	this.dependencies[name] = dependency
    }
    
    inject(name: string) {
    	if(!this.dependencies[name]) throw new Error(&quot;Dependency not found&quot;);
    	return this.dependencies[name];
    }
    
    static getInstance() {
    	if (!Registry.instance) {
			Registry.instance = new MeuSingleton();
		}
		return Registry.instance;
    }
}

export function inject(name: string) {
	return (target: any, propertyKey: string) =&gt; {
    	target[propertyKey] = new Proxy({}, {
        	get(target: any, propertyKey: string, receiver: any) {
            	const dependency = Registry.getInstance().inject(name);
				return dependency[propertyKey];
            }
        })
    }
}</code></pre><figcaption>Implementa&#xE7;&#xE3;o Container</figcaption></figure><p>Declaramos ent&#xE3;o as depend&#xEA;ncias que ser&#xE3;o injetadas na classe desejada:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// MinhaClasse.ts
export class MinhaClasse {
	@inject(&quot;minhaDependencia&quot;)
    dependencia: Dependencia
    
    execute() {
		this.dependencia.fazAlgo();
        console.log(&quot;chamou minha classe&quot;);
    }
}

// Dependencia.ts
export class Dependencia {

	fazAlgo() {
    	console.log(&quot;chamou dependencia&quot;);
    }
}</code></pre><figcaption>Injetando depend&#xEA;ncia</figcaption></figure><p>E, no ponto de entrada (<code>main.ts</code>) da aplica&#xE7;&#xE3;o basta construir as depend&#xEA;ncias e registrar no container que elas estar&#xE3;o dispon&#xED;veis:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">// main.ts
const registry = Registry.getInstance();
registry.register(&quot;minhaDependencia&quot;, new Dependencia());
new MinhaClasse();</code></pre><figcaption>Utilizando Container</figcaption></figure><p>Apesar de precisar de mais c&#xF3;digo e aumentar ligeiramente a complexidade a implementa&#xE7;&#xE3;o de um container de inje&#xE7;&#xE3;o de depend&#xEA;ncia tamb&#xE9;m traz vantagens como:</p><ol><li>A substitui&#xE7;&#xE3;o das depend&#xEA;ncias fica mais simples, pois precisamos alterar um &#xFA;nico ponto (e n&#xE3;o precisamos procurar em diversos arquivos quais classes que utilizam aquela depend&#xEA;ncia);</li><li>C&#xF3;digo fica mais f&#xE1;cil de ser lido e entendido, uma vez que n&#xE3;o temos mais classes com v&#xE1;rios par&#xE2;metros sendo passados no construtor.</li><li>Garantimos que sempre haver&#xE1; uma &#xFA;nica inst&#xE2;ncia de cada depend&#xEA;ncia em todo o processo de execu&#xE7;&#xE3;o da aplica&#xE7;&#xE3;o.</li></ol>]]></content:encoded></item><item><title><![CDATA[Sistemas de Mensageria]]></title><description><![CDATA[Nesse artigo falaremos sobre os principais conceitos envolvendo Sistemas de mensageria.]]></description><link>https://blog.viniboscoa.dev/sistemas-de-mensageria/</link><guid isPermaLink="false">658e08a774e77c0001419522</guid><category><![CDATA[Arquitetura de Software]]></category><category><![CDATA[Desenvolvimento]]></category><category><![CDATA[Development]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Fri, 29 Dec 2023 04:00:10 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2023/12/mao-pressionando-um-envelope-que-e-enviado-para-o-mundo_1232-278.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2023/12/mao-pressionando-um-envelope-que-e-enviado-para-o-mundo_1232-278.jpg" alt="Sistemas de Mensageria"><p>Mensageria ou <em>messaging</em> &#xE9; uma forma de comunica&#xE7;&#xE3;o presente em sistemas distribu&#xED;dos realizada atrav&#xE9;s da troca de mensagens (eventos), sendo essas mensagens gerenciadas por um <em>message broker</em>.</p><p><strong>Mas... o que &#xE9; um message broker? </strong></p><p>De forma resumida, <em>message broker </em>&#xE9;<em> </em>um intermediador: um componente que respons&#xE1;vel por intermediar a troca de mensagens entre produtores (<em>producers</em>) e consumidores (<em>consumers</em>).</p><p><em>Producers</em> e <em>consumers</em>, resumidamente, s&#xE3;o sistemas de software que, respectivamente, enviam e recebem mensagens para/do message broker</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2023/12/Message-1.png" class="kg-image" alt="Sistemas de Mensageria" loading="lazy" width="1161" height="121" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2023/12/Message-1.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2023/12/Message-1.png 1000w, https://blog.viniboscoa.dev/content/images/2023/12/Message-1.png 1161w" sizes="(min-width: 720px) 720px"><figcaption>Comunica&#xE7;&#xE3;o producer - broker - consumer</figcaption></figure><p>Fazendo uma analogia, um carteiro (message broker) &#xE9; respons&#xE1;vel por garantir que uma carta saia do remetente (producer) e chegue at&#xE9; o destinat&#xE1;rio (consumer).</p><p><strong>Componentes b&#xE1;sicos do message broker</strong></p><p>Existem diversos <em>message brokers</em> dispon&#xED;veis no mercado, dentre eles, podemos citar como mais populares: RabbitMQ, Apache Kafka, Amazon SQS, Google Cloud Pub/Sub, dentre outros. No entanto<em>, </em>independente do fornecedor,<em> </em>normalmente encontraremos os seguintes elementos:</p><ul><li><em>Producer - </em>sistema que ir&#xE1; enviar (produzir) uma mensagemm</li><li><em>Consumer - </em>sistema que ir&#xE1; consumir mensagens de uma fila</li><li><em>Queue ou topic - </em>fila (ou t&#xF3;pico) para a qual a mensagem ser&#xE1; enviada e armazenada</li><li><em>Exchange - </em>Componente respons&#xE1;vel por rotear as mensagens para suas respectivas filas e garantir uma distribui&#xE7;&#xE3;o uniforme entre os consumidores</li><li><em>Message ou event - </em>A mensagem que est&#xE1; sendo transmitida entre produtor e consumidor.</li></ul><p><strong>Vantagens e Desvantagens em utilizar mensageria</strong></p><p><strong>Vantagens</strong></p><ul><li>A troca de mensagens entre produtor e consumidor pode ocorrer independente se o um ou outro estiver online, o message broker se encarregar&#xE1; de entregar a mensagem assim que o consumidor ficar ativo novamente.</li><li>Message brokers s&#xE3;o capazes de garantir a entrega da mensagem, tornando o sistema mais confi&#xE1;vel.</li><li>O processamento ass&#xED;ncrono ajuda a melhorar a performance da aplica&#xE7;&#xE3;o e experi&#xEA;ncia do usu&#xE1;rio.</li><li>Message brokers s&#xE3;o capazes de reenviar mensagens n&#xE3;o entregues al&#xE9;m de manter um registro das falhas para an&#xE1;lise futura.</li></ul><p><strong>Desvantagens</strong></p><ul><li>A consist&#xEA;ncia entre os sistemas (produtor e consumidor) &#xE9; eventual, ou seja, em determinado momento poder&#xE1; haver inconsist&#xEA;ncia nos dados entre os sistemas.</li></ul><h3 id="t%C3%A1-na-hora-do-c%C3%B3digo">T&#xE1; na hora do C&#xF3;digo</h3><p><strong>Nota: </strong>Todos os exemplos a seguir utilizar&#xE3;o Rabbit MQ como message broker e typescript como linguagem de programa&#xE7;&#xE3;o.</p><p>Nota2: Focaremos na implementa&#xE7;&#xE3;o dos producers e consumers, portanto n&#xE3;o entraremos em detalhes sobre a implementa&#xE7;&#xE3;o das demais depend&#xEA;ncias nem a organiza&#xE7;&#xE3;o do c&#xF3;digo.</p><p><strong>O que iremos fazer?</strong></p><p>Para nosso exemplo iremos construir uma API que atuar&#xE1; como <em>producer</em> e <em>consumer</em> de mensagens. Iremos explorar todos os tipos de <em>exchange</em> dispon&#xED;veis no RabbitMQ, sendo elas:</p><ul><li><em>direct: </em>a mensagem &#xE9; enviada diretamente para a fila conectada &#xE0; exchange, cuja chave &#xE9; exatamente igual &#xE0; informada;</li><li><em>fanout</em>: a mensagem &#xE9; enviada para todas as filas conectadas &#xE0; <em>exchange</em>;</li><li><em>topic</em>: a mensagem &#xE9; enviada para todas as filas conectadas &#xE0; <em>exchange</em>, cuja chave respeita o padr&#xE3;o configurado.</li></ul><p><em><strong>Producer</strong></em></p><p>Para produzir a mensagem iremos expor 3 endpoints para que possamos criar nossas mensagens, sendo eles:</p><ol><li><code>POST /messages/direct?name=nome_da_fila</code></li><li><code>POST /messages/topic?name=pattern_da_fila</code></li><li><code>POST /messages/fanout</code></li></ol><p>De modo a conseguirmos personalizar a fila para qual vamos enviar as mensagens no caso das 2 primeiras, receberemos o nome via query param.</p><p>O conte&#xFA;do da mensagem dever&#xE1; ser enviado no corpo da requisi&#xE7;&#xE3;o respeitando o seguinte contrato:</p><pre><code class="language-json">{
	&quot;content&quot;: &quot;conte&#xFA;do da mensagem&quot;,
    &quot;date&quot;: &quot;2023-01-01T10:00:00&quot;
}</code></pre><p><strong><em>Consumer</em></strong></p><p>Teremos 2 consumidores que ir&#xE3;o receber a mensagem e salvar num arquivo texto, cada consumidor ir&#xE1; mostrar um log com a seguinte estrutura: <code>consumer-1|data_envio|data_erecebimento|tipo|conteudo</code></p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2023/12/Broker.png" class="kg-image" alt="Sistemas de Mensageria" loading="lazy" width="351" height="321"></figure><p></p><p><strong>Setup</strong></p><p>Para uma melhor organiza&#xE7;&#xE3;o foi criada um adaptador para a biblioteca amqplib, essa classe tem como objetivo adapter a interface da amqplib para nossa interface Queue, que cont&#xE9;m apenas os m&#xE9;todos necess&#xE1;rios para enviar e consumir mensagens.</p><pre><code class="language-typescript">import { Queue } from &quot;./Queue&quot;;
import amqp from &quot;amqplib&quot;;

export class RabbitMQQueueAdapter implements Queue {

    connection: amqp.Connection | undefined;

    constructor(private exchange: string) {

    }

    async connect(): Promise&lt;void&gt; {
        this.connection = await amqp.connect(&quot;amqp://rabbitmq:rabbitmq@rabbit&quot;);
    }


    async close(): Promise&lt;void&gt; {
        if (!this.connection) {
            throw new Error(&quot;connection closed&quot;);
        }
        this.connection.close();
    }

    async publish(exchangeName: string, queue: string, payload: any): Promise&lt;void&gt; {
        if (!this.connection) throw new Error(&quot;connection closed&quot;);
        const channel = await this.connection.createChannel();
        await channel.checkExchange(this.exchange);
        channel.publish(exchangeName, queue, Buffer.from(JSON.stringify(payload)));
    }

    async sendToQueue(queue: string, payload: any): Promise&lt;void&gt; {
        if (!this.connection) throw new Error(&quot;connection closed&quot;);
        const channel = await this.connection.createChannel();
        await channel.assertQueue(queue, { durable: true });
        channel.sendToQueue(queue, Buffer.from(JSON.stringify(payload)));
    }

    async consume(queueName: string, callback: any): Promise&lt;void&gt; {
        if (!this.connection) throw new Error(&quot;connection closed&quot;);
        const channel = await this.connection.createChannel();
        await channel.assertQueue(queueName, { durable: true });
        await channel.consume(queueName, async function (msg: any) {
            await callback(JSON.parse(msg.content.toString()));
        }, {
            noAck: true
        });
    }
}</code></pre><p>Destaco aqui os m&#xE9;todos <code>publish</code> e <code>consume</code>, iremos utilizar o primeiro para enviar mensagens &#xE0; nossa exchange e o segundo para consumir mensagens existentes na fila selecionada.</p><p>O m&#xE9;todo <code>sendToQueue</code> &#xE9; utilizado para ignorar o roteamento da exchange e enviar uma mensagem diretamente &#xE0; fila desejada. N&#xE3;o utilizaremos ele nesse exemplo.</p><p><strong>Producer</strong></p><p>Em nosso exemplo, a classe respons&#xE1;vel por montar a mensagem e chamar o m&#xE9;todo publish &#xE9; a <code>ProduceMessageUseCase</code>:</p><pre><code class="language-typescript">import { Message } from &quot;../domain/entity/Message&quot;;
import { Queue } from &quot;../infra/queue/Queue&quot;;

export class ProduceMessageUseCase {

    types = {
        direct: &quot;directExchange&quot;,
        topic: &quot;topicExchange&quot;,
        fanout: &quot;fanoutExchange&quot;
    }

    constructor(private queue: Queue) {

    }

    async execute(input: ProduceMessageInputDto): Promise&lt;void&gt; {
        const message = new Message(input.origin, input.content, input.date);
        const exchangeName = this.types[input.origin] ?? this.types.fanout;
        await this.queue.publish(exchangeName, input.name, message.getContent());
    }
}

export type ProduceMessageInputDto = {
    origin: &quot;topic&quot; | &quot;direct&quot; | &quot;fanout&quot;;
    name: string;
    content: string;
    date: Date;
}</code></pre><p>A m&#xE1;gica acontece nesse trecho <code>await this.queue.publish(exchangeName, input.name, message.getContent());</code> onde estamos efetivamente enviando a mensagem para nosso <em>message broker</em> especificando o <em>exchange</em> desejado.</p><p><strong>Consumer</strong></p><p>Por fim os consumidores das mensagens que separamos em 2 &#xA0;arquivos simples: controller - que cont&#xE9;m os comandos para ler da fila - e handler - que cont&#xE9;m efetivamente a l&#xF3;gica do que faremos com a mensagem:</p><pre><code class="language-typescript">// Consumer1Controller
import { Consumer1Handler } from &quot;../handler/Consumer1Handler&quot;;
import { Queue } from &quot;../queue/Queue&quot;;

export class Consumer1Controller {
    constructor(queue: Queue) {
        queue.consume(&quot;fila1&quot;, async function (msg: any) {
            const handler = new Consumer1Handler();
            await handler.handle(msg);
        })
    }
}


// Consumer1Handler
export class Consumer1Handler {
    async handle(message: any) {
        console.log(`consumer-1|${message.sentAt}|${(new Date()).toISOString()}|${message.origin}|${message.content}`);
    }
}</code></pre><p>O m&#xE9;todo <code>consume</code> ser&#xE1; chamado cada vez que uma nova mensagem chegar &#xE0; <code>fila1</code> e chamar&#xE1; o m&#xE9;todo <code>handle</code> que poder&#xE1; fazer o que for necess&#xE1;rio com ela, nesse caso, ele apenas organiza e mostra as informa&#xE7;&#xF5;es em tela com o prefixo <code>consumer-1</code></p><p><strong>Resultados</strong></p><p>Ap&#xF3;s tudo configurado e, com a aplica&#xE7;&#xE3;o rodando fizemos os seguintes experimentos:</p><ol><li>Requisi&#xE7;&#xE3;o ao endpoint <code>http://localhost:3001/messages/direct?name=fila1</code></li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2023/12/image.png" class="kg-image" alt="Sistemas de Mensageria" loading="lazy" width="929" height="90" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2023/12/image.png 600w, https://blog.viniboscoa.dev/content/images/2023/12/image.png 929w" sizes="(min-width: 720px) 720px"><figcaption>Resultado consumo exchange direct</figcaption></figure><p>2. Requisi&#xE7;&#xE3;o ao endpoint <code>http://localhost:3001/messages/topic?name=fila.logs</code></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2023/12/image-1.png" class="kg-image" alt="Sistemas de Mensageria" loading="lazy" width="921" height="57" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2023/12/image-1.png 600w, https://blog.viniboscoa.dev/content/images/2023/12/image-1.png 921w" sizes="(min-width: 720px) 720px"><figcaption>Resultado consumo exchange topic</figcaption></figure><p>3. Requisi&#xE7;&#xE3;o ao endpoint <code>http://localhost:3001/messages/fanout</code></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2023/12/image-2.png" class="kg-image" alt="Sistemas de Mensageria" loading="lazy" width="933" height="89" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2023/12/image-2.png 600w, https://blog.viniboscoa.dev/content/images/2023/12/image-2.png 933w" sizes="(min-width: 720px) 720px"><figcaption>Resultado consumo exchange fanout</figcaption></figure><h3 id="conclus%C3%A3o">Conclus&#xE3;o</h3><p><em>Message brokers</em>, e sistemas de mensageria em geral, possuem uma estrutura robusta e s&#xE3;o ferramentas excelentes para comunica&#xE7;&#xE3;o em sistemas distribu&#xED;dos, principalmente onde resili&#xEA;ncia e garantia de entrega s&#xE3;o caracter&#xED;sticas importantes. </p><p>No entanto vale ressaltar que, apesar da implementa&#xE7;&#xE3;o parecer simples, &#xE9; importante lembrar que n&#xE3;o existe bala de prata e sempre considerar os trade-offs antes de implementar esse tipo de solu&#xE7;&#xE3;o, uma vez que acrescenta uma complexidade que, muitas vezes &#xE9; desnecess&#xE1;ria.</p><p>Confira o projeto na &#xED;ntegra em <a href="https://github.com/virb30/rabbitmq-ts?ref=blog.viniboscoa.dev">https://github.com/virb30/rabbitmq-ts</a></p>]]></content:encoded></item><item><title><![CDATA[Desacoplando frameworks no PHP - o Padrão Adapter]]></title><description><![CDATA[O que é e como utilizar o Design Pattern Adapter no PHP para desacoplar micro-frameworks da aplicação.]]></description><link>https://blog.viniboscoa.dev/desacoplando-frameworks-no-php-o-padrao-adapter/</link><guid isPermaLink="false">6438c7e29cade90001313655</guid><category><![CDATA[Clean Architecture]]></category><category><![CDATA[Desenvolvimento]]></category><category><![CDATA[Arquitetura de Software]]></category><category><![CDATA[Clean Code]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Sat, 19 Mar 2022 16:02:05 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/mourizal-zativa-gNMVpAPe3PE-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/mourizal-zativa-gNMVpAPe3PE-unsplash.jpg" alt="Desacoplando frameworks no PHP - o Padr&#xE3;o Adapter"><p>Com a quantidade de <em>frameworks fullstack</em> e de opini&#xE3;o forte - como Laravel, Yii, CodeIgniter e Symfony uma ressalva para esse &#xFA;ltimo - no universo PHP, &#xE0;s vezes parece imposs&#xED;vel que consigamos desacoplar o <em>framework</em> de nossa aplica&#xE7;&#xE3;o.</p><p>Por sorte, temos os chamados <em>micro-frameworks</em> - como Slim, Lumen, Phalcon e podemos considerar, tamb&#xE9;m o Symfony nessa lista - o que torna essa tarefa um pouco menos &#xE1;rdua. </p><p>Nesse artigo iremos desacoplar a utiliza&#xE7;&#xE3;o de um framework utilizando o Padr&#xE3;o de Projeto Adapter e ainda, &quot;chavear&quot; entre tr&#xEA;s frameworks diferentes - Slim, Symfony e Lumen - com pouqu&#xED;ssima altera&#xE7;&#xE3;o no c&#xF3;digo. N&#xE3;o perca!</p><h2 id="o-que-%C3%A9-um-padr%C3%A3o-de-projeto">O que &#xE9; um padr&#xE3;o de projeto?</h2><p>Em resumo um padr&#xE3;o de projeto (ou<em> design pattern</em>) &#xE9; uma solu&#xE7;&#xE3;o conhecida, validada e reutiliz&#xE1;vel para um problema comum.</p><h3 id="o-padr%C3%A3o-adapter">O Padr&#xE3;o Adapter</h3><p>O <em>Design Pattern Adapter </em>&#xE9; um dos 23 padr&#xF5;es de projeto presentes no livro &#xA0;&quot;Design Patterns: Elements of Reusable Object-Oriented Software&quot; - Gang Of Four que se enquadra na categoria de padr&#xF5;es de Estrutura. </p><p>O objetivo principal do padr&#xE3;o <em>adapter </em>&#xE9; converter a interface de uma classe em outra, permitindo que classes com interfaces incompat&#xED;veis trabalhem em conjunto. Veremos esse padr&#xE3;o em a&#xE7;&#xE3;o mais adiante.</p><h2 id="t%C3%A1-na-hora-do-c%C3%B3digo">T&#xE1; na hora do c&#xF3;digo!</h2><p>Sobre o c&#xF3;digo: c&#xF3;digo de exemplo consiste em uma API que retorna uma listagem fixa de 3 livros. Utilizaremos PHP como linguagem de programa&#xE7;&#xE3;o, por&#xE9;m &#xE9; poss&#xED;vel aplicar as t&#xE9;cnicas em qualquer linguagem.</p><p>Nosso desafio: desacoplar o framework da aplica&#xE7;&#xE3;o possibilitando a substitui&#xE7;&#xE3;o do mesmo com pouca ou nenhuma altera&#xE7;&#xE3;o significativa no c&#xF3;digo.</p><h3 id="conhecendo-as-implementa%C3%A7%C3%B5es">Conhecendo as implementa&#xE7;&#xF5;es</h3><p>Antes de utilizarmos o padr&#xE3;o <em>adapter</em>, vamos conhecer as diferentes implementa&#xE7;&#xF5;es afim de identificarmos os pontos em comum entre elas. Vamos come&#xE7;ar com a implementa&#xE7;&#xE3;o do <code>Slim</code> criando a classe <code>SlimHttp</code>.</p><p>Nos exemplos iremos suprimir alguns trechos do c&#xF3;digo para facilitar a leitura</p><figure class="kg-card kg-code-card"><pre><code class="language-PHP">&lt;?php declare(strict_types=1);
...
class SlimHttp
{
  public App $app;

  public function __construct()
  {
    $this-&gt;app = AppFactory::create();
    $this-&gt;app-&gt;addErrorMiddleware(true, false, false);
    $this-&gt;app-&gt;get(&apos;/books&apos;, function(ServerRequestInterface $request, ResponseInterface $response) {
      $books = [
        (object) [&apos;title&apos; =&gt; &apos;Clean Code&apos;],
        (object) [&apos;title&apos; =&gt; &apos;Refactoring&apos;],
        (object) [&apos;title&apos; =&gt; &apos;Implementing Domain-Driven Design&apos;],
      ];
      $response-&gt;getBody()-&gt;write(json_encode($books));
      return $response-&gt;withHeader(&apos;Content-Type&apos;, &apos;application/json&apos;);
    });
    $this-&gt;app-&gt;run();
  }
}</code></pre><figcaption>SlimHttp.php</figcaption></figure><p>O que estamos fazendo nesse c&#xF3;digo:</p><ol><li>Construindo a aplica&#xE7;&#xE3;o Slim</li><li>Criando uma rota <code>/books</code> que retorna uma lista de livros em formato JSON</li><li>Iniciando a aplica&#xE7;&#xE3;o</li></ol><p>Vamos criar uma nova implementa&#xE7;&#xE3;o, semelhante ao Slim, mas utilizando o <code>Lumen</code>, essa &#xE9; nossa classe <code>LumenHttp</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
class LumenHttp
{
  public Application $app;

  public function __construct()
  {
    $this-&gt;app = new Application();
    $this-&gt;app-&gt;router-&gt;addRoute(&apos;GET&apos;, &apos;/books&apos;, function (Request $request, Response $response) {
      $books = [
        (object) [&apos;title&apos; =&gt; &apos;Clean Code&apos;],
        (object) [&apos;title&apos; =&gt; &apos;Refactoring&apos;],
        (object) [&apos;title&apos; =&gt; &apos;Implementing Domain-Driven Design&apos;],
      ];
      $response-&gt;setContent(json_encode($books));
      $response-&gt;header(&apos;Content-Type&apos;, &apos;application/json&apos;);
      $response-&gt;send();
    });
    $this-&gt;app-&gt;run(); 
  }
}</code></pre><figcaption>LumenHttp.php</figcaption></figure><p>Conseguimos identificar alguns padr&#xF5;es entre elas, os principais s&#xE3;o: </p><ol><li>Precisamos definir as rotas</li><li>Precisamos executar a aplica&#xE7;&#xE3;o para que as rotas sejam processadas</li></ol><p>Poder&#xED;amos utilizar ambas as classes normalmente em nossa aplica&#xE7;&#xE3;o simplesmente instanciando o framework desejado, nosso <code>public/index.php</code> seria mais ou menos assim:</p><figure class="kg-card kg-code-card"><pre><code>&lt;?php declare(strict_types=1);
...
require __DIR__ . &apos;/../vendor/autoload.php&apos;;
...
new SlimHttp();
// OU
// new LumenHttp();</code></pre><figcaption>public/index.php</figcaption></figure><p>Nossa aplica&#xE7;&#xE3;o funcionaria normalmente sem grandes altera&#xE7;&#xF5;es, atingimos nosso objetivo, certo? Errado!</p><p>Alguns problemas de implementar dessa maneira:</p><ol><li>Precisar&#xED;amos definir todas as rotas novamente a cada novo <em>framework;</em></li><li>Precisar&#xED;amos replicar as configura&#xE7;&#xF5;es de uma rota nova para todas as implementa&#xE7;&#xF5;es dos <em>framework</em></li></ol><p>Tudo isso seria insustent&#xE1;vel no longo prazo, portanto vamos construir e implementar o padr&#xE3;o <em>adapter.</em></p><h3 id="construindo-o-padr%C3%A3o">Construindo o padr&#xE3;o</h3><p>Agora que j&#xE1; temos caracter&#xED;sticas em comum entre elas, vamos definir a interface do nosso adaptador, que chamaremos de <code>Http</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
interface Http
{
  public function route(string $method, string $url, callable $callback);
  public function run();
}</code></pre><figcaption>Http.php</figcaption></figure><p>Nossa interface &#xE9; bem simples e define apenas dois m&#xE9;todos:</p><ol><li><code>route</code> que recebe o m&#xE9;todo, a url e uma fun&#xE7;&#xE3;o de callback e &#xE9; respons&#xE1;vel pela defini&#xE7;&#xE3;o das rotas</li><li><code>run</code> que &#xE9; respons&#xE1;vel por executar a aplica&#xE7;&#xE3;o</li></ol><p>Lembrando que o nome, assinatura e quantidade de m&#xE9;todos pode variar conforme a necessidade.</p><h3 id="implementando-o-padr%C3%A3o-adapter">Implementando o Padr&#xE3;o Adapter</h3><p>Para que possamos utilizar efetivamente o padr&#xE3;o Adapter precisamos fazer alguns ajustes em nossas implementa&#xE7;&#xF5;es iniciais, vamos come&#xE7;ar alterando a classe <code>SlimHttp</code></p><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
class SlimHttp implements Http
{
  public App $app;

  public function __construct()
  {
    $this-&gt;app = AppFactory::create();
    $this-&gt;app-&gt;addErrorMiddleware(true, false, false);
  }

  public function route(string $method, string $url, callable $callback)
  {
    $method = strtolower($method);
    $this-&gt;app-&gt;$method($url, function(ServerRequestInterface $request, ResponseInterface $response) use($callback) {
      $result = $callback($request-&gt;getQueryParams(), $request-&gt;getParsedBody());
      $response-&gt;getBody()-&gt;write(json_encode($result));
      return $response-&gt;withHeader(&apos;Content-Type&apos;, &apos;application/json&apos;);
    });
  }

  public function run()
  {
    $this-&gt;app-&gt;run();
  }
}</code></pre><p>Agora que implementamos uma interface, precisamos definir as fun&#xE7;&#xF5;es conforme o contrato estabelecido, nesse caso, somos obrigados a definir os m&#xE9;todos <code>route</code> e <code>run</code>.</p><p>Al&#xE9;m disse fizemos algumas altera&#xE7;&#xF5;es na implementa&#xE7;&#xE3;o do m&#xE9;todo <code>route</code> para que a constru&#xE7;&#xE3;o da rota pelo <em>framework</em> ficasse gen&#xE9;rica.</p><p>Vamos repetir esse procedimento em nossa classe <code>LumenHttp</code>:</p><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
class LumenHttp implements Http
{
  public Application $app;

  public function __construct()
  {
    $this-&gt;app = new Application();
  }

  public function route(string $method, string $url, callable $callback)
  {
    $this-&gt;app-&gt;router-&gt;addRoute(strtoupper($method), $url, function (Request $request, Response $response) use($callback) {
      $result = $callback($request-&gt;query-&gt;all(), $request-&gt;request-&gt;all());
      $response-&gt;setContent(json_encode($result));
      $response-&gt;header(&apos;Content-Type&apos;, &apos;application/json&apos;);
      $response-&gt;send();
    });
  }

  public function run()
  {
    $this-&gt;app-&gt;run(); 
  }
}</code></pre><p>Com exce&#xE7;&#xE3;o de alguns detalhes de implementa&#xE7;&#xE3;o que s&#xE3;o caracter&#xED;sticas do Lumen, a implementa&#xE7;&#xE3;o dos m&#xE9;todos <code>route</code> e <code>run</code> s&#xE3;o bem semelhantes.</p><p>Agora vamos atualizar nosso entrypoint <code>public/index.php</code> para utilizar o padr&#xE3;o adapter que acabamos de construir.</p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
require __DIR__ . &apos;/../vendor/autoload.php&apos;;
...
$http = new SlimHttp();

$http-&gt;route(&apos;get&apos;, &apos;/books&apos;, function($params, $body) {
  $books = [
    (object) [&apos;title&apos; =&gt; &apos;Clean Code&apos;],
    (object) [&apos;title&apos; =&gt; &apos;Refactoring&apos;],
    (object) [&apos;title&apos; =&gt; &apos;Implementing Domain-Driven Design&apos;],
  ];
  return $books;
});

$http-&gt;run();</code></pre><figcaption>public/index.php</figcaption></figure><p>O que estamos fazendo:</p><ol><li>Construindo um objeto $http correspondente ao <em>framework </em>que queremos utilizar</li><li>Definindo as rotas</li><li>Executando a aplica&#xE7;&#xE3;o</li></ol><p>Com isso se quisermos utilizar o Lumen, s&#xF3; precisamos alterar a constru&#xE7;&#xE3;o do <code>$http</code> para <code>$http = new LumenHttp()</code> e todo o resto funciona normalmente.</p><h3 id="mas-e-se-eu-quiser-utilizar-um-terceiro-framework">Mas e se eu quiser utilizar um terceiro framework?</h3><p>Utilizando o padr&#xE3;o <em>adapter </em>precisamos apenas seguir os mesmos passos para os dois primeiros:</p><ol><li>Criar uma classe que implementa a interface <code>Http</code></li><li>Trocar a implementa&#xE7;&#xE3;o no <code>public/index.php</code></li></ol><p>Vamos ver na pr&#xE1;tica o que ter&#xED;amos que fazer no nosso c&#xF3;digo para utilizar o Symfony:</p><ol><li>Come&#xE7;amos criando a classe <code>SymfonyHttp</code> que implementa a interface <code>Http</code> </li></ol><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
class SymfonyHttp implements Http
{
  public $routes;
  public RequestContext $context;

  public function __construct()
  {
    $this-&gt;routes = new RouteCollection();
    $this-&gt;context = new RequestContext();
  }

  public function route(string $method, string $url, callable $callback)
  {
    $this-&gt;routes-&gt;add(&apos;books&apos;, new Route(
      &apos;/books&apos;,
      [&apos;handler&apos; =&gt; function(Request $request) use($callback) {
        $result = $callback($request-&gt;query-&gt;all(), $request-&gt;request-&gt;all());
        $response = new Response(json_encode($result));
        $headers = new ResponseHeaderBag([&apos;Content-Type&apos; =&gt; &apos;application/json&apos;]);
        $response-&gt;headers = $headers;
        $response-&gt;send();
      }],
      [],
      [],
      &apos;&apos;,
      [],
      [strtoupper($method)]
    ));
  }

  public function run()
  {
    $request = Request::createFromGlobals();
    $this-&gt;context-&gt;fromRequest($request);
    $matcher = new UrlMatcher($this-&gt;routes, $this-&gt;context);
    $parameters = $matcher-&gt;match($this-&gt;context-&gt;getPathInfo());
    $parameters[&apos;handler&apos;]($request);
  }
}</code></pre><figcaption>SymfonyHttp.php</figcaption></figure><p>2. Trocar a implementa&#xE7;&#xE3;o no <code>public/index.php</code></p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
...
require __DIR__ . &apos;/../vendor/autoload.php&apos;;
...
$http = new SymfonyHttp();

$http-&gt;route(&apos;get&apos;, &apos;/books&apos;, function($params, $body) {
  $books = [
    (object) [&apos;title&apos; =&gt; &apos;Clean Code&apos;],
    (object) [&apos;title&apos; =&gt; &apos;Refactoring&apos;],
    (object) [&apos;title&apos; =&gt; &apos;Implementing Domain-Driven Design&apos;],
  ];
  return $books;
});

$http-&gt;run();</code></pre><figcaption>public/index.php</figcaption></figure><p>E pronto nossa aplica&#xE7;&#xE3;o deve estar funcionando conforme o esperado mas utilizando o Symfony.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Vimos nesse artigo como o Padr&#xE3;o de Projeto Adapter pode nos ajudar quando precisamos variar implementa&#xE7;&#xF5;es de diferentes bibliotecas ou <em>frameworks </em>que servem a um mesmo prop&#xF3;sito (e s&#xE3;o relativamente semelhantes entre si).</p><p>No entanto para frameworks mais opinativos (como Laravel ou Yii) esse tipo de troca &#xE9; invi&#xE1;vel uma vez que eles possuem diversas depend&#xEA;ncias e estruturas exclusivas do framework e que servem justamente para estabelecer um padr&#xE3;o entre todos os usu&#xE1;rio daquele framework.</p><p>O c&#xF3;digo na &#xED;ntegra est&#xE1; dispon&#xED;vel no github e &#xE9; parte de um projeto pessoal de um mini e-commerce utilizando Clean Code e Clean Architecture e que est&#xE1; ainda se encontra desenvolvimento:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/cc-ca-php?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - virb30/cc-ca-php</div><div class="kg-bookmark-description">Contribute to virb30/cc-ca-php development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Desacoplando frameworks no PHP - o Padr&#xE3;o Adapter"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/127064ebbf4d3514c8f6f30b7534c8eb59a52e584582bd3fd3aefd8271e19c5a/virb30/cc-ca-php" alt="Desacoplando frameworks no PHP - o Padr&#xE3;o Adapter"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Testes Automatizados e TDD - Parte 3]]></title><description><![CDATA[Na terceira parte dessa série sobre testes automatizados e TDD falaremos sobre as características que um teste deve possuir e dublês de teste.]]></description><link>https://blog.viniboscoa.dev/testes-automatizados-e-tdd-parte-3/</link><guid isPermaLink="false">6438c7e29cade90001313654</guid><category><![CDATA[Testes Automatizados]]></category><category><![CDATA[TDD]]></category><category><![CDATA[Desenvolvimento]]></category><category><![CDATA[Software Development]]></category><category><![CDATA[Development]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Wed, 16 Feb 2022 03:46:32 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash.jpg" alt="Testes Automatizados e TDD - Parte 3"><p>Dando continuidade na nossa s&#xE9;rie sobre testes automatizados e TDD falaremos um pouco sobre os dubl&#xEA;s de teste, seus tipos e quando utilizar cada um. </p><p>E caso voc&#xEA; ainda n&#xE3;o tenha visto as partes anteriores dessa s&#xE9;rie, recomendo dar uma olhada:</p><ul><li><a href="https://www.viniboscoa.dev/blog/testes-automatizados-tdd-parte-1?ref=blog.viniboscoa.dev">Parte 1 - Conceitos iniciais</a></li><li><a href="https://www.viniboscoa.dev/blog/testes-automatizados-e-tdd-parte-2?ref=blog.viniboscoa.dev">Parte 2 - Anatomia de um teste e exemplos</a></li></ul><h2 id="first">FIRST</h2><p>Antes de avan&#xE7;armos nos dubl&#xEA;s de teste &#xE9; importante ressaltar as caracter&#xED;sticas que um (bom) teste deve possuir, para isso utilizamos a sigla FIRST:</p><ol><li><strong>F</strong>ast - Os testes devem ser r&#xE1;pidos</li><li><strong>I</strong>ndependent<strong> </strong>- Um teste n&#xE3;o deve depender de outro, eles devem poder ser executados de maneira isolada</li><li><strong>R</strong>epeatable - Os testes devem poder serem executados in&#xFA;meras vezes sem que haja altera&#xE7;&#xE3;o no seu resultado</li><li><strong>S</strong>elf-validating - O pr&#xF3;prio teste deve ter uma sa&#xED;da bem definida e ser capaz de indicar se passou ou falhou</li><li><strong>T</strong>imely - Os testes devem ser escritos antes do c&#xF3;digo-fonte (aplic&#xE1;vel ao TDD)</li></ol><h2 id="dubl%C3%AA-de-teste">Dubl&#xEA; de teste</h2><p><em>Test double </em>ou dubl&#xEA; de teste &#xE9; um padr&#xE3;o de teste (<em>test pattern</em>) que tem como objetivo substituir uma implementa&#xE7;&#xE3;o por motivos de performance ou seguran&#xE7;a. Podemos dividir os <em>test patterns </em>em: dummy, stubs, spies, mocks e fake. N&#xE3;o se preocupe, mostraremos cada um deles a seguir.</p><p>Muitas bibliotecas tratam os <em>tests patterns</em> como <code>Mock</code>, portanto &#xE9; comum encontrar bibliotecas do tipo <code>Mockery</code> no PHP, ou mesmo m&#xE9;todos no PHPUnit como <code>createMock</code> sendo utilizado para criar um <em><code>Spy</code>, </em>mas lembre-se cada padr&#xE3;o desempenha um papel diferente.</p><h3 id="dummy">Dummy</h3><p>Podemos dizer que<em> Dummy </em>&#xE9; o <em>test pattern</em> mais simples: <em>dummies</em> s&#xE3;o objetos que criamos apenas para completar a lista de par&#xE2;metros necess&#xE1;rios para invocar determinado m&#xE9;todo.</p><p>Dando sequ&#xEA;ncia a nossa aplica&#xE7;&#xE3;o de cat&#xE1;logo de filmes, vamos agora criar a classe <code>MovieCatalog</code> que ser&#xE1; nosso cat&#xE1;logo de filmes. Essa classe ter&#xE1; um m&#xE9;todo <code>addMovie</code> que receber&#xE1; um objeto do tipo <code>Movie</code> e ir&#xE1; inseri-lo na lista.</p><p>Vamos ver como fica o teste:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);
class MovieCatalogTest extends TestCase
{
  public function testShouldAddMovieToCatalog()
  {
    $catalog = new MovieCatalog();
    $movie = new Movie(
   	  id:&apos;1&apos;,
      name: &apos;Movie 1&apos;,
      summary: &apos;&apos;
    );
    
    $catalog-&gt;addMovie($movie);
    $this-&gt;assertNotEmpty($catalog-&gt;getMovies());
  }
}</code></pre><figcaption>Exemplo de Dummy</figcaption></figure><p>Nesse exemplo estamos criando um objeto do tipo <code>Movie</code> com quaisquer informa&#xE7;&#xF5;es apenas para passarmos para o m&#xE9;todo <code>addMovie</code> uma vez que as informa&#xE7;&#xF5;es contidas no objeto n&#xE3;o s&#xE3;o relevantes para a valida&#xE7;&#xE3;o do nosso teste.</p><h3 id="stubs">Stubs</h3><p>Diferente dos <em>dummies </em>que s&#xE3;o objetos sem comportamento, que servem apenas para completar os par&#xE2;metros necess&#xE1;rios, os <em>stubs </em>s&#xE3;o objetos que retornam respostas prontas para um determinado teste por quest&#xF5;es de performance ou seguran&#xE7;a.</p><p>Vamos supor que, quando um filme for inserido no cat&#xE1;logo iremos fazer uma requisi&#xE7;&#xE3;o a uma API externa para obter a nota m&#xE9;dia da avalia&#xE7;&#xE3;o dos usu&#xE1;rios para esse filme. </p><p>Para ilustrar criamos uma classe chamada <code>MovieApi</code> que retorna um n&#xFA;mero aleat&#xF3;rio a cada execu&#xE7;&#xE3;o.</p><figure class="kg-card kg-code-card"><pre><code class="language-php">&lt;?php declare(strict_types=1);

namespace App;

class MovieApi
{
  public function getMovieRate(Movie $movie)
  {
    return rand(0, 5);
  }
}</code></pre><figcaption>MovieApi.php</figcaption></figure><p>Cada vez que um filme &#xE9; adicionado no cat&#xE1;logo, uma requisi&#xE7;&#xE3;o &#xE0; API &#xE9; feita e a nota m&#xE9;dia do filme &#xE9; inclu&#xED;da no cat&#xE1;logo. Inclu&#xED;mos aqui a <code>MovieApi</code> como inje&#xE7;&#xE3;o de depend&#xEA;ncia, para facilitar nossos testes:</p><pre><code class="language-php"> ...
 
 public function __construct(?MovieApi $movieApi = null)
 {
   $this-&gt;movieApi = $movieApi;
 }
 
 ...
 
 public function addMovie(Movie $movie)
  {
    $movieApi = new MovieApi;
    $this-&gt;sumMovieRates += $movieApi-&gt;getMovieRate($movie);
    array_push($this-&gt;movies, $movie);
  }</code></pre><p>Nosso teste quer validar que que a nota m&#xE9;dia dos filmes no cat&#xE1;logo &#xE9; 4:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">public function testShouldUpdateAvgRate()
  {
    $catalog = new MovieCatalog();

    $movie = new Movie(
      id: &apos;1&apos;,
      name: &apos;Movie 1&apos;,
      summary: &apos;&apos;
    );

    $catalog-&gt;addMovie($movie);

    $this-&gt;assertEquals(4, $catalog-&gt;getAvgRate());
  }</code></pre><figcaption>MovieCatalogTest.php</figcaption></figure><p>Nesse caso se mantivermos a comunica&#xE7;&#xE3;o com uma API real estaremos ferindo o <strong>R </strong>do FIRST, uma vez que a cada execu&#xE7;&#xE3;o obteremos um resultado diferente. Vamos ver como &#xE9; poss&#xED;vel resolver esse problema utilizando um <code>Stub</code> :</p><figure class="kg-card kg-code-card"><pre><code class="language-php"> public function testShouldUpdateAvgRate()
  {
    $stub = $this-&gt;createStub(MovieApi::class);
    $stub-&gt;method(&apos;getMovieRate&apos;)
      -&gt;willReturn(4);

    $catalog = new MovieCatalog($stub);

    $movie = new Movie(
      id: &apos;1&apos;,
      name: &apos;Movie 1&apos;,
      summary: &apos;&apos;
    );

    $catalog-&gt;addMovie($movie);

    $this-&gt;assertEquals(4, $catalog-&gt;getAvgRate());
  }</code></pre><figcaption>MovieCatalogTest.php</figcaption></figure><p>Ap&#xF3;s a altera&#xE7;&#xE3;o estamos indicando &#xE0; nossa classe <code>MovieCatalog</code> para utilizar um dubl&#xEA; da <code>MovieApi</code> cujo m&#xE9;todo <code>getMovieRate</code> sempre retornar&#xE1; o valor <code>4</code>. </p><p>At&#xE9; o momento da escrita desse artigo, o PHPUnit n&#xE3;o possu&#xED;a uma forma nativa para criar <code>stubs</code> de <em>hard dependencies </em>ou seja classes que s&#xE3;o instanciadas diretamente na implementa&#xE7;&#xE3;o. Para esses casos recomendamos a utiliza&#xE7;&#xE3;o da biblioteca <code>Mockery</code> que fornece alguns poderes a mais ao PHPUnit.</p><h3 id="spies">Spies</h3><p><em>Spies</em> s&#xE3;o objetos que espionam a execu&#xE7;&#xE3;o do m&#xE9;todo armazenando seus resultados e alguns meta-dados como: quantas vezes foi chamado ou quais par&#xE2;metros foram passados.</p><p>Vamos duplicar nosso teste anterior para garantir que o m&#xE9;todo <code>getMovieRate</code> da nossa API tenha sido chamado apenas uma vez.</p><p>NOTA: O PHPUnit n&#xE3;o possui um m&#xE9;todo nativo para cria&#xE7;&#xE3;o de <code>spies</code> para isso utilizaremos o m&#xE9;todo <code>createMock</code> que nos d&#xE1; a possibilidade de espionar a execu&#xE7;&#xE3;o do m&#xE9;todo.</p><figure class="kg-card kg-code-card"><pre><code class="language-php">public function testShouldCallGetMovieRateOnce()
  {
    $spy = $this-&gt;createMock(MovieApi::class);
    $spy-&gt;expects($this-&gt;once())
    -&gt;method(&apos;getMovieRate&apos;);

    $catalog = new MovieCatalog($spy);

    $movie = new Movie(
      id: &apos;1&apos;,
      name: &apos;Movie 1&apos;,
      summary: &apos;&apos;
    );

    $catalog-&gt;addMovie($movie);
  }</code></pre><figcaption>MovieCatalogTest.php</figcaption></figure><p>Nesse exemplo se inserirmos uma chamada adicional ao m&#xE9;todo <code>addMovie</code> o teste falhar&#xE1; pois o m&#xE9;todo <code>getMovieRate</code> ter&#xE1; sido chamado mais de uma vez.</p><h3 id="mocks">Mocks</h3><p><em>Mocks</em> s&#xE3;o objetos similares a <code>stubs</code> e <code>spies</code> que permitem que voc&#xEA; diga exatamente o que quer que ele fa&#xE7;a.</p><p>Poder&#xED;amos evoluir o teste acima adicionando o comportamento que inclu&#xED;mos no exemplo do <code>stub</code>, vamos ver como fica:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">public function testShouldCallGetMovieRateOnce()
  {
    $mock = $this-&gt;createMock(MovieApi::class);
    $mock-&gt;expects($this-&gt;once())
      -&gt;method(&apos;getMovieRate&apos;)
      -&gt;will($this-&gt;returnValue(4));

    $catalog = new MovieCatalog($mock);

    $movie = new Movie(
      id: &apos;1&apos;,
      name: &apos;Movie 1&apos;,
      summary: &apos;&apos;
    );

    $catalog-&gt;addMovie($movie);
    $this-&gt;assertEquals(4, $catalog-&gt;getAvgRate());
  }</code></pre><figcaption>MovieCatalog.php</figcaption></figure><p>Nesse exemplo combinamos o comportamento do <code>stub</code> ao retornar um valor fixo e do <code>spy</code> ao verificar a quantidade de vezes que o m&#xE9;todo foi chamado.</p><h3 id="fake">Fake</h3><p><code>Fakes</code> s&#xE3;o objetos cuja implementa&#xE7;&#xE3;o simula o funcionamento da inst&#xE2;ncia real que seria utilizada em produ&#xE7;&#xE3;o. Como exemplo podemos citar um banco de dados em mem&#xF3;ria, ou mesmo a implementa&#xE7;&#xE3;o da nossa API em nossos exemplos que retorna um n&#xFA;mero rand&#xF4;mico simulando um poss&#xED;vel retorno de uma API real.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Conhecemos o conceito de FIRST e <em>tests patterns</em> - padr&#xF5;es para escrever tests de maneira perform&#xE1;tica e segura e que n&#xE3;o ferem o FIRST.</p><p>Apesar das limita&#xE7;&#xF5;es dos m&#xE9;todos nativos do PHPUnit &#xE9; poss&#xED;vel atingirmos o objetivo principal dos dubl&#xEA;s de teste que &#xE9; simular o comportamento da implementa&#xE7;&#xE3;o real, sem correr riscos desnecess&#xE1;rios de performance ou seguran&#xE7;a.</p><p>Citamos tamb&#xE9;m algumas bibliotecas (do PHP) que podem auxiliar nossos testes como <code>Mockery</code> e <code>prophecy</code>. Caso queira experimentar um pouco mais os <em>tests patterns, </em>recomendamos a utiliza&#xE7;&#xE3;o da bilioteca <code>sinon</code> do JavaScript pois ela segue a nomenclatura e os conceitos das t&#xE9;cnicas citadas nesse artigo.</p><p>Na pr&#xF3;xima e &#xFA;ltima parte dessa s&#xE9;rie entraremos nos conceitos do TDD e como essa t&#xE9;cnica pode nos ajudar a desenvolver c&#xF3;digos melhores. Nos vemos l&#xE1;!</p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Testes automatizados e TDD - Parte 2]]></title><description><![CDATA[Na segunda parte da nossa série sobre testes automatizados e TDD traremos mais alguns conceitos e exemplos práticos de testes automatizados utilizando PHP.]]></description><link>https://blog.viniboscoa.dev/testes-automatizados-e-tdd-parte-2/</link><guid isPermaLink="false">6438c7e29cade90001313653</guid><category><![CDATA[TDD]]></category><category><![CDATA[Testes Automatizados]]></category><category><![CDATA[Software Development]]></category><category><![CDATA[Software]]></category><category><![CDATA[PHP]]></category><category><![CDATA[PHPUnit]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Tue, 18 Jan 2022 02:41:42 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash-2.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash-2.jpg" alt="Testes automatizados e TDD - Parte 2"><p>Na segunda parte da nossa s&#xE9;rie sobre testes automatizados e TDD mostraremos alguns exemplos pr&#xE1;ticos de testes automatizados utilizando PHP. Escolhi PHP por ser a linguagem que tenho maior dom&#xED;nio, mas ressalto que os conceitos aqui apresentados podem ser aplicados a qualquer linguagem de programa&#xE7;&#xE3;o e ferramenta (com algumas adapta&#xE7;&#xF5;es, &#xE9; claro).</p><h3 id="planejar-testes">Planejar testes</h3><p>Antes de partirmos para o c&#xF3;digo, vale lembrar que em uma situa&#xE7;&#xE3;o real nem sempre conseguiremos automatizar todos os testes, nesses casos, &#xE9; necess&#xE1;rio, fazer uma an&#xE1;lise criteriosa de quais casos de teste devem ser automatizados. Alguns crit&#xE9;rios de escolha s&#xE3;o:</p><ul><li>Funcionalidades importantes do sistema;</li><li>Funcionalidades utilizadas com frequ&#xEA;ncia;</li><li>Casos de teste que envolvem riscos para o Neg&#xF3;cio.</li></ul><p>Nessa etapa s&#xE3;o planejados o ambiente de teste utilizado, cronograma de execu&#xE7;&#xE3;o, entreg&#xE1;veis gerados com a execu&#xE7;&#xE3;o, equipe envolvida, escopo de automa&#xE7;&#xE3;o, entre outros.</p><h3 id="t%C3%A1-na-hora-do-c%C3%B3digo">T&#xE1; na hora do c&#xF3;digo</h3><p>Um pouco de contexto: imagine uma aplica&#xE7;&#xE3;o bem simples que consiste em um cat&#xE1;logo de programas (filmes/s&#xE9;ries etc.) onde o usu&#xE1;rio deve ser capaz de marcar como assistido/n&#xE3;o assistido.</p><p>O programa (filme/s&#xE9;rie) tem as seguintes propriedades: id, nome, resumo e assistido. Nossa classe <code>Movie.php</code> ficou assim:</p><p>OBS: j&#xE1; estou utilizando alguns recursos novos do PHP 8, como propriedades nomeadas, mas nada impediria de extrair as propriedades de um array ou em par&#xE2;metros separados no construtor.</p><pre><code class="language-php">&lt;?php

declare(strict_types=1);

class Movie 
{
  private $watched = false;

  public function __construct(
    private string $id,
    private string $name,
    private string $summary
  ) {}

  public function toggleWatched() {
    $this-&gt;watched = !$this-&gt;watched;
  }

  public function isWatched() {
    return $this-&gt;watched;
  }
}</code></pre><h4 id="vamos-ao-teste">Vamos ao teste</h4><p>Nosso primeiro teste ser&#xE1; bem simples: o usu&#xE1;rio deve poder marcar um filme como assistido/n&#xE3;o assistido. Faremos esse teste de duas maneiras (com e sem o aux&#xED;lio de uma ferramenta - no caso o PHPUnit), ou seja, queremos se o m&#xE9;todo <code>toggleWatched</code> funciona conforme o esperado.</p><p>Nosso objetivo com esse primeiro teste &#xE9; demonstrar o conceito conhecido como AAA (Arrange, Act, Assert) que consiste em uma divis&#xE3;o l&#xF3;gica do c&#xF3;digo em 3 etapas:</p><ol><li>Arrange: o setup inicial do teste, &#xE9; aqui que definimos tudo que o teste utilizar&#xE1;;</li><li>Act: &#xE9; nessa etapa que executamos de fato o que queremos testar;</li><li>Assert: por fim verificamos se o resultado da execu&#xE7;&#xE3;o &#xE9; o resultado esperado.</li></ol><p>Nosso arquivo <code>test.php</code> ficou da seguinte maneira:</p><pre><code class="language-php">&lt;?php

declare(strict_types=1);

require &apos;Movie.php&apos;;

// Arrange
// Instanciamos o objeto $movie
$movie = new Movie(
  id: &apos;1&apos;,
  name: &apos;Brooklyn 99&apos;,
  summary: &apos;&apos;
);

// Act
$movie-&gt;toggleWatched();

// Assert
if ($movie-&gt;isWatched() === true) {
  echo &quot;PASSED&quot;.PHP_EOL;
} else {
  echo &quot;FAIL&quot;.PHP_EOL;
}</code></pre><p>Vejamos os 3 A&apos;s em a&#xE7;&#xE3;o:</p><ol><li>Arrange: nessa etapa instanciamos um novo objeto da classe <code>Movie</code>;</li><li>Act: nessa etapa chamamos o m&#xE9;todo <code>toggleWatched</code> que, conforme definimos, inverte o valor da propriedade <code>$watched</code>, que inicia como <code>false</code>;</li><li>Assert: por fim verificamos se o resultado &#xE9; o esperado, em caso de sucesso imprime no terminal <code>PASSED</code>, em caso de falha mostra <code>FAIL</code>.</li></ol><p>Para executar esse teste basta executar o comando <code>php test.php</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/01/image.png" class="kg-image" alt="Testes automatizados e TDD - Parte 2" loading="lazy" width="420" height="86"><figcaption>Resultado do teste</figcaption></figure><p>Como vimos, &#xE9; perfeitamente poss&#xED;vel realizar testes automatizados sem o aux&#xED;lio de nenhuma ferramenta, por&#xE9;m &#xE0; medida que os testes crescem e ficam mais complexos sua programa&#xE7;&#xE3;o tamb&#xE9;m ficar&#xE1; mais complexa. </p><p>Al&#xE9;m disso as ferramentas possuem uma interface mais amig&#xE1;vel, facilitando a visualiza&#xE7;&#xE3;o de quais erros passaram e quais falharam.</p><h4 id="utilizando-o-phpunit">Utilizando o PHPUnit</h4><p>Primeiramente precisamos instalar o phpunit, utilizaremos o composer para isso com o comando sugerido pela documenta&#xE7;&#xE3;o oficial <code>composer require --dev phpunit/phpunit ^9.5</code>.</p><p>Por conven&#xE7;&#xE3;o o PHPUnit procura algumas condi&#xE7;&#xF5;es espec&#xED;ficas para executar os testes, s&#xE3;o elas:</p><ol><li>O arquivo da classe de teste deve terminar em Test - &#xE9; recomendado utilizar [nomeDaClasseTestada]Test;</li><li>Os m&#xE9;todos da classe de teste devem iniciar com test - exemplo : testMetodoIsTrueDeveRetornarTrue</li><li>Os m&#xE9;todos de teste devem ter pelo menos um <code>assert</code>.</li><li>A classe de teste deve extender de <code>PHPUnit\Framework\TestCase</code>.</li></ol><p>NOTA: Lembrando que todas essas conven&#xE7;&#xF5;es podem ser configuradas no arquivo <code>phpunit.xml</code>, mas n&#xE3;o entraremos nesse n&#xED;vel detalhe nesse momento.</p><p>A seguir vamos alterar nosso arquivo <code>test.php</code> seguindo as conven&#xE7;&#xF5;es do PHPUnit, sendo assim renomeamos o arquivo para <code>MovieTest.php</code> e seu conte&#xFA;do ficou assim:</p><pre><code class="language-php">&lt;?php declare(strict_types=1);

require &apos;Movie.php&apos;;

use PHPUnit\Framework\TestCase;

class MovieTest extends TestCase
{
  public function testToggleWatched()
  {
    // Arrange
    // Instanciamos o objeto $movie
    $movie = new Movie(
      id: &apos;1&apos;,
      name: &apos;Brooklyn 99&apos;,
      summary: &apos;&apos;
    );

    // Act
    $movie-&gt;toggleWatched();

    // Assert
    $this-&gt;assertTrue($movie-&gt;isWatched());
  }
}</code></pre><p>Perceba que mantivemos a estrat&#xE9;gia dos 3 A&apos;s apenas ajustamos os m&#xE9;todos para que o PHPUnit encontrasse os testes para execu&#xE7;&#xE3;o. Quando executamos o comando <code>php vendor/bin/phpunit MovieTest.php</code> temos o seguinte resultado:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/01/image-1.png" class="kg-image" alt="Testes automatizados e TDD - Parte 2" loading="lazy" width="666" height="144" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/01/image-1.png 600w, https://blog.viniboscoa.dev/content/images/2022/01/image-1.png 666w"><figcaption>Resultado do PHPUnit</figcaption></figure><p>O resultado do teste nos mostra que 1 teste foi executado e 1 assertion passou. Se utilizarmos o par&#xE2;metro <code>--colors</code> temos um resultado mais amig&#xE1;vel:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/01/image-2.png" class="kg-image" alt="Testes automatizados e TDD - Parte 2" loading="lazy" width="678" height="142" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/01/image-2.png 600w, https://blog.viniboscoa.dev/content/images/2022/01/image-2.png 678w"><figcaption>Resultado do PHPUnit com --colors</figcaption></figure><p>Em caso de falha o PHPUnit tamb&#xE9;m nos mostra qual classe / assertion a falha ocorreu:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/01/image-3.png" class="kg-image" alt="Testes automatizados e TDD - Parte 2" loading="lazy" width="673" height="289" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/01/image-3.png 600w, https://blog.viniboscoa.dev/content/images/2022/01/image-3.png 673w"><figcaption>Resultado da falha PHPUnit</figcaption></figure><p>Dessa forma fica muito mais f&#xE1;cil e r&#xE1;pido identificar qual teste falhou.</p><h3 id="conclus%C3%A3o">Conclus&#xE3;o</h3><p>Nessa parte vimos mais alguns conceitos sobre testes automatizados, a import&#xE2;ncia de planejar os testes, como compor e escrever um teste, com e sem o aux&#xED;lio de ferramentas. Tamb&#xE9;m vimos as facilidades que a utiliza&#xE7;&#xE3;o de ferramentas especializadas em testes automatizados nos conferem, tanto na visualiza&#xE7;&#xE3;o, quanto na execu&#xE7;&#xE3;o dos testes.</p><p>Apesar de utilizarmos PHP e o PHPUnit nos exemplos esses conceitos podem ser aplicados em qualquer framework ou linguagem.</p><p>Na pr&#xF3;xima parte entraremos em conceitos um pouco mais avan&#xE7;ados no mundos dos testes: dubl&#xEA;s de teste. Falaremos sobre os tipos de dubl&#xEA;s de testes e traremos exemplos de como utiliz&#xE1;-los no PHPUnit. Nos vemos l&#xE1;!</p>]]></content:encoded></item><item><title><![CDATA[Testes automatizados e TDD - Parte 1]]></title><description><![CDATA[Na primeira parte dessa série sobre testes automatizados e TDD. Falaremos sobre os conceitos de testes automatizados e seus tipos.]]></description><link>https://blog.viniboscoa.dev/testes-automatizados-tdd-parte-1/</link><guid isPermaLink="false">6438c7e29cade90001313652</guid><category><![CDATA[Testes Automatizados]]></category><category><![CDATA[TDD]]></category><category><![CDATA[Development]]></category><category><![CDATA[Software Development]]></category><category><![CDATA[Software]]></category><category><![CDATA[Desenvolvimento]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Mon, 27 Dec 2021 01:01:29 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash-3.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/jeswin-thomas-dfRrpfYD8Iw-unsplash-3.jpg" alt="Testes automatizados e TDD - Parte 1"><p>Inspirado pela <em>playlist </em>sobre testes automatizados do Vin&#xED;cius Dias (<a href="https://www.youtube.com/c/DiasdeDev?ref=blog.viniboscoa.dev">canal DiasdeDev</a>), resolvi trazer para o blog alguns artigos sobre esse tema, come&#xE7;ando com a teoria por tr&#xE1;s dos testes automatizados, alguns exemplos pr&#xE1;ticos e tamb&#xE9;m um pouco sobre TDD.</p><h2 id="o-que-s%C3%A3o-testes-automatizados">O que s&#xE3;o testes automatizados?</h2><p>Testes automatizados ou automa&#xE7;&#xE3;o de testes &#xE9; um m&#xE9;todo de testar softwares, que conta com o aux&#xED;lio (ou n&#xE3;o) de ferramentas especiais para controlar sua execu&#xE7;&#xE3;o e compara o resultado obtido com um resultado esperado. Todo esse processo &#xE9; realizado de maneira autom&#xE1;tica com pouca ou nenhuma interven&#xE7;&#xE3;o humana.</p><h2 id="vantagens">Vantagens</h2><p>As principais vantagens que podemos listar com rela&#xE7;&#xE3;o a testes automatizados s&#xE3;o:</p><ol><li>Testes automatizados n&#xE3;o dependem de recurso humano para ser executado. assim &#xE9; poss&#xED;vel execut&#xE1;-lo em hor&#xE1;rios alternativos, liberando desenvolvedores a executarem outras tarefas;</li><li>Reduz os erros humanos na execu&#xE7;&#xE3;o dos testes. Quando todos os cen&#xE1;rios s&#xE3;o mapeados nos testes automatizados, conseguimos reduzir os erros humanos (ou v&#xED;cios) dos testes manuais.</li><li>Teste automatizados s&#xE3;o facilmente reproduzidos. Com testes automatizados n&#xE3;o &#xE9; necess&#xE1;rio conhecimento completo do funcionamento do sistema, uma vez que qualquer desenvolvedor &#xE9; capaz de reproduzir a execu&#xE7;&#xE3;o dos testes.</li><li>Feedback mais r&#xE1;pido. Com testes automatizados conseguimos identificar mais rapidamente quando o software n&#xE3;o est&#xE1; funcionando da maneira esperada.</li></ol><h2 id="desvantagens">Desvantagens</h2><p>Apesar das in&#xFA;meras vantagens dos testes automatizados, tamb&#xE9;m podemos listar algumas de suas desvantagens:</p><ol><li>&#xC9; necess&#xE1;rio utilizar recursos para desenvolver os testes automatizados. Existe um trabalho adicional durante o desenvolvimento para que os testes sejam escritos.</li><li>Demanda conhecimento sobre automa&#xE7;&#xE3;o de testes. Testes automatizados mal definidos podem causar falsa sensa&#xE7;&#xE3;o de que o software est&#xE1; funcionando corretamente, por isso &#xE9; necess&#xE1;rio capacita&#xE7;&#xE3;o da equipe.</li><li>Podem exigir recursos adicionais. Pode ser necess&#xE1;rio a constru&#xE7;&#xE3;o de um ambiente dedicado aos testes, com banco de dados etc.</li><li>Pode desviar o objetivo principal dos testes. Pode causar o efeito de programar apenas para fazer o teste passar, desviando o foco do objetivo principal que &#xE9; solucionar um problema.</li></ol><h2 id="tipos-de-testes-automatizados">Tipos de testes automatizados</h2><p>Existem v&#xE1;rios tipos de testes automatizados, todos t&#xEA;m um mesmo objetivo: reduzir o trabalho manual no teste de software, cada literatura divide os tipos de testes de uma maneira. Reuni alguns e separei da maneira que foi a mais f&#xE1;cil de entender durante meus estudos, s&#xE3;o eles:</p><ol><li>Teste de Unidade - Testes de unidade (ou unit&#xE1;rios) s&#xE3;o respons&#xE1;veis por testar a menor unidade de c&#xF3;digo poss&#xED;vel, por exemplo, para assegurar o funcionamento correto de um m&#xE9;todo, ou um c&#xE1;lculo etc.</li><li>Testes de Integra&#xE7;&#xE3;o - Teste de integra&#xE7;&#xE3;o asseguram que as unidades est&#xE3;o comunicando entre si da maneira esperada. Geralmente &#xE9; nesse tipo de teste que verificamos se os dados est&#xE3;o sendo salvos corretamente no banco de dados, por exemplo.</li><li>Testes End to End (E2E) - Testes End-to-End (E2E) visam garantir o funcionamento de ponta a ponta da aplica&#xE7;&#xE3;o, normalmente simula o comportamento do usu&#xE1;rio atrav&#xE9;s da navega&#xE7;&#xE3;o pelas telas.</li><li>Teste de carga - Teste de carga servem para determinar o desempenho de um sistema em condi&#xE7;&#xF5;es de carga da vida real. Atrav&#xE9;s desse tipo de teste conseguimos validar o comportamento do sistema quando v&#xE1;rios usu&#xE1;rios acessam simultaneamente.</li></ol><h3 id="pir%C3%A2mide-de-testes">Pir&#xE2;mide de testes</h3><p>Mike Cohn prop&#xF4;s o conceito de pir&#xE2;mide de testes em seu livro <a href="https://www.amazon.com.br/Scrum-essencial-Kenneth-S-Rubin/dp/8550801852/ref=asc_df_8550801852/?tag=googleshopp00-20&amp;linkCode=df0&amp;hvadid=379748610448&amp;hvpos=&amp;hvnetw=g&amp;hvrand=12478214076993724578&amp;hvpone=&amp;hvptwo=&amp;hvqmt=&amp;hvdev=c&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=1001773&amp;hvtargid=pla-1003067041592&amp;psc=1&amp;ref=blog.viniboscoa.dev" rel="noopener ugc nofollow"><em><em>Succeeding with Agile</em></em></a>, por&#xE9;m nem todos concordam com a nomenclatura utilizada por Mike Cohn, por isso, algumas vers&#xF5;es da pir&#xE2;mide foram derivadas da proposta original, uma delas, que gosto de utilizar como base, pode ser vista na figura da direita da imagem abaixo:</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Test-piramid.png" class="kg-image" alt="Testes automatizados e TDD - Parte 1" loading="lazy" width="1440" height="1024" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/Test-piramid.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/Test-piramid.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/Test-piramid.png 1440w" sizes="(min-width: 720px) 720px"></figure><p>A proposta diz que devemos priorizar os testes debaixo para cima (<em>bottom-up</em>), ou seja, devemos ter em nosso sistema uma base s&#xF3;lida de testes de unidade (mais baratos e r&#xE1;pidos de serem executados) seguido de testes de integra&#xE7;&#xE3;o e por fim testes E2E. </p><p>Por&#xE9;m o que vemos na maioria dos cen&#xE1;rios &#xE9; a ilustrado na figura da direita em que vemos a predomin&#xE2;ncia de testes manuais e poucos testes de unidade.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>O desenvolvimento de testes automatizados n&#xE3;o &#xE9; uma tarefa trivial e exige um certo conhecimento da equipe para que os testes automatizados n&#xE3;o causem mais preju&#xED;zo do que benef&#xED;cios.</p><p>&#xC9; recomendado a implanta&#xE7;&#xE3;o de testes automatizados em est&#xE1;gios iniciais do desenvolvimento (falaremos mais sobre isso quando abordarmos TDD). Em se tratando de sistemas j&#xE1; existentes, por exemplo, &#xE9; importante que haja uma an&#xE1;lise criteriosa de como os testes automatizados ser&#xE3;o implementados para que n&#xE3;o representem mais desvantagens do que vantagens.</p><p>Na pr&#xF3;xima parte dessa s&#xE9;rie trarei exemplos pr&#xE1;ticos dos testes automatizados utilizando PHP, com e sem frameworks de teste. Tamb&#xE9;m falaremos sobre dubl&#xEA;s de teste e seus tipos. At&#xE9; l&#xE1;!</p><p></p>]]></content:encoded></item><item><title><![CDATA[AutoML - Prevendo o custo do Plano de Saúde]]></title><description><![CDATA[Nesse projeto utilizamos técnicas de AutoML para construir um modelo de Machine Learning capaz de prever os custos do plano de saúde.]]></description><link>https://blog.viniboscoa.dev/automl-prevendo-o-custo-do-plano-de-saude/</link><guid isPermaLink="false">6438c7e29cade90001313651</guid><category><![CDATA[Data Science]]></category><category><![CDATA[Python]]></category><category><![CDATA[AutoML]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[PyCaret]]></category><category><![CDATA[Análise de Dados]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Tue, 26 Oct 2021 01:15:47 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/health-insurance-thumb.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/health-insurance-thumb.jpg" alt="AutoML - Prevendo o custo do Plano de Sa&#xFA;de"><p>Nessa s&#xE9;rie utilizaremos Auto Machine Learning para realizar as an&#xE1;lises e construir nossos modelos.</p><h3 id="o-que-%C3%A9-auto-machine-learning">O que &#xE9; Auto Machine Learning?</h3><p>Auto Machine Learning (AutoML) s&#xE3;o frameworks, ou conjunto de bibliotecas, que nos auxiliam no processo de an&#xE1;lise dos dados e constru&#xE7;&#xE3;o de modelos de machine learning, poupando tempo do cientista de dados no processo de explora&#xE7;&#xE3;o dos dados e sele&#xE7;&#xE3;o de algoritmos. As bibliotecas mais famosas de AutoML que podemos citar s&#xE3;o PyCaret e FB Prophet.</p><p>Neste artigo iremos construir um modelo de Machine Learning para prever o custo do plano de sa&#xFA;de utilizando AutoML.</p><h2 id="o-problema">O problema</h2><p>O papel do seguro &#xE9; ser uma ferramenta para gerenciar o risco financeiro, &#xE9; evidente que possuir um seguro de sa&#xFA;de melhora a seguran&#xE7;a financeira conforme descrito neste <a href="http://medicine.tums.ac.ir/filegallery/2299016929/nezamoleslami%2031%20ordibehesht.pdf?ref=blog.viniboscoa.dev" rel="nofollow">artigo</a> do <em>The new england journal of medicine</em>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/health-insurance-cover.jpg" class="kg-image" alt="AutoML - Prevendo o custo do Plano de Sa&#xFA;de" loading="lazy" width="2000" height="1333" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/health-insurance-cover.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/health-insurance-cover.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/health-insurance-cover.jpg 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/health-insurance-cover.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@bermixstudio?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Bermix Studio</a> on <a href="https://unsplash.com/s/photos/health-insurance?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Ainda segundo os autores desse artigo, a expans&#xE3;o da cobertura por seguro aumentou, significativamente o acesso de pacientes a servi&#xE7;os de sa&#xFA;de, uso de sa&#xFA;de preventiva, tratamento de doen&#xE7;as cr&#xF4;nicas, medicamentos e cirurgias, promovendo, portanto benef&#xED;cios &#xE0; sa&#xFA;de como detec&#xE7;&#xE3;o precoce de doen&#xE7;as, melhor ader&#xEA;ncia &#xE0; medicamentos e gest&#xE3;o de condi&#xE7;&#xF5;es cr&#xF4;nicas.</p><p>Mas todos esses benef&#xED;cios t&#xEA;m um custo: mais despesas para os &#xF3;rg&#xE3;os estaduais, federais, locais ou privados.</p><p>Diante disso empresas que comercializam seguro de vida, com o objetivo de reduzir os potenciais preju&#xED;zos e ainda assim serem competitivas no mercado, costumam determinar o pre&#xE7;o dos seus servi&#xE7;os atrav&#xE9;s da avalia&#xE7;&#xE3;o de risco do cliente.</p><h2 id="objetivo-geral">Objetivo Geral</h2><p>O objetivo desse projeto &#xE9; construir um modelo de Machine Learning de regress&#xE3;o para prever custos de seguro de vida utilizando AutoML.</p><h3 id="objetivos-espec%C3%ADficos">Objetivos espec&#xED;ficos</h3><p>Para atingirmos o objetivo geral proposto iremos realizar as seguintes etapas de modo a conhecer nossos dados e escolher o melhor modelo para o problema proposto:</p><ol><li>Aquisi&#xE7;&#xE3;o dos dados</li><li>An&#xE1;lise explorat&#xF3;ria</li><li>Tratamento dos dados</li><li>Escolha do modelo</li><li>Treinamento do modelo</li><li>Avalia&#xE7;&#xE3;o do modelo</li><li>Teste do modelo</li></ol><h3 id="aquisi%C3%A7%C3%A3o-dos-dados">Aquisi&#xE7;&#xE3;o dos Dados</h3><p><br>Os dados foram obtidos do <a href="https://www.kaggle.com/annetxu/health-insurance-cost-prediction?ref=blog.viniboscoa.dev">Kaggle</a>, plataforma amplamente utilizada para competi&#xE7;&#xF5;es e ensino de Data Science e Machine Learning.</p><h3 id="an%C3%A1lise-explorat%C3%B3ria">An&#xE1;lise Explorat&#xF3;ria</h3><p>Como primeira etapa de todo projeto de Data Science, precisamos conhecer o <em>dataset</em> que estamos trabalhando. Verificamos que nosso conjunto de dados cont&#xE9;m 1338 registros e 7 vari&#xE1;veis.</p><h4 id="separa%C3%A7%C3%A3o-dos-dados">Separa&#xE7;&#xE3;o dos dados</h4><p>Para evitarmos contaminar nosso modelo com dados que ser&#xE3;o utilizados para teste, primeiramente devemos separar o conjunto de dados entre treino e teste, esse &#xFA;ltimo ser&#xE1; apresentado ao modelo apenas no final do processo, ou seja quando o modelo estiver testado e validado.</p><h3 id="tratamento-dos-dados">Tratamento dos dados</h3><p>Antes de avan&#xE7;armos para o treinamento do modelo, precisaremos realizar alguns tratamentos nos dados. Pela nossa an&#xE1;lise explorat&#xF3;ria podemos perceber que vari&#xE1;veis <code>region</code>, <code>sex</code> e <code>smoker</code> s&#xE3;o do tipo texto, portanto precisaremos convert&#xEA;-las antes de treinar nosso modelo.</p><p>Iremos converter a vari&#xE1;vel <code>smoker</code> para 0 e 1 de modo que <code>no</code> = 0 e <code>yes</code> = 1 Tamb&#xE9;m iremos converter as vari&#xE1;veis <code>region</code> e <code>sex</code> utilizando a t&#xE9;cnica de <em>OneHotEncoding</em>, para esse &#xFA;ltimo deixaremos o PyCaret realizar a transforma&#xE7;&#xE3;o.</p><p>A etapa de tratamento dos dados &#xE9; uma das mais importantes no processo de constru&#xE7;&#xE3;o de um modelo de Machine Learning, apesar o PyCaret ser capaz de realizar as transforma&#xE7;&#xF5;es necess&#xE1;rias de maneira eficiente, optamos por realizar algumas transforma&#xE7;&#xF5;es manuais.</p><h3 id="escolha-do-modelo">Escolha do modelo</h3><p>&#xC9; nessa etapa que vemos todo o poder do AutoML em a&#xE7;&#xE3;o, com um simples comando, conseguimos criar v&#xE1;rios modelos e comparar quais possuem o melhor desempenho.</p><p>Para isso utilizamos a fun&#xE7;&#xE3;o <code>setup</code> do PyCaret, como sabemos que se trata de um problema de regress&#xE3;o, utilizaremos o pacote <code>pycaret.regression</code>:</p><pre><code class="language-python">from pycaret.regression import *

reg = setup(data = train,
            target = &apos;charges&apos;,
            normalize = True,
            log_experiment = False)</code></pre><p>Passamos para a fun&#xE7;&#xE3;o setup nosso <em>dataset</em>, o nome da vari&#xE1;vel alvo e algumas <em>flags</em> para que o PyCaret inclua alguns passos no seu <em>pipeline. </em>A flag <code>normalize</code> indica ao PyCaret que queremos normalizar nossos dados num&#xE9;ricos para que fiquem numa mesma escala.</p><p>Ap&#xF3;s a realiza&#xE7;&#xE3;o do <code>setup</code> podemos verificar qual o melhor modelo utilizando o comando <code>compare_models()</code>.</p><p>Para nosso cen&#xE1;rio selecionamos os 3 primeiros modelos para que, ap&#xF3;s o tuning, verificarmos qual possui o melhor desempenho. Os modelos selecionados foram: Gradient Boosting Regressor (GBR), Random Forest Regressor e Light GBM.</p><h4 id="tunando-os-modelos">Tunando os modelos</h4><p>Quando um modelo &#xE9; criado usando o <code>create_model()</code> ele usa os hiperpar&#xE2;metros padr&#xE3;o para cada modelo. Para fazer o tuning desses par&#xE2;metros, usamos a fun&#xE7;&#xE3;o <code>tune_model()</code>.</p><p>Essa fun&#xE7;&#xE3;o faz o tuning dos par&#xE2;metros automaticamente, e mais uma vez o avalia com valida&#xE7;&#xE3;o cruzada.</p><p>Por padr&#xE3;o o <code>tune_model()</code> utiliza o Random Grid Search para encontrar os par&#xE2;metros otimizados.</p><p>O tuning dos modelos nos revelou algumas caracter&#xED;sticas interessantes:</p><ul><li>o GBR com par&#xE2;metros default apresentou desempenho ligeiramente melhor do que sua vers&#xE3;o tunada.</li><li>o Random Forest tunado apresentou melhor desempenho superando at&#xE9; a vers&#xE3;o <em>default</em> GBR</li><li>o Light GBM tunado apresentou melhor desempenho que sua vers&#xE3;o com par&#xE2;metros <em>default</em>. Mas ainda assim se mostrou inferior ao Random Forest.</li></ul><p>Diante dos resultados e levando em considera&#xE7;&#xE3;o nossa an&#xE1;lise, apesar do modelo de <em>Random Forest</em> ter apresentado desempenho ligeiramente superior ao <em>GBR</em>, seu tempo de execu&#xE7;&#xE3;o &#xE9; consideravelmente maior, n&#xE3;o justificando, portanto o ganho de performance. Em nosso cen&#xE1;rio escolher&#xED;amos o <em>GBR</em>.</p><p>Com o modelo tunado e treinado, basta finaliz&#xE1;-lo, utilizando o m&#xE9;todo <code>finalize_model()</code> e realizar um teste final com dados n&#xE3;o vistos pelo modelo, que ele estar&#xE1; pronto para ser colocado em produ&#xE7;&#xE3;o.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Constru&#xED;mos e comparamos alguns modelos de Machine Learning para realizar a previs&#xE3;o de custos de seguro de vida.</p><p>Todo esse processo de treinamento e avalia&#xE7;&#xE3;o dos modelos foi mais simples e r&#xE1;pido com a utiliza&#xE7;&#xE3;o de frameworks de Auto Machine Learning, no entanto vale ressaltar que tais t&#xE9;cnicas n&#xE3;o devem ser utilizadas sem que haja, primeiro, um entendimento claro do problema e dos dados que estamos lidando.</p><p>Percebemos tamb&#xE9;m que, apesar de apresentar um desempenho ligeiramente superior, o algoritmo de <em>Random Forest</em> mostrou-se mais lento na execu&#xE7;&#xE3;o do que o <em>Gradient Boosting Regressor</em> justificando nossa escolha por esse &#xFA;ltimo.</p><p>Existem diversas melhorias que podemos fazer nesse projeto tais como:</p><ol><li>avaliar outros modelos e/ou outras m&#xE9;tricas de desempenho</li><li>realizar outros tratamentos nos dados como remo&#xE7;&#xE3;o/ajustes de outliers que podem influenciar negativamente nossas previs&#xF5;es - e que optamos por n&#xE3;o faz&#xEA;-lo nessa an&#xE1;lise.</li></ol><p>A an&#xE1;lise completa voc&#xEA; confere abaixo:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/health-insurance-prediction/blob/main/PROJETO_Prevendo_custo_de_Seguro_de_Vida_com_PyCaret.ipynb?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">health-insurance-prediction/PROJETO_Prevendo_custo_de_Seguro_de_Vida_com_PyCaret.ipynb at main &#xB7; virb30/health-insurance-prediction</div><div class="kg-bookmark-description">Contribute to virb30/health-insurance-prediction development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="AutoML - Prevendo o custo do Plano de Sa&#xFA;de"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/fce881e74b934c0bb96c4bc7c9cadaeac478a59a6c8536471f33337a67131dfb/virb30/health-insurance-prediction" alt="AutoML - Prevendo o custo do Plano de Sa&#xFA;de"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Churn Prediction - Prevendo evasão de clientes de uma empresa]]></title><description><![CDATA[Churn prediction - como Machine Learning pode ajudar sua empresa a reter clientes.]]></description><link>https://blog.viniboscoa.dev/churn-prediction/</link><guid isPermaLink="false">6438c7e29cade90001313650</guid><category><![CDATA[Análise de Dados]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Churn]]></category><category><![CDATA[XGBoost]]></category><category><![CDATA[Churn Prediction]]></category><category><![CDATA[Python]]></category><category><![CDATA[Scikitlearn]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Wed, 06 Oct 2021 01:43:29 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/hand-man-making-bad-signal.jpg" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/5372119.jpg" class="kg-image" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa" loading="lazy" width="2000" height="2000" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/5372119.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/5372119.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/5372119.jpg 1600w, https://blog.viniboscoa.dev/content/images/2022/05/5372119.jpg 2000w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://www.freepik.com/vectors/flat-design?ref=blog.viniboscoa.dev">Flat design vector created by freepik - www.freepik.com</a></figcaption></figure><img src="https://blog.viniboscoa.dev/content/images/2022/05/hand-man-making-bad-signal.jpg" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"><p><em>Churn ou Churn rate</em>, &#xE9; uma m&#xE9;trica que representa a taxa de evas&#xE3;o de clientes em determinado per&#xED;odo de tempo, ou seja, quanto menor o <em>Churn Rate</em>, melhor. </p><p>Outra m&#xE9;trica bastante relevante e derivada dessa taxa, &#xE9; o <em>Churn</em> de Receita ou <em>Monthly Recurring Revenue (MMR) Churn </em>que<em> </em>representa o total de receita perdida em raz&#xE3;o desses cancelamentos.</p><p><strong>O que fazer com essa informa&#xE7;&#xE3;o?</strong></p><p>Atrav&#xE9;s da taxa de churn (ou <em>churn rate</em>)<em> </em>e sua an&#xE1;lise ao longo do tempo &#xE9; poss&#xED;vel identificar se h&#xE1; algum problema que deve ser atacado de modo a evitar a evas&#xE3;o de clientes, por exemplo se h&#xE1; algum aspecto do produto que t&#xEA;m desagradado os usu&#xE1;rios a ponto de cancelar o servi&#xE7;o.</p><p><strong>Como utilizar essa informa&#xE7;&#xE3;o?</strong></p><p><em>Churn </em>tamb&#xE9;m pode ser utilizado para identificar potenciais cancelamentos com anteced&#xEA;ncia, possibilitando a tomada de a&#xE7;&#xF5;es para reter tais clientes.</p><p>Para empresas que querem crescer, &#xE9; necess&#xE1;rio investir para adquirir novos clientes. Cada vez que um cliente cancela um servi&#xE7;o isso representa uma perda de investimento significativa, consequentemente mais tempo e esfor&#xE7;o ser&#xE3;o necess&#xE1;rios para repor esse cliente. </p><p>Ser capaz de prever quando um cliente est&#xE1; propenso a cancelar e pr&#xF3;-ativamente oferecer incentivos para que ele fique pode oferecer grandes economias para um neg&#xF3;cio.</p><h3 id="o-que-vamos-fazer">O que vamos fazer?</h3><p>Nesse projeto iremos construir um modelo de <em>Machine Learning</em> utilizando o algoritmo <em><strong>XGBoost </strong></em>e compararemos seu desempenho com um modelo baseado em &#xC1;rvore de Decis&#xE3;o.</p><h2 id="aquisi%C3%A7%C3%A3o-dos-dados">Aquisi&#xE7;&#xE3;o dos dados</h2><p>Os dados utilizados neste projeto foram originalmente disponibilizados na <a href="https://developer.ibm.com/technologies/data-science/patterns/predict-customer-churn-using-watson-studio-and-jupyter-notebooks/?ref=blog.viniboscoa.dev#">plataforma de ensino da IBM Developer</a>, e tratam de um problema t&#xED;pico de uma companhia de telecomunica&#xE7;&#xF5;es. O <em>dataset</em> completo pode ser encontrado <a href="https://raw.githubusercontent.com/virb30/churn-prediction/main/WA_Fn-UseC_-Telco-Customer-Churn.csv?ref=blog.viniboscoa.dev">neste link</a>.</p><p>Apesar de n&#xE3;o haver informa&#xE7;&#xF5;es expl&#xED;citas dispon&#xED;veis, os nomes das colunas permitem um entendimento a respeito do problema.</p><h2 id="an%C3%A1lise-explorat%C3%B3ria">An&#xE1;lise Explorat&#xF3;ria</h2><p>Como todo projeto de <em>Data Science </em>come&#xE7;amos realizando a an&#xE1;lise explorat&#xF3;ria dos dados de modo a identificar com o que estamos lidando: quantas e quais s&#xE3;o as vari&#xE1;veis e seus respectivos tipos, quantos registros o conjunto de dados possui, se temos dados ausentes e quais tratamentos precisaremos realizar.</p><p>Ap&#xF3;s a an&#xE1;lise do nosso <em>dataset</em> chegamos ao seguinte cen&#xE1;rio:</p><ol><li>Nosso <em>dataset </em>possui 7043 registros;</li><li>Nossa vari&#xE1;vel alvo &#xE9; representada pela coluna <code>Churn</code> e possui menos amostras de cancelamento do que n&#xE3;o cancelamento (que era esperado);</li><li>As colunas <code>tenure</code>, <code>MonthlyCharges</code> e <code>TotalCharges</code> s&#xE3;o num&#xE9;ricas, as demais s&#xE3;o categ&#xF3;ricas;</li><li>As vari&#xE1;veis categ&#xF3;ricas possuem, no m&#xE1;ximo 4 categorias poss&#xED;veis;</li><li>A coluna <code>TotalCharges</code> possui 11 registros com o valor &quot; &quot; (texto vazio).</li></ol><h2 id="tratamento-dos-dados">Tratamento dos dados</h2><p>Aplicaremos os passos a seguir para a transforma&#xE7;&#xE3;o dos dados de modo que fiquem preparados para utiliza&#xE7;&#xE3;o no modelo. Essas mesmas etapas dever&#xE3;o ser seguidas tanto nos conjuntos de treino quanto nos de teste:</p><ol><li>Codificar as vari&#xE1;veis categ&#xF3;ricas utilizando OneHotEncoding (se voc&#xEA; n&#xE3;o est&#xE1; familiarizado com esse termo, confira esse <a href="https://www.viniboscoa.dev/blog/codificacao-de-variaveis-label-vs-one-hot-encoder?ref=blog.viniboscoa.dev">artigo</a>);</li><li>Padronizar as vari&#xE1;veis num&#xE9;ricas de modo que elas fiquem numa mesma escala;</li></ol><p>Como nossos dados est&#xE3;o desbalanceados, precisaremos balance&#xE1;-los antes de treinar o modelo, para isso utilizaremos a t&#xE9;cnica de <em>Under sampling - </em>que elimina alguns registros da classe majorit&#xE1;ria at&#xE9; que o conjunto esteja balanceado. Faremos isso apenas com o conjunto de <strong>treino.</strong></p><h2 id="escolhendo-um-modelo">Escolhendo um modelo</h2><p>Como mencionamos anteriormente iremos utilizar o <em><strong>XGBoost </strong></em>para essa an&#xE1;lise, <a href="(https://link.springer.com/article/10.1186/s40537-019-0191-6">nesse artigo</a> o autor compara 4 algoritmos de <em>Machine Learning: </em>&#xC1;rvores de Decis&#xE3;o, Random Forest, Gradient Boosted Machine Tree &#x201C;GBM&#x201D; e XGBoost, como resultado, o XGBoost foi o que apresentou melhor desempenho.</p><h2 id="treinando-e-avaliando-o-modelo">Treinando e avaliando o modelo</h2><p>Com o modelo escolhido podemos continuar para as etapas de treinamento e avalia&#xE7;&#xE3;o do modelo. </p><p>O treinamento consiste em passar os dados de treino para o pipeline, o pipeline &#xE9; respons&#xE1;vel por realizar as transforma&#xE7;&#xF5;es dos dados e treinar <code>fit</code> o modelo.</p><pre><code class="language-python">pipeline = Pipeline([
  (&apos;feature_transformer&apos;, features_transformer),
  (&apos;classifier&apos;, model)
])

pipeline.fit(X_train_balanced, y_train_balanced)</code></pre><p>Com o modelo treinado passamos os dados de valida&#xE7;&#xE3;o para realizar as previs&#xF5;es</p><pre><code class="language-python">y_pred = pipeline.predict(X_val)
y_proba = pipeline.predict_proba(X_val)</code></pre><p>Com as previs&#xF5;es realizadas &#xE9; poss&#xED;vel avaliar o desempenho do modelo, em nosso caso estamos utilizando a m&#xE9;trica <code>recall</code>, ou seja, taxa de previs&#xF5;es de <em>churn </em>positivo (clientes que cancelaram o servi&#xE7;o) realizadas corretamente pelo algoritmo. Os resultados podem ser vistos na imagem abaixo:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/results_xgboost_default.png" class="kg-image" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa" loading="lazy" width="402" height="280"><figcaption>Desempenho do XGBoost (par&#xE2;metros <em>default</em>)</figcaption></figure><p>Obtivemos uma taxa de 76% de acerto para <em>churn</em> = 1 (sim)</p><h3 id="melhorando-o-modelo">Melhorando o Modelo</h3><p>Com uma primeira vers&#xE3;o do modelo criada, vamos tentar melhorar seu desempenho com alguns ajustes nos par&#xE2;metros (ou <em>fine tuning</em>). Iremos contar com a ajuda do <code>GridSearch</code> para descobrirmos os melhores par&#xE2;metros.</p><pre><code class="language-python"># importar pacotes
from sklearn.model_selection import GridSearchCV

# definir par&#xE2;metros que ser&#xE3;o avaliados
parameters = {
    &apos;learning_rate&apos;: [0.01, 0.05, 0.1],
    &apos;n_estimators&apos;: [100, 500],
    &apos;max_depth&apos;: [4, 5, 6],
    &apos;eta&apos;: [0.01, 0.05, 0.1],
    &apos;subsample&apos;: [0.9],
    &apos;colsample_bytree&apos;: [0.2]
}

clf = GridSearchCV(model, parameters)
X_train_transformed = features_transformer.fit_transform(X_train_balanced)
clf.fit(X_train_transformed, y_train_balanced)
print(&quot;Melhor: {} usando {}&quot;.format(clf.best_score_, clf.best_params_))</code></pre><p>O GridSearch sugere que utilizemos: </p><ol><li><code>colsample_bytree = 0.2</code></li><li><code>eta = 0.01</code></li><li><code>learning_rate = 0.05</code></li><li><code>max_depth = 4</code></li><li><code>n_estimators = 100</code></li><li><code>subsample = 0.9</code></li></ol><p>Treinaremos novamente nosso modelo, dessa vez com esses par&#xE2;metros e realizaremos as previs&#xF5;es:</p><pre><code class="language-python"># novo modelo com tunning
model_tuned = XGBClassifier(
  learning_rate=0.05,
  n_estimators=100,
  max_depth=4,
  gamma=1,
  subsample=0.9,
  colsample_bytree=0.2,
  objective=&apos;binary:logistic&apos;,
  eta=0.01,
  random_state=42,
)

# replace pipeline model
pipeline.steps[1] = (&apos;classifier&apos;, model_tuned)

# treinar modelo
pipeline.fit(X_train_balanced, y_train_balanced)

# realizar previs&#xF5;es
y_pred = pipeline.predict(X_val)
y_proba = pipeline.predict_proba(X_val)</code></pre><p>Com o novo modelo treinado obtivemos um desempenho um pouco melhor: 79% de <code>recall</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/results_xgboost_tuned.png" class="kg-image" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa" loading="lazy" width="402" height="280"><figcaption>Desempenho do XGBoost (tunado)</figcaption></figure><h1 id="conclus%C3%A3o">Conclus&#xE3;o</h1><p>Nosso modelo apresentou melhor desempenho do que um modelo baseado em &#xC1;rvore de decis&#xE3;o, conforme exposto <a href="https://towardsdatascience.com/churn-prediction-770d6cb582a5?ref=blog.viniboscoa.dev">nesse artigo</a>.</p><p>Vimos ainda que &#xE9; poss&#xED;vel melhorar o desempenho dos modelos apenas realizando o <em>tunning</em> adequado nos seus par&#xE2;metros. No entanto, a melhora mais significativa foi obtida com o tratamento adequado nos dados e na escolha do modelo.</p><p>Vale ressaltar ainda que como nosso conjunto de dados cont&#xE9;m dados do usu&#xE1;rio somente - e n&#xE3;o possui dados referentes &#xE0; experi&#xEA;ncia do usu&#xE1;rio, por exemplo n&#xFA;mero de reclama&#xE7;&#xF5;es ou solicita&#xE7;&#xF5;es de suporte - n&#xE3;o foi poss&#xED;vel realizar nenhuma engenharia que nos desse mais informa&#xE7;&#xF5;es e possivelmente melhorar ainda mais o desempenho.</p><p>Por fim, podemos dizer que nosso modelo obteve um desempenho satisfat&#xF3;rio ao conseguir prever corretamente aproximadamente 80% dos casos de <code>churn</code>.</p><h2 id="o-projeto">O Projeto</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/churn-prediction/blob/main/Projeto_Churn_Prediction_para_uma_empresa_de_Telecomunica%C3%A7%C3%B5es.ipynb?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">churn-prediction/Projeto_Churn_Prediction_para_uma_empresa_de_Telecomunica&#xE7;&#xF5;es.ipynb at main &#xB7; virb30/churn-prediction</div><div class="kg-bookmark-description">Contribute to virb30/churn-prediction development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/7d6d703801806140fd85c6c39b6cff3d0029b162059fbe95c546da4a795bbdc9/virb30/churn-prediction" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"></div></a></figure><h2 id="refer%C3%AAncias">Refer&#xEA;ncias</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.avaus.com/blog/predicting-customer-churn/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">What is customer churn prediction and why is it important?</div><div class="kg-bookmark-description">Customer churn is one of the most important metrics for a growing business to evaluate. Read on to learn what Customer Churn is and why it is important.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.avaus.com/wp-content/themes/swiss/assets/favicon/apple-touch-icon.png" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"><span class="kg-bookmark-author">Avaus</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.avaus.com/wp-content/uploads/2019/02/Predicting_customer_churn_Avaus-1024x535.jpg" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://resultadosdigitais.com.br/blog/o-que-e-churn/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Churn Rate: o que &#xE9; e como reduzir para sua empresa crescer</div><div class="kg-bookmark-description">Entenda o Churn!</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://resultadosdigitais.com.br/blog/files/2021/04/cropped-ResultadosDigitais_favicon-270x270.png" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"><span class="kg-bookmark-author">Resultados Digitais</span><span class="kg-bookmark-publisher">Resultados Digitais</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://resultadosdigitais.com.br/blog/files/2018/08/churn-rate.jpg" alt="Churn Prediction - Prevendo evas&#xE3;o de clientes de uma empresa"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Codificação de Variáveis - Label vs One-Hot Encoder]]></title><description><![CDATA[As vezes é necessário converter variáveis categóricas para treinar um modelo de Machine Learning. Nesse artigo apresentamos duas das técnicas mais populares e quando elas devem ser utilizadas.]]></description><link>https://blog.viniboscoa.dev/codificacao-de-variaveis-label-vs-one-hot-encoder/</link><guid isPermaLink="false">6438c7e29cade9000131364f</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Python]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Scikitlearn]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Wed, 15 Sep 2021 02:30:57 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/20945347.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/20945347.jpg" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder"><p>A maioria dos algoritmos de <em>Machine Learning </em>trabalham apenas com dados num&#xE9;ricos, por&#xE9;m os problemas do mundo real muitas vezes trar&#xE3;o vari&#xE1;veis categ&#xF3;ricas (ex: sim/n&#xE3;o, alto/m&#xE9;dio/baixo, ruim/regular/bom entre outros exemplos). Como podemos, ent&#xE3;o, construir um modelo de <em>Machine Learning </em>que utiliza vari&#xE1;veis desse tipo? </p><p>As duas maneiras mais comuns de converter/codificar vari&#xE1;veis categ&#xF3;ricas s&#xE3;o: <em>Label Encoder</em> e <em>One-hot Encoder</em>. Ambos os m&#xE9;todos tem o mesmo objetivo: codificar categorias em n&#xFA;meros. Nesse artigo vamos falar um pouco sobre elas, suas vantagens e desvantagens e quando utilizar cada uma.</p><h3 id="label-encoder"><em>Label Encoder</em></h3><p><em>Label Encoding </em>consiste em converter as classes categ&#xF3;ricas em n&#xFA;meros que as representam (ex: masculino/feminino s&#xE3;o convertidos em 0/1, Brasil/EUA/Jap&#xE3;o ser&#xE3;o convertidos em 0/1/2, etc.). Vamos utilizar como exemplo os pa&#xED;ses, que n&#xE3;o possuem rela&#xE7;&#xE3;o de ordem, utilizar o <em>Label Encoder</em> teria o seguinte resultado:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Label-Encoding.png" class="kg-image" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder" loading="lazy" width="272" height="121"><figcaption>Resultado - Label Encoder</figcaption></figure><p>A coluna &quot;Pa&#xED;s&quot; poderia ser removia, por&#xE9;m optamos por mant&#xEA;-la na imagem para fins did&#xE1;ticos.</p><p>Por&#xE9;m o <em>Label Encoder </em>introduz um novo problema para o modelo. Se tomarmos como exemplo os pa&#xED;ses: Brasil/EUA/Jap&#xE3;o (que foram convertidos em 0/1/2) o modelo poder&#xE1; se confundir ao achar que as datas representam algum tipo de ordem: 0 &lt; 1 &lt; 2 e atribuir um peso maior ao Jap&#xE3;o por exemplo.</p><p>Sendo assim fica claro que, se precisarmos converter vari&#xE1;veis sem que haja qualquer rela&#xE7;&#xE3;o de ordem, devemos evitar utilizar o <em>Label Encoder. </em>Podemos implementar o LabelEncoder (presente no pacote Scikitlearn) em python:</p><pre><code class="language-python"># importar pacotes
from sklearn.preprocessing import LabelEncoder

# instanciar encoder
le = LabelEncoder()

# &apos;treinar&apos; encoder
le.fit(y_train)

# realizar a transforma&#xE7;&#xE3;o
y_train = le.transform(y_train)

# visualizar classes encontradas
le.classes_

# recuperar os labels originals
le.inverse_transform(y_train)</code></pre><h3 id="one-hot-encoder">One-hot Encoder</h3><p>Como dissemos, dependendo da situa&#xE7;&#xE3;o, podemos confundir o modelo ao utilizar o <em>Label Encoder</em>, tornando-a uma solu&#xE7;&#xE3;o invi&#xE1;vel. Para evitar isso, podemos utilizar o <em>One-hot Encoder.</em></p><p>De maneira simples, o <em>One-hot Encoder </em>pega as categorias de uma coluna e as divide em v&#xE1;rias colunas, cada uma representando uma categoria, essas colunas s&#xE3;o preenchidas com valor 0 ou 1 - dependendo da categoria original. Tomemos nosso exemplo anterior dos pa&#xED;ses, se utilizarmos o <em>One-hot Encoder</em> teremos um resultado semelhante ao ilustrado abaixo:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/One-Hot-Encoding.png" class="kg-image" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder" loading="lazy" width="298" height="119"><figcaption>Resultado - One-hot Encoder</figcaption></figure><p>Tamb&#xE9;m podemos implementar o OneHotEncoder (presente no pacote Scikitlearn) em python:</p><pre><code class="language-python"># importar pacotes
from sklearn.preprocessing import OneHotEncoder

# instanciar encoder
le = OneHotEncoder()

# &apos;treinar&apos; encoder
le.fit(X_train)

# criar dataframe
X_train_enc = le.transform(X_train)</code></pre><p>A desvantagem desse m&#xE9;todo comparado ao anterior &#xE9; que, se tivermos muitas categorias para uma vari&#xE1;vel, iremos produzir muitas colunas adicionais e dependendo do modelo que estamos utilizando, podemos introduzir outros problemas de desempenho.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>O intuito desse artigo foi trazer um pouco sobre as diferen&#xE7;as das duas principais formas de convers&#xE3;o de vari&#xE1;veis categ&#xF3;ricas para modelos de <em>Machine Learning - Label Encoder e One-hot Encoder.</em></p><p>Em resumo, vimos que o Label Encoder &#xE9; uma excelente ferramenta para converter vari&#xE1;veis categ&#xF3;ricas que possuem alguma rela&#xE7;&#xE3;o de ordem, no entanto n&#xE3;o &#xE9; indicado para vari&#xE1;veis que n&#xE3;o possuem tal rela&#xE7;&#xE3;o devido a possibilidade de introduzir problemas no modelo.</p><p>J&#xE1; o <em>One-hot Encoder</em> &#xE9; ideal para os casos em que o <em>Label Encoder</em> n&#xE3;o &#xE9; indicado, no entanto, dependendo do n&#xFA;mero de categorias, o <em>One-hot Encoder</em> pode introduzir outro tipo de problema no modelo.</p><p>A escolha do m&#xE9;todo de codifica&#xE7;&#xE3;o depender&#xE1; do problema que est&#xE1; sendo analisado.</p><h2 id="refer%C3%AAncias">Refer&#xEA;ncias</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://towardsdatascience.com/choosing-the-right-encoding-method-label-vs-onehot-encoder-a4434493149b?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Choosing the right Encoding method-Label vs OneHot Encoder</div><div class="kg-bookmark-description">how can your choice of encoding play a major role in your prediction model</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://miro.medium.com/fit/c/152/152/1*sHhtYhaCe2Uc3IU0IgKwIQ.png" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder"><span class="kg-bookmark-author">Towards Data Science</span><span class="kg-bookmark-publisher">Rahil Shaikh</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/max/1200/1*d1tead56fBEuxi-MuNhU8w.jpeg" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://contactsunny.medium.com/label-encoder-vs-one-hot-encoder-in-machine-learning-3fc273365621?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Label Encoder vs. One Hot Encoder in Machine Learning</div><div class="kg-bookmark-description">Originally published here: http://blog.contactsunny.com/data-science/label-encoder-vs-one-hot-encoder-in-machine-learning</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://miro.medium.com/fit/c/152/152/1*sHhtYhaCe2Uc3IU0IgKwIQ.png" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder"><span class="kg-bookmark-author">Medium</span><span class="kg-bookmark-publisher">Sunny Srinidhi</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/max/400/1*eWtGdqsEXCnX_ZiVw2Ba0g.png" alt="Codifica&#xE7;&#xE3;o de Vari&#xE1;veis - Label vs One-Hot Encoder"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Detecção de Fraude em Cartões de Crédito]]></title><description><![CDATA[Uma das principais preocupações das instituições financeiras é com a fraude em cartões de crédito. A utilização de algoritmos de machine learning apenas um pouco melhor que os anteriores já representa uma economia de milhões de Reais.]]></description><link>https://blog.viniboscoa.dev/deteccao-de-fraude-em-cartoes-de-credito/</link><guid isPermaLink="false">6438c7e29cade9000131364e</guid><category><![CDATA[Python]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Scikitlearn]]></category><category><![CDATA[Pandas]]></category><category><![CDATA[Análise de Dados]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Sun, 12 Sep 2021 00:08:30 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/ales-nesetril-ex_p4AaBxbs-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/ales-nesetril-ex_p4AaBxbs-unsplash.jpg" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito"><p>Neste projeto, iremos abordar o problema das fraudes em cart&#xF5;es de cr&#xE9;dito, uma das principais preocupa&#xE7;&#xF5;es das institui&#xE7;&#xF5;es financeiras como bancos e <em>fintechs</em>. Em 2020, segundo o <a href="https://blogbr.clear.sale/confira-o-mapa-da-fraude?ref=blog.viniboscoa.dev">Mapa da Fraude</a>, foram registradas 3,5 milh&#xF5;es de transa&#xE7;&#xF5;es potencialmente fraudulentas, cerca de 403 tentativas de fraude por hora - nos segmentos de e-commerce, mercado financeiro, vendas diretas e telecomunica&#xE7;&#xF5;es - , isso representou R$ 3,6 bilh&#xF5;es em <strong>tentativas</strong> de fraude em 2020. (ClearSale, 2021). </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/rupixen-com-Q59HmzK38eQ-unsplash.jpg" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="2000" height="1333" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/rupixen-com-Q59HmzK38eQ-unsplash.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/rupixen-com-Q59HmzK38eQ-unsplash.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/rupixen-com-Q59HmzK38eQ-unsplash.jpg 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/rupixen-com-Q59HmzK38eQ-unsplash.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@rupixen?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">rupixen.com</a> on <a href="https://unsplash.com/s/photos/credit-card?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Dentre essas fraudes, aquelas envolvendo cart&#xF5;es de cr&#xE9;dito s&#xE3;o de grande relev&#xE2;ncia uma vez que a sua n&#xE3;o-detec&#xE7;&#xE3;o acarretar&#xE1; em preju&#xED;zos consider&#xE1;veis, tanto para o consumidor quanto para a institui&#xE7;&#xE3;o financeira.</p><p>Um outro fator a ser considerado &#xE9; a quantidade de falsos positivos, ou seja, aquelas vezes em que voc&#xEA; tentou fazer uma compra e teve seu cart&#xE3;o bloqueado preventivamente - o que provavelmente gerou estresse e constrangimento.</p><p>Ainda que seja desej&#xE1;vel reduzir a quantidade de falsos positivos, &#xE9; prefer&#xED;vel (tanto para institui&#xE7;&#xE3;o financeira quanto para o consumidor) que hajam mais falsos positivos do que falsos negativos.</p><p>Por todos esses motivos, o investimento na &#xE1;rea de detec&#xE7;&#xE3;o de fraudes por meio de Intelig&#xEA;ncia Artificial vem crescendo a cada ano, representando uma grande oportunidade em <em>Data Science</em>.</p><p>Dispondo de grandes volumes de dados como base hist&#xF3;rica, um algoritmo de <em>machine learning </em>apenas um pouco melhor que os anteriores j&#xE1; representa uma economia de milh&#xF5;es de Reais. E esse &#xE9; o desafio, aprimorar cada vez mais o uso de algoritmos visando inibir ou evitar transa&#xE7;&#xF5;es fraudulentas.</p><h2 id="sobre-os-dados">Sobre os dados</h2><p>Os dados que usaremos neste projeto foram disponibilizados por algumas empresas europeias de cart&#xE3;o de cr&#xE9;dito. O <em>dataset</em> representa as opera&#xE7;&#xF5;es financeiras que aconteceram no per&#xED;odo de dois dias, onde foram classificadas 492 fraudes em meio a quase 290 mil transa&#xE7;&#xF5;es.</p><p>Como voc&#xEA; pode notar, este &#xE9; um conjunto de dados extremamente desbalanceado, onde as fraudes representam apenas 0,17% do total.</p><p>Outro detalhe interessante &#xE9; que as <em>features</em> s&#xE3;o todas num&#xE9;ricas, e foram descaracterizadas (por problemas ligados &#xE0; privacidade e seguran&#xE7;a). Assim, os nomes das colunas s&#xE3;o representados por [V1,V2,V3&#x2026;,V28]</p><p><a href="https://www.kaggle.com/mlg-ulb/creditcardfraud?ref=blog.viniboscoa.dev" rel="nofollow">Na p&#xE1;gina original dos dados</a>, tamb&#xE9;m &#xE9; informado que as vari&#xE1;veis passaram por uma transforma&#xE7;&#xE3;o conhecida como An&#xE1;lise de Componentes Principais (<em>Principal Component Analysis</em> - PCA).</p><h4 id="pca">PCA</h4><p>A PCA permite a redu&#xE7;&#xE3;o da dimensionalidade enquanto mant&#xE9;m o maior n&#xFA;mero poss&#xED;vel de informa&#xE7;&#xF5;es. Para conseguir isso, o algoritmo encontra um conjunto novo de recursos - os chamados <strong>componentes</strong>.</p><p>Esses componentes s&#xE3;o em n&#xFA;mero menor ou igual &#xE0;s vari&#xE1;veis originais. No caso deste projeto, os componentes achados pela transforma&#xE7;&#xE3;o da PCA s&#xE3;o as pr&#xF3;prias colunas [V1,V2,V3&#x2026;,V28].</p><h2 id="objetivos">Objetivos</h2><p>O objetivo desse projeto &#xE9; comparar dois modelos de <em>Machine Learning </em>treinados em diferentes condi&#xE7;&#xF5;es - com dados desbalanceados, balanceados &#xA0;com <em>over-sampling</em> e balanceados com <em>under-sampling.</em></p><p>Tamb&#xE9;m iremos realizar o comparativo de desempenho de dois dos algoritmos de classifica&#xE7;&#xE3;o mais comuns em <em>Machine Learning</em>: <strong>Regress&#xE3;o Log&#xED;stica</strong> e <strong>&#xC1;rvores de Decis&#xE3;o. </strong>Ao todo treinaremos 6 modelos para<strong>, </strong>ao final do estudo identificarmos qual algoritmo apresentou os melhores resultados nesse cen&#xE1;rio.</p><p>N&#xE3;o aprofundaremos em todos os conceitos e t&#xE9;cnicas utilizadas, se quiser saber mais sobre todo o processo de an&#xE1;lise, confira &#xA0;a an&#xE1;lise completa no link abaixo:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/fraud-detection/blob/main/Projeto_Detec%C3%A7%C3%A3o_de_Fraude_em_Cart%C3%B5es_de_Cr%C3%A9dito.ipynb?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fraud-detection/Projeto_Detec&#xE7;&#xE3;o_de_Fraude_em_Cart&#xF5;es_de_Cr&#xE9;dito.ipynb at main &#xB7; virb30/fraud-detection</div><div class="kg-bookmark-description">Contribute to virb30/fraud-detection development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/61f08cf9a77322c559c47dc0cf2b7ef9f71ea98e1f4de6c3aa86319091521aae/virb30/fraud-detection" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito"></div></a></figure><h2 id="conhecendo-os-dados">Conhecendo os dados</h2><p>A primeira etapa do projeto consiste em realizar uma an&#xE1;lise explorat&#xF3;ria para conhecer os dados que estamos trabalhando e podermos definir as estrat&#xE9;gias que utilizaremos, dessa an&#xE1;lise conseguimos extrair alguns <em>insights</em> que guiar&#xE3;o os processos seguintes:</p><ul><li>As vari&#xE1;veis <code>Time</code> e <code>Amount</code> possuem seus valores originais, isso nos indica que ser&#xE1; necess&#xE1;rio ajustar a escala desses atributos para que n&#xE3;o impacte no nosso modelo;</li><li>Por causa do processo de PCA, evidenciado pela presen&#xE7;a das vari&#xE1;veis (V1-V28), n&#xE3;o temos mais detalhes sobre o que cada vari&#xE1;vel significa e nem como elas influenciam na classifica&#xE7;&#xE3;o (em fraude ou leg&#xED;tima);</li><li>Tamb&#xE9;m vemos que n&#xE3;o h&#xE1; nenhum registro com dados ausentes, nos poupando uma etapa de tratamento.</li></ul><h4 id="separando-os-conjuntos">Separando os conjuntos</h4><p>Antes de fazermos qualquer tratamento nos dados, &#xE9; interessante dividir o conjunto em dois subconjuntos: treino e teste. Uma recomenda&#xE7;&#xE3;o geral &#xE9; que o modelo s&#xF3; tenha conhecimento dos dados de teste ap&#xF3;s treinado e validado, com isso, al&#xE9;m de conseguirmos simular uma situa&#xE7;&#xE3;o real onde o modelo dever&#xE1; ser capaz de classificar dados novos/desconhecidos, evitamos que o modelo seja &quot;viciado&quot; e funcione apenas para os dados nos quais foi treinado.</p><h3 id="analisando-nossos-dados">Analisando nossos dados</h3><p>O gr&#xE1;fico, Figura 1, deixa claro o desbalanceamento do <em>dataset</em>, com apenas 0,17% do total de registros sendo classificados como fraude.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/dataset_balance.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="1440" height="144" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/dataset_balance.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/dataset_balance.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/dataset_balance.png 1440w" sizes="(min-width: 1200px) 1200px"><figcaption>Figura 1 - Desbalanceamento do Dataset</figcaption></figure><p>Portanto ser&#xE1; necess&#xE1;rio balancearmos os dados antes de treinar nosso modelo de classifica&#xE7;&#xE3;o.</p><h3 id="prepara%C3%A7%C3%A3o-dos-dados">Prepara&#xE7;&#xE3;o dos dados</h3><p>De modo a conseguirmos comparar o desempenho dos modelos que utilizam o mesmo algoritmo, utilizaremos duas t&#xE9;cnicas de balanceamento nos dados de treino: </p><ul><li><em>over-sampling: </em>nessa t&#xE9;cnica, os dados da classe minorit&#xE1;ria (no caso, fraudes) s&#xE3;o replicados at&#xE9; que atinjam a mesma quantidade &#xA0;da classe majorit&#xE1;ria, por exemplo se temos 50 registros da classe 1 e 10 da classe 2 ao final do balanceamento teremos 50 registros de cada classe;</li><li><em>under-sampling: </em>essa t&#xE9;cnica &#xE9; o processo oposto da t&#xE9;cnica anterior, ou seja, exemplos da classe majorit&#xE1;ria s&#xE3;o removidos, aleatoriamente, at&#xE9; que ambas fiquem com a mesma quantidade, se utilizarmos as mesmas condi&#xE7;&#xF5;es do exemplo anterior - 50 registros da classe 1 e 10 da classe 2 - ao final do balanceamento teremos 10 registros de cada classe.</li></ul><h4 id="dividir-entre-treino-e-valida%C3%A7%C3%A3o">Dividir entre treino e valida&#xE7;&#xE3;o</h4><p>Antes de realizarmos qualquer tratamento nos dados, &#xE9; interessante dividirmos nosso subconjunto (lembram-se que j&#xE1; fizemos uma divis&#xE3;o no in&#xED;cio da an&#xE1;lise?) em dois novos: treino e valida&#xE7;&#xE3;o. Os novos conjuntos ficaram com os seguintes tamanhos:</p><ul><li>Treino: 181564</li><li>Valida&#xE7;&#xE3;o: 60522</li><li>Teste: 42721</li></ul><h4 id="padronizar-dados">Padronizar dados</h4><p>Como vimos anteriormente, as vari&#xE1;veis <code>Time</code> e <code>Amount</code> est&#xE3;o em escala diferente das demais e isso pode prejudicar o treinamento do nosso modelo, portanto precisaremos padroniz&#xE1;-las. Como a vari&#xE1;vel <code>Amount</code> possui <em>outliers</em> utilizaremos a classe <code>StandardScaler</code> dispon&#xED;vel no pacote <code>scikitlearn</code>.</p><p>Nesse momento a padroniza&#xE7;&#xE3;o dever&#xE1; ser realizada somente no <em>dataset</em> de treino.</p><p>A classe <code>StandardScaler</code> al&#xE9;m de transformar as vari&#xE1;veis, tamb&#xE9;m armazenar&#xE1; os estados da transforma&#xE7;&#xE3;o, dessa maneira, quando formos validar e testar o modelo, o novo conjunto de dados passar&#xE1; pela mesma transforma&#xE7;&#xE3;o, utilizando os mesmos valores que foram &quot;treinados&quot; pelo conjunto de treino, evitando assim potenciais &quot;v&#xED;cios&quot;.</p><h4 id="balanceamento-dos-dados">Balanceamento dos dados</h4><p>Agora que nossos dados de treino est&#xE3;o padronizados, e conforme citado anteriormente, iremos balancear os dados de duas maneiras: <em>under-sampling</em> e <em>over-sampling </em>ao final do processo teremos dois conjuntos de dados da seguinte maneira:</p><ul><li><em>under-sampling</em>: 313 registros de cada classe</li><li><em>over-samplin</em>g: 181251 registros de cada classe</li></ul><p>Podemos ver nos gr&#xE1;ficos da Figura 2 que agora nossos dados est&#xE3;o balanceados e podemos prosseguir para o treinamento do modelo.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/compare_samples-1.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="720" height="432" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/compare_samples-1.png 600w, https://blog.viniboscoa.dev/content/images/2022/05/compare_samples-1.png 720w" sizes="(min-width: 720px) 720px"><figcaption>Figura 2 - Balanceamento dos dados</figcaption></figure><p></p><h3 id="modelo-de-machine-learning">Modelo de <em>Machine Learning</em></h3><p>Com os dados balanceados podemos seguir com a constru&#xE7;&#xE3;o dos nossos modelos. Como estamos lidando com um problema de classifica&#xE7;&#xE3;o bin&#xE1;ria (ou seja o alvo pode assumir apenas 2 valores - 0 ou 1) e conforme citamos no in&#xED;cio desse artigo, iremos realizar um comparativo entre dois modelos: <strong>regress&#xE3;o log&#xED;stica</strong> e <strong>&#xE1;rvores de decis&#xE3;o.</strong></p><p>Ambos os modelos ser&#xE3;o treinados da seguinte maneira: 1) utilizando os dados originais, 2) com dados balanceados com <em>under-sampling</em> e 3) com dados balanceados com <em>over-sampling.</em></p><p>Em seguida iremos comparar o desempenho dos modelos treinados com mesmo algoritmo mas com balanceamentos diferentes e, por fim, realizaremos a compara&#xE7;&#xE3;o de qual dos 6 modelos apresentou melhores resultados.</p><h4 id="modelo-de-regress%C3%A3o-log%C3%ADstica">Modelo de Regress&#xE3;o Log&#xED;stica</h4><p>O modelo de regress&#xE3;o log&#xED;stica utiliza a fun&#xE7;&#xE3;o sigmoide e mapeia os valores de sa&#xED;da no intervalo [0, 1], sendo, portanto, o mais utilizado em problemas de classifica&#xE7;&#xE3;o bin&#xE1;ria, como: detec&#xE7;&#xE3;o de spam, tumor maligno/benigno, fraudes etc.</p><h4 id="modelo-de-%C3%A1rvore-de-decis%C3%A3o">Modelo de &#xC1;rvore de Decis&#xE3;o</h4><p>O algoritmo de &#xC1;rvore de Decis&#xE3;o &#xE9; mais intuitivo para humanos, uma vez que faz a avalia propriedades e, com base em crit&#xE9;rios calculados durante o treino, toma sucessivas decis&#xF5;es sobre qual caminho seguir (direita ou esquerda) at&#xE9; que n&#xE3;o haja mais decis&#xF5;es a tomar. Para ficar mais claro a Figura 3 mostra parte de uma &#xE1;rvore de decis&#xE3;o.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/decision_tree--2-.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="2000" height="654" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/decision_tree--2-.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/decision_tree--2-.png 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/decision_tree--2-.png 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/decision_tree--2-.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Figura 3 - Parte de uma &#xE1;rvore de decis&#xE3;o</figcaption></figure><h3 id="avaliando-o-modelos">Avaliando o modelos</h3><p>Para avaliar nossos modelos, utilizaremos, com m&#xE9;trica de avalia&#xE7;&#xE3;o, os indicadores: <code>precision</code>, <code>recall</code> e <code>f1-score</code>.</p><p><code>precision</code> nos informa a quantidade proporcional de identifica&#xE7;&#xF5;es positivas feitas corretamente, ou seja, dentre as previs&#xF5;es marcadas como <strong>positivo, </strong>qual foi o percentual de acerto do algoritmo, podemos resumir <code>precision</code> com a equa&#xE7;&#xE3;o:</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/precision.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="140" height="92"></figure><p><code>recall</code> nos diz a propor&#xE7;&#xE3;o de positivos encontrados corretamente, ou seja, dos dados <strong>reais positivos</strong>, qual propor&#xE7;&#xE3;o deles foi realmente marcado como positivo, e &#xE9; dado pela equa&#xE7;&#xE3;o:</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/recall.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="142" height="85"></figure><p>O <code>F1-score</code> &#xE9; a m&#xE9;dia harm&#xF4;nica entre <code>precision</code> e <code>recall</code>e nos d&#xE1; uma boa ideia do desempenho do nosso algoritmo. &#xC9; obtido pela equa&#xE7;&#xE3;o:</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/f1-score.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="289" height="96"></figure><p>Onde:</p><ul><li>TP = true positive (previs&#xF5;es positivas corretas)</li><li>FP = false positive (previs&#xF5;es positivas incorretas)</li><li>FN = false negative (previs&#xF5;es negativas incorretas)</li><li>TN = true negative (previs&#xF5;es negativas corretas)</li></ul><p>Outra m&#xE9;trica interessante, e que utiliza-se dos conceitos acima abordados &#xE9; a <em>AUC ROC, </em>que podem ser definidas como:</p><ul><li><em>ROC</em> ou <em>Receiver Operation Characteristic</em> tra&#xE7;a uma curva baseada nas taxas de verdadeiro positivo e falso positivo.</li><li><em>AUC</em> ou <em>Area Under the Curve</em> &#xE9; uma m&#xE9;trica que traduz a curva <em>ROC</em> em um n&#xFA;mero atrav&#xE9;s do c&#xE1;lculo, como o nome sugere, da &#xE1;rea sob a curva <em>ROC</em>.</li></ul><p>Utilizaremos como m&#xE9;trica para nossa compara&#xE7;&#xE3;o, principalmente os &#xED;ndices <em>AUC ROC</em> e <em>recall.</em></p><h4 id="avaliando-o-modelo-de-regress%C3%A3o">Avaliando o modelo de regress&#xE3;o</h4><p>Para os cen&#xE1;rios de treino propostos obtivemos os seguintes resultados para o modelo de Regress&#xE3;o Log&#xED;stica, a Figura 4 mostra, de forma visual, esses resultados:</p><p><strong>Dataset Original</strong></p><ul><li>Acur&#xE1;cia global: 99,93%</li><li>Recall: 69,52%</li><li>AUC ROC: 84,75%</li></ul><p>O <em>recall</em> nos diz que apenas 69,52% das previs&#xF5;es de fraude foram feitas corretamente, o que significa uma taxa de aproximadamente 31% de falsos positivos, aliado &#xE0; acur&#xE1;cia global de 99,93% temos um forte ind&#xED;cio de que esse modelo est&#xE1; com <em>overfitting.</em></p><p><strong><em>Under-sampling</em></strong></p><ul><li>Acur&#xE1;cia global: 96,86%</li><li>Recall: 91,43%</li><li>AUC ROC: 94,15%</li></ul><p>Quando utilizamos dados balanceados com <em>under-sampling</em> notamos uma melhora no <em>recall</em>, ou seja, 91,43% das previs&#xF5;es de fraude foram corretas - isso provoca uma redu&#xE7;&#xE3;o no n&#xFA;mero de falsos positivos.</p><p><strong><em>Over-sampling</em></strong></p><ul><li>Acur&#xE1;cia global: 97,71%</li><li>Recall: 89,52%</li><li>AUC ROC: 93,62%</li></ul><p>O modelo de regress&#xE3;o treinado com o <em>dataset</em> balanceado em <em>over-sampling </em>apresentou resultados semelhantes aos anteriores. por&#xE9;m ligeiramente piores.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Regression_results.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="1920" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/Regression_results.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/Regression_results.png 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/Regression_results.png 1600w, https://blog.viniboscoa.dev/content/images/2022/05/Regression_results.png 1920w" sizes="(min-width: 1200px) 1200px"><figcaption>Figura 4 - Matrizes de confus&#xE3;o modelo de Regress&#xE3;o Log&#xED;stica</figcaption></figure><p>Notamos, portanto, que, para esse <em>dataset</em>, o modelo de regress&#xE3;o tem melhor desempenho com menos dados e dados reais (n&#xE3;o fabricados).</p><h4 id="avaliando-o-modelo-de-%C3%A1rvore">Avaliando o modelo de &#xC1;rvore</h4><p>Para facilitar a compara&#xE7;&#xE3;o apresentaremos os resultados da mesma maneira que fizemos para o modelo de regress&#xE3;o e a Figura 5 cont&#xE9;m a representa&#xE7;&#xE3;o gr&#xE1;fica dos resultados:</p><p><strong>Dataset Original</strong></p><ul><li>Acur&#xE1;cia global: 99,92%</li><li>Recall: 80,95%</li><li>AUC ROC: 90,45%</li></ul><p>Esses resultados revelam que, para dados desbalanceados, o modelo de &#xE1;rvore de decis&#xE3;o &#xE9; melhor que o modelo de regress&#xE3;o - <em>recall</em> de 80,95%, em contrapartida, a acur&#xE1;cia global de quase 100% &#xE9; um forte ind&#xED;cio de <em>overfitting.</em></p><p><em><strong>Under-sampling</strong></em></p><ul><li>Acur&#xE1;cia global: 87,15%</li><li>Recall: 90,48%</li><li>AUC ROC: 88,81%</li></ul><p>Apesar de ter apresentado menor &#xED;ndice AUC ROC, o modelo de &#xE1;rvore de decis&#xE3;o treinado com apresentou melhores resultados de <em>recall.</em></p><p><strong><em>Over-sampling</em></strong></p><ul><li>Acur&#xE1;cia global: 99,91%</li><li>Recall: 77,14%</li><li>AUC ROC: 88,54%</li></ul><p>J&#xE1; o modelo de &#xE1;rvore de decis&#xE3;o treinado com dados balanceados em <em>Over-sampling </em>apresentou resultados piores at&#xE9; que quando treinado com dados desbalanceados.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Tree_results-1.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="1920" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/Tree_results-1.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/Tree_results-1.png 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/Tree_results-1.png 1600w, https://blog.viniboscoa.dev/content/images/2022/05/Tree_results-1.png 1920w" sizes="(min-width: 1200px) 1200px"></figure><p>Esses resultados nos mostram que o modelo de &#xE1;rvore de decis&#xE3;o &#xE9; menos eficiente quando o volume de dados &#xE9; maior, tamb&#xE9;m nos mostra que esse modelo &#xE9; menos suscet&#xED;vel a dados desbalanceados se comparado com o modelo de regress&#xE3;o.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Nessa an&#xE1;lise comparamos o desempenho de dois modelos de classifica&#xE7;&#xE3;o baseados em algoritmos diferentes - <strong>Regress&#xE3;o Log&#xED;stica</strong> e <strong>&#xC1;rvore de Decis&#xE3;o</strong> - aplicados ao problema de detec&#xE7;&#xE3;o de fraudes em transa&#xE7;&#xF5;es de cart&#xE3;o de cr&#xE9;dito utilizando um mesmo conjunto de dados.</p><p>Consideramos principalmente as m&#xE9;tricas <code>recall</code> e <code>AUC ROC</code> para nosso comparativo, uma vez que s&#xE3;o esses indicadores que melhor representam a taxa de acerto (fraudes detectadas corretamente), os resultados foram resumidos e podem ser vistos na Tabela 1.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/model-summary.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fraude em Cart&#xF5;es de Cr&#xE9;dito" loading="lazy" width="451" height="207"><figcaption>Tabela 1 - Comparativo de desempenho dos modelos</figcaption></figure><p>A tabela acima nos revela caracter&#xED;sticas importantes com rela&#xE7;&#xE3;o ao nosso cen&#xE1;rio (problema e dados):</p><ol><li>O algoritmo de regress&#xE3;o log&#xED;stica apresentou melhor desempenho com o <em>dataset</em> balanceado atrav&#xE9;s da t&#xE9;cnica de <em>under-sampling</em> (com um menor volume de amostras).</li><li>O algoritmo de &#xE1;rvore de decis&#xE3;o apresentou melhor desempenho em rela&#xE7;&#xE3;o a taxa de acertos de transa&#xE7;&#xF5;es fraudulentas (<code>recall</code>), quando treinado utilizando um dataset balanceados com <em>under-sampling</em> (menor volume de amostras).</li><li>O modelo de &#xE1;rvore de decis&#xE3;o apresentou melhor desempenho - comparado ao de regress&#xE3;o - quando os dados estavam desbalanceados, mostrando mais robustez desse algoritmo em situa&#xE7;&#xF5;es semelhantes.</li></ol><p>No cen&#xE1;rio de detec&#xE7;&#xE3;o de fraudes em cart&#xE3;o de cr&#xE9;dito esperamos obter: menor n&#xFA;mero de falsos positivos (melhor experi&#xEA;ncia do cliente) e menor n&#xFA;mero de falsos negativos (mais seguran&#xE7;a tanto para a institui&#xE7;&#xE3;o quanto para o cliente), logo, o modelo recomendado para esse cen&#xE1;rio &#xE9; o de <strong>Regress&#xE3;o Log&#xED;stica</strong> treinado com o <em>dataset</em> balanceado com <em>under-sampling</em>.</p><p>Essa an&#xE1;lise tamb&#xE9;m nos mostrou que, apenas com a realiza&#xE7;&#xE3;o do tratamento adequado nos dados &#xE9; poss&#xED;vel melhorar o desempenho de modelos de <em>Machine Learning</em>, dispensando a necessidade de desenvolver um novo algoritmo ou realizar ajustes finos para atingir resultados satisfat&#xF3;rios. No entanto com um bom <em>tunning</em> nos par&#xE2;metros, aliado a um bom conjunto de dados &#xE9; poss&#xED;vel obtermos modelos cada vez melhores e mais precisos.</p>]]></content:encoded></item><item><title><![CDATA[Panorama do COVID-19 no Brasil: os impactos da vacinação]]></title><description><![CDATA[A vacinação é realmente capaz de frear a pandemia de COVID-19? Analisamos os dados do OWID para obter essa resposta.]]></description><link>https://blog.viniboscoa.dev/panorama-do-covid-19-no-brasil-os-impactos-da-vacinacao/</link><guid isPermaLink="false">6438c7e29cade9000131364d</guid><category><![CDATA[Análise de Dados]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Pandas]]></category><category><![CDATA[COVID-19]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Tue, 24 Aug 2021 02:40:17 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/5150374.jpg" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/virus-banner.jpg" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="2000" height="1333" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/virus-banner.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/virus-banner.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/virus-banner.jpg 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/virus-banner.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://www.freepik.com/vectors/background?ref=blog.viniboscoa.dev">Background vector created by rawpixel.com - www.freepik.com</a></figcaption></figure><img src="https://blog.viniboscoa.dev/content/images/2022/05/5150374.jpg" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o"><p>COVID-19 &#xE9; uma doen&#xE7;a infecciosa causada por uma variante rec&#xE9;m-descoberta do coronav&#xED;rus, denominado Sars-Cov-2.</p><p>Transmitido principalmente por meio de got&#xED;culas provenientes de tosses ou espirros de pessoas infectadas, a gravidade dos sintomas varia muito de pessoa para pessoa.</p><p>Fato &#xE9;, n&#xE3;o se sabe muita coisa a respeito do COVID-19. Estudos est&#xE3;o sendo realizados no mundo todo, por&#xE9;m os resultados ainda n&#xE3;o s&#xE3;o conclusivos e definitivos.</p><p>At&#xE9; o presente momento, observa-se que cerca de 80% dos casos confirmados s&#xE3;o assintom&#xE1;ticos e r&#xE1;pidos. A maioria das pessoas que se encaixam nesse grupo, se recupera sem nenhuma sequela.</p><p>No entanto, 15% das pessoas ter&#xE3;o infec&#xE7;&#xF5;es graves e precisar&#xE3;o de oxig&#xEA;nio. O restante das pessoas, que representam 5%, ser&#xE3;o classificadas como infec&#xE7;&#xF5;es muito graves e precisar&#xE3;o de ventila&#xE7;&#xE3;o assistida, por meio de respiradores mec&#xE2;nicos em ambiente hospitalar.</p><p>Esse risco &#xE9; ainda maior em se tratando da popula&#xE7;&#xE3;o idosa. Idosos, naturalmente, s&#xE3;o mais suscet&#xED;veis a doen&#xE7;as infectocontagiosas, uma vez que o sistema imunol&#xF3;gico enfraquece com a idade, favorecendo a manifesta&#xE7;&#xE3;o de casos mais graves nessa popula&#xE7;&#xE3;o.</p><h3 id="pandemia">Pandemia</h3><p>Ap&#xF3;s mais de 1 ano de decretada pandemia de COVID-19, em mar&#xE7;o de 2020, al&#xE9;m das medidas recomendadas pela OMS - distanciamento social, higieniza&#xE7;&#xE3;o das m&#xE3;os e uso de m&#xE1;scara - a esperan&#xE7;a se concentra na vacina&#xE7;&#xE3;o em massa da popula&#xE7;&#xE3;o para que a vida volte ao normal.</p><h2 id="objetivos">Objetivos</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/covid19-map-1.jpg" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="2000" height="1576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/covid19-map-1.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/covid19-map-1.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/covid19-map-1.jpg 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/covid19-map-1.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://www.freepik.com/psd/infographic?ref=blog.viniboscoa.dev">Infographic psd created by freepik - www.freepik.com</a></figcaption></figure><p>O objetivo dessa an&#xE1;lise &#xE9; obter um panorama da pandemia de COVID-19, no Brasil e no mundo, utilizando os dados p&#xFA;blicos da doen&#xE7;a disponibilizados pelo <em>Our World In Data</em> (OWID) - um projeto da <a href="https://global-change-data-lab.org/?ref=blog.viniboscoa.dev" rel="nofollow"><em>Global Change Data Lab</em></a>, uma organiza&#xE7;&#xE3;o sem fins lucrativos baseada no Reino Unido.</p><h3 id="objetivos-espec%C3%ADficos">Objetivos espec&#xED;ficos</h3><p>Esperamos obter respostas aos seguintes questionamentos:</p><ul><li>Como se deu a evolu&#xE7;&#xE3;o da pandemia no Brasil e no mundo?</li><li>Houve redu&#xE7;&#xE3;o no n&#xFA;mero de novas mortes com o avan&#xE7;o da vacina&#xE7;&#xE3;o?</li><li>Quando foi registrada a primeira morte no Brasil?</li><li>Quantos dias demorou para registrarmos a primeira morte, considerando o primeiro caso de COVID-19 registrado no pa&#xED;s?</li><li>Como se deu a evolu&#xE7;&#xE3;o de casos da doen&#xE7;a no pa&#xED;s?</li></ul><h2 id="evolu%C3%A7%C3%A3o-do-covid-19">Evolu&#xE7;&#xE3;o do COVID-19</h2><p>Toda nossa an&#xE1;lise considerar&#xE1; como data limite: 16 de agosto de 2021, podemos perceber um crescimento exponencial na quantidade de casos de COVID-19 no mundo conforme mostra a figura 1:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/evolucao_casos_mundo.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="720" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/evolucao_casos_mundo.png 600w, https://blog.viniboscoa.dev/content/images/2022/05/evolucao_casos_mundo.png 720w" sizes="(min-width: 720px) 720px"><figcaption>Figura 1 - Evolu&#xE7;&#xE3;o de casos por COVID-19 no Mundo</figcaption></figure><p>Se detalharmos melhor como fica a distribui&#xE7;&#xE3;o de casos e mortes por continente vemos que o continente Asi&#xE1;tico &#xE9; o que possui maior n&#xFA;mero de casos, Figura 2, no entanto, o continente Europeu &#xE9; o que possui maior n&#xFA;mero de mortes, Figura 3.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/total_casos_continente.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="432" height="288"><figcaption>Figura 2 -Total de casos por continente</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/total_mortes_continente.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="432" height="288"><figcaption>Figura 3 - Total de mortes por continente</figcaption></figure><p>Como o COVID-19 &#xE9; uma doen&#xE7;a infectocontagiosa e que se manifesta de maneira mais grave na popula&#xE7;&#xE3;o idosa, nossa hip&#xF3;tese inicial &#xE9; que o continente Europeu possui mais habitantes nessa condi&#xE7;&#xE3;o, Figura 4, justificando a presen&#xE7;a do continente nessa posi&#xE7;&#xE3;o.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/media_populacao_idosa_continente.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="432" height="288"><figcaption>FIgura 4 - M&#xE9;dia da popula&#xE7;&#xE3;o idosa por continente</figcaption></figure><p>Apesar de impreciso (pois utilizamos a m&#xE9;dia da popula&#xE7;&#xE3;o idosa dos pa&#xED;ses que comp&#xF5;em o continente) podemos ver que h&#xE1; uma disparidade entre o percentual da popula&#xE7;&#xE3;o Idosa no continente Europeu em rela&#xE7;&#xE3;o aos demais continentes, essa caracter&#xED;stica &#xE9; um forte ind&#xED;cio que confirma nossa hip&#xF3;tese que justifica o maior n&#xFA;mero de mortes nesse continente.</p><h3 id="pa%C3%ADses-com-maior-n%C3%BAmero-de-casos-e-mortes-no-mundo">Pa&#xED;ses com maior n&#xFA;mero de casos e mortes no mundo</h3><p>Agora que temos uma ideia geral da pandemia no mundo, montamos um ranking com os 5 pa&#xED;ses que mais registraram casos e mortes, nossa lista &#xE9; composta de: nome do pa&#xED;s, total de casos/mortes e percentual de casos/mortes em rela&#xE7;&#xE3;o a popula&#xE7;&#xE3;o total</p><p><strong>Em total de casos</strong></p><ol><li>Estados Unidos - 36.888.952 - 11,15% da popula&#xE7;&#xE3;o total</li><li>&#xCD;ndia - 32.250.679 - 2,34%</li><li>Brasil - 20.378.570 - 9,59%</li><li>Fran&#xE7;a - 6.556.496 - 9,70%</li><li>R&#xFA;ssia - 6.531.585 - 4,48%</li></ol><p><strong>Em total de mortes</strong></p><ol><li>Estados Unidos - 622.321 - 0,19% da popula&#xE7;&#xE3;o total</li><li>Brasil - 569.492 - 0,27%</li><li>&#xCD;ndia - 432.079 - 0,03%</li><li>M&#xE9;xico - 248.652 - 0,19%</li><li>Peru - 197.393 - 0,60%</li></ol><p>Notamos que o Brasil figura entre os 3 pa&#xED;ses com maior n&#xFA;mero absoluto de casos confirmados(20.378.570 - 9,59% da popula&#xE7;&#xE3;o), atr&#xE1;s apenas de Estados Unidos(36.888.952 - 11,15% da popula&#xE7;&#xE3;o) e &#xCD;ndia(32.250.679 - 2,34% da popula&#xE7;&#xE3;o).</p><p>Em se tratando do n&#xFA;mero absoluto de mortes, o Brasil encontra-se em segundo lugar (569.492 - 0,27% da popula&#xE7;&#xE3;o total), atr&#xE1;s apenas dos Estados Unidos (622.321 - 0,19% da popula&#xE7;&#xE3;o total) e seguido da &#xCD;ndia (432.079 - 0,03% da popula&#xE7;&#xE3;o total).</p><h3 id="os-impactos-da-vacina%C3%A7%C3%A3o">Os impactos da vacina&#xE7;&#xE3;o</h3><blockquote>As vacinas para COVID-19 s&#xE3;o efetivas e uma ferramenta importante para controle da pandemia. No entanto, nenhuma vacina &#xE9; 100% eficaz para prevenir pessoas de contrairem a doen&#xE7;a, existe uma pequena porcentagem de pessoas totalmente vacinadas que ainda ficam doentes (OPAS, 2021).</blockquote><p>Portanto o objetivo central da vacina&#xE7;&#xE3;o em massa &#xE9; a redu&#xE7;&#xE3;o no n&#xFA;mero de casos graves e &#xF3;bitos e, consequentemente, o controle da pandemia.</p><p>Na Figura 5, &#xA0;podemos perceber como o avan&#xE7;o da vacina&#xE7;&#xE3;o impactou no n&#xFA;mero de novos casos e novas mortes no mundo. Consideraremos o n&#xFA;mero absoluto de pessoas vacinadas (com 1 ou todas as doses previstas).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/vacina_mortes_mundo.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="1080" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/vacina_mortes_mundo.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/vacina_mortes_mundo.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/vacina_mortes_mundo.png 1080w" sizes="(min-width: 720px) 720px"><figcaption>Figura 5 - Pessoas vacinadas / Novas mortes no mundo</figcaption></figure><p>Vale lembrar que cada pa&#xED;s tem seu ritmo de vacina&#xE7;&#xE3;o e disponibilidade de vacinas, ou seja, o cen&#xE1;rio mundial pode n&#xE3;o se repetir quando analisamos os pa&#xED;ses isoladamente.</p><p>Vemos acima que o avan&#xE7;o da vacina&#xE7;&#xE3;o pode ter contribu&#xED;do para a redu&#xE7;&#xE3;o do n&#xFA;mero de novas mortes ao longo tempo, no entanto, quando comparamos com o n&#xFA;mero de novos casos, Figura 6, &#xA0;n&#xE3;o temos o mesmo resultado:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/vacinas_casos_mundo.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="1080" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/vacinas_casos_mundo.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/vacinas_casos_mundo.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/vacinas_casos_mundo.png 1080w" sizes="(min-width: 720px) 720px"><figcaption>Figura 6 - Pessoas vacinadas / Novos casos no mundo</figcaption></figure><p>Notamos pelo gr&#xE1;fico acima uma oscila&#xE7;&#xE3;o na quantidade de novos casos com picos e vales cada 2 meses aproximadamente. Esse comportamento se repete, independente do avan&#xE7;o da vacina&#xE7;&#xE3;o. No entanto, a partir de Janeiro de 2021 a tend&#xEA;ncia de novos casos est&#xE1; praticamente constante.</p><p>Diversos fatores podem contribuir para esse cen&#xE1;rio, por exemplo, ado&#xE7;&#xE3;o de medidas mais restritivas, ou o aumento da confian&#xE7;a da popula&#xE7;&#xE3;o j&#xE1; vacinada podem ter consequ&#xEA;ncias no aparecimento de novos casos.</p><h2 id="o-cen%C3%A1rio-na-am%C3%A9rica-do-sul">O Cen&#xE1;rio na Am&#xE9;rica do Sul</h2><p>A Figura 7, mostra como o total de novos casos e novas mortes est&#xE3;o distribu&#xED;dos entre os pa&#xED;ses da Am&#xE9;rica do Sul. </p><p>Esses dados nos revelam que o Brasil possui mais casos e mortes por COVID-19 do que todos os pa&#xED;ses da Am&#xE9;rica do Sul juntos. Num primeiro momento esses n&#xFA;meros assustam quando vistos isoladamente. No entanto vale destacar que a popula&#xE7;&#xE3;o total do pa&#xED;s representa quase 50% da popula&#xE7;&#xE3;o total do continente.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/top5_paises_america_do_sul.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="864" height="360" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/top5_paises_america_do_sul.png 600w, https://blog.viniboscoa.dev/content/images/2022/05/top5_paises_america_do_sul.png 864w" sizes="(min-width: 720px) 720px"><figcaption>Figura 7 - Top5 Pa&#xED;ses da Am&#xE9;rica do Sul com maior n&#xFA;mero de novos casos e novas mortes&#xA0;</figcaption></figure><p>Portanto, quando utilizamos a popula&#xE7;&#xE3;o do pa&#xED;s em rela&#xE7;&#xE3;o a popula&#xE7;&#xE3;o do continente como refer&#xEA;ncia, vemos que os n&#xFA;meros de casos/mortes por COVID-19, ainda que elevados, s&#xE3;o proporcionais &#xE0; representatividade populacional do Brasil na Am&#xE9;rica do Sul.</p><h3 id="a-covid-19-no-brasil">A COVID-19 no Brasil</h3><p>O<em> </em>primeiro caso de COVID-19 registrado no Brasil foi em <strong>26/02/2020, </strong>j&#xE1;<strong> </strong>a primeira morte em 17/03/2020, <strong>20 dias</strong> ap&#xF3;s o primeiro caso, nesta data o pa&#xED;s contabilizava 321 casos confirmados.</p><p>Podemos ver pela Figura 8, como a evolu&#xE7;&#xE3;o de casos e mortes ocorreu no pa&#xED;s, notamos uma grande diferen&#xE7;a entre o n&#xFA;mero absoluto de casos e mortes. Isso evidencia a propor&#xE7;&#xE3;o entre casos leves e fatais da doen&#xE7;a.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/comparativo_casos_mortes_brasil.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="583" height="367"><figcaption>Figura 8 - Comparativo Total de Casos e Mortes - Brasil</figcaption></figure><p>Ajustando a escala do gr&#xE1;fico, Figura 9, &#xE9; poss&#xED;vel visualizar melhor a evolu&#xE7;&#xE3;o dos n&#xFA;meros de casos e mortes ao longo do tempo. Ainda que haja uma diferen&#xE7;a significativa entre total de casos e mortes, percebemos que a evolu&#xE7;&#xE3;o desses dois indicadores deu-se de maneira similar.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/comparativo_casos_mortes_brasil_escala.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="580" height="367"><figcaption>Figura 9 - Comparativo Total de Casos / Mortes - Brasil - escala</figcaption></figure><p>Esse comportamento &#xE9; esperado uma vez que o n&#xFA;mero total de mortes est&#xE1; diretamente relacionado com o n&#xFA;mero total de casos.</p><h3 id="os-impactos-da-vacina%C3%A7%C3%A3o-1">Os impactos da Vacina&#xE7;&#xE3;o</h3><p>Percebemos que o Brasil possui n&#xFA;meros expressivos no total de casos e mortes por COVID-19. Mas ser&#xE1; que o avan&#xE7;o da vacina&#xE7;&#xE3;o no pa&#xED;s tem contribu&#xED;do para a melhora desse cen&#xE1;rio? A Figura 10 mostra como foi a evolu&#xE7;&#xE3;o de novos casos no pa&#xED;s com rela&#xE7;&#xE3;o &#xE0; vacina&#xE7;&#xE3;o. Lembrando que o in&#xED;cio da vacina&#xE7;&#xE3;o no Brasil se deu em 17/01/2021.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/vacinas_mortes_brasil.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="1080" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/vacinas_mortes_brasil.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/vacinas_mortes_brasil.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/vacinas_mortes_brasil.png 1080w" sizes="(min-width: 720px) 720px"><figcaption>Figura 10 - Pessoas vacinadas / Novas mortes no Brasil</figcaption></figure><p>Podemos perceber algumas caracter&#xED;sticas interessantes sobre a vacina&#xE7;&#xE3;o no pa&#xED;s e que merecem destaque:</p><p>Notamos momentos sem dados, provavelmente causados pelas pausas por falta de vacina ou per&#xED;odos em que n&#xE3;o houve registro.</p><p>Tamb&#xE9;m percebemos uma redu&#xE7;&#xE3;o expressiva no n&#xFA;mero de novas mortes &#xE0; medida que a vacina&#xE7;&#xE3;o avan&#xE7;a, demonstrando a efic&#xE1;cia da vacina&#xE7;&#xE3;o em massa contra a pandemia.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/vacinas_casos_brasil.png" class="kg-image" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o" loading="lazy" width="1080" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/vacinas_casos_brasil.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/vacinas_casos_brasil.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/vacinas_casos_brasil.png 1080w" sizes="(min-width: 720px) 720px"><figcaption>Figura 11 - Pessoas vacinadas / Novos casos no Brasil</figcaption></figure><p>Diferente do que ocorre em &#xE2;mbito mundial, notamos uma tend&#xEA;ncia de queda no n&#xFA;mero de novos casos no cen&#xE1;rio nacional, Figura 11, por&#xE9;m vale destacar que a propor&#xE7;&#xE3;o dos testes pode n&#xE3;o refletir o cen&#xE1;rio real.</p><p>Temos visto tamb&#xE9;m que alguns dias n&#xE3;o h&#xE1; divulga&#xE7;&#xE3;o de novos casos ainda que eles existam, essa situa&#xE7;&#xE3;o pode ser vista no gr&#xE1;fico atrav&#xE9;s dos per&#xED;odos em que n&#xE3;o h&#xE1; registro de novos casos.</p><p>Mas, no geral, podemos ver que a vacina&#xE7;&#xE3;o tem impactado diretamente o n&#xFA;mero de novos casos, contribuindo para sua diminui&#xE7;&#xE3;o ao longo do tempo.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Fizemos uma an&#xE1;lise superficial dos dados relativos &#xE0; Pandemia de COVID-19 e disponibilizados pela <em>Our World In Data</em> de modo a obter um panorama da doen&#xE7;a no Brasil e no Mundo e, principalmente, quais os impactos da vacina&#xE7;&#xE3;o no controle da pandemia.</p><p>No cen&#xE1;rio mundial, notamos uma melhora na quantidade de novas mortes, principalmente &#xE0; medida que a vacina&#xE7;&#xE3;o avan&#xE7;a, no entanto, o mesmo n&#xE3;o se repete quando o assunto s&#xE3;o novos casos, para esses notamos que h&#xE1; uma certa estabilidade, ou seja, nem melhor, nem pior.</p><p>Esses dados nos revelam ainda que o Brasil &#xE9; um dos pa&#xED;ses que mais apresenta casos e mortes por COVID-19 no mundo, no entanto, diferente do cen&#xE1;rio mundial, notamos sinais de redu&#xE7;&#xE3;o no n&#xFA;mero de casos e mortes com o avan&#xE7;o da vacina&#xE7;&#xE3;o, mostrando que essa t&#xEA;m sido uma medida eficaz no controle da pandemia no pa&#xED;s.</p><p>Existem muitos fatores envolvidos que podem influenciar a coleta e distribui&#xE7;&#xE3;o de dados referentes &#xE0; pandemia, portanto as an&#xE1;lises aqui realizadas n&#xE3;o refletem a realidade em sua totalidade.</p><p>A an&#xE1;lise completa voc&#xEA; confere aqui:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/analise-covid/blob/main/Projeto_Panorama_do_COVID_19_no_Brasil.ipynb?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">analise-covid/Projeto_Panorama_do_COVID_19_no_Brasil.ipynb at main &#xB7; virb30/analise-covid</div><div class="kg-bookmark-description">Contribute to virb30/analise-covid development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/2b388ac89a079d9a77dc47e5eb33dede49d27625fa64b13f67606b5566e180d3/virb30/analise-covid" alt="Panorama do COVID-19 no Brasil: os impactos da vacina&#xE7;&#xE3;o"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Analisando dados do Airbnb - Grande Lisboa]]></title><description><![CDATA[Neste projeto, iremos analisar os dados referentes à Grande Lisboa, e ver quais insights podem ser extraídos a partir de dados brutos.]]></description><link>https://blog.viniboscoa.dev/analisando-dados-do-airbnb-lisboa/</link><guid isPermaLink="false">6438c7e29cade9000131364c</guid><category><![CDATA[Data Science]]></category><category><![CDATA[Análise de Dados]]></category><category><![CDATA[Python]]></category><category><![CDATA[Pandas]]></category><category><![CDATA[Matplotlib]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Fri, 13 Aug 2021 03:55:00 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/Design-sem-nome.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/Design-sem-nome.png" alt="Analisando dados do Airbnb - Grande Lisboa"><p>O <a href="https://www.airbnb.com.br/?ref=blog.viniboscoa.dev" rel="nofollow">Airbnb</a> j&#xE1; &#xE9; considerado como sendo a <strong>maior empresa hoteleira da atualidade</strong>. Ah, o detalhe &#xE9; que ele <strong>n&#xE3;o possui nenhum hotel</strong>!</p><p>Conectando pessoas que querem viajar (e se hospedar) com anfitri&#xF5;es que querem alugar seus im&#xF3;veis de maneira pr&#xE1;tica, o Airbnb fornece uma plataforma inovadora para tornar essa hospedagem alternativa.</p><p>No final de 2018, a Startup fundada em 2008, j&#xE1; havia <strong>hospedado mais de 300 milh&#xF5;es</strong> de pessoas ao redor de todo o mundo, desafiando as redes hoteleiras tradicionais.</p><p>Uma das iniciativas do Airbnb &#xE9; disponibilizar dados do site, para algumas das principais cidades do mundo. Por meio do portal <a href="http://insideairbnb.com/get-the-data.html?ref=blog.viniboscoa.dev" rel="nofollow">Inside Airbnb</a>, &#xE9; poss&#xED;vel baixar uma grande quantidade de dados para desenvolver projetos e solu&#xE7;&#xF5;es de <em>Data Science</em>.</p><h2 id="destino-lisboa">Destino: Lisboa</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/claudio-schwarz-K3XGEoZgA0s-unsplash.jpg" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="2000" height="1333" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/claudio-schwarz-K3XGEoZgA0s-unsplash.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/claudio-schwarz-K3XGEoZgA0s-unsplash.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/claudio-schwarz-K3XGEoZgA0s-unsplash.jpg 1600w, https://blog.viniboscoa.dev/content/images/size/w2400/2022/05/claudio-schwarz-K3XGEoZgA0s-unsplash.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@purzlbaum?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Claudio Schwarz</a> on <a href="https://unsplash.com/s/photos/lisbon?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>O turismo em Portugal est&#xE1; na moda, e nada melhor do que come&#xE7;ar um passeio pelo pa&#xED;s pela sua capital, Lisboa.</p><p>O clima em Lisboa &#xE9; agrad&#xE1;vel o ano todo, mas as melhores &#xE9;pocas para visit&#xE1;-la s&#xE3;o na primavera e no outono, quando as temperaturas s&#xE3;o amenas e n&#xE3;o costuma chover. Os meses de ver&#xE3;o podem ser bem quentes, principalmente em agosto, per&#xED;odo de f&#xE9;rias escolares na Europa, quando alguns estabelecimentos podem permanecer fechados. O inverno costuma ser chuvoso e raramente atinge temperaturas abaixo de 0<sup>o</sup>C.</p><p>Esse cen&#xE1;rio clim&#xE1;tico faz com que as Praias tornem-se a atra&#xE7;&#xE3;o preferida dos turistas. Al&#xE9;m disso os pontos hist&#xF3;ricos, a gastronomia e os jardins s&#xE3;o passeios que valem a pena.</p><p>Com tantas op&#xE7;&#xF5;es de passeio o deslocamento tamb&#xE9;m &#xE9; um aspecto a ser considerado. Felizmente circular por Lisboa &#xE9; muito f&#xE1;cil e agrad&#xE1;vel, partindo do centro da cidade, boa parte das atra&#xE7;&#xF5;es pode ser alcan&#xE7;ada a p&#xE9;, os passeios mais distantes podem ser alcan&#xE7;ados utilizando os Metros, el&#xE9;ctricos (bondes) e autocarros (&#xF4;nibus), que s&#xE3;o op&#xE7;&#xF5;es acess&#xED;veis, al&#xE9;m dos tradicionais: T&#xE1;xi, Uber e afins.</p><p>Com todas essas facilidades, onde se hospedar passa a ser uma quest&#xE3;o de escolher como curtir a cidade e n&#xE3;o uma busca estrat&#xE9;gica visando o itiner&#xE1;rio.</p><p>A Grande Lisboa - semelhante ao que ocorre com a Grande S&#xE3;o Paulo no Brasil - &#xE9; composta de v&#xE1;rios munic&#xED;pios: Alenquer, Amadora, Arruda dos Vinhos, Azambuja, Cadaval, Cascais, Lisboa, Loures, Lourinh&#xE3;, Mafra, Odivelas, Oeiras, Sintra, Sobral de Monte Agra&#xE7;o, Torres Vedras e Vila Franca de Xira.</p><h3 id="airbnb-em-portugal">Airbnb em Portugal</h3><p>Em Portugal, para um anfitri&#xE3;o anunciar um im&#xF3;vel do tipo Alojamento Local (casas, apartamentos ou alojamentos) para alugu&#xE9;is de curta dura&#xE7;&#xE3;o &#xE9; necess&#xE1;rio que seja feito um registro no Portal Cidad&#xE3;o. Esse registro deve ser feito para todos os an&#xFA;ncios do Airbnb em Portugal. Essa regulamenta&#xE7;&#xE3;o n&#xE3;o determina os n&#xFA;meros m&#xED;nimo e m&#xE1;ximo de noites para aluguel.</p><h3 id="obten%C3%A7%C3%A3o-dos-dados">Obten&#xE7;&#xE3;o dos Dados</h3><p>Os dados est&#xE3;o dispon&#xED;veis no site <a href="http://insideairbnb.com/get-the-data.html?ref=blog.viniboscoa.dev" rel="nofollow">Inside Airbnb</a>.</p><p>Para essa an&#xE1;lise utilizamos o arquivo <code>listings.csv</code> que cont&#xE9;m informa&#xE7;&#xF5;es resumidas e m&#xE9;tricas para a cidade de Lisboa.</p><p>Esse conjunto &#xE9; composto (no momento da an&#xE1;lise) de 19458 registros com 16 atributos (vari&#xE1;veis). A vers&#xE3;o completa desse <em>dataset</em> tamb&#xE9;m est&#xE1; dispon&#xED;vel no portal e conta com 106 vari&#xE1;veis para an&#xE1;lise.</p><h2 id="objetivos">Objetivos</h2><p>O objetivo da nossa an&#xE1;lise &#xE9; identificar alguns padr&#xF5;es sobre o aluguel de im&#xF3;veis do Airbnb em Lisboa. Analisaremos, principalmente os locais &#xA0;com maior oferta, a m&#xE9;dia de pre&#xE7;o e qual a m&#xE9;dia do m&#xED;nimo de noites para aluguel na cidade.</p><h2 id="an%C3%A1lise-dos-dados">An&#xE1;lise dos Dados</h2><p>Nessa etapa iremos entender como os dados est&#xE3;o estruturados e quais caracter&#xED;sticas nos chamam mais aten&#xE7;&#xE3;o.</p><p>Segundo sua documenta&#xE7;&#xE3;o as vari&#xE1;veis est&#xE3;o divididas da seguinte maneira:</p><p><strong>Dicion&#xE1;rio das vari&#xE1;veis</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/neighbourhood_group_unique.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="567" height="151"><figcaption>Lista de mun&#xED;cipios existente no conjunto de dados</figcaption></figure><ul><li><code>id</code> - identificador &#xFA;nico do airbnb</li><li><code>name</code> - descri&#xE7;&#xE3;o do im&#xF3;vel</li><li><code>host_id</code> - c&#xF3;digo do propriet&#xE1;rio (anfitri&#xE3;o) do im&#xF3;vel</li><li><code>host_name</code> - nome do anfitri&#xE3;o</li><li><code>neighbourhood_group</code> - grupo do bairro</li><li><code>neighbourhood</code> - nome do bairro</li><li><code>latitude</code> - latitude do im&#xF3;vel</li><li><code>longitude</code> - longitude do im&#xF3;vel</li><li><code>room_type</code> - tipo de quarto</li><li><code>price</code> - pre&#xE7;o da di&#xE1;ria em moeda local (no caso, euros)</li><li><code>minimum_nights</code> - n&#xFA;mero m&#xED;nimo de noites</li><li><code>number_of_reviews</code> - quantidade de reviews</li><li><code>last_review</code> - data da &#xFA;ltima review</li><li><code>reviews_per_month</code> - m&#xE9;dia de reviews por m&#xEA;s</li><li><code>calculated_host_listings_count</code> - quantidade de im&#xF3;veis do host na mesma regi&#xE3;o</li><li><code>availability_365</code> - quantidade de dias dispon&#xED;veis nos pr&#xF3;ximos 365 dias.</li></ul><p>Verificando os valores &#xFA;nicos da vari&#xE1;vel <code>neighbourhood_group</code> (grupo do bairro) percebemos que eles representam os 16 mun&#xED;cipios da Grande Lisboa.</p><h3 id="an%C3%A1lise-explorat%C3%B3ria">An&#xE1;lise explorat&#xF3;ria</h3><p>Antes de iniciar a an&#xE1;lise &#xE9; importante sabermos como est&#xE1; a qualidade dos nossos dados, para isso, verificamos algumas informa&#xE7;&#xF5;es sobre o <em>dataset, </em>tais como tipos das vari&#xE1;veis, quantidade de valores ausentes e como as vari&#xE1;veis est&#xE3;o distribu&#xED;das.</p><p>Utilizando esses crit&#xE9;rios, alguns pontos nos chamam a aten&#xE7;&#xE3;o:</p><ul><li>18% do <em>dataset </em>n&#xE3;o possui informa&#xE7;&#xE3;o<em> </em>nos campos de review (<code>reviews_per_month</code> e <code>last_review</code>);</li><li>A vari&#xE1;vel <code>host_name</code> possui pouco mais de 0,1% de valores ausentes e</li><li>A vari&#xE1;vel <code>name</code> possui 0,05% de valores ausentes.</li></ul><p>Nesse aspecto temos um conjunto de dados bom uma vez que as vari&#xE1;veis de <em>review</em> n&#xE3;o s&#xE3;o relevantes para nossa an&#xE1;lise. No entanto ao verificar a distribui&#xE7;&#xE3;o das vari&#xE1;veis, notamos a presen&#xE7;a de <em>outliers </em>- valores que destoam - que podem prejudicar nossa an&#xE1;lise.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/descricao-variaveis-numericas.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="499" height="359"><figcaption>Descri&#xE7;&#xE3;o das vari&#xE1;veis pre&#xE7;o, n&#xFA;mero m&#xED;nimo de noites e disponibilidade - <em>dataset</em> original</figcaption></figure><p>Pelo resumo acima vemos que:</p><ul><li>a vari&#xE1;vel <code>price</code> possui 75% dos valores abaixo de &#x20AC;110,00, por&#xE9;m seu valor m&#xE1;ximo &#xE9; &#x20AC; 20628,00.</li><li>notamos tamb&#xE9;m a presen&#xE7;a de valores zerados, em se tratando de alugu&#xE9;is, n&#xE3;o faz sentido que existam alugu&#xE9;is gratuitos, portanto, tamb&#xE9;m consideramos esses valores como <em>outliers</em>.</li><li>a vari&#xE1;vel <code>minimum_nights</code> (m&#xED;nimo de noites) possui valores acima de 365 dias.</li></ul><p>Portanto, antes de continuarmos, faz-se necess&#xE1;rio realizar uma limpeza nos dados, visando, principalmente eliminar (ou reduzir) esses <em>outliers.</em></p><h4 id="limpeza-dos-dados">Limpeza dos Dados</h4><p>Para remover <em>outliers</em> de um <em>dataset, </em>podemos excluir os registros ou alterar os valores seguindo algum crit&#xE9;rio. A escolha da estrat&#xE9;gia depende de diversos fatores, tais como natureza da vari&#xE1;vel escolhida, objetivo do projeto, conjunto de dados analisado, etc.</p><p>Primeiro delimitamos os valores de corte dos <em>outliers: </em>200 para <code>price</code> e 6 para <code>minimum_nights</code>, ou seja, iremos excluir todos os registros que possuam pre&#xE7;o acima de &#x20AC; 200,00 e m&#xED;nimo de noites acima de 6. Tamb&#xE9;m iremos remover os registros com <code>price = 0</code>. O c&#xE1;lculo realizado para determinar os valores de corte pode ser visto na <a href="https://github.com/virb30/airbnb-lisboa/blob/main/Projeto_Analisando_os_Dados_do_Airbnb_Grande_Lisboa.ipynb?ref=blog.viniboscoa.dev">an&#xE1;lise</a>.</p><p>Esses registros representam, respectivamente 8% e 6,5% do conjunto de dados. Nosso <em>dataset</em> limpo ficou com 16751 registros, uma redu&#xE7;&#xE3;o de pouco mais de 13%. Devido &#xE0; quantidade de dados dispon&#xED;veis, acreditamos que esse &#xE9; um n&#xFA;mero aceit&#xE1;vel e n&#xE3;o causar&#xE1; grandes preju&#xED;zos &#xE0; an&#xE1;lise.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/descricao-variaveis-numericas-cleaned-1.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="547" height="348"><figcaption>Descri&#xE7;&#xE3;o das vari&#xE1;veis pre&#xE7;o, n&#xFA;mero m&#xED;nimo de noites e disponibilidade - <em>dataset</em> limpo</figcaption></figure><p>Verificamos alguns pontos que merecem destaque:</p><ul><li>A mediana de <code>price</code> quase n&#xE3;o foi afetada pela limpeza, isso significa que nossa limpeza n&#xE3;o provocou grandes distor&#xE7;&#xF5;es nos pre&#xE7;os;</li><li>A m&#xE9;dia e o desvio padr&#xE3;o de <code>price</code> foram reduzidos consideravelmente enfatizando a sensibilidade dessa vari&#xE1;vel a <em>outliers</em> e;</li><li>Agora n&#xE3;o temos dados que n&#xE3;o fazem sentido (por exemplo <code>price = 0</code> e <code>minimum_nights &gt; 365</code>).</li></ul><h3 id="continuando-a-an%C3%A1lise">Continuando a an&#xE1;lise</h3><p>Com os dados limpos podemos prosseguir com nossa an&#xE1;lise respondendo &#xE0; quest&#xE3;o mais importante para quem deseja visitar Lisboa.</p><h4 id="qual-a-m%C3%A9dia-de-pre%C3%A7os-do-aluguel">Qual a m&#xE9;dia de pre&#xE7;os do aluguel?</h4><p>Segundo a documenta&#xE7;&#xE3;o do <em>dataset</em> todos os dados referentes a pre&#xE7;o s&#xE3;o dados em moeda local, portanto, a m&#xE9;dia de pre&#xE7;o dos alugu&#xE9;is em Lisboa &#xE9; de</p><h1 id="%E2%82%AC-7700">&#x20AC; 77,00</h1><h3 id="qual-o-tipo-de-im%C3%B3vel-mais-alugado-no-airbnb">Qual o tipo de im&#xF3;vel mais alugado no Airbnb?</h3><p>Em nosso <em>dataset</em> a vari&#xE1;vel <code>room_type</code> indica qual o tipo do im&#xF3;vel que est&#xE1; anunciado no Airbnb. As op&#xE7;&#xF5;es dispon&#xED;veis s&#xE3;o: &#xA0;<em>Entire home/apt (</em>apartamentos/casas inteiras), <em>Hotel room </em>(quarto de hotel), <em>Shared Room </em>(quarto compartilhado) ou <em>Private room </em>(aluguel de um quarto).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/pW37C-tipo-de-im-vel.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="1240" height="414" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/pW37C-tipo-de-im-vel.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/pW37C-tipo-de-im-vel.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/pW37C-tipo-de-im-vel.png 1240w" sizes="(min-width: 720px) 720px"><figcaption>Prefer&#xEA;ncia de tipo de im&#xF3;vel</figcaption></figure><p>Vemos que em Lisboa a prefer&#xEA;ncia de aluguel &#xE9; para apartamentos/casas inteiras, e representa 63% do conjunto de dados.</p><h3 id="qual-%C3%A9-a-m%C3%A9dia-do-m%C3%ADnimo-de-noites-para-aluguel"><strong>Qual &#xE9; a m&#xE9;dia do m&#xED;nimo de noites para aluguel?</strong></h3><h1 id="2-noites">2 noites</h1><p>A m&#xE9;dia do m&#xED;nimo de noites em um im&#xF3;vel &#xE9; de aproximadamente 2 noites, o que indica que as pessoas costumam ficar em um im&#xF3;vel em Lisboa o per&#xED;odo equivalente ao final de semana.</p><h3 id="qual-a-localidade-mais-cara-do-dataset">Qual a localidade mais cara do <em>dataset</em>?</h3><p>Vamos dividir essa pergunta em 2 partes. Primeiro vamos ver qual a cidade mais cara da Grande Lisboa, em seguida vamos analisar quais os bairros mais caros da capital Lisboa (cidade mais populosa de Portugal).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/ycy5o-cidades-mais-caras.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="1240" height="990" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/ycy5o-cidades-mais-caras.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/ycy5o-cidades-mais-caras.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/ycy5o-cidades-mais-caras.png 1240w" sizes="(min-width: 720px) 720px"><figcaption>Cidades da Grande Lisboa com maior pre&#xE7;o m&#xE9;dio de aluguel</figcaption></figure><p>Pelo gr&#xE1;fico acima vemos que Arruda Dos Vinhos &#xE9; a cidade com maior m&#xE9;dia de pre&#xE7;o, no entanto, se analisarmos a quantidade de im&#xF3;veis por cidade, vemos que nosso <em>dataset</em> possui apenas 5 im&#xF3;veis nesse munic&#xED;pio, o que, consequentemente, eleva sua m&#xE9;dia. Portanto consideramos Mafra como o mun&#xED;cipio como localidade mais cara.</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/quantidade-imoveis-municipio.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="346" height="342"></figure><p>Ainda dentro dessa an&#xE1;lise, vemos que Lisboa &#xE9; a cidade que possui maior n&#xFA;mero de im&#xF3;veis cadastrados. Vamos ver tamb&#xE9;m qual &#xE9; o bairro mais caro da cidade de Lisboa.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/2yc03-bairros-mais-caros-de-lisboa.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="1240" height="840" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/2yc03-bairros-mais-caros-de-lisboa.png 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/2yc03-bairros-mais-caros-de-lisboa.png 1000w, https://blog.viniboscoa.dev/content/images/2022/05/2yc03-bairros-mais-caros-de-lisboa.png 1240w" sizes="(min-width: 720px) 720px"><figcaption>Bairros mais caros de Lisboa</figcaption></figure><p>Pelo gr&#xE1;fico acima vemos que o Parque das Na&#xE7;&#xF5;es &#xE9; o bairro com maior m&#xE9;dia de pre&#xE7;o, segundo a Imobili&#xE1;ria Engel &amp; V&#xF6;lkers &#x201C;[O Parque das Na&#xE7;&#xF5;es] &#xE9; atualmente um dos mais procurados para arrendamento, apresentando um pre&#xE7;o por metro quadrado em torno dos 4.300 euros&quot;, isso, aliado &#xE0; uma oferta relativamente baixa (193 im&#xF3;veis no nosso <em>dataset</em>) pode ter contribu&#xED;do com a m&#xE9;dia dos pre&#xE7;os mais elevada nessa localidade.</p><h4 id="mapa-de-calor">Mapa de calor</h4><p>Vamos visualizar melhor a distribui&#xE7;&#xE3;o dos pre&#xE7;os dos im&#xF3;veis na Grande Lisboa atrav&#xE9;s do mapa de calor:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/mapa_calor.png" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="864" height="576" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/mapa_calor.png 600w, https://blog.viniboscoa.dev/content/images/2022/05/mapa_calor.png 864w" sizes="(min-width: 720px) 720px"><figcaption>Mapa de calor - Im&#xF3;veis Grande Lisboa</figcaption></figure><p>Para efeitos comparativos, vamos confrontar o mapa de calor acima, com o mapa da Vimeca, empresa respons&#xE1;vel pelo transporte em Lisboa.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/rea-Metropolitana-de-Lisboa-2.jpg" class="kg-image" alt="Analisando dados do Airbnb - Grande Lisboa" loading="lazy" width="2000" height="2210" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/rea-Metropolitana-de-Lisboa-2.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/rea-Metropolitana-de-Lisboa-2.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/rea-Metropolitana-de-Lisboa-2.jpg 1600w, https://blog.viniboscoa.dev/content/images/2022/05/rea-Metropolitana-de-Lisboa-2.jpg 2393w" sizes="(min-width: 720px) 720px"><figcaption>Mapa da Grande Lisboa - Vimeca</figcaption></figure><p>Comparando os dois mapas, vemos que a grande predomin&#xE2;ncia de im&#xF3;veis se d&#xE1; nas regi&#xF5;es litor&#xE2;nea (incluindo as banhadas pelo reio Tejo) e na regi&#xE3;o central de Lisboa.</p><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Foi realizada uma an&#xE1;lise gen&#xE9;rica dos dados do Airbnb para a Grande Lisboa. O objetivo principal era ter uma vis&#xE3;o geral sobre o conjunto de dados e gerar <em>insights</em> para an&#xE1;lises mais aprofundadas. Vimos que esse <em>dataset</em> possui alguns <em>outliers</em> fazendo-se necess&#xE1;ria uma limpeza antes de prosseguirmos com a an&#xE1;lise.</p><p>Algumas localidades possuem poucos im&#xF3;veis dispon&#xED;veis, o que pode provocar distor&#xE7;&#xF5;es na m&#xE9;dia dos pre&#xE7;os.</p><p>Os locais com maior oferta est&#xE3;o localizados nas regi&#xF5;es litor&#xE2;neas (e banhadas pelo rio Tejo) e no centro de Lisboa. O tempo m&#xE9;dio despendido pelos visitantes na cidade &#xE9; de pouco mais de 2 dias.</p><p>Por fim vale ressaltar que esse <em>dataset</em> cont&#xE9;m uma parcela dos dados dispon&#xED;veis pelo Airbnb, sendo ideal para uma an&#xE1;lise inicial. No entanto recomenda-se a utiliza&#xE7;&#xE3;o da vers&#xE3;o completa desse <em>dataset</em> que possui 106 atributos para an&#xE1;lise.</p><p>Confira a an&#xE1;lise completa <a href="https://github.com/virb30/airbnb-lisboa/blob/main/Projeto_Analisando_os_Dados_do_Airbnb_Grande_Lisboa.ipynb?ref=blog.viniboscoa.dev">aqui</a>.</p><p>&#x200C;&#x200C;</p><p>&#x200C;&#x200C;</p>]]></content:encoded></item><item><title><![CDATA[YOLO-COCO - Detecção de Objetos em Tempo Real]]></title><description><![CDATA[Neste post vamos aprender a construir um detector de objetos, em tempo real, com Python, OpenCV e a rede neural YOLO pré-treinada no COCO Dataset.]]></description><link>https://blog.viniboscoa.dev/yolo-coco-deteccao-de-objetos-em-tempo-real/</link><guid isPermaLink="false">6438c7e29cade9000131364b</guid><category><![CDATA[Computer Vision]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[Image preprocessing]]></category><category><![CDATA[Python]]></category><category><![CDATA[OpenCV]]></category><category><![CDATA[Visão Computacional]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Wed, 14 Jul 2021 02:14:54 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/yolo.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/yolo.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"><p>Neste &#xFA;ltimo post da s&#xE9;rie de vis&#xE3;o computacional iremos construir um detector de objetos, em tempo real, utilizando nossos queridinhos: Python, OpenCV e, dessa vez, a YOLO pr&#xE9;-treinada no COCO Dataset.</p><h2 id="you-only-look-once-yolo-common-objects-in-context-coco-dataset">You Only Look Once (YOLO) + Common Objects in Context (COCO) Dataset</h2><p>A YOLO &#xE9; o estado da arte dos sistemas de detec&#xE7;&#xE3;o de objetos em tempo real, extremamente r&#xE1;pida e precisa, </p><p>O COCO <em>dataset &#xE9; um </em>conjunto de dados de ampla escala que cont&#xE9;m a classifica&#xE7;&#xE3;o de 80 tipos de objetos diferentes com mais de 330K imagens para realiza&#xE7;&#xE3;o da segmenta&#xE7;&#xE3;o de objetos.</p><p>Combinados, formam uma poderosa ferramenta para detec&#xE7;&#xE3;o e classifica&#xE7;&#xE3;o de objetos em tempo real. Deixo alguns links a seguir se desejar conhecer mais sobre a <a href="https://pjreddie.com/darknet/yolo/?ref=blog.viniboscoa.dev">YOLO </a>e o <a href="https://cocodataset.org/?ref=blog.viniboscoa.dev#home">COCO Dataset</a>.</p><h2 id="o-projeto">O Projeto</h2><p>Nesse projeto iremos utilizar algumas t&#xE9;cnicas apresentadas nos artigos anteriores dessa s&#xE9;rie, por isso n&#xE3;o entraremos em detalhes sobre elas e vamos nos concentrar na parte da detec&#xE7;&#xE3;o e identifica&#xE7;&#xE3;o dos objetos.</p><p>Dessa vez iremos implementar CLI (Command Line Interface), em que o usu&#xE1;rio pode informar, atrav&#xE9;s do terminal, a c&#xE2;mera que ser&#xE1; utilizada ou um arquivo de v&#xED;deo para ser analisado. As funcionalidades que iremos implementar s&#xE3;o:</p><ul><li>Inicializar o streaming de v&#xED;deo - ou enviar um arquivo de v&#xED;deo para ser analisado - &#xE0; escolha do usu&#xE1;rio;</li><li>Realizar pr&#xE9;-processamento da imagem para reduzir os ru&#xED;dos e;</li><li>Detectar os objetos que desejamos - no nosso caso optamos por detectar somente: pessoa(0), monitor(62), mouse(64), controle remoto(65) e teclado(66).</li></ul><h3 id="preparando-nossa-aplica%C3%A7%C3%A3o">Preparando nossa aplica&#xE7;&#xE3;o</h3><p>Primeiramente importaremos as bibliotecas necess&#xE1;rias e configuraremos as constantes necess&#xE1;rias para o correto funcionamento.</p><pre><code class="language-python">import numpy as np
import argparse
import os
from os.path import join, dirname
import cv2
import time
from imutils.video import VideoStream, FileVideoStream
from imutils.video import FPS</code></pre><p>Al&#xE9;m das bibliotecas que utilizaremos para a captura e processamento das imagens <code>cv2</code>, <code>imutils</code> e <code>numpy</code>, importamos tamb&#xE9;m a biblioteca <code>os</code> para ler o sistema arquivos.</p><p>Como iremos construir um CLI, importamos tamb&#xE9;m a biblioteca <code>argparse</code> permitindo que o usu&#xE1;rio informe os par&#xE2;metros necess&#xE1;rios via terminal.</p><p>Em seguida configuramos algumas constantes:</p><ul><li>N&#xED;vel de confian&#xE7;a m&#xED;nimo</li><li>Non-maximum Suppression threshold - um limite para filtrar as &quot;caixas&quot; que destacar&#xE3;o os objetos detectados, evitando m&#xFA;ltiplas detec&#xE7;&#xF5;es para um mesmo objeto.</li><li>O diret&#xF3;rio que contem nosso modelo treinado e os nomes das classes.</li></ul><pre><code class="language-python">CONFIDENCE_MIN = 0.4
NMS_THRESHOLD = 0.2
MODEL_BASE_PATH = join(dirname(__file__), &quot;yolo-coco&quot;)</code></pre><p>Em seguida iremos definir algumas fun&#xE7;&#xF5;es:</p><ol><li><code>build_parser</code>: essa fun&#xE7;&#xE3;o ir&#xE1; construir nosso <em>parser, </em>que possibilitar&#xE1; que nossa aplica&#xE7;&#xE3;o receba argumentos atrav&#xE9;s do terminal:</li></ol><pre><code class="language-python">def build_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument(&quot;-i&quot;, &quot;--input&quot;, required=True, help=&quot;Endere&#xE7;o do streaming, camera index ou caminho do arquivo&quot;)
    return parser
</code></pre><p>2. <code>load_classes</code>: essa fun&#xE7;&#xE3;o ir&#xE1; extrair os nomes das classes a partir do arquivo <code>coco.names</code> - retornar&#xE1; os <em>labels</em> e uma cor aleat&#xF3;ria para cada label existente no arquivo:</p><pre><code class="language-python">    def load_classes():
    	with open(os.path.sep.join([MODEL_BASE_PATH, &apos;coco.names&apos;])) as f:
            labels = f.read().strip().split(&apos;\n&apos;)

            # gerar cores para cada label
            np.random.seed(42)
            colors = np.random.randint(0, 255, size=(len(labels), 3), dtype=&apos;uint8&apos;)
            return labels, colors</code></pre><p>3. <code>load_model</code>: ir&#xE1; carregar o modelo treinado no <code>cv2</code>:</p><pre><code class="language-python">def load_model():
    net = cv2.dnn.readNetFromDarknet(
        os.path.sep.join([MODEL_BASE_PATH, &apos;yolov3.cfg&apos;]),
        os.path.sep.join([MODEL_BASE_PATH, &apos;yolov3.weights&apos;]))

    return net</code></pre><p>4. <code>extract_layers</code>: essa fun&#xE7;&#xE3;o ir&#xE1; extrair as camadas n&#xE3;o conectadas da arquitetura YOLO</p><pre><code class="language-python">def extract_layers():
    ln = net.getLayerNames()
    ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
    return ln</code></pre><p>5. <code>start_streaming</code>: respons&#xE1;vel por instanciar e inicializar o streaming (ou carregar o arquivo) de v&#xED;deo:</p><pre><code class="language-python">def start_streaming(streaming_path):
    if os.path.isfile(streaming_path):
        vs = FileVideoStream(streaming_path)
    elif streaming_path.isnumeric:
        vs = VideoStream(int(streaming_path))
    else:
        vs = VideoStream(streaming_path)

    vs.start()
    return vs</code></pre><p>Agora que nossas fun&#xE7;&#xF5;es est&#xE3;o definidas vamos para o c&#xF3;digo principal, irei separar os blocos que dizem respeito &#xE0; mesma funcionalidade e comentarei brevemente sobre cada parte no decorrer do artigo:</p><pre><code>if __name__ == &apos;__main__&apos;:
    parser = build_parser()
    streaming_path = vars(parser.parse_args())[&apos;input&apos;]

    print(&quot;[+] Carregando labels das classes treinadas...&quot;)
    labels, colors = load_classes()

    print(&quot;[+] Carregando o modelo YOLO treinado&quot;)
    net = load_model()

    ln = extract_layers()

    print(&quot;[+] Iniciando a recep&#xE7;&#xE3;o do streaming...&quot;)
    vs = start_streaming(streaming_path)
    time.sleep(1)
    fps = FPS().start()</code></pre><p>Nessa primeira parte estamos fazendo o setup do nosso CLI: </p><ul><li>construir o parser, </li><li>capturar a entrada do usu&#xE1;rio, </li><li>carregar as classes e cores, </li><li>carregar o modelo, </li><li>extrair as camadas desconectadas da arquitetura YOLO e;</li><li>inicializar o streaming de v&#xED;deo.</li></ul><pre><code class="language-python">    while True:
        frame = vs.read()

        # redimensionar os frames
        # frame = cv2.resize(frame, None, fx=0.2, fy=0.2)

        # capturar a largura e altura do frame
        (H, W) = frame.shape[:2]

        # construir um container blob e fazer uma passagem na YOLO
        blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (W, H), swapRB=True, crop=False)
        net.setInput(blob)
        layer_outputs = net.forward(ln)

        # criar listas com boxes, n&#xED;vel de confian&#xE7;a e ids das classes
        boxes = []
        confidences = []
        class_ids = []</code></pre><p>Nosso loop infinito que ir&#xE1; capturar o frame do stream, construir o blob a partir do frame e passar pela YOLO. Iremos tamb&#xE9;m criar as listas de <em>boxes, n</em>&#xED;veis de confian&#xE7;a e ids das classes, mais adiante utilizaremos essas listas para exibir a localiza&#xE7;&#xE3;o, n&#xED;vel de confian&#xE7;a e classe dos objetos detectados.</p><pre><code class="language-python">        for output in layer_outputs:
            for detection in output:
                scores = detection[5:]
                class_id = np.argmax(scores)
                confidence = scores[class_id]

                # filtar pelo threshold da confian&#xE7;a
                # selecionar somente se for pessoa, monitor, teclado, mouse ou controle remoto
                # verificar  arquivo coco.name caso precise utilizar outras classes
                if confidence &gt; CONFIDENCE_MIN and class_id in [0, 62, 64, 65, 66]:
                    box = detection[0:4] * np.array([W, H, W, H])
                    (center_x, center_y, width, height) = box.astype(&quot;int&quot;)

                    x = int(center_x - (width / 2))
                    y = int(center_y - (height / 2))

                    boxes.append([x, y, int(width), int(height)])
                    confidences.append(float(confidence))
                    class_ids.append(class_id)</code></pre><p>Nesse trecho estamos percorrendo as sa&#xED;das e detec&#xE7;&#xF5;es realizadas pela YOLO e filtrando-as tanto pelo n&#xED;vel de confian&#xE7;a m&#xED;nimo (que definimos na etapa de prepara&#xE7;&#xE3;o) quanto pelas classe que desejamos detectar.</p><p>Para as detec&#xE7;&#xF5;es que se enquadram nesses crit&#xE9;rios, iremos adicionar as respectivas informa&#xE7;&#xF5;es nas listas que criamos anteriormente:</p><ul><li> <code>boxes</code> - coordenadas da caixa que destaca cada objeto detectado;</li><li><code>confidence</code> - os n&#xED;veis de confian&#xE7;a de cada objeto</li><li><code>class_ids</code> - as classes de cada um dos objetos detectados</li></ul><pre><code class="language-python"># eliminar ruido e redund&#xE2;ncias aplicando non-maxima suppression
        new_ids = cv2.dnn.NMSBoxes(boxes, confidences, CONFIDENCE_MIN, NMS_THRESHOLD)
        if len(new_ids) &gt; 0:
            for i in new_ids.flatten():
                (x, y) = (boxes[i][0], boxes[i][1])
                (w, h) = (boxes[i][2], boxes[i][3])

                # plotar ret&#xE2;ngulo e texto das classes detectadas no frame atual
                color_picked = [int(c) for c in colors[class_ids[i]]]
                cv2.rectangle(frame, (x, y), (x + w, y + h), color_picked, 2)
                text = &quot;{}: {:.4f}&quot;.format(labels[class_ids[i]], confidences[i])
                cv2.putText(frame, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color_picked, 2)</code></pre><p>Como a YOLO n&#xE3;o aplica o Non-maximum Supression (NMS) para n&#xF3;s, iremos aplic&#xE1;-lo para remover a sobreposi&#xE7;&#xE3;o das &quot;caixas&quot; que detectam os objetos, mantendo apenas a detec&#xE7;&#xE3;o mais confi&#xE1;vel.</p><p>Para cada uma dessas detec&#xE7;&#xF5;es (j&#xE1; com o filtro NMS aplicado) iremos extrair os pontos das caixas e plotar o ret&#xE2;ngulo - ao redor do objeto com o texto e o n&#xED;vel de confian&#xE7;a - no frame atual.</p><p>Em seguida vamos exibir o frame atualizado e aguardar o usu&#xE1;rio pressionar a tecla de sa&#xED;da (esc):</p><pre><code class="language-python">        # exibir o frame atual
        cv2.imshow(&apos;Frame&apos;, frame)

        # sair caso seja pressionada a tecla ESC
        c = cv2.waitKey(1)
        if c == 27:
            break

        # atualiza o fps
        fps.update()</code></pre><p>Por fim, eliminamos os processos e janelas:</p><pre><code class="language-python">  # eliminar processos e janelas
    fps.stop()
    cv2.destroyAllWindows()
    vs.stop()
</code></pre><h3 id="demonstra%C3%A7%C3%A3o">Demonstra&#xE7;&#xE3;o</h3><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/object_detection_example.gif" class="kg-image" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real" loading="lazy" width="640" height="480"></figure><p></p><h3 id="conclus%C3%A3o">Conclus&#xE3;o</h3><p>Apesar de ser um projeto simples, como conseguimos extrair diversos objetos diferentes podemos imaginar diversas aplica&#xE7;&#xF5;es, tais como: em carros aut&#xF4;nomos, detec&#xE7;&#xE3;o de invas&#xE3;o em espa&#xE7;os restritos, identificar utiliza&#xE7;&#xE3;o de objetos proibido em determinado local, entre outras.</p><h2 id="reposit%C3%B3rio-do-projeto">Reposit&#xF3;rio do Projeto</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/realtime_object_detection?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">virb30/realtime_object_detection</div><div class="kg-bookmark-description">Realtime Object Detection with Python &amp; OpenCV. Contribute to virb30/realtime_object_detection development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/6394f27906a8bba78588625583b46b1fb49a6bd860a4434991939b30913004c1/virb30/realtime_object_detection" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"></div></a></figure><h2 id="refer%C3%AAncias">Refer&#xEA;ncias</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.pyimagesearch.com/2018/11/12/yolo-object-detection-with-opencv/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">YOLO object detection with OpenCV - PyImageSearch</div><div class="kg-bookmark-description">In this guide you will learn how to use the YOLO object detector to detect objects in images and video using OpenCV, Python, and Deep Learning.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">PyImageSearch</span><span class="kg-bookmark-publisher">Adrian Rosebrock</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.pyimagesearch.com/wp-content/uploads/2018/11/yolo_design.jpg" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://towardsdatascience.com/non-maximum-suppression-nms-93ce178e177c?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Non-maximum Suppression (NMS)</div><div class="kg-bookmark-description">A Technique to remove duplicates and false positives in object detection</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://miro.medium.com/fit/c/152/152/1*sHhtYhaCe2Uc3IU0IgKwIQ.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"><span class="kg-bookmark-author">Towards Data Science</span><span class="kg-bookmark-publisher">Sambasivarao. K</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/max/1200/1*6d_D0ySg-kOvfrzIRwHIiA.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://pjreddie.com/darknet/yolo/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">YOLO: Real-Time Object Detection</div><div class="kg-bookmark-description">You only look once (YOLO) is a state-of-the-art, real-time object detection system.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://pjreddie.com/static/icon.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"><span class="kg-bookmark-publisher">Joseph Redmon</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://pjreddie.com/static/img/darknet_notext.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://cocodataset.org/?ref=blog.viniboscoa.dev#home"><div class="kg-bookmark-content"><div class="kg-bookmark-title">COCO - Common Objects in Context</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Common Objects in Context</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cocodataset.org/images/coco-logo.png" alt="YOLO-COCO - Detec&#xE7;&#xE3;o de Objetos em Tempo Real"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Detecção de Fadiga em tempo real com Python, OpenCV e dlib]]></title><description><![CDATA[Neste artigo iremos construir um detector de fadiga utilizando Python, OpenCV e a biblioteca de detecção de landmarks dlib. Projeto baseado no artigo de Adrian Rosebrock.]]></description><link>https://blog.viniboscoa.dev/deteccao-de-fadiga-com-python-opencv-e-dlib/</link><guid isPermaLink="false">6438c7e29cade9000131364a</guid><category><![CDATA[Computer Vision]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[Python]]></category><category><![CDATA[OpenCV]]></category><category><![CDATA[Visão Computacional]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Tue, 06 Jul 2021 03:39:52 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/deteccao_fadiga-thumb.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/deteccao_fadiga-thumb.png" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib"><p>Neste artigo iremos construir um detector de fadiga utilizando Python, OpenCV e a biblioteca de detec&#xE7;&#xE3;o de <em>landmarks</em> <code>dlib</code>. Esse projeto foi baseado <a href="https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/?ref=blog.viniboscoa.dev">neste artigo</a> do mestre da vis&#xE3;o computacional Adrian Rosebrock.</p><h3 id="o-que-iremos-utilizar">O que iremos utilizar?</h3><p>Para esse projeto iremos utilizar Python e algumas bibliotecas, dentre elas: OpenCV, <code>dlib</code> e <code>imutils</code>. Utilizaremos alguns recursos adicionais, por&#xE9;m essas tr&#xEA;s bibliotecas s&#xE3;o as principais para atingirmos nosso objetivo central: Detectar Fadiga.</p><h4 id="opencv">OpenCV</h4><p>Em nosso projeto o OpenCV ser&#xE1; utilizado, principalmente, para realizar o pr&#xE9;-processamento das imagens que ser&#xE3;o utilizadas para a detec&#xE7;&#xE3;o. Al&#xE9;m disso, iremos utiliz&#xE1;-la para exibir as imagem (frames), aplicar o contorno aos pontos de interesse (em nosso caso, os olhos), adicionar texto sobre a imagem, etc.</p><h4 id="imutils">Imutils</h4><p>A biblioteca <code>imutils</code> ir&#xE1; nos auxiliar na captura do v&#xED;deo na forma de streaming, para que possamos realizar a detec&#xE7;&#xE3;o em tempo real.</p><h4 id="dlib">Dlib</h4><p>Ser&#xE1; com a biblioteca <code>dlib</code>, em conjunto com um modelo pr&#xE9;-treinado de deep learning (presente na mesma) - e capaz de detectar 68 <em>landmarks </em>-, que iremos detectar as <em>landmarks</em> faciais.</p><p>Mas o que s&#xE3;o <em><strong>landmarks </strong></em><strong>faciais?</strong></p><p>Em nosso contexto, as <em>landmarks </em>faciais s&#xE3;o pontos de interesse ao longo de uma &#xE1;rea, ou seja, o objetivo de um detector de <em>landmarks </em>faciais &#xE9; identificar estruturas importantes de um rosto utilizando m&#xE9;todos previs&#xE3;o de formas (<em>shape prediction</em>). Detectar essas marca&#xE7;&#xF5;es, em geral utiliza dois processos:</p><ol><li>Localizar a face na imagem</li><li>Detectar as estruturas faciais chave do objeto de interesse</li></ol><p>A localiza&#xE7;&#xE3;o da face pode ser realizada de diversas maneiras com diversos algoritmos, mas, para que nossa detec&#xE7;&#xE3;o funcione, precisamos extrair dessa imagem a <em>bounding box </em>(uma esp&#xE9;cie de caixa) que representa a face.</p><p>Com a &#xE1;rea do rosto, podemos aplicar a segunda etapa: detectar as estruturas faciais chave. Existem diversos algoritmos para tais tarefas, mas em ess&#xEA;ncia, a maioria tenta detectar e rotular as regi&#xF5;es:</p><ul><li>boca;</li><li>nariz;</li><li>sobrancelhas (direita e esquerda)</li><li>olhos (direito e esquero);</li><li>mand&#xED;bula;</li></ul><h4 id="detectar-fadiga">Detectar Fadiga</h4><p>At&#xE9; agora conseguimos carregar o v&#xED;deo e detectar os pontos de interesse dos rostos nas imagens, mas como detectamos a fadiga?</p><p>Nesse projeto iremos considerar a propor&#xE7;&#xE3;o de abertura dos olhos, assim, se a propor&#xE7;&#xE3;o for menor que nosso limiar, temos a fadiga.</p><p>Como estamos utilizando a biblioteca <code>dlib</code> com o detector de 68 <em>landmarks</em>, temos, para cada olho, 6 pontos, conforme imagem abaixo:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/facial_landmarks_68markup.jpg" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib" loading="lazy" width="1856" height="1496" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/facial_landmarks_68markup.jpg 600w, https://blog.viniboscoa.dev/content/images/size/w1000/2022/05/facial_landmarks_68markup.jpg 1000w, https://blog.viniboscoa.dev/content/images/size/w1600/2022/05/facial_landmarks_68markup.jpg 1600w, https://blog.viniboscoa.dev/content/images/2022/05/facial_landmarks_68markup.jpg 1856w" sizes="(min-width: 720px) 720px"><figcaption>Fonte:<a href="https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/?ref=blog.viniboscoa.dev"> https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/</a></figcaption></figure><p>Podemos calcular a propor&#xE7;&#xE3;o do aspecto do olho com a seguinte equa&#xE7;&#xE3;o:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.viniboscoa.dev/content/images/2022/05/1_S4_3ba7ySnM_g3OCStrCDQ.png" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib" loading="lazy" width="632" height="142" srcset="https://blog.viniboscoa.dev/content/images/size/w600/2022/05/1_S4_3ba7ySnM_g3OCStrCDQ.png 600w, https://blog.viniboscoa.dev/content/images/2022/05/1_S4_3ba7ySnM_g3OCStrCDQ.png 632w"><figcaption>Fonte: <a href="https://towardsdatascience.com/drowsiness-detection-system-in-real-time-using-opencv-and-flask-in-python-b57f4f1fcb9e?ref=blog.viniboscoa.dev">https://towardsdatascience.com/drowsiness-detection-system-in-real-time-using-opencv-and-flask-in-python-b57f4f1fcb9e</a></figcaption></figure><p>Com esse valor, conseguiremos identificar se h&#xE1; ou n&#xE3;o sinais de fadiga na imagem/v&#xED;deo.</p><h3 id="juntando-tudo">Juntando tudo</h3><p>Combinaremos essas e outras bibliotecas para criar nosso detector de fadiga. Come&#xE7;aremos instalando e importando todas elas. </p><pre><code class="language-bash">pip install opencv-python imutils playsound numpy</code></pre><p><strong>NOTA: </strong>para a instala&#xE7;&#xE3;o correta da <code>dlib</code> recomendo seguir <a href="https://www.pyimagesearch.com/2017/03/27/how-to-install-dlib/?ref=blog.viniboscoa.dev">esse tutorial</a>.</p><pre><code class="language-python"># importar pacotes necess&#xE1;rios
from os.path import join, dirname
from scipy.spatial import distance as dist
from imutils.video import VideoStream
from imutils import face_utils
from threading import Thread
import numpy as np
import playsound
import imutils
import time
import dlib
import cv2
import matplotlib.pyplot as plt
import os</code></pre><ol><li>Definiremos algumas constantes que ser&#xE3;o utilizadas ao longo do algoritmo</li></ol><pre><code class="language-python"># definir constantes
ALARM_SOUND = join(dirname(__file__), &quot;buzina.wav&quot;) # local do arquivo do alarme
WEBCAM = os.environ.get(&apos;WEBCAM&apos;, 1) # indice da c&#xE2;mera que capturar&#xE1; o stream
EYE_THRESHOLD = 0.25 # limiar de &quot;abertura&quot; dos olhos
FRAMES_SEQ = 40 # quantidade de frames seguidos que o EAR m&#xE9;dio deve permanecer abaixo do limiar antes de soar o alarme
COUNTER = 0 # contador
ALARM_TRIGGERED = False # indica se o alarme est&#xE1; tocando ou n&#xE3;o
SHAPE_PREDICTOR = join(dirname(__file__), &apos;shape_predictor_68_face_landmarks.dat&apos;) # caminho do modelo pr&#xE9;-treinado</code></pre><p>2. Carregar o modelo para a <code>dlib</code> e capturar os &#xED;ndices dos olhos do previsor</p><pre><code class="language-python"># carregar o dlib para detectar rostos
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(SHAPE_PREDICTOR)

# pegar os indices do previsor, para olhos esquero e direito
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS[&apos;left_eye&apos;]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS[&apos;right_eye&apos;]</code></pre><p>3. Capturar o v&#xED;deo como stream (imutils)</p><pre><code class="language-python">print(&quot;[INFO] inicializando streaming de video&quot;)
vs = VideoStream(src=WEBCAM).start()
time.sleep(2.0)</code></pre><p>Como nosso objetivo &#xE9; capturar os frames em stream, os passos a seguir ser&#xE3;o colocados dentro de um loop infinito <code>while True</code></p><p>4. Converter a imagem para escala de cinza (opencv)</p><pre><code class="language-python"># ler o frame do stream
frame = vs.read()
# redimensionar imagem para 800px de largura
frame = imutils.resize(frame, width=800)
# converter para escala de cinza
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)</code></pre><p>5. Detectar faces (dlib)</p><pre><code class="language-python"># detecar faces da imagem em escala de cinza utilizando o detector configurado anteriormente
rects = detector(gray, 0)</code></pre><p>6. Extrair olhos (direito e esquerdo) - para cada face detectada</p><p>7. Calcular o EAR de cada olho e o EAR m&#xE9;dio dos dois olhos - Separamos o calculo do EAR na fun&#xE7;&#xE3;o:</p><pre><code class="language-python">def calculate_eye_aspect_ratio(eye):
    # calcular a dist&#xE2;ncia euclidiana entre os conjuntos das
    # landmarks verticais do olho coordenadas-(x, y)
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])

    # calcular a dist&#xE2;ncia euclidiana entre as
    # landmarks horizontais do olho coordenadas-(x, y)
    C = dist.euclidean(eye[0], eye[3])

    # calcular o EAR
    ear = (A + B) / (2.0 * C)
    return ear</code></pre><p>Utilizamos a distancia euclidiana entre as <em>landmarks</em> verticais e a <em>landmark</em> horizontal e aplicamos a f&#xF3;rmula citada anteriormente.</p><p>8. Disparar alarme e exibir mensagem na tela caso: EAR ficar abaixo do limiar 40 quadros consecutivos (frames).</p><pre><code>    # loop nas faces detectadas
    for rect in rects:
        shape = predictor(gray, rect)
        shape = face_utils.shape_to_np(shape)

        # extrair coordenadas dos olhos e calcular a propor&#xE7;&#xE3;o de abertura
        leftEye = shape[lStart:lEnd]
        rightEye = shape[rStart:rEnd]
        leftEAR = calculate_eye_aspect_ratio(leftEye)
        rightEAR = calculate_eye_aspect_ratio(rightEye)

        # ratio medio para os dois olhos
        ear = (leftEAR + rightEAR) / 2.0

        # convex hull para os olhos
        leftEyeHull = cv2.convexHull(leftEye)
        rightEyeHull = cv2.convexHull(rightEye)
        cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
        cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

        # exibe gr&#xE1;fico
        draw_graph(ear)

        # checar ratio do olho x threshold
        if ear &lt; EYE_THRESHOLD:
            COUNTER += 1

            # verificar crit&#xE9;rio para soar o alarme
            if COUNTER &gt;= FRAMES_SEQ:
                # ligar alarme
                if not ALARM_TRIGGERED:
                    ALARM_TRIGGERED = True
                    t = Thread(target=trigger_alarm)
                    t.daemon = True
                    t.start()

                cv2.putText(frame, &quot;[ALERTA] FADIGA!&quot;, (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

            # se acima do threshold, desliga alarme e reseta contador
        else:
            ALARM_TRIGGERED = False
            COUNTER = 0

        # desenhar a propor&#xE7;&#xE3;o de abertura dos olhos
        cv2.putText(frame, &quot;EAR {:.2f}&quot;.format(ear), (300, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)</code></pre><p>9. Mostrar frame e aguardar tecla de sa&#xED;da</p><pre><code class="language-python"># ainda dentro do loop while True
# mostrar frame
cv2.imshow(&quot;Frame&quot;, frame)
key = cv2.waitKey(1) &amp; 0xFF

# tecla para sair do script &quot;q&quot;
if key == ord(&quot;q&quot;):
	break</code></pre><p>10. Finalizar tudo</p><pre><code class="language-python"># fora do loop while
# clean
cv2.destroyAllWindows()
vs.stop()</code></pre><p>Demonstra&#xE7;&#xE3;o:</p><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Frame-2021-07-05-22-39-09_Trim.gif" class="kg-image" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib" loading="lazy" width="800" height="600"></figure><h2 id="conclus%C3%A3o">Conclus&#xE3;o</h2><p>Os recursos de detec&#xE7;&#xE3;o de <em>landmarks</em> da <code>dlib</code> s&#xE3;o bastante poderosos, e as aplica&#xE7;&#xF5;s s&#xE3;o in&#xFA;meras, com eles podemos, por exemplo:</p><ul><li> aplicar filtros semelhantes aos do Instagram (bigode, &#xF3;culos, modificar a boca, etc.);</li><li>construir um detector de fadiga semelhante aos encontrados em ve&#xED;culos mais modernos (como nosso exemplo);</li><li>ou at&#xE9; mesmo &#xA0;extrair emo&#xE7;&#xF5;es de express&#xF5;es faciais.</li></ul><h2 id="refer%C3%AAncias">Refer&#xEA;ncias</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Facial landmarks with dlib, OpenCV, and Python - PyImageSearch</div><div class="kg-bookmark-description">Learn how to detect and extract facial landmarks from images using dlib, OpenCV, and Python.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">PyImageSearch</span><span class="kg-bookmark-publisher">Adrian Rosebrock</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.pyimagesearch.com/wp-content/uploads/2017/04/facial_landmarks_example_01_result.jpg" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://towardsdatascience.com/drowsiness-detection-system-in-real-time-using-opencv-and-flask-in-python-b57f4f1fcb9e?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Drowsiness Detection System in Real-Time using OpenCV and Flask in Python</div><div class="kg-bookmark-description">This article provides an overview of a system that detects whether a person is drowsy while driving and if so, alerts him by using voice&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://miro.medium.com/fit/c/152/152/1*sHhtYhaCe2Uc3IU0IgKwIQ.png" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib"><span class="kg-bookmark-author">Towards Data Science</span><span class="kg-bookmark-publisher">Souvik Ghosh</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/max/1200/1*9G6RJ3UDmP7gaZBX8Vl11A.jpeg" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib"></div></a></figure><p><a href="http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf?ref=blog.viniboscoa.dev">http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf</a></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.pyimagesearch.com/2017/05/08/drowsiness-detection-opencv/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Drowsiness detection with OpenCV - PyImageSearch</div><div class="kg-bookmark-description">In this tutorial, I&#x2019;ll demonstrate how to build a driver drowsiness detector using OpenCV, Python, and computer vision techniques.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">PyImageSearch</span><span class="kg-bookmark-publisher">Adrian Rosebrock</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.pyimagesearch.com/wp-content/uploads/2017/05/drowsiness_detection_alert.jpg" alt="Detec&#xE7;&#xE3;o de Fadiga em tempo real com Python, OpenCV e dlib"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Manipulação de imagens em tempo real.]]></title><description><![CDATA[Nesse artigo, simularemos o funcionamento dos filtros do Instagram demonstrando como é possível aplicar filtros em tempo real utilizando  apenas Python, OpenCV e uma webcam. Para não estendermos demais esse post, não detalharemos o algoritmo por trás de cada filtro.]]></description><link>https://blog.viniboscoa.dev/manipulacao-de-imagens-em-tempo-real/</link><guid isPermaLink="false">6438c7e29cade90001313649</guid><category><![CDATA[Deep Learning]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[OpenCV]]></category><category><![CDATA[Python]]></category><category><![CDATA[Image preprocessing]]></category><category><![CDATA[Visão Computacional]]></category><dc:creator><![CDATA[Vinícius Bôscoa]]></dc:creator><pubDate>Wed, 30 Jun 2021 02:29:22 GMT</pubDate><media:content url="https://blog.viniboscoa.dev/content/images/2022/05/thumb.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.viniboscoa.dev/content/images/2022/05/thumb.png" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."><p>Nesse artigo, simularemos o funcionamento dos filtros do Instagram demonstrando como &#xE9; poss&#xED;vel aplicar filtros em tempo real utilizando &#xA0;apenas Python, OpenCV e uma webcam. Para n&#xE3;o estendermos demais esse post, n&#xE3;o detalharemos o algoritmo por tr&#xE1;s de cada filtro. Utilizaremos a classe <code>filters.py</code> que discutimos neste <a href="https://www.viniboscoa.dev/blog/manipulacao-de-imagens-com-python-e-opencv-parte-1?ref=blog.viniboscoa.dev">artigo</a>.</p><p>Nosso algoritmo ser&#xE1; composto de 3 etapas principais:</p><ol><li>Inicializa&#xE7;&#xE3;o: nessa etapa configuramos as depend&#xEA;ncias e inicializamos o streaming</li><li>Execu&#xE7;&#xE3;o: etapa principal do nosso projeto, que ser&#xE1; respons&#xE1;vel por aplicar o filtro selecionado nos frames do streaming iniciado na etapa 1.</li><li>Finaliza&#xE7;&#xE3;o: nessa etapa encerramos o streaming e liberamos os processos que foram inicializados na etapa 1.</li></ol><p>Ap&#xF3;s conhecer o fluxo da nossa aplica&#xE7;&#xE3;o.... <strong>hora do c&#xF3;digo.</strong></p><h2 id="hora-do-c%C3%B3digo">Hora do C&#xF3;digo</h2><p>Como de costume, come&#xE7;amos importando os pacotes que iremos utilizar, e definimos algumas constantes importantes (para evitar n&#xFA;meros m&#xE1;gicos no meio do c&#xF3;digo):</p><pre><code class="language-python"># importar pacotes
import os

from imutils.video import VideoStream
import imutils
import numpy as np
import cv2
import time
from os.path import dirname, join
import os

# importamos nossos filtros
from filters import grayscale, original, sketch, sepia, blur, canny

# constantes
WEBCAM = os.environ.get(&apos;WEBCAM&apos;, 1)</code></pre><p>Lembrando que tamb&#xE9;m precisamos instalar as depend&#xEA;ncias:</p><pre><code class="language-bash">pip install imutils numpy opencv-python</code></pre><p>Note que nossa constante <code>WEBCAM</code> &#xE9; um dado do tipo inteiro e foi obtida a partir das vari&#xE1;veis de ambiente do sistema operacional. Veremos mais adiante que essa constante ser&#xE1; respons&#xE1;vel por indicar &#xE0; nossa aplica&#xE7;&#xE3;o qual webcam deve ser utilizada.</p><h3 id="quero-te-verinicializando-o-streaming">&quot;Quero te ver&quot; - Inicializando o streaming</h3><p>Nossa proposta &#xE9; manipular imagens em tempo real, por isso n&#xE3;o podemos simplesmente enviar uma imagem como fizemos em artigos anteriores, precisamos iniciar um streaming de v&#xED;deo .</p><p>Um Streaming de V&#xED;deo nada mais &#xE9; do que a transmiss&#xE3;o de um v&#xED;deo (ou uma sequ&#xEA;ncia de imagens - frames) em tempo real, nesse caso, de uma webcam para o computador que est&#xE1; executando nossa aplica&#xE7;&#xE3;o.</p><p>Para nos ajudar nessa tarefa utilizaremos a classe <code>VideoStream</code> da biblioteca <code>imutils</code> que torna esse processo t&#xE3;o simples quanto executar um comando:</p><pre><code class="language-python">def main():
    print(&apos;[INFO] starting video stream&apos;)
    vs = VideoStream(src=WEBCAM).start()
    time.sleep(2.0)

    filters = {
        &apos;0&apos;: original,
        &apos;1&apos;: grayscale,
        &apos;2&apos;: sketch,
        &apos;3&apos;: sepia,
        &apos;4&apos;: blur,
        &apos;5&apos;: canny,
        &apos;6&apos;: None,
        &apos;7&apos;: None
    }

    print(&quot;&quot;&quot;Press any of the following keys to:
        0: Original Image
        1: Grayscale
        2: Sketch
        3: Sepia
        4: Blur
        5: Canny
        6: Face detection
        7: Blur face
        q: Quit&quot;&quot;&quot;)

    # initial_filter
    selected_filter = &apos;0&apos;</code></pre><p>No c&#xF3;digo acima estamos definindo uma fun&#xE7;&#xE3;o <code>main</code> que abrigar&#xE1; todo nosso c&#xF3;digo.</p><p>Em seguida instanciamos e inicializamos o streaming de v&#xED;deo utilizando como fonte da transmiss&#xE3;o a <code>webcam</code> , que foi definida anteriormente, para evitar poss&#xED;veis erros, indicamos ao nosso algoritmo para aguardar 2 segundos antes de prosseguirmos a execu&#xE7;&#xE3;o <code>time.sleep(2.0)</code>.</p><p>Como &#xE9; desej&#xE1;vel que o usu&#xE1;rio seja capaz de alterar o filtro em tempo de execu&#xE7;&#xE3;o, criamos um dicion&#xE1;rio <code>filters</code> que traduzir&#xE1; o filtro selecionado para a fun&#xE7;&#xE3;o correspondente. </p><p>Para finalizar esse etapa mostramos um texto com os filtros dispon&#xED;veis e inicializamos o filtro inicial para &quot;0&quot; - imagem original.</p><h3 id="vamos-mudaraplicando-filtros-no-streaming">&quot;Vamos mudar&quot; - Aplicando filtros no streaming</h3><p>Com o streaming iniciado podemos agora ler os frames e aplicar as transforma&#xE7;&#xF5;es que o usu&#xE1;rio selecionar. Para termos o efeito de v&#xED;deo, iremos colocar todo o processamento em um <em>loop:</em></p><pre><code class="language-python"> while True:
        # ler frames
        frame = vs.read()
        frame = imutils.resize(frame, width=400)

        # pegar filtro selecionado
        filter = filters.get(selected_filter)
        if filter is not None:
            # aplicar filtro no frame
            frame = filter(frame)

        # exibir frame na tela
        cv2.imshow(&quot;Frame&quot;, frame)
        key = cv2.waitKey(1) &amp; 0xFF
        if key == ord(&apos;q&apos;):
            break

        if key in [ord(k) for k in filters.keys()]:
            selected_filter = chr(key)</code></pre><p>Dentro do <em>loop </em>fazemos a leitura do frame capturado <code>vs.read()</code> redimensionamos a imagem <code>mutils.resize(frame, width=400)</code>, em seguida pegamos o filtro a partir do dicion&#xE1;rio que configuramos na etapa anterior <code>filter = filter.get(selected_filter)</code> e o aplicamos &#xE0; imagem <code>frame = filter(frame)</code> , nesse momento nossa vari&#xE1;vel <code>frame</code> est&#xE1; com o filtro aplicado.</p><p>Por fim utilizamos o OpenCV para exibir a imagem <code>cv2.imshow(&quot;Frame&quot;, frame)</code>.</p><p>Como todo nosso c&#xF3;digo est&#xE1; sendo executado em um <em>loop</em> infinito &#xE9; importante configurarmos uma condi&#xE7;&#xE3;o de parada, nesse caso utilizamos o <code>waitKey</code> do OpenCV. </p><p>O <code>waitKey</code> ficar&#xE1; &quot;escutando&quot; e, quando alguma tecla for pressionada, ela ser&#xE1; armazenada na vari&#xE1;vel <code>key</code>. Em nosso exemplo realizamos 2 testes:</p><ul><li>O primeiro verifica se a tecla &#xE9; a letra &quot;q&quot;, em caso positivo, interrompe a execu&#xE7;&#xE3;o do loop.</li><li>O segundo verifica se a tecla existe no dicion&#xE1;rio de filtros, em caso positivo, aplica o filtro selecionado.</li></ul><h3 id="ao-sair-apague-a-luzfinalizando-a-aplica%C3%A7%C3%A3o">&quot;Ao sair, apague a luz&quot; - Finalizando a aplica&#xE7;&#xE3;o</h3><p>Quando interrompemos o loop, precisamos destruir as janelas abertas pelo <code>imshow</code> do OpenCV e encerrar o stream:</p><pre><code class="language-python"> # destruir as janelas e interromper o stream
 cv2.destroyAllWindows()
 vs.stop()</code></pre><p>Da maneira que constru&#xED;mos, para aplicarmos qualquer outro filtro, basta que ele seja programado - no nosso caso os filtros localizados em <code>filters.py</code> - e inclu&#xED;do no dicion&#xE1;rio <code>filters</code>.</p><h3 id="b%C3%B4nusdeep-learning-para-detec%C3%A7%C3%A3o-de-rostos">B&#xF4;nus - Deep Learning para Detec&#xE7;&#xE3;o de Rostos</h3><p>Para irmos um pouco al&#xE9;m, como etapa b&#xF4;nus, utilizaremos um modelo de Deep Learning pr&#xE9;-treinado e discutido neste <a href="https://www.pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/?ref=blog.viniboscoa.dev">artigo</a>.</p><p>Iremos implementar a funcionalidade de detec&#xE7;&#xE3;o e censura de rostos da mesma maneira dos filtros, ou seja, o usu&#xE1;rio poder&#xE1; selecion&#xE1;-los atrav&#xE9;s das teclas.</p><p>Como utilizaremos Deep Learning e um modelo j&#xE1; treinado, precisamos, primeiro carregar os arquivos, informamos o n&#xED;vel de confian&#xE7;a desejado (nesse caso, 70% ou 0.7) e carregamos o modelo:</p><pre><code>PROTOTXT = join(dirname(__file__), &quot;deploy.prototxt.txt&quot;)
MODEL = join(dirname(__file__), &quot;res10_300x300_ssd_iter_140000.caffemodel&quot;)
CONFIDENCE_THRESHOLD = 0.7

# carregar o modelo
net = cv2.dnn.readNetFromCaffe(PROTOTXT, MODEL)</code></pre><p>Nota: utilizamos as fun&#xE7;&#xF5;es <code>join</code> e <code>dirname(__file__)</code> , ao inv&#xE9;s de simplesmente informar o nome do arquivo, para melhor compatibilidade entre sistemas operacionais.</p><p>Com nosso modelo carregado, iremos incluir as fun&#xE7;&#xF5;es de detec&#xE7;&#xE3;o e censura de rostos no dicion&#xE1;rio (op&#xE7;&#xF5;es 6 e 7) e dentro do nosso <em>loop:</em></p><pre><code>    h, w = frame.shape[:2]
    
    blob = cv2.dnn.blobFromImage(frame, 1, (300, 300), (104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
     
    # iterar ao longo das deteccoes
    for i in range(0, detections.shape[2]):
        # exemplo de intervalo de confianca
        confidence = detections[0, 0, i, 2]
        
        # selecionar apenas intervalos acima do threshold
        if confidence &gt; CONFIDENCE_THRESHOLD:
            # label da confian&#xE7;a
            text = &quot;{:.2f}%&quot;.format(confidence * 100)
            
            # calcular o bounding box
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype(&apos;int&apos;)
            
            if selected_filter == &apos;6&apos;:
                frame = cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 1)
                
            if selected_filter == &apos;7&apos;:
                face = frame[startY:endY, startX:endX]
                blured = cv2.GaussianBlur(face, (33, 33), 0)
                frame[startY:endY, startX:endX] = blured</code></pre><p>Primeiramente extra&#xED;mos o tamanho (altura e largura) do frame que estamos analisando.</p><p>Como estamos trabalhando com deep learning, precisamos realizar alguns pr&#xE9;-processamentos na imagen antes de pass&#xE1;-la atrav&#xE9;s da rede. Para nos auxiliar nesse processo, o OpenCV possui a fun&#xE7;&#xE3;o <code>cv2.dnn.blobFromImage</code>, que executa os seguintes passos:</p><ol><li>Subtra&#xE7;&#xE3;o da m&#xE9;dia</li><li>Escala</li><li>Opcionalmente troca de canais (alterar R[ed] com B[lue])</li></ol><p>Mais detalhes sobre esse recurso podem ser encontrados <a href="https://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/?ref=blog.viniboscoa.dev">aqui</a>.</p><p>Para a subtra&#xE7;&#xE3;o da m&#xE9;dia utilizamos os valores descritos no <em><a href="https://github.com/opencv/opencv/blob/4560909a5e5cb284cdfd5619cdf4cf3622410388/modules/dnn/misc/face_detector_accuracy.py?ref=blog.viniboscoa.dev#L148">benchmark </a></em>do pr&#xF3;prio OpenCV.</p><p>Definimos, ent&#xE3;o, o <code>blob</code> gerado como input da rede neural <code>net.setInput(blob)</code> e a executamos <code>net.forward()</code> esse processo resultar&#xE1; nas detec&#xE7;&#xF5;es realizadas. Como a rede ir&#xE1; retornar mais de uma detec&#xE7;&#xE3;o e seus respectivos graus de confian&#xE7;a, iremos iterar sobre elas procurando pelo primeiro resultado que esteja acima do limiar de 70% que definimos anteriormente.</p><p>Quando encontrado, iremos extrair os pontos que delimitam o rosto detectado formando uma &quot;caixa&quot; (<em>bounding box</em>):</p><pre><code class="language-python">box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype(&apos;int&apos;)</code></pre><p>Com os pontos da <em>bounding box - </em>que delimitam a &#xE1;rea de rosto detectado -, podemos aplicar os filtros desejados.</p><p>Em nosso exemplo nosso filtro 6 (detec&#xE7;&#xE3;o de rosto) ir&#xE1; exibir um ret&#xE2;ngulo verde ao redor do rosto <code>frame = cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 1)</code>. Enquanto que nosso filtro 7 (censura) ir&#xE1; aplicar o <code>GaussianBlur</code> para emba&#xE7;ar e censurar o rosto.</p><pre><code>face = frame[startY:endY, startX:endX]
blured = cv2.GaussianBlur(face, (33, 33), 0)
frame[startY:endY, startX:endX] = blured</code></pre><h4 id="demo">Demo</h4><figure class="kg-card kg-image-card"><img src="https://blog.viniboscoa.dev/content/images/2022/05/Frame-2021-06-26-22-44-13_Trim.gif" class="kg-image" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real." loading="lazy" width="640" height="480"></figure><p></p><p>A vers&#xE3;o final do projeto pode ser conferida no link abaixo</p><h2 id="projeto">Projeto</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/virb30/realtime_face_filters?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">virb30/realtime_face_filters</div><div class="kg-bookmark-description">Contribute to virb30/realtime_face_filters development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">virb30</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/c6d3859f1a5aa5975524bef054e3d66e226a8de0314be1bd63dd58e3c6d70b01/virb30/realtime_face_filters" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."></div></a></figure><h2 id="refer%C3%AAncias">Refer&#xEA;ncias</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Face detection with OpenCV and deep learning - PyImageSearch</div><div class="kg-bookmark-description">Learn how to perform face detection in images and face detection in video streams using OpenCV, Python, and deep learning.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">PyImageSearch</span><span class="kg-bookmark-publisher">Adrian Rosebrock</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.pyimagesearch.com/wp-content/uploads/2018/02/deep_learning_face_detection_featured.jpg" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/opencv/opencv/blob/4560909a5e5cb284cdfd5619cdf4cf3622410388/modules/dnn/misc/face_detector_accuracy.py?ref=blog.viniboscoa.dev#L148"><div class="kg-bookmark-content"><div class="kg-bookmark-title">opencv/opencv</div><div class="kg-bookmark-description">Open Source Computer Vision Library. Contribute to opencv/opencv development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">opencv</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/fd97decdc69cef3074f98d0a5e8524ed4c7ef19d771171d35b627cb246492b31/opencv/opencv" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/?ref=blog.viniboscoa.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Deep learning: How OpenCV&#x2019;s blobFromImage works - PyImageSearch</div><div class="kg-bookmark-description">Today&#x2019;s blog post is inspired by a number of PyImageSearch readers who have commented on previous deep learning tutorials wanting to understand what exactly OpenCV&#x2019;s blobFromImage function is doing under the hood. You see, to obtain (correct) predictions from deep&#x2026;</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">PyImageSearch</span><span class="kg-bookmark-publisher">Adrian Rosebrock</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.pyimagesearch.com/wp-content/uploads/2017/11/blob_from_images_header.png" alt="Manipula&#xE7;&#xE3;o de imagens em tempo real."></div></a></figure>]]></content:encoded></item></channel></rss>