Come importare dati da Mongodb a Panda?


97

Ho una grande quantità di dati in una raccolta in mongodb che devo analizzare. Come faccio a importare quei dati sui panda?

Sono nuovo di panda e intorpidimento.

MODIFICA: La raccolta mongodb contiene i valori dei sensori contrassegnati con data e ora. I valori del sensore sono di tipo float.

Dati di esempio:

{
"_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
}

L'utilizzo di un tipo di campo personalizzato con MongoEngine può rendere la memorizzazione e il recupero di Pandas DataFrame semplice comemongo_doc.data_frame = my_pandas_df
Jthorpe

Risposte:


131

pymongo potrebbe darti una mano, di seguito sono riportati alcuni codici che sto utilizzando:

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

Grazie, questo è il metodo che ho finito per usare. Avevo anche una serie di documenti incorporati in ogni riga. Quindi ho dovuto iterare anche quello all'interno di ogni riga. C'è un modo migliore per farlo ??
Nithin

È possibile fornire alcuni campioni della struttura del tuo mongodb?
waitkuo

3
Nota che l' list()interno df = pd.DataFrame(list(cursor))valuta come un elenco o un generatore, per mantenere la CPU fresca. Se disponi di un'infinità di elementi di dati e le prossime righe li avessero suddivisi in modo ragionevole, dettagliati e ritagliati, è comunque possibile inserire l'intero shmegegge. Bello.
Phlip

2
È molto lento @ df = pd.DataFrame(list(cursor)). La query Pure db è molto più veloce. Potremmo cambiare il listcasting con qualcos'altro?
Peter.k

1
@Peter anche quella linea ha catturato i miei occhi. Il cast di un cursore di database, che è progettato per essere iterabile e potenzialmente avvolge grandi quantità di dati, in un elenco in memoria non mi sembra intelligente.
Rafa

39

Puoi caricare i tuoi dati mongodb su DataFrame panda usando questo codice. Per me funziona. Spero anche per te.

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()))

24

Monaryfa esattamente questo ed è super veloce . ( un altro collegamento )

Guarda questo fantastico post che include un breve tutorial e alcuni tempi.


Monary supporta il tipo di dati stringa?
Snehal Parmar

Ho provato Monary, ma ci vuole molto tempo. Mi manca qualche ottimizzazione? Tried client = Monary(host, 27017, database="db_tmp") columns = ["col1", "col2"] data_type = ["int64", "int64"] arrays = client.query("db_tmp", "coll", {}, columns, data_type)For 50000records prende in giro 200s.
nishant

Sembra estremamente lento ... Francamente, non so quale sia lo stato di questo progetto, ora, 4 anni dopo ...
shx2

16

Secondo PEP, semplice è meglio che complicato:

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

Puoi includere condizioni come faresti con un normale database mongoDB o persino usare find_one () per ottenere solo un elemento dal database, ecc.

e voilà!


pd.DataFrame.from_records sembra essere lento quanto DataFrame (list ()), ma i risultati sono molto incoerenti. Il tempo %% ha mostrato un valore compreso tra 800 ms e 1,9 s
AFD

1
Questo non va bene per i record di grandi dimensioni in quanto questo non mostra errori di memoria, istruire blocca il sistema per dati troppo grandi. mentre pd.DataFrame (list (cursore)) mostra un errore di memoria.
Amulya Acharya

13
import pandas as pd
from odo import odo

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

9

Per gestire i dati out-of-core (che non si adattano alla RAM) in modo efficiente (cioè con l'esecuzione parallela), puoi provare l' ecosistema Python Blaze : Blaze / Dask / Odo.

Blaze (e Odo ) ha funzioni pronte all'uso per gestire MongoDB.

Alcuni articoli utili per iniziare:

E un articolo che mostra quali cose straordinarie sono possibili con lo stack Blaze: Analisi di 1,7 miliardi di commenti su Reddit con Blaze e Impala (essenzialmente, interrogando 975 GB di commenti Reddit in pochi secondi).

PS Non sono affiliato con nessuna di queste tecnologie.


1
Ho anche scritto un post utilizzando Jupyter Notebook con un esempio di come Dask aiuta a velocizzare l'esecuzione anche su un dato che si adatta alla memoria utilizzando più core su una singola macchina.
Dennis Golomazov,

8

Un'altra opzione che ho trovato molto utile è:

from pandas.io.json import json_normalize

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

in questo modo si ottengono i documenti mongodb annidati gratuitamente.


2
Ho ricevuto un errore con questo metodoTypeError: data argument can't be an iterator
Gabriel Fair

2
Strano, funziona sul mio pitone che 3.6.7usa i panda 0.24.2. Forse puoi provare df = json_normalize(list(cursor))invece?
Ikar Pohorský

Per +1. docs, l'argomento max_level definisce il livello massimo di profondità del dict. Ho appena fatto un test e non è vero, quindi alcune colonne dovrebbero essere divise con .str accesrors. Comunque, una caratteristica molto bella per lavorare con mongodb.
Mauricio Maroto,

5

Utilizzando

pandas.DataFrame(list(...))

consumerà molta memoria se il risultato dell'iteratore / generatore è grande

meglio generare piccoli pezzi e concatenare alla fine

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)


1

Seguendo questa ottima risposta di waitkuo vorrei aggiungere la possibilità di farlo usando chunksize in linea con .read_sql () e .read_csv () . Ingrandisco la risposta di Deu Leung evitando di andare uno per uno ogni "record" dell '"iteratore" / "cursore". Prenderò in prestito la precedente funzione read_mongo .

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

1

Un approccio simile come Rafael Valero, waitkuo e Deu Leung usando l' impaginazione :

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

0

Puoi ottenere ciò che desideri con pdmongo in tre righe:

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

Se i tuoi dati sono molto grandi, puoi prima eseguire una query aggregata filtrando i dati che non desideri, quindi mapparli alle colonne desiderate.

Di seguito è riportato un esempio di mappatura Readings.asu colonna ae filtro per reportCountcolonna:

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_mongoaccetta gli stessi argomenti di pymongo aggregate

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.