Pandas合理展開嵌套JSON數(shù)據(jù)的全過程
在數(shù)據(jù)分析實(shí)踐中,我們經(jīng)常需要處理來自 REST API、日志系統(tǒng)或 NoSQL 數(shù)據(jù)庫(如 MongoDB)的嵌套 JSON 數(shù)據(jù)。這類數(shù)據(jù)結(jié)構(gòu)靈活,但往往包含多層嵌套,例如用戶信息中嵌套地址,同時(shí)關(guān)聯(lián)多個(gè)訂單記錄。直接將這樣的數(shù)據(jù)導(dǎo)入 pandas 通常會(huì)得到一列包含字典或列表的“半成品” DataFrame,無法直接用于分析。
雖然 pandas 提供了 pd.json_normalize() 等工具來展開嵌套結(jié)構(gòu),但在處理一對多關(guān)系(如一個(gè)用戶對應(yīng)多個(gè)訂單)時(shí),容易產(chǎn)生大量重復(fù)字段,導(dǎo)致內(nèi)存膨脹和計(jì)算效率下降。本文將通過一個(gè)典型三層嵌套 JSON 示例,系統(tǒng)講解如何合理展開嵌套數(shù)據(jù),并根據(jù)分析目標(biāo)選擇最優(yōu)策略,避免不必要的冗余。
一、問題示例:三層嵌套 JSON
考慮如下數(shù)據(jù)結(jié)構(gòu):
data = [
{
"user_id": 1,
"name": "Alice",
"profile": {
"age": 30,
"address": {
"city": "Beijing",
"country": "China"
}
},
"orders": [
{"order_id": "O1", "amount": 100},
{"order_id": "O2", "amount": 200}
]
},
{
"user_id": 2,
"name": "Bob",
"profile": {
"age": 25,
"address": {
"city": "Shanghai",
"country": "China"
}
},
"orders": [
{"order_id": "O3", "amount": 150}
]
}
]目標(biāo)是將每個(gè)訂單作為一行,同時(shí)附帶用戶的基本信息,形成如下表格:
| user_id | name | age | city | country | order_id | amount |
|---|---|---|---|---|---|---|
| 1 | Alice | 30 | Beijing | China | O1 | 100 |
| 1 | Alice | 30 | Beijing | China | O2 | 200 |
| 2 | Bob | 25 | Shanghai | China | O3 | 150 |
乍看之下,這似乎是一個(gè)簡單的扁平化任務(wù)。但若某個(gè)用戶有成百上千個(gè)訂單,其基本信息將在每一行重復(fù)出現(xiàn),造成顯著的數(shù)據(jù)冗余。
二、基礎(chǔ)方法:使用json_normalize+explode
pandas 的 pd.json_normalize() 是處理嵌套 JSON 的核心工具。它能自動(dòng)將 a.b.c 形式的路徑展開為列名:
import pandas as pd df = pd.json_normalize(data) # 列包括:user_id, name, profile.age, profile.address.city, ...
然而,orders 字段是一個(gè)字典列表,仍以列表形式存在。需進(jìn)一步展開:
# 保留非 orders 字段
df_base = df.drop(columns=['orders'])
# 展開 orders
df_orders = df[['user_id', 'orders']].explode('orders').reset_index(drop=True)
df_orders_flat = pd.json_normalize(df_orders['orders'])
# 合并
result = pd.concat([
df_base.loc[df_base.index.repeat(df['orders'].apply(len))].reset_index(drop=True),
df_orders_flat
], axis=1)
此方法可得到所需寬表,但如前所述,用戶信息被重復(fù)復(fù)制。若訂單數(shù)量龐大,這種冗余將帶來以下問題:
- 內(nèi)存占用急劇增加
- 后續(xù)聚合或分組操作效率降低
- 存儲(chǔ)成本上升(尤其在持久化為 CSV/Parquet 時(shí))
因此,是否展開應(yīng)取決于分析目標(biāo),而非技術(shù)便利性。
三、根據(jù)分析場景選擇策略
場景 1:以訂單為分析單元(如計(jì)算每單金額、地域分布)
此時(shí)需要將用戶屬性“附著”到訂單上,展開是合理的。但可通過以下方式優(yōu)化:
- 使用 category 類型壓縮重復(fù)字符串:
for col in ['name', 'city', 'country']:
result[col] = result[col].astype('category')
這可將內(nèi)存占用降低 50% 以上,尤其適用于高基數(shù)分類變量。
- 僅保留必要字段:避免將整個(gè)用戶對象展開,只提取分析所需的字段(如
city而非完整address)。
場景 2:以用戶為分析單元(如統(tǒng)計(jì)用戶總數(shù)、平均年齡)
此時(shí)不應(yīng)展開訂單。更優(yōu)做法是構(gòu)建兩個(gè)獨(dú)立表,模仿星型模型:
用戶表(users):
| user_id | name | age | city | country |
|---|
訂單表(orders):
| user_id | order_id | amount |
|---|
實(shí)現(xiàn)方式:
# 用戶表
users = pd.json_normalize(data)[[
'user_id', 'name', 'profile.age',
'profile.address.city', 'profile.address.country'
]]
users.columns = ['user_id', 'name', 'age', 'city', 'country']
# 訂單表
orders_list = []
for user in data:
for order in user['orders']:
orders_list.append({
'user_id': user['user_id'],
'order_id': order['order_id'],
'amount': order['amount']
})
orders = pd.DataFrame(orders_list)
后續(xù)分析時(shí)按需關(guān)聯(lián):
# 例如:計(jì)算各城市訂單總額
merged = orders.merge(users[['user_id', 'city']], on='user_id')
summary = merged.groupby('city')['amount'].sum()
這種方式避免了冗余,且便于維護(hù)和擴(kuò)展。
場景 3:數(shù)據(jù)規(guī)模極大(百萬級(jí)以上)
當(dāng)數(shù)據(jù)量超出單機(jī)內(nèi)存限制時(shí),建議:
- 使用 Polars 或 Dask:它們對嵌套結(jié)構(gòu)支持更好,且支持惰性計(jì)算。
- 采用 列式存儲(chǔ)格式(如 Parquet) 并按
user_id分區(qū),減少 I/O 開銷。 - 在 ETL 階段保留原始嵌套結(jié)構(gòu),在查詢時(shí)動(dòng)態(tài)展開所需部分。
四、不要為了“整齊”而盲目展開
一個(gè)常見誤區(qū)是認(rèn)為“所有數(shù)據(jù)都必須變成寬表才便于分析”。實(shí)際上,展開是一種分析前的數(shù)據(jù)準(zhǔn)備手段,不是存儲(chǔ)規(guī)范。
在實(shí)際項(xiàng)目中,推薦的做法是:
- ETL 階段:保留原始結(jié)構(gòu)或拆分為規(guī)范化表;
- 分析階段:根據(jù)具體問題臨時(shí)展開或 join;
- 避免持久化高度冗余的寬表,除非有明確的 BI 報(bào)表需求。
此外,手寫循環(huán)解析雖直觀,但難以處理缺失字段、類型不一致等問題,且不可復(fù)用。相比之下,json_normalize + explode 的組合更具健壯性和擴(kuò)展性。
五、總結(jié)
處理嵌套 JSON 時(shí),pandas 提供了強(qiáng)大而靈活的工具鏈,但關(guān)鍵在于理解數(shù)據(jù)語義與分析目標(biāo)之間的關(guān)系:
- 若分析單位是“子記錄”(如訂單、日志事件),可接受適度冗余,但應(yīng)優(yōu)化存儲(chǔ)類型;
- 若分析單位是“主實(shí)體”(如用戶、設(shè)備),應(yīng)保持?jǐn)?shù)據(jù)規(guī)范化,通過關(guān)聯(lián)獲取細(xì)節(jié);
- 對于超大規(guī)模數(shù)據(jù),考慮更現(xiàn)代的分析引擎(如 Polars)或分布式方案。
最終,高效的數(shù)據(jù)處理不在于“能否展開”,而在于“何時(shí)展開、展開多少、如何管理冗余”。掌握這一思維,才能在復(fù)雜數(shù)據(jù)結(jié)構(gòu)面前游刃有余。
以上就是Pandas合理展開嵌套JSON數(shù)據(jù)的全過程的詳細(xì)內(nèi)容,更多關(guān)于Pandas展開嵌套JSON數(shù)據(jù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Iconfont(矢量圖標(biāo))+iconmoon(圖標(biāo)svg互轉(zhuǎn))配合javascript實(shí)現(xiàn)社交分享系統(tǒng)
這篇文章主要介紹了Iconfont(矢量圖標(biāo))+iconmoon(圖標(biāo)svg互轉(zhuǎn))配合javascript實(shí)現(xiàn)社交分享系統(tǒng),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Python實(shí)現(xiàn)獲取本機(jī)網(wǎng)卡的MAC地址,IP地址和路由表
本文主要為大家詳細(xì)介紹了如何在Windows和Mac系統(tǒng)下獲取本機(jī)MAC地址和IP地址的方法,以及如何使用ARP協(xié)議獲取局域網(wǎng)內(nèi)所有計(jì)算機(jī)的IP地址與MAC地址,感興趣的小伙伴可以了解下2026-05-05
Python 安裝 virturalenv 虛擬環(huán)境的教程詳解
這篇文章主要介紹了Python 安裝 virturalenv 虛擬環(huán)境的教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
關(guān)于Python如何避免循環(huán)導(dǎo)入問題詳解
在大型的Python工程中,由于架構(gòu)設(shè)計(jì)不當(dāng),可能會(huì)出現(xiàn)模塊間相互引用的情況。下面這篇文章主要給大家介紹了關(guān)于如何避免Python的循環(huán)導(dǎo)入問題的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09

