Pandas 提供了 category 和 datetime64 等特殊資料型別,用於最佳化資料儲存和運算效能。category 型別適用於處理大量重複值的字串資料,透過將字串對映為整數索引,有效降低記憶體消耗。datetime64[ns] 型別則支援納秒級精確度,並提供豐富的時間序列處理功能,方便提取時間元件。在實際應用中,pd.Timedelta 可用於日期時間的加減運算,而 PyArrow 函式庫則提供了更廣泛的日期和列表型別,例如 pa.date32() 和 pa.list_(),以增強與資料函式庫的互操作性和處理更複雜的資料結構。使用 .list 存取器,可以方便地操作 PyArrow 列表型別的資料。
資料型別最佳化:類別型資料與時間型資料處理
在資料分析與處理的過程中,適當的資料型別選擇對於提升效能與降低記憶體使用至關重要。Pandas 提供了多種強大的資料型別以最佳化資料儲存與運算效能,其中最值得注意的是 category 與 datetime64 型別。
使用類別型別(Categorical Dtype)最佳化記憶體使用
在處理包含大量重複值的字串資料時,使用 category 型別可以顯著降低記憶體消耗。這是因為 category 型別內部將重複的字串值對映為整數索引,從而減少重複儲存相同字串所佔用的空間。
如何建立和使用類別型別
首先,我們需要定義一個包含允許值的 CategoricalDtype:
accepted_values = ["foo", "bar"]
cat = pd.CategoricalDtype(accepted_values)
ser = pd.Series(["foo", "bar", "foo"], dtype=cat)
print(ser)
輸出結果:
0 foo
1 bar
2 foo
dtype: category
Categories (2, object): ['foo', 'bar']
檢視 ser.cat.codes 可見字串已被對映為整數:
print(ser.cat.codes)
輸出結果:
0 0
1 1
2 0
dtype: int8
同時,ser.cat.categories 儲存了類別的實際值:
print(ser.cat.categories)
輸出結果:
Index(['foo', 'bar'], dtype='object')
記憶體使用比較
比較使用 StringDtype 與 CategoricalDtype 的記憶體使用差異:
import pandas as pd
# 使用 StringDtype
str_series = pd.Series(["foo", "bar", "baz"] * 100, dtype=pd.StringDtype())
print(f"StringDtype memory usage: {str_series.memory_usage()}")
# 使用 CategoricalDtype
cat = pd.CategoricalDtype(["foo", "bar", "baz"])
cat_series = pd.Series(["foo", "bar", "baz"] * 100, dtype=cat)
print(f"CategoricalDtype memory usage: {cat_series.memory_usage()}")
輸出結果顯示,使用 CategoricalDtype 能顯著降低記憶體使用。
處理缺失值
使用 CategoricalDtype 時需注意缺失值的處理。直接建構 CategoricalDtype 可能會導致非預期的 np.nan:
ser = pd.Series(["foo", "bar", pd.NA], dtype=pd.CategoricalDtype())
print(ser)
輸出結果:
0 foo
1 bar
2 NaN
dtype: category
Categories (2, object): ['bar', 'foo']
正確的做法是先建立包含允許值的 CategoricalDtype:
values = pd.Series(["foo", "bar"], dtype=pd.StringDtype())
cat = pd.CategoricalDtype(values)
ser = pd.Series(["foo", "bar", pd.NA], dtype=cat)
print(ser)
輸出結果:
0 foo
1 bar
2 <NA>
dtype: category
Categories (2, string): ['foo', 'bar']
時間型別(Temporal Types)處理
Pandas 使用 datetime64[ns] 型別處理日期與時間資料。此型別支援納秒級精確度,並提供豐富的時間序列處理功能。
建立與操作時間序列
建立一個 datetime64[ns] 型別的 Series:
ser = pd.Series([
"2024-01-01 00:00:00",
"2024-01-02 00:00:01",
"2024-01-03 00:00:02"
], dtype="datetime64[ns]")
print(ser)
輸出結果:
0 2024-01-01 00:00:00
1 2024-01-02 00:00:01
2 2024-01-03 00:00:02
dtype: datetime64[ns]
使用 .dt 存取器提取時間元件:
print(ser.dt.year)
print(ser.dt.month)
print(ser.dt.day)
輸出結果分別顯示年份、月份和日期。
#### 內容解密:
在上述程式碼中,我們使用了 Pandas 的 dt 存取器來提取 datetime64[ns] 型別 Series 中的年份、月份和日期等資訊。這種方法大大簡化了時間序列資料的處理流程,使我們能夠快速取得所需的時間元件進行進一步分析。
最佳實踐與注意事項
類別型別最佳實踐:
- 明確定義允許值以避免非預期資料出現。
- 注意缺失值的正確處理方式。
- 在資料包含大量重複值時優先使用。
時間型別注意事項:
- 使用
datetime64[ns]確保納秒級精確度。 - 利用
.dt存取器進行時間元件提取。 - 注意日期與時間的儲存差異,避免資料截斷。
- 使用
綜上所述,合理選擇與使用 Pandas 中的特殊資料型別,不僅能夠提升資料處理的效率,還能有效降低記憶體消耗,為大規模資料分析提供堅實的基礎。
日期時間資料處理與時區管理
在處理日期時間資料時,時區的管理是至關重要的。Pandas 提供了豐富的功能來處理日期時間資料,包括時區的轉換和管理。
時區感知與非時區感知的日期時間
預設情況下,Pandas 中的日期時間資料是時區非感知的(timezone-naive),這意味著它們不包含任何時區資訊。這種情況下,同一日期時間在不同時區代表不同的絕對時間點。
建立時區感知日期時間
可以使用 pd.DatetimeTZDtype 並指定 tz 引數來建立時區感知的日期時間序列。
import pandas as pd
# 建立 UTC 時區的日期時間序列
ser_utc = pd.Series([
"2024-01-01 00:00:01",
"2024-01-02 00:00:01",
"2024-01-03 00:00:01"
], dtype=pd.DatetimeTZDtype(tz="UTC"))
print(ser_utc)
內容解密:
- 使用
pd.DatetimeTZDtype(tz="UTC")指定了時區為 UTC。 - 日期時間字串被轉換為帶有時區資訊的日期時間物件。
- 輸出的日期時間包含時區偏移量
+00:00,表示 UTC 時區。
時區轉換
Pandas 提供了 dt.tz_localize 和 dt.tz_convert 方法來處理時區轉換。
將非時區感知的日期時間轉換為時區感知
# 建立非時區感知的日期時間序列
ser_no_tz = pd.Series([
"2024-01-01 00:00:00",
"2024-01-01 00:01:10",
"2024-01-01 00:02:42"
], dtype="datetime64[ns]")
# 將其轉換為 America/New_York 時區
ser_et = ser_no_tz.dt.tz_localize("America/New_York")
print(ser_et)
內容解密:
dt.tz_localize("America/New_York")將非時區感知的日期時間序列轉換為 America/New_York 時區。- 輸出的日期時間包含時區偏移量
-05:00,表示 America/New_York 時區的標準時間。
時區之間的轉換
# 將 America/New_York 時區的日期時間轉換為 America/Los_Angeles 時區
ser_pt = ser_et.dt.tz_convert("America/Los_Angeles")
print(ser_pt)
內容解密:
dt.tz_convert("America/Los_Angeles")將 America/New_York 時區的日期時間轉換為 America/Los_Angeles 時區。- 輸出的日期時間包含時區偏移量
-08:00,表示 America/Los_Angeles 時區的標準時間。
日期時間的標準化
可以使用 dt.normalize 方法將日期時間標準化到當天的零點。
# 將日期時間標準化到當天的零點
ser_pt_normalized = ser_pt.dt.normalize()
print(ser_pt_normalized)
內容解密:
dt.normalize()將日期時間標準化到當天的零點,但仍保留原有的時區資訊。- 輸出的日期時間為當天的零點,時區偏移量保持不變。
缺失值處理
Pandas 使用 pd.NaT 表示日期時間資料中的缺失值。
# 建立包含缺失值的日期時間序列
ser = pd.Series([
"2024-01-01",
None,
"2024-01-03"
], dtype="datetime64[ns]")
print(ser)
print(pd.isna(ser))
內容解密:
None值被自動轉換為pd.NaT,表示缺失的日期時間值。pd.isna(ser)正確識別出pd.NaT為缺失值。
時間差(Timedelta)
Pandas 中的 timedelta64 資料型別用於表示兩個日期時間之間的差值。
建立時間差
# 建立日期時間序列
ser = pd.Series([
"2024-01-01",
"2024-01-02",
"2024-01-03"
], dtype="datetime64[ns]")
# 計算與特定日期的時間差
time_diff = ser - pd.Timestamp("2023-12-31 12:00:00")
print(time_diff)
內容解密:
- 兩個日期時間相減得到
timedelta64[ns]型別的結果,表示時間差。 - 時間差包含了天、時、分、秒等資訊。
時間相關資料型別與 PyArrow 擴充套件應用
在 pandas 中,除了常見的 datetime 型別外,還有 pd.Timedelta 標量可供使用,用於對日期時間進行持續時間的加減運算。例如,若要對一個 pd.Series 中的每個日期時間加上 3 天,可以使用以下程式碼:
ser + pd.Timedelta("3 days")
輸出結果如下:
0 2024-01-04
1 2024-01-05
2 2024-01-06
dtype: datetime64[ns]
內容解密:
這段程式碼展示瞭如何使用 pd.Timedelta 對 datetime 型別的 Series 進行運算。pd.Timedelta("3 days") 建立了一個表示 3 天持續時間的 Timedelta 物件,將其加到 ser 的每個元素上,相當於將每個日期往後推 3 天。輸出的結果仍然是一個 datetime64[ns] 型別的 Series。
手動建立 Timedelta Series
若需要手動建立一個包含 timedelta 物件的 Series,可以使用 dtype="timedelta64[ns]":
pd.Series([
"-1 days",
"6 hours",
"42 minutes",
"12 seconds",
"8 milliseconds",
"4 microseconds",
"300 nanoseconds",
], dtype="timedelta64[ns]")
輸出結果如下:
0 -1 days +00:00:00
1 0 days 06:00:00
2 0 days 00:42:00
3 0 days 00:00:12
4 0 days 00:00:00.008000
5 0 days 00:00:00.000004
6 0 days 00:00:00.000000300
dtype: timedelta64[ns]
內容解密:
這段程式碼展示瞭如何建立一個包含不同時間單位的 timedelta Series。透過指定 dtype="timedelta64[ns]",pandas 可以正確解析並儲存這些字串所表示的時間差。輸出的結果顯示了每個 timedelta 的具體數值和單位,其中負數表示往前推算的時間差。
使用 PyArrow 的日期與列表型別
在某些情況下,pandas 的內建資料型別可能無法滿足需求,例如與資料函式庫的互操作性。Apache Arrow 專案定義了真正的 DATE 型別,從 pandas 2.0 版本開始,使用者可以透過 PyArrow 函式庫利用這些型別。
建立 PyArrow 日期型別 Series
要使用 PyArrow 的日期型別,可以透過 pd.ArrowDtype(pa.date32()) 建立:
ser = pd.Series([
"2024-01-01",
"2024-01-02",
"2024-01-03",
], dtype=pd.ArrowDtype(pa.date32()))
ser
輸出結果如下:
0 2024-01-01
1 2024-01-02
2 2024-01-03
dtype: date32[day][pyarrow]
內容解密:
這段程式碼展示瞭如何使用 PyArrow 的 date32() 型別建立一個日期 Series。pa.date32() 可以表達更廣泛的日期範圍,而不需要調整精確度。輸出的結果顯示了正確的日期值和對應的資料型別。
處理複雜資料結構:PyArrow 的列表型別
在實際應用中,資料往往不會簡單地符合 DataFrame 的結構。例如,若要表示公司員工之間的隸屬關係,可以使用 PyArrow 的 pa.list_() 資料型別。
建立包含列表型別的 DataFrame
ser = pd.Series([
["Bob", "Michael"],
None,
None,
["Janice"],
None,
], dtype=pd.ArrowDtype(pa.list_(pa.string())))
df["direct_reports"] = ser
df
輸出結果如下:
name years_exp direct_reports
0 Alice 10 [Bob, Michael]
1 Bob 2 <NA>
2 Janice 4 <NA>
3 Jim 8 [Janice]
4 Michael 6 <NA>
內容解密:
這段程式碼展示瞭如何使用 PyArrow 的 list_ 型別來表示員工之間的隸屬關係。透過將 pa.list_(pa.string()) 用於儲存下屬員工姓名的列表,可以更自然地表達這種層級結構。輸出的結果顯示了 DataFrame 如何包含具有不同下屬數量的員工記錄。
使用列表存取器(list accessor)
當 Series 使用 PyArrow 的列表型別時,可以利用 .list 存取器來解鎖更多功能,例如取得列表長度或存取特定位置的元素:
ser.list.len()
ser.list[0]
輸出結果如下:
0 2
1 <NA>
2 <NA>
3 1
4 <NA>
dtype: int32[pyarrow]
0 Bob
1 <NA>
2 <NA>
3 Janice
4 <NA>
dtype: string[pyarrow]
內容解密:
這段程式碼展示瞭如何使用 .list.len() 方法計算每個列表元素的長度,以及如何使用 .list[0] 存取每個列表的第一個元素。這對於進一步分析和處理複雜的列表資料結構非常有用。輸出的結果分別顯示了列表長度和第一個元素的值。