Pipeline de agregação: aplicando a lei de Benford aos dados de COVID-19
John Page, Maxime Beugnet16 min read • Published Jan 07, 2022 • Updated Jan 26, 2023
Avalie esse Artigo
{Nesta postagem do blog, mostrarei como criei um pipeline de agregação para aplicar a lei de Benford no19 conjunto de dados COVID- que disponibilizamos no cluster a seguir:
1 mongodb+srv://readonly:readonly@covid-19.hip2i.mongodb.net/covid19
Se você quiser saber mais sobre esse cluster e como transformamos os arquivos CSV do repositório da UniversidadeJohnsHopkins em documentos MongoDB limpos, confira esta publicação no blog.
Finalmente, com base nesse pipeline, conseguir produzir um dashboard no MongoDB Charts. Por exemplo, aqui está um gráfico que aplica a lei de BenFord nos casos diários de #COVID-19 em todo o mundo:
Isenção de responsabilidade: este artigo se concentrará no pipeline de agregação e nos estágios que usei para produzir o resultado que eu queria obter para poder produzir esses gráficos - e não tanto nos resultados em si, que podem ser interpretados de muitas maneiras diferentes. Um dos muitos problemas aqui é a falta de dados. A pandemia não começou ao mesmo tempo em todos os países, portanto, muitos países não têm dados suficientes para que as porcentagens sejam precisas. Mas fique à vontade para interpretar esses resultados da maneira que quiser...
Esta publicação do blog pressupõe que você já conhece os principais princípios do pipeline de agregação e já está familiarizado com os estágiosmais comuns.
Se você quiser acompanhar, fique à vontade para usar o cluster mencionado acima ou fazer uma cópia usando mongodump ou mongoexport, mas a principal conclusão desta publicação do blog são as técnicas que usei para produzir a saída que desejava.
Além disso, não posso recomendá-lo o suficiente para usar o construtor de pipeline de agregação no MongoDB Atlas ou noCompass para criar seus pipelines e jogar com os que você verá nesta publicação do blog.
Antes de prosseguirmos, deixe-me contar um pouco mais sobre a lei de BenFord. O que a Wikipedia diz?
A lei de Benford [...] é uma observação sobre a distribuição de frequência de dígitos iniciais em muitos conjuntos de dados numéricos da vida real. A lei declara que, em muitas collection de números que ocorrem naturalmente, o dígito principal provavelmente será pequeno. Em conjuntos que obedecem a lei, o número 1 aparece como o dígito significativo principal cerca 30% das vezes, enquanto 9 aparece como o dígito significativo principal menos de 5% das vezes. Se os dígitos fossem distribuídos uniformemente, cada um deles ocorreria cerca de 11.1% do tempo. A lei de Benford também faz previsões sobre a distribuição de segundos dígitos, terceiros dígitos, combinações de dígitos e assim por diante.
Aqui está a distribuição de frequência dos primeiros dígitos que podemos esperar para um conjunto de dados que respeita a lei de Benford:
Um pouco mais abaixo no artigo da Wikipedia, na seção "Aplicações", você também pode ler o seguinte:
Detecção de fraude contábil
Em 1972, Hal Varian sugeriu que a lei poderia ser usada para detectar possíveis fraudes em listas de dados socioeconômicos apresentados em apoio às decisões de planejamento público. Com base na suposição plausível de que as pessoas que fabricam figuras tendem a distribuir seus dígitos de maneira bastante uniforme, uma simples comparação da distribuição de frequência do primeiro dígito dos dados com a distribuição esperada de acordo com a lei de Benford deve mostrar quaisquer resultados anômalos.
Simplesmente, se a distribuição do seu conjunto de dados está seguindo a lei de Benford, então é teoricamente possível detectar dados fraudulentos se um determinado subconjunto dos dados não seguir a lei.
Em nossa situação, com base na observação do primeiro gráfico acima, parece que os casos confirmados diariamente em todo o mundo de COVID-19 estão seguindo a lei de Benford. Mas isso é verdade para todos os países?
Se eu quiser responder a essa pergunta (não quiser), terei que construir um aggregation pipeline relativamente complexo (Quero ?!).
Nesta postagem do blog, vou me concentrar apenas em uma única collection:
covid19.countries_summary
.Como o próprio nome sugere, é uma collection que construí (também usando um aggregation pipeline) que contém um documento diário para cada país do conjunto de dados.
Aqui está um exemplo:
1 { 2 _id: ObjectId("608b24d4e7a11f5710a66b05"), 3 uids: [ 504 ], 4 confirmed: 19645, 5 deaths: 305, 6 country: 'Morocco', 7 date: 2020-07-25T00:00:00.000Z, 8 country_iso2s: [ 'MA' ], 9 country_iso3s: [ 'MAR' ], 10 country_codes: [ 504 ], 11 combined_names: [ 'Morocco' ], 12 population: 36910558, 13 recovered: 16282, 14 confirmed_daily: 811, 15 deaths_daily: 6, 16 recovered_daily: 182 17 }
Como você pode ver, para cada dia e país, tenho contagens diárias dos casos confirmados e mortes por COVID-19 .
Vamos aplicar a lei de Benford nessas duas séries de números.
Antes de começarmos a aplicar estágios (transformações) aos nossos documentos, vamos definir a forma dos documentos finais que facilitará a plotação no MongoDB Charts.
É fácil de fazer e define explicitamente por onde começar (o documento na seção anterior) e para onde vamos:
1 { 2 country: 'US', 3 confirmed_size: 435, 4 deaths_size: 424, 5 benford: [ 6 { digit: 1, confirmed: 22.3, deaths: 36.1 }, 7 { digit: 2, confirmed: 21.1, deaths: 14.4 }, 8 { digit: 3, confirmed: 11.5, deaths: 10.6 }, 9 { digit: 4, confirmed: 11.7, deaths: 8 }, 10 { digit: 5, confirmed: 11, deaths: 5 }, 11 { digit: 6, confirmed: 11.7, deaths: 4.7 }, 12 { digit: 7, confirmed: 6.7, deaths: 6.8 }, 13 { digit: 8, confirmed: 2.3, deaths: 6.4 }, 14 { digit: 9, confirmed: 1.6, deaths: 8 } 15 ] 16 }
A definição do objetivo final nos mantém focados na meta enquanto realizamos nossas sucessivas transformações.
Agora que temos um ponto de partida e um ponto final, vamos tentar escrever nosso pipeline primeiro em inglês:
- Reagrupar todos os primeiros dígitos de cada contagem em uma array para os casos confirmados e em outra para as falecimentos de cada país.
- Limpe as arrays (remova zeros e números negativos - consulte a nota abaixo).
- Calcule o tamanho dessas arrays.
- Remover países com arrays vazias (países sem casos ou falecimentos).
- Calcule as porcentagens de 1s, 2s, ..., 9s em cada array.
- Adicione um país falso "BenfordTheory" com os valores teóricos de 1s, 2s, etc. que devemos encontrar.
- Projeção final para obter o documento na forma final que desejar.
Observação: os campos diários que forneço nesta coleção
covid19.countries_summary
são calculados a partir das contagens cumulativas fornecidas pela Johns Hopkins University (JHU). Simplesmente: A contagem de hoje, para cada país, é a contagem cumulativa de hoje menos a contagem cumulativa de ontem. Em teoria, eu deveria ter zeros (nenhuma morte ou nenhum caso naquele dia), mas nunca números negativos. Mas, às vezes, a JHU aplica correções nas contagens sem aplicá-las retroativamente no passado (já que essas contagens eram contagens oficiais em algum momento, eu acho). Portanto, existem valores negativos e optei por ignorá-los nesse pipeline.Agora que temos um plano, vamos executá-lo. Cada um dos pontos da lista acima é um estágio do pipeline de agregação e agora "apenas" precisamos traduzi-los.
Primeiro, preciso ser capaz de extrair o primeiro caractere de
$confirmed_daily
, que é um número inteiro.O MongoDB fornece um operador$substring que podemos usar se transformarmos esse número inteiro em uma string. Isso é fácil de fazer com o operador$toString .
1 { "$substr": [ { "$toString": "$confirmed_daily" }, 0, 1 ] }
Aqui está o primeiro estágio:
1 { 2 "$group": { 3 "_id": "$country", 4 "confirmed": { 5 "$push": { 6 "$substr": [ 7 { 8 "$toString": "$confirmed_daily" 9 }, 10 0, 11 1 12 ] 13 } 14 }, 15 "deaths": { 16 "$push": { 17 "$substr": [ 18 { 19 "$toString": "$deaths_daily" 20 }, 21 0, 22 1 23 ] 24 } 25 } 26 } 27 }
Aqui está a forma dos meus documentos neste ponto se eu aplicar esta transformação:
1 { 2 _id: 'Japan', 3 confirmed: [ '1', '3', '7', [...], '7', '5' ], 4 deaths: [ '7', '6', '0', [...], '-' , '2' ] 5 }
Como mencionado acima, minhas arrays podem conter zeros e
-
, que é o caractere principal de um número negativo. Decidi ignorar isso para a minha pequena experimentação matemática.Se eu agora traduzir "clean the arrays" em algo mais "amigável para o computador", o que eu realmente gostaria de fazer é "filtrar os arrays". Podemos aproveitar o operador$filter e substituir nossos arrays existentes por suas versões filtradas sem zeros e traços usando o estágio$addFields.
1 { 2 "$addFields": { 3 "confirmed": { 4 "$filter": { 5 "input": "$confirmed", 6 "as": "elem", 7 "cond": { 8 "$and": [ 9 { 10 "$ne": [ 11 "$$elem", 12 "0" 13 ] 14 }, 15 { 16 "$ne": [ 17 "$$elem", 18 "-" 19 ] 20 } 21 ] 22 } 23 } 24 }, 25 "deaths": { ... } // same as above with $deaths 26 } 27 }
Nesse ponto, nossos documentos no pipeline têm a mesma forma de anteriormente.
O objetivo final aqui é calcular as porcentagens de 1s, 2s, ..., 9s nessas duas arrays, respectivamente. Para calcular isso, precisarei do tamanho das arrays para aplicar a regra de três.
1 { 2 "$addFields": { 3 "confirmed_size": { 4 "$size": "$confirmed" 5 }, 6 "deaths_size": { 7 "$size": "$deaths" 8 } 9 } 10 }
Para ser totalmente honesto, eu poderia computar isso rapidamente mais tarde, quando eu realmente precisar. Mas vou precisar dele várias vezes mais tarde, e esse estágio é barato e acalma minha mente, então... Vamos nos beijar.
Esta é a forma de nossos documentos neste ponto:
1 { 2 _id: 'Japan', 3 confirmed: [ '1', '3', '7', [...], '7', '5' ], 4 deaths: [ '7', '6', '9', [...], '2' , '1' ], 5 confirmed_size: 452, 6 deaths_size: 398 7 }
Como você pode ver no Japão, nossas matrizes são relativamente longas, então podemos esperar que nossas porcentagens sejam um pouco precisas.
Está longe de ser verdade para todos os países...
1 { 2 _id: 'Solomon Islands', 3 confirmed: [ 4 '4', '1', '1', '3', 5 '1', '1', '1', '2', 6 '1', '5' 7 ], 8 deaths: [], 9 confirmed_size: 10, 10 deaths_size: 0 11 }
1 { 2 _id: 'Fiji', 3 confirmed: [ 4 '1', '1', '1', '2', '2', '1', '6', '2', 5 '2', '1', '2', '1', '5', '5', '3', '1', 6 '4', '1', '1', '1', '2', '1', '1', '1', 7 '1', '2', '4', '1', '1', '3', '1', '4', 8 '3', '2', '1', '4', '1', '1', '1', '5', 9 '1', '4', '8', '1', '1', '2' 10 ], 11 deaths: [ '1', '1' ], 12 confirmed_size: 46, 13 deaths_size: 2 14 }
Não sou bom o suficiente em matemática para decidir qual tamanho é significativo o suficiente para ser estatisticamente preciso, mas sou bom o suficiente para saber que minha regra de três precisará ser dividida pelo tamanho da matriz.
Como dividir por zero é ruim para a saúde, preciso remover arrays vazios. Um estatística sólida provavelmente também removeria as arrays pequenas ... mas não eu .
1 { 2 "$match": { 3 "confirmed_size": { 4 "$gt": 0 5 }, 6 "deaths_size": { 7 "$gt": 0 8 } 9 } 10 }
Finalmente estamos no estágio central do nosso pipeline. Preciso aplicar uma regra de três para calcular a porcentagem de 1s em uma matriz:
- Encontre quantos 1s estão na array.
- Multiplique por 100.
- Divida pelo tamanho da array.
- Arredonde a porcentagem final para uma casa decimal. (Não preciso de mais precisão para meus gráficos.)
Então, preciso repetir esta operação para cada dígito e cada array.
Para descobrir quantas vezes um dígito aparece na array, posso reutilizar técnicas que aprenderam anteriormente:
1 { 2 "$size": { 3 "$filter": { 4 "input": "$confirmed", 5 "as": "elem", 6 "cond": { 7 "$eq": [ 8 "$$elem", 9 "1" 10 ] 11 } 12 } 13 } 14 }
Estou criando uma nova array que contém apenas os 1s com
$filter
e cálculo seu tamanho com $size
.Agora posso $multiplicar esse valor (vamos nomeá-lo X) por 100, $dividir pelo tamanho da array
confirmed
e $arredondar o resultado final para um decimal.1 { 2 "$round": [ 3 { 4 "$divide": [ 5 { "$multiply": [ 100, X ] }, 6 "$confirmed_size" 7 ] 8 }, 9 1 10 ] 11 }
Como um lembrete, aqui está o documento final que queremos:
1 { 2 country: 'US', 3 confirmed_size: 435, 4 deaths_size: 424, 5 benford: [ 6 { digit: 1, confirmed: 22.3, deaths: 36.1 }, 7 { digit: 2, confirmed: 21.1, deaths: 14.4 }, 8 { digit: 3, confirmed: 11.5, deaths: 10.6 }, 9 { digit: 4, confirmed: 11.7, deaths: 8 }, 10 { digit: 5, confirmed: 11, deaths: 5 }, 11 { digit: 6, confirmed: 11.7, deaths: 4.7 }, 12 { digit: 7, confirmed: 6.7, deaths: 6.8 }, 13 { digit: 8, confirmed: 2.3, deaths: 6.4 }, 14 { digit: 9, confirmed: 1.6, deaths: 8 } 15 ] 16 }
O valor que acabamos de calcular acima corresponde ao
22.3
que temos neste documento.Neste ponto, só precisamos repetir esta operação nove vezes para cada dígito da array
confirmed
e outras nove vezes para a arraydeaths
, e atribuir os resultados de acordo na nova arraybenford
de documentos.Veja como fica no final:
1 { 2 "$addFields": { 3 "benford": [ 4 { 5 "digit": 1, 6 "confirmed": { 7 "$round": [ 8 { 9 "$divide": [ 10 { 11 "$multiply": [ 12 100, 13 { 14 "$size": { 15 "$filter": { 16 "input": "$confirmed", 17 "as": "elem", 18 "cond": { 19 "$eq": [ 20 "$$elem", 21 "1" 22 ] 23 } 24 } 25 } 26 } 27 ] 28 }, 29 "$confirmed_size" 30 ] 31 }, 32 1 33 ] 34 }, 35 "deaths": { 36 "$round": [ 37 { 38 "$divide": [ 39 { 40 "$multiply": [ 41 100, 42 { 43 "$size": { 44 "$filter": { 45 "input": "$deaths", 46 "as": "elem", 47 "cond": { 48 "$eq": [ 49 "$$elem", 50 "1" 51 ] 52 } 53 } 54 } 55 } 56 ] 57 }, 58 "$deaths_size" 59 ] 60 }, 61 1 62 ] 63 } 64 }, 65 {"digit": 2...}, 66 {"digit": 3...}, 67 {"digit": 4...}, 68 {"digit": 5...}, 69 {"digit": 6...}, 70 {"digit": 7...}, 71 {"digit": 8...}, 72 {"digit": 9...} 73 ] 74 } 75 }
Neste ponto em nosso pipeline, nossos documentos ficam assim:
1 { 2 _id: 'Luxembourg', 3 confirmed: [ 4 '1', '5', '2', '1', '1', '4', '3', '1', '2', '5', '8', '4', 5 '1', '4', '1', '1', '1', '2', '3', '1', '9', '5', '3', '2', 6 '2', '2', '1', '7', '4', '1', '2', '5', '1', '2', '1', '8', 7 '9', '6', '8', '1', '1', '3', '7', '8', '6', '6', '4', '2', 8 '2', '1', '1', '1', '9', '5', '8', '2', '2', '6', '1', '6', 9 '4', '8', '5', '4', '1', '2', '1', '3', '1', '4', '1', '1', 10 '3', '3', '2', '1', '2', '2', '3', '2', '1', '1', '1', '3', 11 '1', '7', '4', '5', '4', '1', '1', '1', '1', '1', '7', '9', 12 '1', '4', '4', '8', 13 ... 242 more items 14 ], 15 deaths: [ 16 '1', '1', '8', '9', '2', '3', '4', '1', '3', '5', '5', '1', 17 '3', '4', '2', '5', '2', '7', '1', '1', '5', '1', '2', '2', 18 '2', '9', '6', '1', '1', '2', '5', '3', '5', '1', '3', '3', 19 '1', '3', '3', '4', '1', '1', '2', '4', '1', '2', '2', '1', 20 '4', '4', '1', '3', '6', '5', '8', '1', '3', '2', '7', '1', 21 '6', '8', '6', '3', '1', '2', '6', '4', '6', '8', '1', '1', 22 '2', '3', '7', '1', '8', '2', '1', '6', '3', '3', '6', '2', 23 '2', '2', '3', '3', '3', '2', '6', '3', '1', '3', '2', '1', 24 '1', '4', '1', '1', 25 ... 86 more items 26 ], 27 confirmed_size: 342, 28 deaths_size: 186, 29 benford: [ 30 { digit: 1, confirmed: 36.3, deaths: 32.8 }, 31 { digit: 2, confirmed: 16.4, deaths: 19.9 }, 32 { digit: 3, confirmed: 9.1, deaths: 14.5 }, 33 { digit: 4, confirmed: 8.8, deaths: 7.5 }, 34 { digit: 5, confirmed: 6.4, deaths: 6.5 }, 35 { digit: 6, confirmed: 9.6, deaths: 8.6 }, 36 { digit: 7, confirmed: 5.8, deaths: 3.8 }, 37 { digit: 8, confirmed: 5, deaths: 4.8 }, 38 { digit: 9, confirmed: 2.6, deaths: 1.6 } 39 ] 40 }
Observação: neste ponto, não precisamos mais das arrays. O documento de destino está quase lá.
Em meus gráficos finais, eu queria poder exibir também os valores teóricos de Bendord, juntamente com os valores reais dos diferentes países, para poder identificar facilmente qual deles está potencialmente produzindo dados falsos (além do ruído estatístico e muitos outros motivos).
Só para se ter uma ideia, parece que, globalmente, todos os países estão produzindo dados legítimos, mas algumas arrays são pequenas e produzem "acidentes estatísticas".
Para poder inserir esse documento "perfeito", preciso introduzir em meu pipeline um país falso e completo que tenha as porcentagens perfeitas. Resolvi chamá-lo de "BenFordTheory".
Mas (como sempre há um), até onde eu saiba, não há nenhum estágio que possa simplesmente me permitir inserir um novo documento como esse no meu pipeline.
tão perto...
Para minha sorte, encontrou uma solução alternativa para esse problema com o novo (desde 4.4) $unionWith estágio. Tudo que preciso fazer é inserir meu documento criado em uma collection e posso "inserir" todos os documentos dessa collection no meu pipeline neste estágio.
Inseri meu documento falso na nova collection chamada aleatoriamente
benford
. Observe que deixei este documento parecido com os documentos neste estágio atual do meu pipeline. Não me importo de inserir as duas arrays porque estou prestes a descartá-las de qualquer maneira.1 { 2 _id: 'BenfordTheory', 3 benford: [ 4 { digit: 1, confirmed: 30.1, deaths: 30.1 }, 5 { digit: 2, confirmed: 17.6, deaths: 17.6 }, 6 { digit: 3, confirmed: 12.5, deaths: 12.5 }, 7 { digit: 4, confirmed: 9.7, deaths: 9.7 }, 8 { digit: 5, confirmed: 7.9, deaths: 7.9 }, 9 { digit: 6, confirmed: 6.7, deaths: 6.7 }, 10 { digit: 7, confirmed: 5.8, deaths: 5.8 }, 11 { digit: 8, confirmed: 5.1, deaths: 5.1 }, 12 { digit: 9, confirmed: 4.6, deaths: 4.6 } 13 ], 14 confirmed_size: 999999, 15 deaths_size: 999999 16 }
Com essa nova coleção pronta, tudo o que preciso fazer é
$unionWith
.1 { 2 "$unionWith": { 3 "coll": "benford" 4 } 5 }
Neste ponto, nossos documentos se parecem quase com o documento de destino inicial que definimos no início desta publicação do blog. Duas diferenças:
- O nome dos países está na chave
_id
, não na chavecountry
. - Os dois arrays ainda estão aqui.
1 { 2 "$project": { 3 "country": "$_id", 4 "_id": 0, 5 "benford": 1, 6 "confirmed_size": 1, 7 "deaths_size": 1 8 } 9 }
Observe que escolhai qual campo deve estar aqui ou não no documento final por inclusão aqui.
_id
é uma exceção e precisa ser explicitamente excluído. Como as duas arrays não estão explicitamente incluídas, elas são excluídas por padrão, como qualquer outro campo que estivesse lá. Consulte considerações.Aqui está o nosso resultado final:
1 { 2 confirmed_size: 409, 3 deaths_size: 378, 4 benford: [ 5 { digit: 1, confirmed: 32.8, deaths: 33.6 }, 6 { digit: 2, confirmed: 20.5, deaths: 13.8 }, 7 { digit: 3, confirmed: 15.9, deaths: 11.9 }, 8 { digit: 4, confirmed: 10.8, deaths: 11.6 }, 9 { digit: 5, confirmed: 5.9, deaths: 6.9 }, 10 { digit: 6, confirmed: 2.9, deaths: 7.7 }, 11 { digit: 7, confirmed: 4.4, deaths: 4.8 }, 12 { digit: 8, confirmed: 3.2, deaths: 5.6 }, 13 { digit: 9, confirmed: 3.7, deaths: 4.2 } 14 ], 15 country: 'Bulgaria' 16 }
E lembre-se de que alguns documentos ainda ficam assim no pipeline porque não me dei ao trabalho de filtrá-los:
1 { 2 confirmed_size: 2, 3 deaths_size: 1, 4 benford: [ 5 { digit: 1, confirmed: 0, deaths: 0 }, 6 { digit: 2, confirmed: 50, deaths: 100 }, 7 { digit: 3, confirmed: 0, deaths: 0 }, 8 { digit: 4, confirmed: 0, deaths: 0 }, 9 { digit: 5, confirmed: 0, deaths: 0 }, 10 { digit: 6, confirmed: 0, deaths: 0 }, 11 { digit: 7, confirmed: 50, deaths: 0 }, 12 { digit: 8, confirmed: 0, deaths: 0 }, 13 { digit: 9, confirmed: 0, deaths: 0 } 14 ], 15 country: 'MS Zaandam' 16 }
Meu pipeline final é bem longo devido ao fato de que estou repetindo o mesmo bloco para cada dígito e cada array em um total de 9*2=18 vezes.
1 use covid19; 2 3 let groupBy = { 4 "$group": { 5 "_id": "$country", 6 "confirmed": { 7 "$push": { 8 "$substr": [{ 9 "$toString": "$confirmed_daily" 10 }, 0, 1] 11 } 12 }, 13 "deaths": { 14 "$push": { 15 "$substr": [{ 16 "$toString": "$deaths_daily" 17 }, 0, 1] 18 } 19 } 20 } 21 }; 22 23 let createConfirmedAndDeathsArrays = { 24 "$addFields": { 25 "confirmed": { 26 "$filter": { 27 "input": "$confirmed", 28 "as": "elem", 29 "cond": { 30 "$and": [{ 31 "$ne": ["$$elem", "0"] 32 }, { 33 "$ne": ["$$elem", "-"] 34 }] 35 } 36 } 37 }, 38 "deaths": { 39 "$filter": { 40 "input": "$deaths", 41 "as": "elem", 42 "cond": { 43 "$and": [{ 44 "$ne": ["$$elem", "0"] 45 }, { 46 "$ne": ["$$elem", "-"] 47 }] 48 } 49 } 50 } 51 } 52 }; 53 54 let addArraySizes = { 55 "$addFields": { 56 "confirmed_size": { 57 "$size": "$confirmed" 58 }, 59 "deaths_size": { 60 "$size": "$deaths" 61 } 62 } 63 }; 64 65 let removeCountriesWithoutConfirmedCasesAndDeaths = { 66 "$match": { 67 "confirmed_size": { 68 "$gt": 0 69 }, 70 "deaths_size": { 71 "$gt": 0 72 } 73 } 74 }; 75 76 function calculatePercentage(inputArray, digit, sizeArray) { 77 return { 78 "$round": [{ 79 "$divide": [{ 80 "$multiply": [100, { 81 "$size": { 82 "$filter": { 83 "input": inputArray, 84 "as": "elem", 85 "cond": { 86 "$eq": ["$$elem", digit] 87 } 88 } 89 } 90 }] 91 }, sizeArray] 92 }, 1] 93 } 94 } 95 96 function calculatePercentageConfirmed(digit) { 97 return calculatePercentage("$confirmed", digit, "$confirmed_size"); 98 } 99 100 function calculatePercentageDeaths(digit) { 101 return calculatePercentage("$deaths", digit, "$deaths_size"); 102 } 103 104 let calculateBenfordPercentagesConfirmedAndDeaths = { 105 "$addFields": { 106 "benford": [{ 107 "digit": 1, 108 "confirmed": calculatePercentageConfirmed("1"), 109 "deaths": calculatePercentageDeaths("1") 110 }, { 111 "digit": 2, 112 "confirmed": calculatePercentageConfirmed("2"), 113 "deaths": calculatePercentageDeaths("2") 114 }, { 115 "digit": 3, 116 "confirmed": calculatePercentageConfirmed("3"), 117 "deaths": calculatePercentageDeaths("3") 118 }, { 119 "digit": 4, 120 "confirmed": calculatePercentageConfirmed("4"), 121 "deaths": calculatePercentageDeaths("4") 122 }, { 123 "digit": 5, 124 "confirmed": calculatePercentageConfirmed("5"), 125 "deaths": calculatePercentageDeaths("5") 126 }, { 127 "digit": 6, 128 "confirmed": calculatePercentageConfirmed("6"), 129 "deaths": calculatePercentageDeaths("6") 130 }, { 131 "digit": 7, 132 "confirmed": calculatePercentageConfirmed("7"), 133 "deaths": calculatePercentageDeaths("7") 134 }, { 135 "digit": 8, 136 "confirmed": calculatePercentageConfirmed("8"), 137 "deaths": calculatePercentageDeaths("8") 138 }, { 139 "digit": 9, 140 "confirmed": calculatePercentageConfirmed("9"), 141 "deaths": calculatePercentageDeaths("9") 142 }] 143 } 144 }; 145 146 let unionBenfordTheoreticalValues = { 147 "$unionWith": { 148 "coll": "benford" 149 } 150 }; 151 152 let finalProjection = { 153 "$project": { 154 "country": "$_id", 155 "_id": 0, 156 "benford": 1, 157 "confirmed_size": 1, 158 "deaths_size": 1 159 } 160 }; 161 162 let pipeline = [groupBy, 163 createConfirmedAndDeathsArrays, 164 addArraySizes, 165 removeCountriesWithoutConfirmedCasesAndDeaths, 166 calculateBenfordPercentagesConfirmedAndDeaths, 167 unionBenfordTheoreticalValues, 168 finalProjection]; 169 170 let cursor = db.countries_summary.aggregate(pipeline); 171 172 printjson(cursor.next());
Se você quiser ver mais visualmente como esse pipeline funciona passo a passo, importe-o para o MongoDB Compass quando estiver conectado ao cluster (consulte o URI na introdução). Use a opção
New Pipeline From Text
na collectioncovid19.countries_summary
para importá-lo.Você achou que esse pipeline que acabei de apresentar era perfeito?
Bem, bem... É definitivamente fazer o trabalho, mas podemos torná-lo melhor de várias maneiras. Já mencionei nesta publicação do blog que poderíamos remover o Estágio 3, por exemplo, se quisermos. Pode não ser tão ideal, mas seria mais curto.
Além disso, ainda há Stage 5, no qual eu literalmente copio e colo o mesmo pedaço de código 18 vezes ... e Estágio 6, onde tenho que usar uma solução alternativa para inserir um documento no meu pipeline.
Outra solução poderia ser reescrever esse pipeline com um estágio$facet e executar dois subpipelines em paralelo para calcular os resultados que queremos para a array confirmada e a array de morte. Mas essa solução é, na verdade, cerca de duas vezes mais lenta.
No entanto, meu colega João Page surgiu com este pipeline que é apenas melhor que o meu porque está aplicando mais ou menos o mesmo algoritmo, mas não está se repetindo. O código está muito mais limpo e eu simplesmenteadoro, então lembrei de compartilhá-lo com você.
John está usando de forma muito inteligente um estágio$map para iterar mais de nove dígitos, o que torna o código muito mais simples de manter .
Nesta publicação do blog, procurei compartilhar com você o processo de criação de um aggregation pipeline relativamente complexo e alguns truques para transformar seus documentos da forma mais eficiente possível.
Falamos sobre e usamos em um pipeline real os seguintes estágios e operadores do pipeline de agregação:
- $map.
Se você for um estatística e conseguir entender esses resultados, publique uma mensagem no Fórum da comunidade e me envie um ping!
Além disso, deixe-me saber se você consegue descobrir se alguns países estão explicitamente gerando dados falsos.
Se tiver dúvidas, acesse o site da nossa comunidade de desenvolvedores, no qual os engenheiros e a comunidade do MongoDB ajudarão você a desenvolver sua próxima grande ideia com o MongoDB.
Relacionado
Tutorial
Criar um microsserviço de preços dinâmicos com Vertex AI e MongoDB Atlas
Oct 09, 2024 | 18 min read
Tutorial
Um guia para novatos sobre como integrar o MongoDB com o TensorFlow usando JavaScript
Sep 04, 2024 | 15 min read