Pandas 提供了 to_json 方法,可以將 DataFrame 轉換成 JSON 格式,其中 orient 引數扮演著關鍵角色,它決定了 JSON 輸出的結構。orient 提供了多種選項,例如 columns、records、split、index、values 和 table 等,每種選項都適用於不同的場景。選擇正確的 orient 值,可以有效地控制 JSON 輸出的結構,方便後續的資料處理和應用。在資料科學領域,處理 JSON 資料是常見的需求,Pandas 的 to_json 方法提供了一個便捷的工具,可以將 DataFrame 轉換成各種 JSON 格式,方便資料交換和儲存。理解 orient 引數的用法,可以提升資料處理的效率和靈活性。
JSON 資料格式化:pandas DataFrame 的不同表現方式
在實務上,將表格資料表示為 JSON 格式的方法有許多種。不同的使用者可能會希望以不同的方式呈現資料,例如將 DataFrame 的每一行表示為 JSON 陣列,或者將每一列表示為陣列。有時候,使用者可能希望將行標籤、列標籤及資料分別表示為獨立的 JSON 物件,或者完全不關心行列標籤。為了滿足這些需求,pandas 提供了 orient 引數,讓使用者可以根據需求選擇 JSON 的佈局方式。
以下是 orient 的各種選項及其特性:
- columns(預設):產生 JSON 物件,其中鍵是列標籤,值是另一個物件,這個物件將行標籤對應到資料點。
- records:每一行 DataFrame 都以 JSON 陣列表示,包含將欄位名稱對應到資料點的物件。
- split:對映到
{"columns": [...], "index": [...], "data": [...]}。欄位和索引值都是標籤的陣列,而資料包含陣列中的陣列。 - index:類別似於
columns,但將行和欄位標籤作為鍵的使用方式相反。 - values:將 DataFrame 的資料對映到陣列中的陣列。行和欄位標籤被丟棄。
- table:遵循 JSON Table Schema。
這些 orient 選項之間存在權衡,涉及資料損失、冗長度以及最終使用者需求。例如,orient="table" 是最少損失且產生最大負載的選項,而 orient="values" 則完全處於另一端。
實際範例
以下是一個簡單的 DataFrame 範例:
import pandas as pd
beatles = {
'first': ['Paul', 'John', 'Richard', 'George'],
'last': ['McCartney', 'Lennon', 'Starkey', 'Harrison'],
'birth': [1942, 1940, 1940, 1943]
}
df = pd.DataFrame(beatles, index=["row 0", "row 1", "row 2", "row 3"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")
columns 型態
當使用 orient="columns" 時,資料會以 {"column":{"row": value, "row": value, ...}, ...} 的形式儲存。
serialized = df.to_json(orient="columns")
print(f'Length of orient="columns": {len(serialized)}')
print(serialized[:100])
records 型態
當使用 orient="records" 時,每一行都會以 [{"col": value, "col": value, ...}, ...] 的形式儲存。
serialized = df.to_json(orient="records")
print(f'Length of orient="records": {len(serialized)}')
print(serialized[:100])
split 型態
當使用 orient="split" 時,行標籤、欄位標籤和資料會分別儲存。
serialized = df.to_json(orient="split")
print(f'Length of orient="split": {len(serialized)}')
print(serialized[:100])
index 型態
當使用 orient="index" 時,與 columns 型態類別似,但將行和欄位標籤作為鍵的使用方式相反。
serialized = df.to_json(orient="index")
print(f'Length of orient="index": {len(serialized)}')
print(serialized[:100])
values 型態
當使用 orient="values" 時,既不保留行也不保留欄位標籤。
serialized = df.to_json(orient="values")
print(f'Length of orient="values": {len(serialized)}')
print(serialized[:100])
table 型態
當使用 orient="table" 時,這是最冗長但唯一被標準支援(JSON Table Schema)的形式。
serialized = df.to_json(orient="table")
print(f'Length of orient="table": {len(serialized)}')
print(serialized[:100])
各種 orient 的比較
| orient | 特性 | 說明 | |
–|
-|
-| | columns | 預設格式 | 每個欄位以物件形式呈現,包含所有行 | | records | 每行以物件形式呈現 | 不保留行索引 | | split | 權衡呈現方式 | 分別保留欄位、索引和資料 | | index | 行和欄位互換 | 與 columns 類別似但角色互換 | | values | 最簡單呈現方式 | 不保留任何索引 | | table | 最完整且標準化呈現方式 | 採用 JSON Table Schema |
各種 orient 在實務上的應用
不同的 orient 型態在實務上有不同的應用場景:
- columns 和 index:適合需要保留詳細索引資訊的情況。
- records:適合需要快速讀取每行資料的情況。
- split:適合需要完整重建 DataFrame 的情況。
- values:適合只關心資料本身而不在乎索引的情況。
- table:適合需要遵循 JSON Table Schema 標準的情況。
內容解密:
這段程式碼展示瞭如何在 pandas 中將 DataFrame 轉換成 JSON 格式,並介紹了不同的 orient 引數及其特性。透過實際範例展示瞭如何使用各種 orient 引數來轉換 DataFrame。這些方法在不同的應用場景中有不同的優勢和劣勢,玄貓建議根據具體需求來選擇最合適的方法。
將 JSON 資料轉換為資料框架
在處理 JSON 資料時,我們通常會面臨如何將其轉換為資料框架(DataFrame)的挑戰。Pandas 提供了多種方法來完成這一任務,但每種方法都有其特定的應用場景和優缺點。以下玄貓將詳細介紹如何使用不同的 orient 引數來讀取和寫入 JSON 資料,並解釋其背後的技術原理。
使用 JSON 表格格式
JSON 表格格式(Table Schema)之所以更為詳細,是因為它儲存了關於被序列化資料的後設資料,這與 Apache Parquet 格式類別似(儘管功能上不如 Parquet)。當使用其他 orient 引數時,Pandas 需要在讀取資料的過程中推斷資料型別,而 JSON 表格格式則保留了這些資訊。
假設我們有一個 DataFrame,並且已經使用 Pandas 擴充套件型別進行了型別轉換:
import pandas as pd
import io
# 假設我們有一個 DataFrame
data = {
"birth": [1990, 1995, 2000]
}
df = pd.DataFrame(data)
# 轉換為 UInt16Dtype
df["birth"] = df["birth"].astype(pd.UInt16Dtype())
# 序列化為 JSON 表格格式
serialized = df.to_json(orient="table")
# 認真讀取 JSON 表格格式
df_read = pd.read_json(
io.StringIO(serialized),
orient="table",
)
# 檢查資料型別
print(df_read.dtypes)
內容解密:
在此範例中,我們首先建立了一個簡單的 DataFrame,並將其中的一列轉換為 UInt16Dtype。接著,我們使用 to_json 方法將 DataFrame 序列化為 JSON 表格格式。這樣做的好處是,我們不需要在讀取時指定 dtype_backend 引數,因為 JSON 表格格式已經保留了資料型別資訊。
當我們再次讀取這些序列化的資料時,Pandas 能夠準確地還原原始的資料型別。這是因為 JSON 表格格式在序列化過程中保留了關於資料型別的後設資料。
排除不必要的鍵值
在某些情況下,JSON 資料中可能包含不必要的鍵值,這些鍵值可能會干擾到我們的資料處理。例如,假設我們從一個 REST API 取得了以下 JSON 資料:
data = {
"records": [{
"name": "human",
"characteristics": {
"num_leg": 2,
"num_eyes": 2
}
}, {
"name": "dog",
"characteristics": {
"num_leg": 4,
"num_eyes": 2
}
}, {
"name": "horseshoe crab",
"characteristics": {
"num_leg": 10,
"num_eyes": 10
}
}],
"type": "animal",
"pagination": {
"next": "23978sdlkusdf97234u2io",
"has_more": 1
}
}
在這個例子中,pagination 鍵值對於我們的報告沒有什麼價值,反而可能會影響到 JSON 的序列化過程。我們實際上只關心 records 鍵值所對應的陣列。
排除 pagination 鍵值
我們可以使用 pd.json_normalize 函式來處理這個問題。這個函式可以將 JSON 資料轉換為表格格式,並排除不必要的鍵值:
# 忽略 pagination 鍵值
normalized_data = pd.json_normalize(
data,
record_path="records"
).convert_dtypes(dtype_backend="numpy_nullable")
print(normalized_data)
內容解密:
在這裡,我們使用 record_path 引數來指定要處理的鍵值路徑。這樣做可以忽略掉 pagination 鍵值,但也會丟失掉 type 鍵值所包含的後設資料。如果我們需要保留 type 鍵值中的資訊,可以使用 meta 引數:
# 忽略 pagination 鍵值並保留 type 鍵值
normalized_data_with_meta = pd.json_normalize(
data,
record_path="records",
meta="type"
).convert_dtypes(dtype_backend="numpy_nullable")
print(normalized_data_with_meta)
內容解密:
在此範例中,我們除了指定 record_path 外,還使用了 meta 引數來保留 type 鍵值中的資訊。這樣做可以確保我們在轉換過程中不會丟失重要的後設資料。
從網頁抓取與處理表格資料
擷取特定表格
在實務應用中,常常需要從網頁中抓取特定的表格資料。以「The Beatles」的音樂專輯為例,這些資料通常會以表格形式呈現在Wikipedia上。我們可以使用Pandas的read_html函式來擷取這些表格。
首先,我們需要指定要擷取的表格,這可以透過match引數來達成。match引數可以是字串或正規表示式,用來比對HTML中的<caption>標籤內容。例如,Wikipedia頁面上的「The Beatles」音樂專輯列表標題為「List of studio albums」,我們可以將這個字串作為match引數的值。
以下是擷取「The Beatles」音樂專輯列表的完整程式碼:
import pandas as pd
url = "https://en.wikipedia.org/wiki/The_Beatles_discography"
dfs = pd.read_html(
url,
match=r"List of studio albums",
dtype_backend="numpy_nullable",
)
print(f"Number of tables returned was: {len(dfs)}")
dfs[0].filter(regex=r"Title|UK|AUS|CAN").head()
內容解密:
- 擷取特定表格:使用
pd.read_html函式並指定match=r"List of studio albums"來擷取包含「List of studio albums」標題的表格。 - 指定資料型態:透過
dtype_backend="numpy_nullable"來確保資料型態處理正確。 - 篩選特定欄位:使用
.filter(regex=r"Title|UK|AUS|CAN")來篩選出我們感興趣的欄位(專輯標題、英國、澳洲及加拿大的排行榜位置)。 - 顯示前五筆資料:使用
.head()方法顯示篩選後的前五筆資料。
處理多層次欄位名稱
在Wikipedia的表格中,常見的是多層次的欄位名稱,例如「Peak chart positions」下方有不同國家的欄位名稱。Pandas會將這些多層次的欄位名稱轉換成pd.MultiIndex。為了讓表格更易讀,我們可以使用header=1引數來忽略第一層的多層次欄位名稱。
以下是修改後的程式碼:
import pandas as pd
url = "https://en.wikipedia.org/wiki/The_Beatles_discography"
dfs = pd.read_html(
url,
match="List of studio albums",
header=1,
dtype_backend="numpy_nullable",
)
dfs[0].filter(regex=r"Title|UK|AUS|CAN").head()
內容解密:
- 忽略第一層欄位名稱:透過
header=1引數來忽略第一層的多層次欄位名稱,使得表格結構更簡單。 - 篩選與顯示:與之前相同,使用
.filter(regex=r"Title|UK|AUS|CAN")篩選出我們感興趣的欄位,並使用.head()顯示前五筆資料。
處理缺失值
Wikipedia常用「—」來表示缺失值。我們可以使用Pandas的na_values引數來將這些符號轉換成缺失值。
以下是處理缺失值的程式碼:
import pandas as pd
url = "https://en.wikipedia.org/wiki/The_Beatles_discography"
dfs = pd.read_html(
url,
match="List of studio albums",
header=1,
na_values=["—"],
dtype_backend="numpy_nullable",
)
dfs[0].filter(regex=r"Title|UK|AUS|CAN").head()
內容解密:
- 指定缺失值符號:透過
na_values=["—"]引數來指定「—」為缺失值。 - 篩選與顯示:與之前相同,使用
.filter(regex=r"Title|UK|AUS|CAN")篩選出我們感興趣的欄位,並使用.head()顯示前五筆資料。
非程式碼主題:資料來源與信任度
在處理外部資料時,來源的信任度至關重要。Wikipedia是一個開放編輯的平台,雖然其內容通常經過社群檢視和修正,但仍可能存在誤誤或過時資訊。因此,在使用Wikipedia資料時,應該結合其他可靠來源進行交叉驗證。此外,如果這些資料將用於正式報告或研究,應該考慮使用更正式和可靠的資料來源。