返回文章列表

Pandas資料型別最佳化與時間處理技巧

本文探討Pandas中`category`和`datetime64`資料型別的應用,有效降低記憶體使用並提升效能。文章涵蓋了類別型別的建立、使用、記憶體比較以及缺失值處理,同時也詳細介紹了時間型別的建立、操作、時區轉換、標準化和缺失值處理。此外,文章還介紹了`pd.Timedelta`和PyArrow擴充套件應用,包括

資料科學 Python

Pandas 提供了 categorydatetime64 等特殊資料型別,用於最佳化資料儲存和運算效能。category 型別適用於處理大量重複值的字串資料,透過將字串對映為整數索引,有效降低記憶體消耗。datetime64[ns] 型別則支援納秒級精確度,並提供豐富的時間序列處理功能,方便提取時間元件。在實際應用中,pd.Timedelta 可用於日期時間的加減運算,而 PyArrow 函式庫則提供了更廣泛的日期和列表型別,例如 pa.date32()pa.list_(),以增強與資料函式庫的互操作性和處理更複雜的資料結構。使用 .list 存取器,可以方便地操作 PyArrow 列表型別的資料。

資料型別最佳化:類別型資料與時間型資料處理

在資料分析與處理的過程中,適當的資料型別選擇對於提升效能與降低記憶體使用至關重要。Pandas 提供了多種強大的資料型別以最佳化資料儲存與運算效能,其中最值得注意的是 categorydatetime64 型別。

使用類別型別(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')

記憶體使用比較

比較使用 StringDtypeCategoricalDtype 的記憶體使用差異:

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 中的年份、月份和日期等資訊。這種方法大大簡化了時間序列資料的處理流程,使我們能夠快速取得所需的時間元件進行進一步分析。

最佳實踐與注意事項

  1. 類別型別最佳實踐

    • 明確定義允許值以避免非預期資料出現。
    • 注意缺失值的正確處理方式。
    • 在資料包含大量重複值時優先使用。
  2. 時間型別注意事項

    • 使用 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)

內容解密:

  1. 使用 pd.DatetimeTZDtype(tz="UTC") 指定了時區為 UTC。
  2. 日期時間字串被轉換為帶有時區資訊的日期時間物件。
  3. 輸出的日期時間包含時區偏移量 +00:00,表示 UTC 時區。

時區轉換

Pandas 提供了 dt.tz_localizedt.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)

內容解密:

  1. dt.tz_localize("America/New_York") 將非時區感知的日期時間序列轉換為 America/New_York 時區。
  2. 輸出的日期時間包含時區偏移量 -05:00,表示 America/New_York 時區的標準時間。

時區之間的轉換

# 將 America/New_York 時區的日期時間轉換為 America/Los_Angeles 時區
ser_pt = ser_et.dt.tz_convert("America/Los_Angeles")

print(ser_pt)

內容解密:

  1. dt.tz_convert("America/Los_Angeles") 將 America/New_York 時區的日期時間轉換為 America/Los_Angeles 時區。
  2. 輸出的日期時間包含時區偏移量 -08:00,表示 America/Los_Angeles 時區的標準時間。

日期時間的標準化

可以使用 dt.normalize 方法將日期時間標準化到當天的零點。

# 將日期時間標準化到當天的零點
ser_pt_normalized = ser_pt.dt.normalize()

print(ser_pt_normalized)

內容解密:

  1. dt.normalize() 將日期時間標準化到當天的零點,但仍保留原有的時區資訊。
  2. 輸出的日期時間為當天的零點,時區偏移量保持不變。

缺失值處理

Pandas 使用 pd.NaT 表示日期時間資料中的缺失值。

# 建立包含缺失值的日期時間序列
ser = pd.Series([
    "2024-01-01",
    None,
    "2024-01-03"
], dtype="datetime64[ns]")

print(ser)
print(pd.isna(ser))

內容解密:

  1. None 值被自動轉換為 pd.NaT,表示缺失的日期時間值。
  2. 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)

內容解密:

  1. 兩個日期時間相減得到 timedelta64[ns] 型別的結果,表示時間差。
  2. 時間差包含了天、時、分、秒等資訊。

時間相關資料型別與 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] 存取每個列表的第一個元素。這對於進一步分析和處理複雜的列表資料結構非常有用。輸出的結果分別顯示了列表長度和第一個元素的值。