├── index.php ├── data └── adoption.json ├── README.md └── scripts └── simulation.min.js /index.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/adoption.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": { 3 | 4 | "total": 100, 5 | 6 | "race": { 7 | "black": 0.1931174089, 8 | "indigenous": 0.004048582996, 9 | "mixed": 0.5036437247, 10 | "white": 0.2971659919, 11 | "yellow": 0.002024291498 12 | }, 13 | 14 | "siblings": { 15 | "true": 0.6024291498, 16 | "false": 0.3975708502 17 | }, 18 | 19 | "mental": { 20 | "true": 0.1402834008, 21 | "false": 0.8597165992 22 | }, 23 | 24 | "physical": { 25 | "true": 0.05546558704, 26 | "false": 0.94453441296 27 | }, 28 | 29 | "gender": { 30 | "female": 0.4926286442, 31 | "male": 0.5073713558 32 | }, 33 | 34 | "age": { 35 | "1": 0.05507767684, 36 | "2": 0.05532102008, 37 | "3": 0.05431087222, 38 | "4": 0.05471582423, 39 | "5": 0.05584397923, 40 | "6": 0.0547113599, 41 | "7": 0.05397547077, 42 | "8": 0.0541175481, 43 | "9": 0.05392509824, 44 | "10": 0.05377422246, 45 | "11": 0.05457529082, 46 | "12": 0.05494517923, 47 | "13": 0.05537593135, 48 | "14": 0.05680384755, 49 | "15": 0.05750041337, 50 | "16": 0.05768458562, 51 | "17": 0.05821245553, 52 | "18": 0.05912922446 53 | } 54 | 55 | }, 56 | 57 | "applicants": { 58 | 59 | "accepts": { 60 | 61 | "race": { 62 | 63 | "all": 0.5047478024, 64 | "some": 0.295632962, 65 | "only": 0.1996192357, 66 | 67 | "details": { 68 | 69 | "some": { 70 | "black": { 71 | "true": 0.5603582005, 72 | "false": 0.4396417995 73 | }, 74 | "indigenous": { 75 | "true": 0.5460677855, 76 | "false": 0.4539322145 77 | }, 78 | "mixed": { 79 | "true": 0.8284445071, 80 | "false": 0.1715554929 81 | }, 82 | "white": { 83 | "true": 0.924857801, 84 | "false": 0.075142199 85 | }, 86 | "yellow": { 87 | "true": 0.5840972124, 88 | "false": 0.4159027876 89 | } 90 | }, 91 | 92 | "only": { 93 | "black": 0.04015071235, 94 | "indigenous": 0.002472624514, 95 | "mixed": 0.2036971624, 96 | "white": 0.7482632756, 97 | "yellow": 0.005416225127 98 | } 99 | 100 | } 101 | 102 | }, 103 | 104 | "siblings": { 105 | "true": 0.3688713393, 106 | "false": 0.6311286607, 107 | "details": { 108 | "twins": 0.348798947 109 | } 110 | }, 111 | 112 | "mental": { 113 | "true": 0.03309359282, 114 | "false": 0.96690640718 115 | }, 116 | 117 | "physical": { 118 | "true": 0.06193296667, 119 | "false": 0.93806703333 120 | }, 121 | 122 | "gender": { 123 | "female": 0.2730691487, 124 | "male": 0.08195835096, 125 | "all": 0.6449725004 126 | }, 127 | 128 | "age": { 129 | "1": 0.1149109199, 130 | "2": 0.1492267193, 131 | "3": 0.1837305505, 132 | "4": 0.1535279462, 133 | "5": 0.1560898792, 134 | "6": 0.1054153152, 135 | "7": 0.05826634701, 136 | "8": 0.03229445776, 137 | "9": 0.01377332769, 138 | "10": 0.01386734358, 139 | "11": 0.005405913599, 140 | "12": 0.00411319513, 141 | "13": 0.002397405161, 142 | "14": 0.001316222442, 143 | "15": 0.0009871668312, 144 | "16": 0.0007051191651, 145 | "17": 0.0007051191651, 146 | "18": 0.003267052132 147 | } 148 | 149 | } 150 | 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simulação mostra quais crianças são adotadas (e quais não são) no Brasil 2 | 3 | Esse repositório contém o código que gerou as análises [desta matéria](https://arte.estadao.com.br/brasil/adocao/criancas/). 4 | 5 | Menino de 14 anos, pardo e com irmãos. Menina de 2 anos, branca e filha única. Este material ajuda a entender o contraste entre as crianças disponíveis para adoção e as características mais buscadas por possíveis pais. 6 | 7 | 8 | ## Como foi feito? 9 | 10 | Os dados apresentados nesta página possuem três origens diferentes. 11 | 12 | Duas delas são o Instituto Brasileiro de Geografia e Estatística (IBGE), que traz números sobre as características da população de crianças e adolescentes no Brasil, e o Cadastro Nacional de Adoção (CNA), que detalha quais são as características que os pretendentes buscam ao adotar. 13 | 14 | A outra é resultado das simulações feitas pelo Estadão. Com base em números reais provenientes das duas fontes acima, criamos um algoritmo que gera crianças e pretendentes – e depois checa se houve “match” entre eles. O “match” acontece quando há uma criança com todas as características que os pais buscam. 15 | 16 | 17 | ### Quer um exemplo? 18 | 19 | Segundo o sistema do [CNA](http://www.cnj.jus.br/cnanovo/pages/publico/index.jsf) – em consulta feita no dia 10 de agosto de 2019 – havia 42.546 pretendentes no Brasil. Entre eles, 15.694 aceitavam adotar uma criança que tivesse irmãos. Isto indica que aproximadamente 37% dos pretendentes aceitam adotar crianças com irmãos. 20 | 21 | Quando o simulador gera um novo pretendente, ele faz uma espécie de cara ou coroa para decidir se este pretendente aceita crianças com irmãos. Porém, diferente de uma moeda normal, em que há 50% de chance de acontecer uma coisa ou outra, esta moeda tem 37% de chance de cair em uma das faces. Se ela cair deste lado, este pretendente aceita adotar irmãos. 22 | 23 | Agora imagine que fizemos 100 arremessos desta moeda. O número de vezes em que ela caiu com a face do “aceita adotar crianças com irmãos” tende a ser 37. Isso significa que ele deve ficar próximo a 37, mas pode ser 40 ou 35, por exemplo. 24 | 25 | De forma similar a este arremesso de moeda, o simulador define todas as outras características que este pretendente vai aceitar: idade máxima, sexo, cor/raça, presença de irmãos e existência de deficiências ou doenças. 26 | 27 | 28 | ### E as crianças disponíveis para adoção? 29 | 30 | Assim como no caso dos pretendentes, cada criança da simulação é criada com base em probabilidades reais. Para isso, utilizamos dados do CNA. 31 | 32 | A única exceção é a idade das crianças e adolescentes, que é gerada com [números do IBGE](https://www.ibge.gov.br/estatisticas/sociais/populacao/9109-projecao-da-populacao.html?=&t=resultados) (uma previsão para 2018 com base no último Censo). Desta forma, evitamos um viés estatístico que exageraria o resultado da simulação. 33 | 34 | É importante ressaltar que o “match” entre possíveis pais e crianças para adoção é um modelo simplificado da realidade. Sendo assim, ele desconsidera limites geográficos (como pretendentes que moram em outro estado) e desistências no processo de adoção. Além disso, considera apenas a existência de deficiências físicas e cognitivas (desconsidera HIV, por ter baixa prevalência entre as crianças disponíveis e também a categoria “outro tipo de doença detectada”, por ser muito abrangente). 35 | 36 | 37 | ### Como foi calculado o tempo da simulação? 38 | 39 | Por dia, 4 possíveis pais precisam ter suas preferências de adoção checadas em relação às crianças para adoção neste abrigo fictício. Assim, o percentual de crianças adotadas (a quantidade que deu “match”) seria condizente com a realidade brasileira. 40 | 41 | Vamos aos números. No dia da coleta de dados, existiam 4.940 crianças disponíveis para adoção no País. Tomando por base os anos de 2016 a 2018, a média de crianças adotadas por ano é de 2.014 crianças. Ou seja, são adotadas 6 crianças por dia. 42 | 43 | Executando a simulação até atingir um número de 100.000 crianças adotadas, descobrimos que, em geral, uma criança é adotada a cada 31 possíveis pais. Portanto, no Brasil devem existir 62.230 pretendentes ao longo de um ano. 44 | 45 | Como criamos uma simulação que reduz estes números para um abrigo com capacidade para 100 crianças, precisamos fazer uma regra de três: 4.940 crianças no Brasil equivalem a 100%, assim como 100 crianças na simulação equivalem a 2%. 46 | 47 | Se aplicarmos esta proporção ao número de pretendentes (62.230), teremos 1.260. Esta é a quantidade de possíveis pais que buscam crianças para adoção neste abrigo fictício, em um ano. Ao dividir pela quantidade de dias em um ano, chegamos aos 4 pretendentes por dia. 48 | 49 | 50 | ## Como usar? 51 | 52 | A simulação foi feita em JavaScript. Para rodá-la, acesse o arquivo `index.html` no navegador*. Estão previstas 4 opções: 53 | 54 | ``` 55 | options = { 56 | adopted: 100, 57 | applicantsPerDay: 4, 58 | equalAgeDistribution: false, 59 | randomBirthdays: true, 60 | } 61 | ``` 62 | 63 | Abaixo, uma breve descrição destas opções: 64 | 65 | | Propriedade | Descrição | 66 | | :------------ | :------------ | 67 | | `adopted` | No exemplo acima, a simulação seria rodada milhares de vezes, até atingir 100 crianças adotadas. | 68 | | `applicantsPerDay` | Esta opção define quão rápido se passa o tempo. Quanto mais pretendentes “visitarem”o abrigo por dia, mais rápido as crianças serão adotadas | 69 | | `equalAgeDistribution` | Buscando isolar variáveis para análise, pode ser interessante gerar crianças que tenham a mesma chance de ter cada idade (e não usar a distribuição real de idade). Se este for o caso, basta ativar esta opção | 70 | | `randomBirthdays` | Se esta opção estiver desabilitada, toda criança terá nascido no dia 1º de janeiro | 71 | 72 | O resultado da simulação aparece como um JSON impresso na página. 73 | 74 | 75 | 76 | **Seria ideal ter uma versão em Node ou em Python. Entretanto, a simulação nasceu como um gráfico interativo e, só depois, o processamento foi isolado da renderização visual.* 77 | -------------------------------------------------------------------------------- /scripts/simulation.min.js: -------------------------------------------------------------------------------- 1 | let options = { 2 | 3 | // run simulation until N children are adopted 4 | adopted: 100, 5 | 6 | // how fast should time pass 7 | applicantsPerDay: 4, 8 | 9 | // same odds of children being created with any age 10 | equalAgeDistribution: false, 11 | 12 | // if disabled, every kid is born on the 1st of January 13 | randomBirthdays: true, 14 | 15 | }; 16 | 17 | (function() { 18 | 19 | let app = { 20 | 21 | dependencies : [ 22 | { 23 | type: 'json', 24 | name: 'adoption', 25 | url : 'data/adoption.json' 26 | } 27 | ], 28 | 29 | responses : 0, 30 | 31 | data : {}, 32 | 33 | get : { 34 | 35 | json : function( url, name ) { 36 | 37 | fetch( url ) 38 | .then( ( response ) => response.json() ) 39 | .then( function( data ) { 40 | 41 | app.data[ name ] = data 42 | 43 | app.responses++ 44 | 45 | if ( app.dependencies.length == app.responses ) 46 | app.initialize() 47 | 48 | } ) 49 | 50 | }, 51 | 52 | }, 53 | 54 | chance : function( object ) { 55 | 56 | let chances = [] 57 | 58 | for ( let key in object ) { 59 | 60 | if ( key !== 'details' ) { 61 | 62 | chance = { 63 | "property" : key, 64 | "percentage" : object[ key ] 65 | } 66 | 67 | chances.push( chance ) 68 | 69 | } 70 | 71 | } 72 | 73 | chances.sort( function( a, b ) { 74 | return parseFloat( b.percentage ) - parseFloat( a.percentage ) 75 | } ) 76 | 77 | let random = Math.random() 78 | let increment = 0 79 | 80 | for ( let chance of chances ) { 81 | 82 | if ( random <= ( increment + chance.percentage ) ) 83 | return chance.property 84 | 85 | else 86 | increment += chance.percentage 87 | 88 | } 89 | 90 | return chances[0].property 91 | 92 | }, 93 | 94 | simulation : { 95 | 96 | children : [], 97 | 98 | applicants : 0, 99 | 100 | applicantsPerDay : options.applicantsPerDay, 101 | 102 | counter : { 103 | 104 | adopted: [], 105 | admitted: [], 106 | families: [], 107 | applicantsList : [], 108 | expelled: [], 109 | applicants: 0, 110 | previousDays: 0, 111 | days: 0 112 | }, 113 | 114 | child : function( amount ) { 115 | 116 | let children = app.simulation.children 117 | let data = app.data.adoption.children 118 | let total = data.total 119 | 120 | if ( typeof amount === 'number' ) 121 | total = amount 122 | 123 | for ( let i = 0; i < total; i++ ) { 124 | 125 | let child = { 126 | 127 | "race" : app.chance( data.race ), 128 | "siblings" : app.chance( data.siblings ), 129 | "gender" : app.chance( data.gender ), 130 | "age" : app.chance( data.age ), 131 | "days" : Math.floor( Math.random() * 365 ) + 1, 132 | 133 | "mental" : app.chance( data.mental ), 134 | "physical" : app.chance( data.physical ), 135 | 136 | } 137 | 138 | if ( options.randomBirthdays ) 139 | child.days = 1 140 | 141 | child.status = 'admitted' 142 | child.admission = child.age 143 | 144 | 145 | if ( child.mental === 'true' || child.physical === 'true' ) 146 | child.disability = 'true' 147 | else 148 | child.disability = 'false' 149 | 150 | if ( typeof amount === 'number' ) 151 | child.status = 'admitted' 152 | 153 | app.simulation.children.push( child ) 154 | app.simulation.counter.admitted.push( child ) 155 | 156 | } 157 | 158 | }, 159 | 160 | applicant : function() { 161 | 162 | let data = app.data.adoption.applicants.accepts 163 | 164 | let accepted = [] 165 | 166 | let raceGroup = app.chance( data.race ) 167 | 168 | if ( raceGroup === 'all' ) { 169 | 170 | for ( let possibility in data.race.details.some ) { 171 | accepted.push( possibility ) 172 | } 173 | 174 | } else if ( raceGroup === 'some' ) { 175 | 176 | for ( let possibility in data.race.details.some ) { 177 | 178 | if ( app.chance( data.race.details.some[ possibility ] ) === 'true' ) 179 | accepted.push( possibility ) 180 | 181 | } 182 | 183 | } else { 184 | 185 | accepted.push( app.chance( data.race.details.only ) ) 186 | 187 | } 188 | 189 | let applicant = { 190 | 191 | "race" : accepted, 192 | "siblings" : app.chance( data.siblings ), 193 | "gender" : app.chance( data.gender ), 194 | "age" : app.chance( data.age ), 195 | 196 | "mental" : app.chance( data.mental ), 197 | "physical" : app.chance( data.physical ) 198 | 199 | } 200 | 201 | if ( applicant.mental === 'true' || applicant.physical === 'true' ) 202 | applicant.disability = 'true' 203 | 204 | else 205 | applicant.disability = 'false' 206 | 207 | app.simulation.counter.applicantsList.push( applicant ) 208 | 209 | return applicant 210 | 211 | }, 212 | 213 | match : function( applicant, child ) { 214 | 215 | if ( parseInt( applicant.age ) > parseInt( child.age ) ) { 216 | 217 | if ( applicant.siblings === 'true' || ( applicant.siblings === 'false' && child.siblings === 'false' ) ) { 218 | 219 | if ( 220 | 221 | ( applicant.mental === 'true' || ( applicant.mental === 'false' && child.mental === 'false' ) ) && 222 | ( applicant.physical === 'true' || ( applicant.physical === 'false' && child.physical === 'false' ) ) 223 | 224 | ) { 225 | 226 | if ( applicant.gender === 'all' || applicant.gender === child.gender ) { 227 | 228 | if ( applicant.race.includes( child.race ) ) { 229 | 230 | return true 231 | 232 | } 233 | 234 | } 235 | 236 | } 237 | 238 | } 239 | 240 | } 241 | 242 | }, 243 | 244 | simulate : function() { 245 | 246 | let vacancies = 0 247 | 248 | // remove adopted and expelled 249 | 250 | for ( let i = app.simulation.children.length - 1; i >= 0; --i ) { 251 | 252 | let child = app.simulation.children[ i ] 253 | 254 | if ( child.status == 'adopted' || child.status == 'expelled' ) { 255 | 256 | app.simulation.children.splice( i, 1 ) 257 | vacancies++ 258 | 259 | } 260 | 261 | } 262 | 263 | 264 | // generate new kids 265 | 266 | app.simulation.child( vacancies ) 267 | 268 | 269 | // create new applicant 270 | 271 | let applicant = app.simulation.applicant() 272 | 273 | 274 | // check if children were adopted 275 | 276 | let children = app.simulation.children 277 | 278 | for ( let i = 0; i < children.length; i++ ) { 279 | 280 | let child = children[ i ] 281 | 282 | if ( app.simulation.match( applicant, child ) ) { 283 | 284 | app.simulation.adopt( child, applicant ) 285 | break 286 | 287 | } 288 | 289 | } 290 | 291 | // check if children were expelled 292 | 293 | for ( let i = 0; i < children.length; i++ ) { 294 | 295 | let child = children[ i ] 296 | 297 | if ( parseInt( child.age ) > 18 ) { 298 | 299 | app.simulation.expel( child ) 300 | break 301 | 302 | } 303 | 304 | } 305 | 306 | 307 | // update days 308 | 309 | app.simulation.update.hours() 310 | 311 | 312 | }, 313 | 314 | calculate : function( amount ) { 315 | 316 | // calculating 317 | 318 | while ( app.simulation.counter.adopted.length < amount ) { 319 | 320 | app.simulation.simulate() 321 | app.simulation.counter.applicants++ 322 | 323 | } 324 | 325 | let pre = document.createElement( 'pre' ) 326 | 327 | // TEMP: 328 | // delete app.simulation.counter.adopted 329 | // delete app.simulation.counter.expelled 330 | 331 | let output = app.simulation.counter 332 | output = JSON.stringify( output, null, 2 ) 333 | 334 | pre.innerHTML = output 335 | document.body.appendChild( pre ) 336 | 337 | }, 338 | 339 | update : { 340 | 341 | hours : function() { 342 | 343 | let applicants = app.simulation.counter.applicants 344 | 345 | let days = Math.floor( applicants / app.simulation.applicantsPerDay ) 346 | 347 | app.simulation.counter.days = days 348 | 349 | if ( app.simulation.counter.previousDays !== days ) { 350 | 351 | app.simulation.update.age() 352 | 353 | app.simulation.counter.previousDays = days 354 | 355 | } 356 | 357 | }, 358 | 359 | age : function() { 360 | 361 | let children = app.simulation.children 362 | 363 | for ( let child of children ) { 364 | 365 | child.days++ 366 | 367 | if ( child.days > 365 ) { 368 | 369 | child.age++ 370 | child.days = 1 371 | 372 | } 373 | 374 | } 375 | 376 | } 377 | 378 | }, 379 | 380 | clear : function( previously ) { 381 | 382 | previously.adopted.forEach( e => e.remove() ) 383 | previously.expelled.forEach( e => e.remove() ) 384 | 385 | }, 386 | 387 | adopt : function( child, applicant ) { 388 | 389 | app.simulation.counter.adopted.push( child ) 390 | child.status = 'adopted' 391 | 392 | let family = { 393 | applicant: applicant, 394 | child: child 395 | } 396 | 397 | app.simulation.counter.families.push( family ) 398 | 399 | }, 400 | 401 | expel : function( child ) { 402 | 403 | app.simulation.counter.expelled.push( child ) 404 | child.status = 'expelled' 405 | 406 | } 407 | 408 | }, 409 | 410 | load : function() { 411 | 412 | for ( let dependency of this.dependencies ) { 413 | 414 | if ( dependency.type == 'json' ) 415 | app.get.json( dependency.url, dependency.name ) 416 | 417 | } 418 | 419 | }, 420 | 421 | initialize : function() { 422 | 423 | if ( options.equalAgeDistribution ) { 424 | 425 | app.data.adoption.children.age = { 426 | "1": 0.0555555555556, 427 | "2": 0.0555555555556, 428 | "3": 0.0555555555556, 429 | "4": 0.0555555555556, 430 | "5": 0.0555555555556, 431 | "6": 0.0555555555556, 432 | "7": 0.0555555555556, 433 | "8": 0.0555555555556, 434 | "9": 0.0555555555556, 435 | "10": 0.0555555555556, 436 | "11": 0.0555555555556, 437 | "12": 0.0555555555556, 438 | "13": 0.0555555555556, 439 | "14": 0.0555555555556, 440 | "15": 0.0555555555556, 441 | "16": 0.0555555555556, 442 | "17": 0.0555555555556, 443 | "18": 0.0555555555556 444 | } 445 | 446 | } 447 | 448 | app.simulation.child() 449 | 450 | app.simulation.calculate( options.adopted ) 451 | 452 | } 453 | 454 | } 455 | 456 | app.load() 457 | 458 | })() 459 | --------------------------------------------------------------------------------