Pandas:JSON 将包含字典列表的列规范化为列

问题描述 投票:0回答:1

我有一个数据文件,其中包含一大堆用作关系数据库的 JSON 文件。将它们放入 pandas 中很简单,但我正在尝试生成结合来自多个列和多个数据帧的数据的表格报告。每个表行都有一个 id 字段,该字段由其他表中的一个或多个单元格引用,因此我可以像在 SQL 中一样进行联接。

但是,其中一些键/ID 包含在各个单元格中的嵌套 JSON 对象中。例如,一列包含一个字典列表,每个字典都包含一对键:值对,表示数据帧给定行中对象的键:值标签元数据。第一个键:值对是引用不同表中标签键的 id,第二个键:值对包含标签的实际值。因此,最终目标是提取这些键/值标签并将值放入以键命名的列中。我需要对尽可能多的标签执行此操作,该数字从一行到下一行都是可变的。

如果我进行 json 标准化,它会将该列表扩展为数据框中具有数字索引(对应于列表)的列。

传入的 JSON 如下所示:

deviceJSON = {
  "devices": [
    {
      "name": "foo",
      "noteIds": [],
      "vendor": "Aruba",
      "model": "2920F",
      "tags": [],
      "id": <guid>,
    },
    {
      "location": {
        "floor": <guid>,
        "coord": {
          "x": 100,
          "y": 200
        }
      },
      "name": "bar",
      "noteIds": ["32b60e81-8f9d-4ea2-9af0-8e441d31649b"],
      "vendor": "Cisco",
      "model": "6500",
      "tags": [
        {
          "tagKeyId": "68a9a2e4-9d03-42dd-8744-ed0d241f1746",
          "value": "IDF 1"
        },
        {
          "tagKeyId": "22a1adc4-bf73-4c52-a3f2-3bfca9d64011",
          "value": "e-Waste"
        }
      ],
      "id": <guid>,
    }
  ]
}

deviceDF=pd.DataFrame(deviceJSON['devices'])

因此,当将此 JSON 导入 Pandas 时,tagsnoteIds,但这超出了本问题的范围)被导入到单个列中,并且该列的内容是该字典中包含的列表。

我最终需要得到的是标签键为 tag_keyname 或只是 keyname 的列,以及该列中的值(键名位于由 tagKeyId 索引的另一个 Dataframe 中):

tagKeysJSON = {
  "tagKeys": [
    {
      "key": "IDF",
      "id": "68a9a2e4-9d03-42dd-8744-ed0d241f1746",
    },
    {
      "key": "Disposition",
      "id": "22a1adc4-bf73-4c52-a3f2-3bfca9d64011",
    }
  ]
}

tagKeysDF=pd.DataFrame(tagKeysJSON['tagKeys'])

当我这样做时:

deviceDF=deviceDF.join(pd.json_normalize(deviceDF.tags))

我在包含字典的标签中得到了标记为列表索引的列:

第 0 列:

{"tagKeyId": "68a9a2e4-9d03-42dd-8744-ed0d241f1746","value": "IDF 1"}

第 1 栏:

{"tagKeyId": "22a1adc4-bf73-4c52-a3f2-3bfca9d64011","value": "e-Waste"}

由于列表中标签的数量可能会有所不同并且它们可以按任何顺序排列,因此我无法真正进行另一轮规范化并使用各自的键保留正确的值,因为另一轮规范化给我留下了列tagKeyIdvalue,而不是将值放在相关键的列中。

我以前曾这样做过,但它已被弃用并且效率低下:

tagsRawDF=deviceDF[['id','tags']]
tagsListDF=pd.DataFrame()
tagnameList=[]

for row in tagsRawDF.iterrows():
    taglist=row[1][1]
    df_dict={'deviceId' : row[1][0]}
    for tag in row[1][1]:
        tagrec=tagKeysDF.loc[tagKeysDF["id"]==tag['tagKeyId']]
        tagname='tag_'+tagrec.key.values[0].replace(" ","_")

        # Have we seen this tag name before? 
        # (this list is being built to be used later in the output phase)
        if tagname not in tagnameList : tagnameList.append(tagname) 

        df_dict[tagname]=tag['value']
    tmp_df = pd.DataFrame(df_dict, index=[0])

    tagsListDF = tagsListDF._append(tmp_df) # append the tmp_df to our final df

tagsListDF.reset_index(drop=True) 

deviceDF=pd.merge(deviceDF, tagsListDF, left_on='id', right_on='id',how='left')

它给出的错误消息是常见的:

FutureWarning:Series.getitem 将键视为位置已被弃用。在未来的版本中,整数键将始终被视为标签(与 DataFrame 行为一致)。要按位置访问值,请使用

ser.iloc[pos]
对于行[1][1]中的标签:

这是奇怪的具体但同时没有帮助,因为我无法找到任何好的文档来解释如何重构代码以使用 iloc 来代替。

所以,我对如何实际解决这个问题有点困惑——如果标签的数量是可预测的,那就很简单了,但这现在已经超出了我对 Pandas 的有限知识(虽然文档很好,但它并不如果我不知道我真正在寻找什么,请帮助我)。

python pandas
1个回答
0
投票
import pandas as pd

# Input JSON data
deviceJSON = {
    "devices": [
        {
            "name": "foo",
            "noteIds": [],
            "vendor": "Aruba",
            "model": "2920F",
            "tags": [],
            "id": "device1",
        },
        {
            "location": {
                "floor": "floor1",
                "coord": {
                    "x": 100,
                    "y": 200
                }
            },
            "name": "bar",
            "noteIds": ["32b60e81-8f9d-4ea2-9af0-8e441d31649b"],
            "vendor": "Cisco",
            "model": "6500",
            "tags": [
                {
                    "tagKeyId": "68a9a2e4-9d03-42dd-8744-ed0d241f1746",
                    "value": "IDF 1"
                },
                {
                    "tagKeyId": "22a1adc4-bf73-4c52-a3f2-3bfca9d64011",
                    "value": "e-Waste"
                }
            ],
            "id": "device2",
        }
    ]
}

tagKeysJSON = {
    "tagKeys": [
        {
            "key": "IDF",
            "id": "68a9a2e4-9d03-42dd-8744-ed0d241f1746",
        },
        {
            "key": "Disposition",
            "id": "22a1adc4-bf73-4c52-a3f2-3bfca9d64011",
        }
    ]
}

# Create DataFrames
deviceDF = pd.DataFrame(deviceJSON['devices'])
tagKeysDF = pd.DataFrame(tagKeysJSON['tagKeys'])

# Expand the tags column into multiple rows
tags_expanded = deviceDF.explode('tags')

# Extract tagKeyId and value from the tags column
tags_expanded['tagKeyId'] = tags_expanded['tags'].apply(lambda x: x['tagKeyId'] if isinstance(x, dict) else None)
tags_expanded['tagValue'] = tags_expanded['tags'].apply(lambda x: x['value'] if isinstance(x, dict) else None)

# Keep only the necessary columns
tags_expanded = tags_expanded[['id', 'tagKeyId', 'tagValue']]

# Merge with tagKeysDF to get tag names
merged_tags = pd.merge(tags_expanded, tagKeysDF, left_on='tagKeyId', right_on='id', how='left')

# Pivot to place tag values into columns named after the tags
pivot_tags = merged_tags.pivot(index='id', columns='key', values='tagValue')

# Merge back with the original deviceDF
final_df = deviceDF.merge(pivot_tags, on='id', how='left')

# Display the final result
print(final_df)
© www.soinside.com 2019 - 2024. All rights reserved.