MongoDB

O MongoDB é um banco de dados NoSQL focado em alta performance e amplamente utilizado em aplicações web e cenários envolvendo big data. O software é livre e gratuito, podendo ser obtido diretamente em https://www.mongodb.com/.

Objetivos

Ao fim deste laboratório, você deverá ser capaz de:

Instalação de pacotes essenciais

O pacote essencial para esta atividade é o mongolite. Ele deve estar instalado antes de você iniciar a atividade. Confirme, após a instalação, que ele funcionamento apropriadamente (dica: tente carregar o pacote). Se você estiver utilizando MS Windows para esta atividade, você pode se beneficiar do script apresentado abaixo. Se o sistema perguntar sobre instalar novos pacotes a partir do código-fonte (install newer version from source), responda n (para indicar não).

options(install.packages.check.source = "no")
install.packages('mongolite', type='win.binary', dependencies = TRUE)

Requerimentos

Para este laboratório, serão utilizadas algumas observações do conjunto de dados diamonds disponibilizado via ggplot2. Adicionalmente, este laboratório assume que você já criou um usuário e senha na plataforma MongoDB Atlas (resultado do apontamento via mlab.com).

Atividade

  1. Conecte-se à plataforma MongoDB Atlas (https://www.mongodb.com/) usando as credenciais escolhidas por você na criação da conta (este login/senha costuma ser diferente do usuário/senha do banco de dados).

  2. No menu à esquerda, escolha a opção Network Access, sob Security, clique em + ADD IP ADDRESS.

    1. Se você estiver em um computador fora da UNICAMP, escolha ADD CURRENT IP ADDRESS (isso permitirá acesso a partir do endereço IP que seu computador está utilizando neste momento e também deve funcionar nos laboratórios do IMECC).
    2. Se você estiver utilizando a máquina (jupyter.ime.unicamp.br), adicione manualmente o endereço 143.106.77.33
    3. Você, alternativamente, poderá escolher ALLOW ACCESS FROM ANYWHERE, o que é muito menos seguro, por permitir que qualquer máquina (no mundo) acesse o banco de dados, desde que use o login/senha corretos.
    • Finalize clicando em CONFIRM.
  3. Ainda no menu à esquerda, escolha a opção Clusters (em ATLAS). Clique em Connect e escolha Connect Your Application. Copie a expressão em Connection String Only. Observe que a expressão inicia-se com mongodb+srv://, que indica o protocolo de conexão. Em seguida, há o nome do usuário (no exemplo abaixo, benilton), seguido do símbolo : e da senha (em texto puro, aqui representada como <password>). Após a senha, o símbolo @ indica que a informação seguinte indicará o nome do servidor a ser utilizado (cluster0-s8gg0.mongodb.net). Após o nome do servidor a informação remanescente deverá ser removida (pois trata-se de um código de exemplo).

Desta maneira, a informação originalmente disponível:

mongodb+srv://benilton:<password>@cluster0-s8gg0.mongodb.net/test?retryWrites=true&w=majority

deve ser transformada em

mongodb+srv://benilton:SenhaReal@cluster0-s8gg0.mongodb.net

e armazenada numa variável chamada myurl na sua sessão R:

myurl = "mongodb+srv://dbenilton:senhadificil@cluster0-s8gg0.mongodb.net"
  1. Conecte-se à URL identificada por você na questão anterior. Crie um banco de dados chamado me315mongodb e, dentro deste, uma coleção chamada diamantes.
library(mongolite)
con = mongo(collection = "diamantes", db = "me315mongodb", url=myurl)
  1. Exiba o conteúdo de con. Explore o método insert() e descreva textualmente que tipo de objeto o argumento data pode receber.

  2. Explore o conjunto de dados diamonds que é distribuído por meio do pacote ggplot2. Quantas observações e quantas colunas possui este conjunto de dados?

library(tidyverse)
ggplot2::diamonds
## # A tibble: 53,940 x 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1 0.23  Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
##  2 0.21  Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
##  3 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
##  4 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
##  5 0.31  Good      J     SI2      63.3    58   335  4.34  4.35  2.75
##  6 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
##  7 0.24  Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
##  8 0.26  Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
##  9 0.22  Fair      E     VS2      65.1    61   337  3.87  3.78  2.49
## 10 0.23  Very Good H     VS1      59.4    61   338  4     4.05  2.39
## # … with 53,930 more rows
dim(ggplot2::diamonds)
## [1] 53940    10
  1. Grave a tabela acima na coleção MongoDB criada anteriormente.
con$insert(ggplot2::diamonds)
## List of 5
##  $ nInserted  : num 53940
##  $ nMatched   : num 0
##  $ nRemoved   : num 0
##  $ nUpserted  : num 0
##  $ writeErrors: list()
  1. Recupere do banco de dados as informações de todos os diamantes cuja variável cut seja igual a Premium e custem menos de USD 1.000,00. Armazene o resultado na variável p1000a. Quantos diamantes ‘premium’ custam menos de 1.000 dólares na sua amostra?
p1000a = con$find('{"cut":"Premium", "price":{"$lt": 1000}}')
p1000a %>% as_tibble()
## # A tibble: 3,200 x 10
##    carat cut     color clarity depth table price     x     y     z
##    <dbl> <chr>   <chr> <chr>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1 0.21  Premium E     SI1      59.8    61   326  3.89  3.84  2.31
##  2 0.290 Premium I     VS2      62.4    58   334  4.2   4.23  2.63
##  3 0.22  Premium F     SI1      60.4    61   342  3.88  3.84  2.33
##  4 0.2   Premium E     SI2      60.2    62   345  3.79  3.75  2.27
##  5 0.32  Premium E     I1       60.9    58   345  4.38  4.42  2.68
##  6 0.24  Premium I     VS1      62.5    57   355  3.97  3.94  2.47
##  7 0.290 Premium F     SI1      62.4    58   403  4.24  4.26  2.65
##  8 0.22  Premium E     VS2      61.6    58   404  3.93  3.89  2.41
##  9 0.22  Premium D     VS2      59.3    62   404  3.91  3.88  2.31
## 10 0.3   Premium J     SI2      59.3    61   405  4.43  4.38  2.61
## # … with 3,190 more rows
  1. Para a consulta acima, refaça a chamada via MongoDB, de forma a obter apenas as colunas cut, clarity e price. Armazene o resultado na variável p1000b.
p1000b = con$find('{"cut":"Premium", "price":{"$lt": 1000}}',
                 fields='{"_id":0, "cut":1, "clarity":1, "price":1}')
p1000b %>% as_tibble()
## # A tibble: 3,200 x 3
##    cut     clarity price
##    <chr>   <chr>   <int>
##  1 Premium SI1       326
##  2 Premium VS2       334
##  3 Premium SI1       342
##  4 Premium SI2       345
##  5 Premium I1        345
##  6 Premium VS1       355
##  7 Premium SI1       403
##  8 Premium VS2       404
##  9 Premium VS2       404
## 10 Premium SI2       405
## # … with 3,190 more rows
  1. Refaça a consulta anterior e retorne apenas os 5 diamantes mais caros presentes nesta amostra. O resultado deve ser armazenado na variável p1000c.
p1000c = con$find('{"cut":"Premium", "price":{"$lt": 1000}}',
                 fields='{"_id":0, "cut":1, "clarity":1, "price":1}',
                 sort='{"price":-1}', limit=5)
p1000c %>% as_tibble()
## # A tibble: 5 x 3
##   cut     clarity price
##   <chr>   <chr>   <int>
## 1 Premium VS2       999
## 2 Premium VS2       999
## 3 Premium VS2       999
## 4 Premium VVS2      999
## 5 Premium VS1       999
  1. Consulte o banco de dados (500 observações) para identificar quais são os 400 diamantes mais caros presentes na sua amostra. Qual foi o tempo total de execução desta consulta?
system.time(caro <- con$find(sort='{"price":-1}', limit=400))
##    user  system elapsed 
##   0.005   0.001   0.428
caro %>% head
##   carat       cut color clarity depth table price    x    y    z
## 1  2.29   Premium     I     VS2  60.8    60 18823 8.50 8.47 5.16
## 2  2.00 Very Good     G     SI1  63.5    56 18818 7.90 7.97 5.04
## 3  1.51     Ideal     G      IF  61.7    55 18806 7.37 7.41 4.56
## 4  2.07     Ideal     G     SI2  62.5    55 18804 8.20 8.13 5.11
## 5  2.00 Very Good     H     SI1  62.8    57 18803 7.95 8.00 5.01
## 6  2.29   Premium     I     SI1  61.8    59 18797 8.52 8.45 5.24
  1. Crie um índice para a variável price.
con$index('{"price": -1}')
##   v key._id key.price     name                     ns
## 1 2       1        NA     _id_ me315mongodb.diamantes
## 2 2      NA        -1 price_-1 me315mongodb.diamantes
  1. Consulte novamente qual é o diamante mais caro e meça o tempo de execução, agora que um índice foi criado.
system.time(caro2 <- con$find(sort='{"price":-1}', limit=400))
##    user  system elapsed 
##   0.006   0.001   0.288
caro2 %>% head
##   carat       cut color clarity depth table price    x    y    z
## 1  2.29   Premium     I     VS2  60.8    60 18823 8.50 8.47 5.16
## 2  2.00 Very Good     G     SI1  63.5    56 18818 7.90 7.97 5.04
## 3  1.51     Ideal     G      IF  61.7    55 18806 7.37 7.41 4.56
## 4  2.07     Ideal     G     SI2  62.5    55 18804 8.20 8.13 5.11
## 5  2.00 Very Good     H     SI1  62.8    57 18803 7.95 8.00 5.01
## 6  2.29   Premium     I     SI1  61.8    59 18797 8.52 8.45 5.24

Observação: Diferenças entre os tempos de execução das questões serão mínimas, assumindo que aconteçam. Estas diferenças poderão ser notadas mais explicitamente se o servidor do MongoDB for local. Pelo fato de estarmos utilizando um servidor público via internet, a maior parte do tempo mensurado é devido à conexão propriamente dita.

  1. Utilize o método iterate() para trabalhar com lotes de linhas do banco de dados. Armazene o resultado de iterate() na variável it. Observe que o método iterate possui um argumento limit, que determina o número máximo de documentos da coleção a serem considerados. Explore a documentação do método e realize a iteração apenas para diamantes de cut="Premium", ordenando por preço (de maneira decresente). Aplique o método batch(n), com n=53, para determinar as estatísticas suficientes da média de preços. Saiba que, muito provavelmente, ao chegar ao fim do banco de dados, o número de observações no seu subconjunto será inferior ao tamanho do lote.
# se o lote terá tamanho 53, quantos lotes no total serão necessários?
n = 53
themax = con$count()
lotes = ceiling(themax/n)
it = con$iterate('{"cut":"Premium"}', sort='{"price":-1}', limit=themax)
lst = vector('list', lotes)
i = 1
fim = FALSE
while(!fim){
  x = it$batch(n)
  x = do.call('rbind', lapply(x, as.data.frame))
  if (nrow(x) < n) fim=TRUE
  lst[[i]] = data.frame(soma=sum(x$price), n=nrow(x))
  i = i+1
}
lst = lst[1:(i-1)]
lst = do.call('rbind', lst)
lst %>% summarise(media=sum(soma)/sum(n))
##      media
## 1 4584.258
  1. Utilize o método aggregate() para determinar o número de diamantes e o respectivo preço médio, estratificando pela variável cut. Renomeie as colunas para que sejam, respectivamente, tipo, n e media. Use o ggplot2 para apresentar um gráfico de barras com o preço.
res = con$aggregate('[{"$group":
                        {"_id":"$cut",
                           "n":{"$sum":1},
                       "media":{"$avg":"$price"}
                        }
                    }]')
names(res) = c("tipo", "n", "media")
library(ggplot2)
ggplot(res, aes(tipo, media)) + geom_bar(stat='identity') + theme_bw()

  1. Utilizando o método export(), exporte o banco de dados para um arquivo no seu computador (chamado meusdiamantes.json). O arquivo deve ser gravado no formato JSON. Explore o arquivo e confirme a validade do seu formato.
con$export(file("meusdiamantes.json"))
  1. Exporte o banco de dados para um arquivo no seu computador. O arquivo deve ser gravado no formato BSON e chamado meusdiamantes.bson. Explore o arquivo e confirme a validade do seu formato. Compare os tamanhos de ambos os arquivos.
con$export(file("meusdiamantes.bson"), bson=TRUE)
  1. Remova do banco de dados a coleção com que você está trabalhando. Conte o número de observações existentes no banco de dados após a remoção.
con$drop()
con$count()
## [1] 0
  1. Utilizando o método import(), importe o arquivo BSON criado por você diretamente para o banco de dados. Conte o número de observações existentes após a importação.
con$import(file('meusdiamantes.bson'), bson=TRUE)
## [1] 53940
con$count()
## [1] 53940
con$drop()