Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

É seguro sair? Investigação de dados com o MongoDB

Mark Smith11 min read • Published Jan 18, 2022 • Updated Sep 23, 2022
AtlasChartsPython
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Esta pesquisa começou há alguns meses. O bloqueio daVID-19 na Escocia estava começando a diminuir, e era possível (embora desaconselhado) viajar para outras cidades na Escocia. Eu moro em uma cidade pequena fora de Egipto, e foi tentado viajar para a cidade para experimentar algo um pouco mais movimentado do que os caminhos semi-rurais que foram a única coisa que eu realmente vi desde março.
A pergunta que eu precisava responder era: É seguro sair de casa? Qual era a diferença de risco entre caminhar pelo meu bairro e viajar para a cidade para caminhar por lá?
Eu sabia que o NHS escocês publicava dados relacionados a infecções por COVID-19 , mas foi um pouco difícil de encontrar.
Inicialmente, encontrei uma planilha do Excel contendo taxas de infecção em diferentes partes do país, mas estava fortemente formatada e não foi realmente projetada para ser ingerida em um banco de dados como o MongoDB. Então descobri a plataformaScottish Health and Social Care Open Data, que hospedava algumas APIs para acessar dados de infecção por COVID-19 , divididos por diferentes áreas e outras métricas. Escolhi os dados fornecidos pela autoridade local, que é o tipo de área geográfica em que estou interessado.
Há uma pequena complicação com a forma como os dados são fornecidos: eles são fornecidos em dois pontos de extremidade. O primeiro endpoint, que chamei dedaily, fornece dados históricos de infecção, excluindo os resultados do último dia. Para obter também os dados do dia mais recente, preciso obter dados de outro endpoint, que chamei delatest, que fornece apenas os dados de um único dia.
Vou orientá-lo pela abordagem que usei usando o Jupyter Notebook para explorar o formato de dados da API, carregá-los no MongoDB e, em seguida, fazer algumas análises no MongoDB Charts.

Pré-requisitos

Este blog post pressupõe que você tenha um conhecimento prático de Python. Há apenas um trecho de código Python um pouco complicado aqui, que tentei descrever em detalhes, mas isso não afetará sua compreensão do resto da postagem se estiver um pouco além do seu nível de Python.
Se quiser acompanhar, você deve ter uma instalação funcional do Python 3.6 ou posterior, com o Jupyter Notebook instalado. Você também precisará de uma conta MongoDB Atlas, junto com um MongoDB 4 gratuito.4 Cluster. Tudo neste tutorial funciona com um cluster compartilhado gratuito do MongoDB Atlas.
Se você quiser experimentar o MongoDB, não há maneira melhor do que se inscrever em uma conta *grátis* do MongoDB Atlas e configurar um cluster de camada grátis.
A camada gratuita não permitirá que você armazene grandes quantidades de dados ou lide com um grande número de consultas, mas é suficiente para criar algo razoavelmente pequeno e experimentar todos os recursos que o MongoDB Atlas tem a oferecer, e não é um teste, portanto não há limite de tempo.

Configuração do ambiente

Antes de iniciar o Jupyter Notebook, defina uma variável de ambiente usando o seguinte comando:
1export MDB_URI="mongodb+srv://username:password@cluster0.abcde.mongodb.net/covid?retryWrites=true&w=majority"
Essa variável de ambiente, MDB_URI, permitirá que eu carregue os detalhes da conexão do MongoDB sem mantê-los de forma insegura no meu Jupyter Notebook. Se estiver fazendo isso por conta própria, precisará obter o URL de conexão do seu próprio cluster na interface da Web do Atlas.
Depois disso, iniciei o servidor do Jupyter Notebook (executando jupyter notebook na linha de comando) e, em seguida, criei um novo bloco de anotações.
Na primeira célula, tenho o seguinte código, que usa um truque habilidoso para instalar bibliotecas Python de terceiros no ambiente Python atual. Neste caso, estou instalando o driver Python MongoDB, pymongoe urllib3, que uso para fazer solicitações HTTP.
1import sys
2
3!{sys.executable} -m pip install pymongo[srv]==3.11.0 urllib3==1.25.10
A segunda célula consiste no código a seguir, que importa os módulos que usarei neste notebook. Em seguida, ele configura alguns URLs para os pontos de extremidade da API que usarei para obter dados da COVID. Por fim, ele configura um gerenciador de pool de conexões HTTP http, conecta-se ao meu cluster MongoDB Atlas e cria uma referência ao banco de dadoscovidno qual carregarei os dados.
1from datetime import datetime
2import json
3import os
4from urllib.parse import urljoin
5import pymongo
6import urllib3
7
8# Historical COVID stats endpoint:
9daily_url = 'https://www.opendata.nhs.scot/api/3/action/datastore_search?resource_id=427f9a25-db22-4014-a3bc-893b68243055'
10
11# Latest, one-day COVID stats endpoint:
12latest_url = 'https://www.opendata.nhs.scot/api/3/action/datastore_search?resource_id=e8454cf0-1152-4bcb-b9da-4343f625dfef'
13
14http = urllib3.PoolManager()
15
16client = pymongo.MongoClient(os.environ["MDB_URI"])
17db = client.get_database('covid')

Explorar a API

A primeira coisa que fez foi solicitar uma página de amostra de dados de cada ponto de extremidade da API, com código que se parece um pouco com o código abaixo. Estou pulando algumas etapas em que dei uma olhada na estrutura dos dados que estão sendo retornados.
Veja os dados que estão retornando:
1data = json.loads(http.request('GET', daily_url).data)
2pprint(data['result']['records'])
Os dados retornados ficaram mais ou menos assim:
daily_url
1{'CA': 'S12000005',
2'CAName': 'Clackmannanshire',
3'CrudeRateDeaths': 0,
4'CrudeRateNegative': 25.2231276678308,
5'CrudeRatePositive': 0,
6'CumulativeDeaths': 0,
7'CumulativeNegative': 13,
8'CumulativePositive': 0,
9'DailyDeaths': 0,
10'DailyPositive': 0,
11'Date': 20200228,
12'PositivePercentage': 0,
13'PositiveTests': 0,
14'TotalPillar1': 6,
15'TotalPillar2': 0,
16'TotalTests': 6,
17'_id': 1}
18 -
latest_url
1{'CA': 'S12000005',
2'CAName': 'Clackmannanshire',
3'CrudeRateDeaths': 73.7291424136593,
4'CrudeRateNegative': 27155.6072953046,
5'CrudeRatePositive': 1882.03337213815,
6'Date': 20201216,
7'NewDeaths': 1,
8'NewPositive': 6,
9'TotalCases': 970,
10'TotalDeaths': 38,
11'TotalNegative': 13996,
12'_id': 1}
Observe que há uma pequena diferença no formato dos dados. O campodaily_url do endpoint DailyPositive corresponde ao campo latest_urlNewPositive}. Isso também vale para DailyDeaths vs NewDeaths.
Outra coisa a notar é que cada região tem um identificador único, armazenado no campo CA. Uma combinação de CA e Date deve ser exclusiva na collection, então tenho um registro para cada região para cada dia.

Fazendo o upload dos dados

Configurei os seguintes índices para garantir que a combinação de Date e CA seja única e adicionei um índice para CAName para que os dados de uma região possam ser pesquisados com eficiência:
1db.daily.create_index([('Date', pymongo.ASCENDING), ('CA', pymongo.ASCENDING)], unique=True)
2db.daily.create_index([('CAName', pymongo.ASCENDING)])
Vou escrever uma pequena quantidade de código para percorrer cada registro em cada API endpoint e fazer upload de cada registro na minha daily collectionno MongoDB. Primeiro, há um método que pega um registro (como um Python dict) e o carrega no MongoDB.
1def upload_record(record):
2 del record['_id']
3 record['Date'] = datetime.strptime(str(record['Date']), "%Y%m%d")
4 if 'NewPositive' in record:
5 record['DailyPositive'] = record['NewPositive']
6 del record['NewPositive']
7 if 'NewDeaths' in record:
8 record['DailyDeaths'] = record['NewDeaths']
9 del record['NewDeaths']
10 db.daily.replace_one({'Date': record['Date'], 'CA': record['CA']}, record, upsert=True)
Como o valor_idfornecido não é exclusivo nos dois endpoints da API dos quais importar dados, a função o remove do dicionário de registro fornecido. Em seguida, ele analisa o campoDate em um objeto Python datetime, para que ele seja reconhecido como um tipo MongoDB Date. Em seguida, ele renomeia os camposNewPositive e NewDeaths para corresponder aos nomes de campo do endpointdaily .
Finalmente, ele insere os dados no MongoDB, usando replace_one, portanto, se você executar o script várias vezes, os dados no MongoDB serão atualizados com os resultados mais recentes fornecidos pela API. Isso é útil porque, às vezes, os dados do endpointdaily são atualizados retroativamente para serem mais precisos.
Seria ótimo se eu pudesse escrever um loop simples para carregar todos os registros, assim:
1for record in data['result']['records']:
2 upload_record(record)
Infelizmente, o endpoint é paginado e fornece apenas 100 registros por vez. Os dados de paginação são armazenados em um campo chamado _links, que se parece com isto:
1pprint(data['result']['_links'])
2
3{'next':
4'/api/3/action/datastore_search?offset=100&resource_id=e8454cf0-1152-4bcb-b9da-4343f625dfef',
5'start':
6'/api/3/action/datastore_search?resource_id=e8454cf0-1152-4bcb-b9da-4343f625dfef'}
Eu escrevi uma função geradora" clever ", que usa um URL inicial como ponto de partida e, em seguida, gera cada registro (para que você possa iterar sobre os registros individuais). Nos bastidores, ele segue cada next link até que não haja mais registros para consumir. Veja o que isso parece, junto com o código que percorre os resultados :
1def paged_wrapper(starting_url):
2 url = starting_url
3 while url is not None:
4 print(url)
5 try:
6 response = http.request('GET', url)
7 data = response.data
8 page = json.loads(data)
9 except json.JSONDecodeError as jde:
10 print(f"""
11Failed to decode invalid json at {url} (Status: {response.status}
12
13{response.data}
14""")
15 raise
16 records = page['result']['records']
17 if records:
18 for record in records:
19 yield record
20 else:
21 return
22
23 if n := page['result']['_links'].get('next'):
24 url = urljoin(url, n)
25 else:
26 url = None
Em seguida, preciso carregar todos os registros no latest_url que contém os registros do dia mais recente. Depois disso, posso carregar todos os registrosdaily_url que contêm todos os dados desde que o SNS começou a coletá-los, para garantir que todos os registros que tenham sido atualizados na API também sejam refletidos na coleção do MongoDB.
Observe que eu poderia armazenar a data de atualização mais recente dos dadosdaily_urlno MongoDB e verificar se ela foi alterada antes de atualizar os registros, mas estou tentando manter o código simples aqui, e não é um conjunto de dados muito grande para atualizar.
Usar o wrapper paginado e a upload_record função juntos agora parece com isto:
1# This gets the latest figures, released separately:
2records = paged_wrapper(latest_url)
3for record in records:
4 upload_record(record)
5
6# This backfills, and updates with revised figures:
7records = paged_wrapper(daily_url)
8for record in records:
9 upload_record(record)
Uauhoo! Agora eu tenho um Notebook Jupyter que carregará todos esses dados do COVID para o MongoDB quando ele for executado.
Embora esses Cadernos sejam ótimos para escrever código com dados que você não conhece, é um pouco desajeitado carregar o Jupyter e executar o bloco de anotações toda vez que quiser atualizar os dados em meu banco de dados. Se eu quisesse executar isso com um agendador como cron no Unix, poderia selecionar File > Download as > Python, o que me forneceria um script Python que eu poderia executar facilmente em um agendador ou apenas na linha de comando.
Depois de executar o bloco de anotações e esperar um pouco para que todos os dados voltassem, eu tinha uma collection chamada daily contendo todos os dados CoViD desde 2020 de fevereiro .

Visualizando os dados com gráficos

O restante desta publicação do blog poderia ter sido um detalhamento do uso da Estrutura de agregação do MongoDB para consultar e analisar os dados que carreguei. Mas achando que poderia ser mais legal observar os dados usando o MongoDB Charts.
Para começar a criar alguns Atlas Charts, abri uma nova aba do navegador e acessei https://Atlas Charts.mongodb.com/. Antes de criar um novo dashboard, adicionei uma nova fonte de dados, clicando em "Fontes de dados" no lado esquerdo da janela. Selecionei meu cluster e, em seguida, certifiquei-me de que meu banco de dados e coleção fossem selecionados.
Adicionando uma nova fonte de dados.
Adicionando uma nova fonte de dados.
Com a fonte de dados configurada, era hora de criar alguns gráficos a partir dos dados! Selecionei "Dashboards" à esquerda e, em seguida, cliquei em "Add dashboard" no canto superior direito. Cliquei no novo painel e pressionei o botão "Add chart".
A primeira coisa que eu queria fazer era traçar o número de resultados de testes positivos ao longo do tempo. Selecionei minha fonte de dados docovid.daily no canto superior esquerdo, e isso resultou nos campos da coleção do daily sendo listados no lado esquerdo. Esses campos podem ser arrastar e soltar em várias outras partes da interface do MongoDB Charts para alterar a visualização dos dados.
Um gráfico de linhas é uma boa visualização de dados de séries temporais, por isso selecionei um Line Chart Type. Em seguida, arrastei e soltei o campo Datedo lado esquerdo para a caixa X Axis (eixo X) e o campoDailyPositive para a caixa Y Axis (eixo Y).
Um gráfico de linha do tempo com binning ativado
Um gráfico de linha do tempo com binning ativado
Isso gerou um gráfico de resolução muito baixa. Isso se deve ao fato de o campo Date ser selecionado automaticamente com o binning ativado e definido como MONTH binning. Isso significa que todos os valoresDailyPositivesão agregados para cada mês, o que não é o que eu queria fazer. Então, desmarquei o binning e obtive o gráfico abaixo.
O gráfico corrigido
O gráfico corrigido
Vale a pena observar que o gráfico acima foi regenerado no início de janeiro e, portanto, mostra um grande pico no final do gráfico. Isso possivelmente se deve ao relaxamento das regras de distanciamento no Natal, combinado com uma mutação da doença que se espalha mais rapidamente no Reino Unido.
Embora os dados estejam separados por área (ou CAName) na collection, os dados no gráfico são automaticamente combinados em uma única linha, mostrando os números totais em toda a Escócia. Eu queria manter esse gráfico, mas também ter um gráfico semelhante mostrando os números separados por área.
Criei uma duplicata desse gráfico, clicando em "Save & Close" no canto superior direito. Em seguida, no painel, cliquei no botão "..." do gráfico e selecionei "Duplicar gráfico" no menu. Peguei um dos dois gráficos idênticos e cliquei em "Editar".
De volta à tela de edição de gráfico do novo gráfico, arraste e solte CAName na caixaSeries. Isso exibe quase o gráfico que temos em mente, mas revela um problema...
Muitos dados!
Muitos dados!
Observe que, embora este gráfico tenha sido gerado no início de janeiro, os dados exibidos vão apenas para o início de agosto. Isso se deve ao problema descrito na mensagem de aviso na parte superior do gráfico. "Este gráfico pode estar exibindo dados incompletos. O tamanho máximo da resposta da query de 5,000 documentos para gráficos do tipo Discreto foi atingido.
A solução para esse problema é simples na teoria: Reduzir o número de documentos que estão sendo usados para exibir o gráfico. Na prática, envolve decidir sobre um compromisso:
  • Eu poderia reduzir o número de documentos classificando os dados por data (como aconteceu automaticamente no início!).
  • Eu poderia limitar o intervalo de datas usado pelo gráfico.
  • Eu poderia filtrar algumas áreas nas quais não estou interessado.
Decidi pela segunda opção: limitar o intervalo de datas. Issocostumava exigir a adição de uma consulta personalizada à caixa de texto " Consulta " na parte superior da tela, mas uma atualização recente dos gráficos permite filtrar por data, usando operações de apontar e clicar. Então, cliquei na guia " Filtro " e arrastei o campoDateda coluna à esquerda até a caixa " + filter ". Acho que provavelmente é útil ver os números mais recentes, sempre que possível, então saí do painel com " Relativo " selecionado e optei por filtrar os dados dos últimos 90 dias.
Intervalo de datas reduzido
Intervalo de datas reduzido
A filtragem por datas recentes tem o benefício de dimensionar o eixo Y para os números mais recentes. Mas ainda há muitas linhas lá, então adicionei CAName à caixa "Filtro" arrastando-a da coluna "Campos" e, em seguida, verifiquei os valores deCANamenos quais estava interessado. Finalmente, cliquei em Save & Close para Go ao painel.
Filtrado para as áreas em que estou interessado
Filtrado para as áreas em que estou interessado
Idealmente, eu gostaria de normalizar esses dados com base na população, mas vamos deixar isso de fora deste blog post para mantê-lo em um comprimento razoável.

Mapas e MongoDB

Em seguida, gostaria de mostrar como pode ser rápido visualizar dados geográficos nos MongoDB Charts. Cliquei em "Adicionar gráfico" e selecionei covid.daily como minha fonte de dados novamente, mas desta vez selecionei "Geoespacial" como meu "Tipo de Gráfico". Em seguida, arrastei o campoCAName para a caixa "Localização" e DailyPositive para a caixa "Cor".
As formas não correspondem!
As formas não correspondem!
Ops! Não reconheceu as formas! O que isso significa? A resposta está na guia " Personalizar ", em " Shape Scheme, ", atualmente definida como " Países e Regiões. " Altere o valor para "Condados e distritos do Reino Unido". Você deve ver imediatamente um gráfico como este:
Um mapa não muito sombreado da Escocia
Um mapa não muito sombreado da Escocia
Estranhamente, há áreas não sombreadas em parte do país. Acontece que elas correspondem a "Dumfries & Galloway" e "Argyll & Bute." Esses valores são armazenados com o E comercial (&) na collectiondaily, mas as formas do gráfico só são reconhecidas se contiverem a palavra completa "and." Felizmente, eu poderia corrigir isso com um pequeno pipeline de agregação na caixa "Query" na parte superior da janela.
Observação: o operador$replaceOne está disponível somente no MongoDB 4.4! Se você configurou um Atlas cluster com uma versão mais antiga do MongoDB, esta etapa não funcionará.
1[ { $addFields: { CAName: { $replaceOne: { input: "$CAName", find: " & ", replacement: " and "} } }}]
Esse pipeline de agregação consiste em uma única operação$addFields que substitui " & " no campo CAName por "e". Isso corrige o mapa para que fique assim:
Um mapa de resultados de testes em diferentes áreas da Escocia.
Um mapa de resultados de testes em diferentes áreas da Escocia.
Vou importar alguns dados populacionais para minha coleção, para que eu possa ver qual éaconcentração de infecções e ter uma ideia melhor do grau de segurança da minha área, mas esse é o fim deste tutorial!
Esperam que você tenha aproveitado esta introdução desconexa à transferência e importação de dados com o Jupyter Notebook e da execução de uma coleção de recursos do MongoDB Charts. Desconheço diferentes conjuntos de dados. Sempre me surpreendo com a força dos MongoDB Charts, especialmente com uma pequena aggregation pipeline.
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.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Crie uma Cocktail API com o Beanie e o MongoDB


Oct 01, 2024 | 6 min read
Tutorial

Crie uma API RESTful com Flask, MongoDB e Python


Sep 11, 2024 | 10 min read
Artigo

Capturando e armazenando óptica do mundo real com o MongoDB Atlas, o GPT-4 da OpenAI e o PyMongo


Sep 04, 2024 | 7 min read
Tutorial

Parte #1: Crie seu próprio Vector Search com o MongoDB Atlas e o Amazon SageMaker


Sep 18, 2024 | 4 min read
Sumário