Como importar dados de mongodb para pandas?

97

Eu tenho uma grande quantidade de dados em uma coleção no mongodb que preciso analisar. Como faço para importar esses dados para o pandas?

Eu sou novo em pandas e entorpecido.

EDITAR: A coleção mongodb contém valores de sensor marcados com data e hora. Os valores do sensor são do tipo de dados flutuante.

Dados de amostra:

{
"_cls" : "SensorReport",
"_id" : ObjectId("515a963b78f6a035d9fa531b"),
"_types" : [
    "SensorReport"
],
"Readings" : [
    {
        "a" : 0.958069536790466,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:26:35.297Z"),
        "b" : 6.296118156595,
        "_cls" : "Reading"
    },
    {
        "a" : 0.95574014778624,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:27:09.963Z"),
        "b" : 6.29651468650064,
        "_cls" : "Reading"
    },
    {
        "a" : 0.953648289182713,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:27:37.545Z"),
        "b" : 7.29679823731148,
        "_cls" : "Reading"
    },
    {
        "a" : 0.955931884300997,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:28:21.369Z"),
        "b" : 6.29642922525632,
        "_cls" : "Reading"
    },
    {
        "a" : 0.95821381,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:41:20.801Z"),
        "b" : 7.28956613,
        "_cls" : "Reading"
    },
    {
        "a" : 4.95821335,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:41:36.931Z"),
        "b" : 6.28956574,
        "_cls" : "Reading"
    },
    {
        "a" : 9.95821341,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:42:09.971Z"),
        "b" : 0.28956488,
        "_cls" : "Reading"
    },
    {
        "a" : 1.95667927,
        "_types" : [
            "Reading"
        ],
        "ReadingUpdatedDate" : ISODate("2013-04-02T08:43:55.463Z"),
        "b" : 0.29115237,
        "_cls" : "Reading"
    }
],
"latestReportTime" : ISODate("2013-04-02T08:43:55.463Z"),
"sensorName" : "56847890-0",
"reportCount" : 8
}
Nithin
fonte
Usar um tipo de campo personalizado com o MongoEngine pode tornar o armazenamento e a recuperação de DataFrames do Pandas tão simples quantomongo_doc.data_frame = my_pandas_df
Jthorpe

Respostas:

131

pymongo pode lhe dar uma mão, a seguir estão alguns códigos que estou usando:

import pandas as pd
from pymongo import MongoClient


def _connect_mongo(host, port, username, password, db):
    """ A util for making a connection to mongo """

    if username and password:
        mongo_uri = 'mongodb://%s:%s@%s:%s/%s' % (username, password, host, port, db)
        conn = MongoClient(mongo_uri)
    else:
        conn = MongoClient(host, port)


    return conn[db]


def read_mongo(db, collection, query={}, host='localhost', port=27017, username=None, password=None, no_id=True):
    """ Read from Mongo and Store into DataFrame """

    # Connect to MongoDB
    db = _connect_mongo(host=host, port=port, username=username, password=password, db=db)

    # Make a query to the specific DB and Collection
    cursor = db[collection].find(query)

    # Expand the cursor and construct the DataFrame
    df =  pd.DataFrame(list(cursor))

    # Delete the _id
    if no_id:
        del df['_id']

    return df
esperandokuo
fonte
Obrigado, este é o método que acabei usando. Eu também tinha uma série de documentos incorporados em cada linha. Então, eu tive que iterar isso também em cada linha. Existe uma maneira melhor de fazer isso??
Nithin
É possível fornecer algumas amostras da estrutura do seu mongodb?
esperandokuo
3
Observe que o list()interior é df = pd.DataFrame(list(cursor))avaliado como uma lista ou gerador, para manter a refrigeração da CPU. Se você tiver zilhão e um itens de dados, e as próximas linhas os tivessem particionado razoavelmente, nível de detalhe e cortado, todo o shmegegge ainda é seguro para entrar. Legal.
Phlip
2
É muito lento @ df = pd.DataFrame(list(cursor)). A consulta de banco de dados puro é muito mais rápida. Podemos mudar o listelenco para outra coisa?
Peter.k
1
@Peter essa linha também chamou minha atenção. Lançar um cursor de banco de dados, que é projetado para ser iterável e potencialmente agrupar grandes quantidades de dados, em uma lista na memória não parece inteligente para mim.
Rafa
39

Você pode carregar seus dados mongodb para pandas DataFrame usando este código. Funciona para mim. Esperançosamente para você também.

import pymongo
import pandas as pd
from pymongo import MongoClient
client = MongoClient()
db = client.database_name
collection = db.collection_name
data = pd.DataFrame(list(collection.find()))
saimadhu.polamuri
fonte
24

Monaryfaz exatamente isso e é super rápido . ( outro link )

Veja este post legal que inclui um tutorial rápido e alguns horários.

shx2
fonte
O Monary suporta o tipo de dados string?
Snehal Parmar
Tentei Monary, mas está demorando muito. Estou perdendo alguma otimização? Tentou client = Monary(host, 27017, database="db_tmp") columns = ["col1", "col2"] data_type = ["int64", "int64"] arrays = client.query("db_tmp", "coll", {}, columns, data_type)Para 50000registros leva cerca 200s.
nishant
Parece extremamente lento ... Francamente, não sei qual é o status desse projeto agora, 4 anos depois ...
shx2
16

De acordo com o PEP, simples é melhor do que complicado:

import pandas as pd
df = pd.DataFrame.from_records(db.<database_name>.<collection_name>.find())

Você pode incluir condições como faria ao trabalhar com o banco de dados mongoDB regular ou até mesmo usar find_one () para obter apenas um elemento do banco de dados, etc.

e voila!

Cy Bu
fonte
pd.DataFrame.from_records parece ser tão lento quanto DataFrame (list ()), mas os resultados são muito inconsistentes. %% time mostrou qualquer coisa de 800 ms a 1,9 s
AFD de
1
Isso não é bom para registros grandes, pois não mostra erro de memória, o instread trava o sistema para dados muito grandes. enquanto pd.DataFrame (list (cursor)) mostra erro de memória.
Amulya Acharya
13
import pandas as pd
from odo import odo

data = odo('mongodb://localhost/db::collection', pd.DataFrame)
fengwt
fonte
9

Para lidar com dados fora do núcleo (não cabendo na RAM) de forma eficiente (ou seja, com execução paralela), você pode tentar o ecossistema Python Blaze : Blaze / Dask / Odo.

Blaze (e Odo ) tem funções prontas para lidar com o MongoDB.

Alguns artigos úteis para começar:

E um artigo que mostra que coisas incríveis são possíveis com a pilha Blaze: Analisando 1,7 bilhões de comentários do Reddit com Blaze e Impala (essencialmente, consultar 975 Gb de comentários do Reddit em segundos).

PS Não sou afiliado a nenhuma dessas tecnologias.

Dennis Golomazov
fonte
1
Também escrevi um post usando o Jupyter Notebook com um exemplo de como o Dask ajuda a acelerar a execução mesmo em um ajuste de dados na memória usando vários núcleos em uma única máquina.
Dennis Golomazov
8

Outra opção que achei muito útil é:

from pandas.io.json import json_normalize

cursor = my_collection.find()
df = json_normalize(cursor)

desta forma, você obtém o desdobramento de documentos mongodb aninhados gratuitamente.

Ikar Pohorský
fonte
2
Recebi um erro com este métodoTypeError: data argument can't be an iterator
Gabriel Fair,
2
Estranho, isso funciona no meu python 3.6.7usando pandas 0.24.2. Em df = json_normalize(list(cursor))vez disso, você pode tentar ?
Ikar Pohorský
Para +1. docs, o argumento max_level define o nível máximo de profundidade de dicionário. Acabei de fazer um teste e não é verdade, então algumas colunas precisariam ser divididas com acessórios .str. Ainda assim, um recurso muito bom para trabalhar com mongodb.
Mauricio Maroto
5

Usando

pandas.DataFrame(list(...))

irá consumir muita memória se o resultado do iterador / gerador for grande

melhor gerar pequenos pedaços e concat no final

def iterator2dataframes(iterator, chunk_size: int):
  """Turn an iterator into multiple small pandas.DataFrame

  This is a balance between memory and efficiency
  """
  records = []
  frames = []
  for i, record in enumerate(iterator):
    records.append(record)
    if i % chunk_size == chunk_size - 1:
      frames.append(pd.DataFrame(records))
      records = []
  if records:
    frames.append(pd.DataFrame(records))
  return pd.concat(frames)
Deo Leung
fonte
1

Seguindo esta ótima resposta esperandokuo , gostaria de adicionar a possibilidade de fazer isso usando chunksize em linha com .read_sql () e .read_csv () . Eu amplio a resposta de Deu Leung evitando ir um por um cada 'registro' do 'iterador' / 'cursor'. Vou pedir emprestada a função read_mongo anterior .

def read_mongo(db, 
           collection, query={}, 
           host='localhost', port=27017, 
           username=None, password=None,
           chunksize = 100, no_id=True):
""" Read from Mongo and Store into DataFrame """


# Connect to MongoDB
#db = _connect_mongo(host=host, port=port, username=username, password=password, db=db)
client = MongoClient(host=host, port=port)
# Make a query to the specific DB and Collection
db_aux = client[db]


# Some variables to create the chunks
skips_variable = range(0, db_aux[collection].find(query).count(), int(chunksize))
if len(skips_variable)<=1:
    skips_variable = [0,len(skips_variable)]

# Iteration to create the dataframe in chunks.
for i in range(1,len(skips_variable)):

    # Expand the cursor and construct the DataFrame
    #df_aux =pd.DataFrame(list(cursor_aux[skips_variable[i-1]:skips_variable[i]]))
    df_aux =pd.DataFrame(list(db_aux[collection].find(query)[skips_variable[i-1]:skips_variable[i]]))

    if no_id:
        del df_aux['_id']

    # Concatenate the chunks into a unique df
    if 'df' not in locals():
        df =  df_aux
    else:
        df = pd.concat([df, df_aux], ignore_index=True)

return df
Rafael Valero
fonte
1

Uma abordagem semelhante, como Rafael Valero, waitingkuo e Deu Leung usando paginação :

def read_mongo(
       # db, 
       collection, query=None, 
       # host='localhost', port=27017, username=None, password=None,
       chunksize = 100, page_num=1, no_id=True):

    # Connect to MongoDB
    db = _connect_mongo(host=host, port=port, username=username, password=password, db=db)

    # Calculate number of documents to skip
    skips = chunksize * (page_num - 1)

    # Sorry, this is in spanish
    # https://www.toptal.com/python/c%C3%B3digo-buggy-python-los-10-errores-m%C3%A1s-comunes-que-cometen-los-desarrolladores-python/es
    if not query:
        query = {}

    # Make a query to the specific DB and Collection
    cursor = db[collection].find(query).skip(skips).limit(chunksize)

    # Expand the cursor and construct the DataFrame
    df =  pd.DataFrame(list(cursor))

    # Delete the _id
    if no_id:
        del df['_id']

    return df
Jordy Cuan
fonte
0

Você pode conseguir o que deseja com pdmongo em três linhas:

import pdmongo as pdm
import pandas as pd
df = pdm.read_mongo("MyCollection", [], "mongodb://localhost:27017/mydb")

Se seus dados forem muito grandes, você pode fazer uma consulta agregada primeiro, filtrando os dados que não deseja e, em seguida, mapeá-los para as colunas desejadas.

Aqui está um exemplo de mapeamento Readings.apara coluna ae filtragem por reportCountcoluna:

import pdmongo as pdm
import pandas as pd
df = pdm.read_mongo("MyCollection", [{'$match': {'reportCount': {'$gt': 6}}}, {'$unwind': '$Readings'}, {'$project': {'a': '$Readings.a'}}], "mongodb://localhost:27017/mydb")

read_mongoaceita os mesmos argumentos do agregado pymongo

Pakallis
fonte