mongodbからpandasにデータをインポートする方法は?


97

mongodbのコレクションに大量のデータがあり、分析する必要があります。そのデータをパンダにインポートするにはどうすればよいですか?

私はパンダとナンピーが初めてです。

編集:mongodbコレクションには、日付と時刻でタグ付けされたセンサー値が含まれています。センサー値は、floatデータ型です。

サンプルデータ:

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

MongoEngineでカスタムフィールドタイプを使用 すると、Pandas DataFrameの保存と取得が次のように簡単になりますmongo_doc.data_frame = my_pandas_df
Jthorpe

回答:


131

pymongo あなたに手を与えるかもしれません、以下は私が使っているいくつかのコードです:

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

おかげで、これは私が最終的に使用した方法です。また、各行に一連の埋め込みドキュメントがありました。そのため、各行内でも同じことを繰り返す必要がありました。これを行うより良い方法はありますか?
Nithin 2013

mongodbの構造のいくつかのサンプルを提供することは可能ですか?
waitingkuo 2013

3
list()内部df = pd.DataFrame(list(cursor))がリストまたはジェネレータとして評価され、CPUをクールに保つことに注意してください。膨大な数のデータアイテムがあり、次の数行が合理的に分割され、詳細レベルになり、それらがクリップされた場合、shmegegge全体は引き続き安全です。
Phlip

2
@は非常に遅いdf = pd.DataFrame(list(cursor))です。純粋なdbクエリははるかに高速です。listキャストを別のものに変更できますか?
Peter.k

1
@ピーターそのラインも私の目を引いた。反復可能であり、大量のデータを潜在的にラップするデータベースカーソルをメモリ内のリストにキャストすることは、私には賢く思えません。
Rafa

39

このコードを使用して、mongodbデータをpandas DataFrameにロードできます。わたしにはできる。うまくいけばあなたにも。

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

Monaryまさにそれを行い、それは超高速です。(別のリンク

簡単なチュートリアルといくつかのタイミングを含むこのクールな投稿を参照してください。


Monaryは文字列データ型をサポートしていますか?
Snehal Parmar、2015

Monaryを試しましたが、かなり時間がかかります。最適化が不足していますか?試したclient = Monary(host, 27017, database="db_tmp") columns = ["col1", "col2"] data_type = ["int64", "int64"] arrays = client.query("db_tmp", "coll", {}, columns, data_type)ため50000、レコードの周りになります200s
ニシャント2017年

それは非常に遅いように聞こえます...率直に言って、私は、このプロジェクトのステータスが、現在、4年後の現在の状況がわかりません...
shx2

16

PEPに従って、単純な方が複雑なよりも優れています。

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

通常のmongoDBデータベースでの作業と同じように条件を含めることも、データベースから1つの要素のみを取得するためにfind_one()を使用することもできます。

そして出来上がり!


pd.DataFrame.from_recordsはDataFrame(list())と同じくらい遅いようですが、結果は非常に一貫していません。%% timeは、800ミリ秒から1.9秒までを示しました
AFD

1
これはメモリエラーを示さないため、巨大なレコードには適していません。instreadは、システムが非常に大きなデータのためにハングします。一方、pd.DataFrame(list(cursor))はメモリエラーを示します。
Amulya Acharya

13
import pandas as pd
from odo import odo

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

9

コア外(RAMに適合しない)のデータを効率的に(つまり、並列実行で)処理するには、Python Blazeエコシステムを試すことができます:Blaze / Dask / Odo。

Blaze(およびOdo)には、MongoDBを処理するためのすぐに使用できる関数があります。

始めるためのいくつかの有用な記事:

また、Blazeスタックで可能な驚くべきことを示す記事:BlazeとImpalaを使用した17億のRedditコメントの分析(基本的に、975 GbのRedditコメントを数秒でクエリ)。

PS私はこれらのテクノロジーのいずれとも提携していません。


1
また、Dupkが単一のマシンで複数のコアを使用することにより、メモリに収まるデータでも実行を高速化する方法を例に、Jupyter Notebookを使用して投稿を書きました。
Dennis Golomazov 16

8

私が非常に便利な別のオプションは次のとおりです。

from pandas.io.json import json_normalize

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

このようにして、ネストされたmongodbドキュメントを無料で展開できます。


2
この方法でエラーが発生しましたTypeError: data argument can't be an iterator
ガブリエルフェア

2
奇妙なことに、これは3.6.7パンダを使用する私のpythonで動作します0.24.2df = json_normalize(list(cursor))代わりに試すことができますか?
IkarPohorský19年

+1。docs、max_level引数は、辞書の深さの最大レベルを定義します。私はテストを実行しましたが、それは真実ではないため、一部の列は.strアクセサで分割する必要があります。それでも、mongodbを操作するための非常に優れた機能です。
Mauricio Maroto

5

使用する

pandas.DataFrame(list(...))

イテレータ/ジェネレータの結果が大きい場合、大量のメモリを消費します

小さなチャンクを生成し、最後に連結する方が良い

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

waitingkuoによるこの素晴らしい回答に続いて、.read_sql()および.read_csv()に沿ってチャンクサイズを使用してそれを実行する可能性を追加したいと思います。「イテレータ」/「カーソル」の「レコード」ごとに1つずつ行くのを避けて、Deu Leungからの回答を拡大します。以前の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

Rafael Valero、waitingkuo、およびDeu Leungのような、ページネーションを使用した同様のアプローチ:

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

pdmongoを使用すると、次の3行で目的の結果を得ることができます。

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

データが非常に大きい場合は、最初に不要なデータをフィルタリングして集計クエリを実行し、次にそれらを目的の列にマップできます。

Readings.a列へのマッピングと列aによるフィルタリングの例を次に示しreportCountます。

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_mongopymongo aggregateと同じ引数を受け入れます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.