Вставьте кадр данных Pandas в mongodb, используя PyMongo

Каков самый быстрый способ вставить кадр данных pandas в mongodb с помощью PyMongo?

Попытки

db.myCollection.insert(df.to_dict())

выдал ошибку

InvalidDocument: documents must have only string keys, the key was Timestamp('2013-11-23 13:31:00', tz=None)


 db.myCollection.insert(df.to_json())

выдал ошибку

TypeError: 'str' object does not support item assignment


 db.myCollection.insert({id: df.to_json()})

выдал ошибку

InvalidDocument: documents must have only string a keys, key was <built-in function id>


дф

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 150 entries, 2013-11-23 13:31:26 to 2013-11-23 13:24:07
Data columns (total 3 columns):
amount    150  non-null values
price     150  non-null values
tid       150  non-null values
dtypes: float64(2), int64(1)

person Nyxynyx    schedule 23.11.2013    source источник
comment
что ты хочешь делать потом? вам нужен один документ на запись или один документ на кадр данных?   -  person alko    schedule 24.11.2013
comment
Каждая запись монго будет иметь поля date, amount, price и tid. tid должно быть уникальным полем   -  person Nyxynyx    schedule 24.11.2013
comment
вы можете преобразовать фрейм данных в список диктов: records = json.loads(df.to_json(orient='records')), результат будет таким: [{'c1': 1, 'c2': 1},{'c1': 2, 'c2': 2},{'c1': 3, 'c2': 3}], затем просто используйте db.coll.insert_many(records). Кстати, используйте df.to_dict('recoreds'), чтобы противостоять Type error   -  person Ferris    schedule 22.05.2018


Ответы (10)


Я сомневаюсь, что существует как самый быстрый, так и простой метод. Если вы не беспокоитесь о преобразовании данных, вы можете сделать

>>> import json
>>> df = pd.DataFrame.from_dict({'A': {1: datetime.datetime.now()}})
>>> df
                           A
1 2013-11-23 21:14:34.118531

>>> records = json.loads(df.T.to_json()).values()
>>> db.myCollection.insert(records)

Но если вы попытаетесь загрузить данные обратно, вы получу:

>>> df = read_mongo(db, 'myCollection')
>>> df
                     A
0  1385241274118531000
>>> df.dtypes
A    int64
dtype: object

поэтому вам придется преобразовать столбец «A» обратно в datetimes, а также все поля, отличные от int, float или str, в вашем DataFrame. Для этого примера:

>>> df['A'] = pd.to_datetime(df['A'])
>>> df
                           A
0 2013-11-23 21:14:34.118531
person alko    schedule 23.11.2013
comment
db.myCollection.insert(records) следует заменить на db.myCollection.insert_many(records) см. предупреждение //anaconda/bin/ipython:1: DeprecationWarning: insert is deprecated. Use insert_one or insert_many instead. #!/bin/bash //anaconda/bin/python.app - person Femto Trader; 24.12.2015

Здесь у вас есть самый быстрый способ. Используя insert_many метод из pymongo 3 и параметр 'records' в to_dict метод.

db.collection.insert_many(df.to_dict('records'))
person dieguico    schedule 26.06.2015
comment
Это лучшая идея, хотя я не думаю, что синтаксис будет работать для исходного варианта использования. Основная проблема заключается в том, что mongo нужны строковые ключи, тогда как ваш df имеет индекс Timestamp. Вам нужно использовать параметры, переданные to_dict(), чтобы ключи в монго были чем-то отличным от дат. У меня был частый случай использования, когда вы действительно хотите, чтобы каждая строка в df была записью с дополнительным полем «дата». - person Marshall Farrier; 16.02.2016
comment
Вы должны исправить фрагмент кода, чтобы включить коллекцию. - person hui chen; 21.02.2020
comment
Это не сохраняет какие-либо типы данных, не так ли? Пример {'numfield': NumberLong(16797951)} - person NealWalters; 16.11.2020

odo может сделать это, используя

odo(df, db.myCollection)
person Femto Trader    schedule 27.12.2015
comment
Мне очень нравится odo, но он ужасно терпит неудачу, когда mongo uri имеет не альфа-имя пользователя, passwd. Я бы не рекомендовал его ни для чего, кроме использования неаутентифицированного монго. - person armundle; 25.08.2016
comment
Я думаю, что разработка odo была остановлена ​​или отложена в последнее время, начиная с 2019 года. - person wordsforthewise; 03.06.2019

Если в вашем фрейме данных отсутствуют данные (например, None, nan), и вы не хотите, чтобы в ваших документах были нулевые значения ключа:

db.insert_many(df.to_dict("records")) будет вставлять ключи с нулевыми значениями. Если вам не нужны пустые значения ключей в ваших документах, вы можете использовать модифицированную версию кода pandas .to_dict("records") ниже:

from pandas.core.common import _maybe_box_datetimelike
my_list = [dict((k, _maybe_box_datetimelike(v)) for k, v in zip(df.columns, row) if v != None and v == v) for row in df.values]
db.insert_many(my_list)

где if v != None and v == v я добавил проверки, чтобы убедиться, что значение не равно None или nan, прежде чем помещать его в словарь строки. Теперь ваш .insert_many будет включать только ключи со значениями в документах (и не null типы данных).

person Radical Edward    schedule 15.06.2016
comment
Это хороший способ, потому что при загрузке кадра данных в mongodb действительно необходимо иметь дело с нулевыми значениями, и этот метод быстрее, чем DataFrame.to_dict(), BTW, columns = list(df.columns), а затем [{k: _maybe_box_datetimelike(v) for k, v in zip(columns, row) if v != None and v == v} for row in df.values] еще быстрее. - person Woods Chen; 31.12.2018

Я думаю, что в этом вопросе есть классные идеи. В моем случае я тратил больше времени на перемещение больших фреймов данных. В этом случае pandas, как правило, позволяет вам выбрать chunksize (например, в pandas.DataFrame.to_sql). Поэтому я думаю, что могу внести свой вклад, добавив функцию, которую я использую в этом направлении.

def write_df_to_mongoDB(  my_df,\
                          database_name = 'mydatabasename' ,\
                          collection_name = 'mycollectionname',
                          server = 'localhost',\
                          mongodb_port = 27017,\
                          chunk_size = 100):
    #"""
    #This function take a list and create a collection in MongoDB (you should
    #provide the database name, collection, port to connect to the remoete database,
    #server of the remote database, local port to tunnel to the other machine)
    #
    #---------------------------------------------------------------------------
    #Parameters / Input
    #    my_list: the list to send to MongoDB
    #    database_name:  database name
    #
    #    collection_name: collection name (to create)
    #    server: the server of where the MongoDB database is hosted
    #        Example: server = 'XXX.XXX.XX.XX'
    #    this_machine_port: local machine port.
    #        For example: this_machine_port = '27017'
    #    remote_port: the port where the database is operating
    #        For example: remote_port = '27017'
    #    chunk_size: The number of items of the list that will be send at the
    #        some time to the database. Default is 100.
    #
    #Output
    #    When finished will print "Done"
    #----------------------------------------------------------------------------
    #FUTURE modifications.
    #1. Write to SQL
    #2. Write to csv
    #----------------------------------------------------------------------------
    #30/11/2017: Rafael Valero-Fernandez. Documentation
    #"""



    #To connect
    # import os
    # import pandas as pd
    # import pymongo
    # from pymongo import MongoClient

    client = MongoClient('localhost',int(mongodb_port))
    db = client[database_name]
    collection = db[collection_name]
    # To write
    collection.delete_many({})  # Destroy the collection
    #aux_df=aux_df.drop_duplicates(subset=None, keep='last') # To avoid repetitions
    my_list = my_df.to_dict('records')
    l =  len(my_list)
    ran = range(l)
    steps=ran[chunk_size::chunk_size]
    steps.extend([l])

    # Inser chunks of the dataframe
    i = 0
    for j in steps:
        print j
        collection.insert_many(my_list[i:j]) # fill de collection
        i = j

    print('Done')
    return
person Rafael Valero    schedule 06.03.2018
comment
Это действительно полезно, спасибо. Вы можете обновить раздел Args (Input) текущими входными данными. - person ximiki; 09.06.2018
comment
AttributeError: объект «диапазон» не имеет атрибута «расширить» - person Aakash Basu; 25.01.2021

как насчет этого:

db.myCollection.insert({id: df.to_json()})

id будет уникальной строкой для этого df

person PasteBT    schedule 23.11.2013
comment
Спасибо, я получаю ошибку InvalidDocument: documents must have only string keys, key was <built-in function id> - person Nyxynyx; 24.11.2013
comment
вы должны сгенерировать этот идентификатор самостоятельно - person PasteBT; 24.11.2013
comment
Этот идентификатор такой же, как обычный _.id в документах монго? Если да, то это выглядит как случайный хеш, как мне его сгенерировать? - person Nyxynyx; 24.11.2013
comment
Это не удается для @Nyxynyx, поскольку id является встроенной функцией в Python, переопределение не рекомендуется. Вы можете сгенерировать простой тестовый идентификатор, используя id(df), но, поскольку идентификаторы объектов не сохраняются между сеансами, это может вызвать проблемы в зависимости от того, как вы его используете. Хотя работает для тестирования. - person erb; 21.04.2014
comment
Я получил maximum recursion level reached ошибку. Исправил с помощью sys.setrecursionlimit(1000000) - person Gabriel Fair; 24.04.2018

Просто сделайте струнные ключи!

import json
dfData = json.dumps(df.to_dict('records'))
savaData = {'_id': 'a8e42ed79f9dae1cefe8781760231ec0', 'df': dfData}
res = client.insert_one(savaData)

##### load dfData
data = client.find_one({'_id': 'a8e42ed79f9dae1cefe8781760231ec0'}).get('df')
dfData = json.loads(data)
df = pd.DataFrame.from_dict(dfData)
person Jon Pan    schedule 27.10.2018

Если вы хотите отправить несколько одновременно:

db.myCollection.insert_many(df.apply(lambda x: x.to_dict(), axis=1).to_list())
person Raphael Mazzine    schedule 23.08.2020

Если вы хотите убедиться, что вы не вызываете ошибки InvalidDocument, хорошей идеей будет что-то вроде следующего. Это связано с тем, что монго не распознает такие типы, как np.int64, np.float64 и т. д.

from pymongo import MongoClient
client = MongoClient()
db = client.test 
col = db.col


def createDocsFromDF(df, collection = None, insertToDB=False):
    docs = [] 
    fields = [col for col in df.columns]
    for i in range(len(df)):
        doc = {col:df[col][i] for col in df.columns if col != 'index'}
        for key, val in doc.items():
            # we have to do this, because mongo does not recognize these np. types
            if type(val) == np.int64:
                doc[key] = int(val)
            if type(val) == np.float64:
                doc[key] = float(val)
            if type(val) == np.bool_:
                doc[key] = bool(val)
        docs.append(doc) 
    if insertToDB and collection:
        db.collection.insert_many(docs)
    return docs 
person bpbirch    schedule 22.01.2021

Я использую следующую часть, чтобы вставить фрейм данных в коллекцию в базе данных.

df.reset_index(inplace=True)
data_dict = df.to_dict("records")
myCollection.insert_many(data_dict)
person Oshidi    schedule 05.06.2021