熊猫将嵌套值与其他列一起切片

Posted

技术标签:

【中文标题】熊猫将嵌套值与其他列一起切片【英文标题】:pandas slicing nested values along with other columns 【发布时间】:2016-06-02 01:12:18 【问题描述】:

我正在尝试从下面的 json 数据中获取嵌套值。


    "region_id": 60763,
    "phone": "",
    "address": 
        "region": "NY",
        "street-address": "147 West 43rd Street",
        "postal-code": "10036",
        "locality": "New York City"
    ,
    "id": 113317,
    "name": "Casablanca Hotel Times Square"


    "region_id": 32655,
    "phone": "",
    "address": 
        "region": "CA",
        "street-address": "300 S Doheny Dr",
        "postal-code": "90048",
        "locality": "Los Angeles"
    ,
    "id": 76049,
    "name": "Four Seasons Hotel Los Angeles at Beverly Hills"

我刚刚将上述数据加载到我的熊猫数据框中,使用:

with open("file path") as f:
    df = pd.DataFrame(json.loads(line) for line in f)

现在我的数据框如下所示:

   address                                              Phone
0  u'region': u'NY', u'street-address': u'147 We...      
1  u'region': u'CA', u'street-address': u'300 S ...   

       id                                             name  region_id
0  113317                    Casablanca Hotel Times Square  60763   
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills  32655   

我可以使用这个来获取列子集 - data = df[['id', 'name']]

但不知道如何获取regionstreet-address 以及idname 的值。我的输出数据框应该有id, name, region, street-address

注意:我试图弹出并将这个嵌套列address 与我的数据框连接起来。但是由于我的数据很大 - 348MB,当我尝试按列 - (轴 - 1)时,连接会占用大量内存。

另外,我正在寻找一种有效的方法来处理这个问题,我是否应该使用将直接使用 C 扩展的 Numpy。或者写入一些数据库,比如 MongoDB。我正在考虑这个,因为在对这些数据进行子集化后,我需要根据 id 列加入这个其他数据集以获取其他几个字段。

【问题讨论】:

【参考方案1】:

一个小的辅助函数就可以解决问题:

def get_entries(line):
    data = json.loads(line)
    res = k: data[k] for k in ['id', 'name']
    res.update(k: data['address'][k] for k in ['region', 'street-address'])
    return res

with open("file path") as f:
    df = pd.DataFrame(get_entries(line) for line in f)

输出:

       id                                             name region  \
0  113317                    Casablanca Hotel Times Square     NY   
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills     CA   

         street-address  
0  147 West 43rd Street  
1       300 S Doheny Dr  

或者,更好看一点:

【讨论】:

你有同样的想法,但你更快。我对你的回答投了赞成票。【参考方案2】:

以下方法可行(不过,我在下面添加了一个更有效的解决方案;只需向下滚动到 EDIT):

import pandas as pd

# read the updated json file
df = pd.read_json('data.json')

# convert column with the nested json structure
tempdf = pd.concat([pd.DataFrame.from_dict(item, orient='index').T for item in df.address])

# get rid of the converted column
df.drop('address', 1, inplace=True)

# prepare concat
tempdf.index = df.index

# merge the two dataframes back together
df = pd.concat([df, tempdf], axis=1)

输出:

       id                                             name phone  region_id  \
0  113317                    Casablanca Hotel Times Square            60763   
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills            32655   

  region        street-address postal-code       locality  
0     NY  147 West 43rd Street       10036  New York City  
1     CA       300 S Doheny Dr       90048    Los Angeles 

现在您可以使用drop 命令删除不需要的列。

我修改了实际上无效的 json 文件;你可以检查一下,例如在JSONLint:

[
    "region_id": 60763,
    "phone": "",
    "address": 
        "region": "NY",
        "street-address": "147 West 43rd Street",
        "postal-code": "10036",
        "locality": "New York City"
    ,
    "id": 113317,
    "name": "Casablanca Hotel Times Square"
, 
    "region_id": 32655,
    "phone": "",
    "address": 
        "region": "CA",
        "street-address": "300 S Doheny Dr",
        "postal-code": "90048",
        "locality": "Los Angeles"
    ,
    "id": 76049,
    "name": "Four Seasons Hotel Los Angeles at Beverly Hills"
]

编辑

在@MaxU 的回答(对我不起作用)的基础上,您还可以执行以下操作:

import pandas as pd
import ujson
from pandas.io.json import json_normalize

# this is the json file from above
with open('data.json') as f:
    data = ujson.load(f)

现在,按照@MaxU 的建议,您可以使用json_normalize 摆脱嵌套结构:

df3 = json_normalize(data)

这给了你:

  address.locality address.postal-code address.region address.street-address      id                                             name phone  region_id
0    New York City               10036             NY   147 West 43rd Street  113317                    Casablanca Hotel Times Square            60763
1      Los Angeles               90048             CA        300 S Doheny Dr   76049  Four Seasons Hotel Los Angeles at Beverly Hills            32655

您可以像这样重命名要保留的列:

df3.rename(columns='address.region': 'region', 'address.street-address': 'street-address', inplace=True)

然后选择您要保留的列:

df3 = df3[['id', 'name', 'region', 'street-address']]

给你想要的输出:

       id                                             name region        street-address
0  113317                    Casablanca Hotel Times Square     NY  147 West 43rd Street
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills     CA       300 S Doheny Dr

【讨论】:

【参考方案3】:

原生 Pandas 解决方案 - pandas.io.json.json_normalize():

更正和工作版本:

import ujson
import pandas as pd
from pandas.io.json import json_normalize

pd.set_option('display.expand_frame_repr', False)

with open('aaa') as f:
    data = ujson.load(f)

df = json_normalize(data)[['id', 'name', 'address.region', 'address.street-address']].rename(columns='address.region': 'region', 'address.street-address': 'street-address')
print(df)

输出:

       id                                             name region        street-address
0  113317                    Casablanca Hotel Times Square     NY  147 West 43rd Street
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills     CA       300 S Doheny Dr

NOT WORKING 版本(正如 Cleb 指出的那样):

import ujson
from pandas.io.json import json_normalize

with open('data.json') as f:
    data = ujson.load(f)

df = json_normalize(data, 'address', ['region', 'street-address'])

pd.set_option('display.expand_frame_repr', False)
print(df)

或者,您可以使用ujson(超快速 JSON)来生成字典列表,然后从中生成一个 DataFrame:

import ujson
import pandas as pd

data_list = []

with open('data.json') as f:
    for line in f:
        d = ujson.loads(line)
        data_list.append(
            "id":d["id"],
             "name":d["name"],
             "region":d["address"]["region"],
             "street-address":d["address"]["street-address"]
            
        )

df = pd.DataFrame(data_list)

pd.set_option('display.expand_frame_repr', False)
print(df)

我不知道哪种解决方案会更高效/更快 - 请根据您的真实数据 (348MiB) 尝试一下,并给我们一个简短的反馈。

PS 如果可能的话,只调用一次 pd.DataFrame/pd.read_json ,否则会慢很多。

输出:

       id                                             name region        street-address
0  113317                    Casablanca Hotel Times Square     NY  147 West 43rd Street
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills     CA       300 S Doheny Dr

【讨论】:

您的第一个方法看起来很棒!但是,我得到KeyError: 'region'。可能是什么原因? 我使用您的方法更新了我的答案,但由于您的脚本无法在我的计算机上运行,​​因此稍作修改。我赞成您的回答,因为我真的很喜欢这种方法并且之前没有听说过json_normalize @Cleb,你是对的。感谢您指出这一点!我测试了另一个(旧)脚本,而不是这个。我会修复它并更新我的答案。 @Cleb 谢谢你们俩。我尝试了这两种选择。不知道为什么,但是当我在代码运行 125 秒左右后尝试正常化时,我不断收到关键错误。但是,ujson 很棒。它只需 10 秒即可运行整个文件,并且可以正常工作。再次感谢。 @Jeeva:很高兴它成功了。但是,您仍然收到密钥错误,这很奇怪;它既不应该出现在 MaxU 的更正版本中,也不应该出现在我下面的版本中(你试过那个)?【参考方案4】:

首先,使用dfidname 列创建一个新数据框。然后遍历每个目标字段(都位于address)并应用lambda 函数从字典中获取项目。

df2 = df[['id', 'name']]
for col in ['region', 'street-address']:
    df2[col] = df.address.apply(lambda j: j.get(col))

>>> df2
       id                                             name region        street-address
0  113317                    Casablanca Hotel Times Square     NY  147 West 43rd Street
1   76049  Four Seasons Hotel Los Angeles at Beverly Hills     CA       300 S Doheny Dr

【讨论】:

以上是关于熊猫将嵌套值与其他列一起切片的主要内容,如果未能解决你的问题,请参考以下文章

遍历熊猫数据框中的列

GroupBy 每周在熊猫中与其他列一起计数

使用熊猫根据正则表达式分离列数据

熊猫数据框中的分割行

如何将熊猫数据框中的嵌套逗号分隔列转换为Python中的特定格式

熊猫:对两列一起排序[重复]