将嵌套的 mongoDB 文档转换为平面 pandas DataFrame(对象数组中的对象数组)

Posted

技术标签:

【中文标题】将嵌套的 mongoDB 文档转换为平面 pandas DataFrame(对象数组中的对象数组)【英文标题】:Convert nested mongoDB document into flat pandas DataFrame (Array of objects within array of objects) 【发布时间】:2019-09-30 07:06:43 【问题描述】:

我正在尝试将 mongoDB 文档转换为平面 pandas 数据框结构。

我的 mongoDB 集合结构示例:

data = collection.find_one('ID':300)
print(data)

'_id': "ObjectId('5cd932299f6b7d4c9b95af6c')",
 'ID': 300,
 'updated': 23424,
 'data': [
      'meta': 8,
       'data': [
           'value1': 1, 'value2': 2, 
           'value1': 3, 'value2': 4
       ]
     ,
      'meta': 9,
       'data': [
           'value1': 5, 'value2': 6
       ]
     
  ]

当我把它放入熊猫数据框时,我得到了

df = pd.DataFrame(data)
print(df)

| _id                      | ID  | updated | data                                              
|
|--------------------------|-----|---------|------------------------ ---------------------------|
| 5cd936779f6b7d4c9b95af6d | 300 | 23424   | 'meta': 8, 'data': ['value1': 1, 'value2': 2... |
| 5cd936779f6b7d4c9b95af6d | 300 | 23424   | 'meta': 9, 'data': ['value1': 5, 'value2': 6] |

当我使用 pd.concat 遍历数据框时,我得到了

df.rename(columns='data':'data1', inplace=True)
df2 = pd.concat([df, pd.DataFrame(list(df['data1']))], axis=1).drop('data1', 1)
df3 = pd.concat([df2, pd.DataFrame(list(df2['data']))], axis=1).drop('data', 1)
print(df3)

| _id                      | ID  | updated | meta | 0                          | 1                          |
|--------------------------|-----|---------|------|----------------------------|----------------------------|
| 5cd936779f6b7d4c9b95af6d | 300 | 23424   | 8    | 'value1': 1, 'value2': 2 | 'value1': 3, 'value2': 4 |
| 5cd936779f6b7d4c9b95af6d | 300 | 23424   | 9    | 'value1': 5, 'value2': 6 | None                       |

最低层数组的最低层对象始终具有相同的名称。

所以我想要:

| ID  | updated | meta | value1 | value2 |
|-----|---------|------|--------|--------|
| 300 | 23424   | 8    | 1      | 2      |
| 300 | 23424   | 8    | 3      | 4      |
| 300 | 23424   | 9    | 5      | 6      |

我是不是走错了路?

解决这个问题最方便的方法是什么?

【问题讨论】:

【参考方案1】:

@sinB - 您可以通过删除 for 循环来进一步改进这一点(在处理包含许多文档的数据库时会导致问题)。无论如何您都不需要循环,因为可以使用单个命令将结果转换为 pandas 数据帧。

而不是这个:

#add each doc as a new row in dataframe
for doc in collection.aggregate(pipeline): 
    df = df.append(doc,ignore_index=True)

你可以用这个

query_result = collection.aggregate(pipeline)
query_result = list(query_result)
df = pd.io.json.json_normalize(query_result)

【讨论】:

【参考方案2】:

我意识到 mongoDB 可以完成所有繁重的工作。

工作代码:

import pandas as pd
from pymongo import MongoClient
mongoClient = MongoClient('localhost', 27017)
db = mongoClient.DB_NAME
collection = db.COLLECTION_NAME

pipeline = [
    '$match':'ID':300,
    "$unwind":'path': '$data', 'preserveNullAndEmptyArrays': True,
    "$unwind":'path': '$data.data', 'preserveNullAndEmptyArrays': True,
    '$project':
      'ID':'$ID',
      'updated':"$updated",
      'meta':"$data.meta",
      'value1':"$data.data.value1",
      'value2':"$data.data.value2"
    
]

#Make empty dataframe
df = pd.DataFrame() 

#add each doc as a new row in dataframe
for doc in collection.aggregate(pipeline): 
    df = df.append(doc,ignore_index=True)

print(df)

输出:

| ID  | updated | meta | value1 | value2 |
|-----|---------|------|--------|--------|
| 300 | 23424   | 8    | 1      | 2      |
| 300 | 23424   | 8    | 3      | 4      |
| 300 | 23424   | 9    | 5      | 6      |

【讨论】:

感谢@sinB .... df 也可以直接创建为 df = pd.DataFrame(collection.aggregate(pipeline))【参考方案3】:

好吧,我设法以最可怕的方式解决了它。

def flatten(data):
    a = 
    def make_dict(data):
        for i in list(data):
            if isinstance(data[i], list):
                for j in data[i]:
                    make_dict(j)
            else:
                a.update(i:[])
        return data

    def add_to_dict(data):
        for i in list(data):
            if isinstance(data[i], list):
                for j in data[i]:
                    add_to_dict(j)
            else:
                a[i].append(data[i])
        max = 0
        for i in a:
            if len(a[i]) > max:
                max = len(a[i])
        for i in a:
            if len(a[i]) < max:
                a[i].append(a[i][-1])

    make_dict(data)
    add_to_dict(data)
    return a


pd.DataFrame(flatten(data))

输出:

| ID  | updated | meta | value1 | value2 |
|-----|---------|------|--------|--------|
| 300 | 23424   | 8    | 1      | 2      |
| 300 | 23424   | 8    | 3      | 4      |
| 300 | 23424   | 9    | 5      | 6      |

我无法想象这是一个好的解决方案,所以请随时帮助我找到更好的解决方案。

【讨论】:

以上是关于将嵌套的 mongoDB 文档转换为平面 pandas DataFrame(对象数组中的对象数组)的主要内容,如果未能解决你的问题,请参考以下文章

将查询构建器条件转换为 MongoDB 操作,包括嵌套的子文档数组

将平面对象数组转换为嵌套对象数组[重复]

将嵌套集合转换为平面数据表

将嵌套的 MongoDB 导入到 Pandas

使用 python 将嵌套 json 转换为平面 json 时遇到困难

如何将 JSON 对象的嵌套部分转换为点链式平面 JSON?