Pandas 提供了多種資料型別,選擇正確的型別對於資料處理效能至關重要。類別型資料適用於有限分類別變數,透過內部整數對映有效降低記憶體佔用。時間型資料則用於處理日期與時間,納秒精確度足以應付大多數應用場景,並可透過 dt 存取器輕鬆提取時間資訊。然而,預設的時間戳是時區無知的,需使用 pd.DatetimeTZDtype 或 tz_localize、tz_convert 確保跨時區資料處理的正確性。時間差計算則可透過兩個時間序列相減獲得,單位可依需求調整。更進一步地,PyArrow 擴充套件了 Pandas 的功能,提供更精確的時間和十進位資料型別。pd.Timedelta 可用於日期時間加減運算,而 PyArrow 的 list_ 型別則能處理更複雜的資料結構,例如儲存員工及其直屬下屬的列表。最後,PyArrow 的十進位制資料型別,例如 pa.decimal128(),能有效避免浮點數誤差,確保金融資料或高精確度計算的準確性,彌補了 Pandas 原生型別系統的不足。
資料型別最佳化:類別型資料與時間型資料處理
在資料分析過程中,適當的資料型別選擇對於提升效能和降低記憶體使用至關重要。本篇將探討 pandas 中兩種重要的資料型別:類別型(Categorical)與時間型(Temporal),並介紹如何有效運用這兩種型別來最佳化資料處理。
類別型資料(Categorical Data)
類別型資料是一種特殊的資料型別,用於表示有限且固定的分類別變數。在 pandas 中,類別型資料透過 pd.CategoricalDtype 來定義。
建立類別型資料
accepted_values = ["foo", "bar"]
cat = pd.CategoricalDtype(accepted_values)
ser = pd.Series(["foo", "bar", "foo"], dtype=cat)
ser
輸出結果:
0 foo
1 bar
2 foo
dtype: category
Categories (2, object): ['foo', 'bar']
內容解密:
- 類別對映:pandas 內部將類別值對映為整數索引,例如
foo對應0,bar對應1。 - 記憶體最佳化:類別型資料顯著降低記憶體使用,特別是在重複值較多的情況下。
- 使用限制:需注意缺失值的處理,因為直接使用
pd.CategoricalDtype()可能引入np.nan而非預期的pd.NA。
實際應用與記憶體比較
# 一般字串型別記憶體使用
pd.Series(["foo", "bar", "baz"] * 100, dtype=pd.StringDtype()).memory_usage()
# 2528 位元組
# 類別型別記憶體使用
cat = pd.CategoricalDtype(["foo", "bar", "baz"])
pd.Series(["foo", "bar", "baz"] * 100, dtype=cat).memory_usage()
# 552 位元組
內容解密:
- 類別型別的記憶體佔用遠低於字串型別。
- 這種最佳化在處理大量重複分類別資料時尤為重要。
時間型資料(Temporal Data)
時間型資料用於處理日期與時間相關的資料,是時間序列分析的基礎。pandas 使用 datetime64[ns] 來表示納秒精確度的時間資料。
建立時間型資料
ser = pd.Series([
"2024-01-01 00:00:00",
"2024-01-02 00:00:01",
"2024-01-03 00:00:02"
], dtype="datetime64[ns]")
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存取器可方便提取年、月、日等資訊。 - 內部儲存:即使只顯示日期,pandas 內部仍以完整時間戳儲存。
時間資訊提取範例
# 提取年份資訊
ser.dt.year
# 提取月份資訊
ser.dt.month
# 提取日期資訊
ser.dt.day
輸出結果範例(年份):
0 2024
1 2024
2 2024
dtype: int32
圖表翻譯:
以下 Mermaid 圖表展示了時間型資料的內部處理流程:
graph LR
A[原始字串] --> B[轉換為 datetime64]
B --> C[內部以納秒儲存]
C --> D[使用 dt 存取器提取資訊]
D --> E[取得年/月/日等屬性]
圖表翻譯:
- 原始字串首先被轉換為
datetime64格式。 - 資料內部以納秒精確度儲存。
- 使用
dt存取器可以進一步提取所需時間資訊。 - 可獲得年、月、日等具體屬性值。
時間序列資料處理:深入理解時區與時間差
在處理時間序列資料時,時區和時間差的概念至關重要。pandas 函式庫提供了豐富的功能來處理這些挑戰,從建立時區感知(timezone-aware)的時間戳到計算時間差(timedeltas)。
時區感知時間戳
預設情況下,pandas 中的時間戳是時區無知(timezone-naive)的,這意味著它們不包含任何時區資訊。這在處理跨時區的資料時會造成混淆。為瞭解決這個問題,pandas 提供了 pd.DatetimeTZDtype 來建立時區感知的時間戳。
建立時區感知時間戳
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 時區的偏移量為零。 - 這種方式確保了時間戳的時區資訊被正確儲存。
時區轉換
當資料的時區資訊缺失,但已知其所屬時區時,可以使用 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)
# 轉換到 America/Los_Angeles 時區
ser_pt = ser_et.dt.tz_convert("America/Los_Angeles")
print(ser_pt)
內容解密:
dt.tz_localize("America/New_York")為無時區資訊的時間序列增加了 America/New_York 的時區資訊。dt.tz_convert("America/Los_Angeles")將時間序列從 America/New_York 時區轉換到了 America/Los_Angeles 時區。- 輸出結果中的
-05:00和-08:00分別表示兩個時區相對於 UTC 的偏移量。
時間差(Timedeltas)
時間差用於測量兩個時間點之間的持續時間。在 pandas 中,時間差可以透過對兩個 datetime 型別的 Series 進行減法運算得到。
計算時間差
# 建立 datetime 型別的 Series
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)
內容解密:
- 透過對 datetime 型別的 Series 和一個特定的 Timestamp 進行減法運算,得到一個 timedelta64[ns] 型別的 Series。
- 輸出結果表示每個日期與指定日期之間的時間差。
- 時間差的單位可以是納秒(ns)、微秒(us)、毫秒(ms)或秒(s),取決於指定的精確度。
時間相關資料型別與 PyArrow 擴充套件應用
在 pandas 中,除了常見的日期時間(datetime)處理外,還有 pd.Timedelta 這個標量可用於對日期時間進行加減運算。例如,若要對一個 pd.Series 中的每個日期時間加上 3 天,可以使用以下程式碼:
ser + pd.Timedelta("3 days")
內容解密:
pd.Timedelta用於表示時間間隔,可以用在運算式中對日期時間進行加減。- 在範例中,
ser是一個包含日期時間的pd.Series,透過加上pd.Timedelta("3 days"),每個日期時間都被往後推 3 天。 - 這種操作對於處理需要時間偏移的資料非常有用,例如計算未來日期或過去日期。
手動建立 Timedelta 物件的 Series
如果需要手動建立一個包含 timedelta 物件的 pd.Series,可以使用 dtype="timedelta64[ns]" 引數:
pd.Series([
"-1 days",
"6 hours",
"42 minutes",
"12 seconds",
"8 milliseconds",
"4 microseconds",
"300 nanoseconds",
], dtype="timedelta64[ns]")
內容解密:
- 這段程式碼展示瞭如何手動建立一個包含不同時間單位的
timedelta物件的Series。 - 使用
dtype="timedelta64[ns]"保證了所有時間間隔都被轉換為奈秒(nanoseconds)單位,這使得不同單位的時間可以直接比較和運算。 - 輸出的結果顯示了每個時間間隔在轉換後的具體數值。
無法建立月份為單位的 Timedelta
若嘗試建立一個以月份為單位的 timedelta,會遇到錯誤:
pd.Series([
"1 months",
], dtype="timedelta64[ns]")
執行結果會出現 ValueError: invalid unit abbreviation: months,原因是月份作為時間單位並不固定(可能是 28、29、30 或 31 天),不符合 timedelta 對固定時間間隔的要求。
內容解密:
timedelta需要表示一個固定的時間長度,而月份的天數不固定,因此無法直接用於timedelta。- 若需要進行涉及月份的時間運算,可以考慮使用
pd.DateOffset物件,這將在後續章節中介紹。
PyArrow 的 Temporal 資料型別
Apache Arrow 專案定義了真正的 DATE 型別,從 pandas 2.0 版本開始,使用者可以透過 PyArrow 函式庫利用這些型別。
如何使用 PyArrow 建立日期型別
可以使用 pd.ArrowDtype(pa.date32()) 建立一個日期型別的 Series:
ser = pd.Series([
"2024-01-01",
"2024-01-02",
"2024-01-03",
], dtype=pd.ArrowDtype(pa.date32()))
內容解密:
- 這裡使用
pa.date32()定義了一個日期型別,並透過pd.ArrowDtype將其套用到Series中。 - 這種型別可以表示更廣泛的日期範圍,且不需要擔心精確度切換的問題。
PyArrow 的 List 型別
在某些情況下,資料並不能簡單地放在單一的 DataFrame 中,例如公司員工及其直屬下屬的關係。這時可以使用 PyArrow 的 pa.list_() 資料型別來表達更複雜的資料結構。
如何使用 PyArrow 的 List 型別
首先,建立一個包含員工名稱及其直屬下屬的 Series:
ser = pd.Series([
["Bob", "Michael"],
None,
None,
["Janice"],
None,
], dtype=pd.ArrowDtype(pa.list_(pa.string())))
然後,將這個 Series 新增到原有的 DataFrame 中:
df["direct_reports"] = ser
內容解密:
- 這段程式碼展示瞭如何使用 PyArrow 的
list_型別來表示複雜的資料關係,例如員工與其直屬下屬。 - 使用
.list存取器可以進一步操作這些列表資料,例如取得列表長度或存取特定位置的元素。
使用 .list 存取器操作 List 資料
對於具有 PyArrow list 型別的 Series,可以使用 .list.len() 方法來取得列表長度,或使用 .list[0] 語法來存取特定位置的元素:
ser.list.len()
ser.list[0]
內容解密:
.list.len()用於計算每個列表中的元素數量。.list[0]用於存取每個列表中的第一個元素。- 這些操作對於分析和處理複雜的列表資料非常有用。
綜上所述,pandas 結合 PyArrow 可以有效地處理多種時間相關和複雜結構的資料,為資料分析和處理提供了更大的彈性和功能。
精確計算的關鍵:PyArrow 的十進位制資料型別
在處理金融資料或需要高度精確的計算時,浮點數的誤差可能會導致嚴重的後果。PyArrow 的十進位制資料型別(Decimal Data Types)提供了一種解決方案,能夠確保計算的精確性。
為什麼需要十進位制資料型別?
浮點數在電腦中是以二進製表示的,這可能導致精確度的損失。例如,簡單的加法運算可能會產生微小的誤差。在大多數情況下,這些誤差是可以接受的,但對於金融或科學計算來說,這些誤差可能是無法容忍的。
如何使用 PyArrow 的十進位制資料型別?
PyArrow 提供了 pa.decimal128() 和 pa.decimal256() 兩種十進位制資料型別。其中,pa.decimal128() 可以支援最多 38 位有效數字,而 pa.decimal256() 則可以支援更多的有效數字。
使用範例
import pandas as pd
import pyarrow as pa
# 建立一個包含十進位制數字的 Series
data = pd.Series([
"123456789.123456789",
"-987654321.987654321",
"99999999.9999999999",
], dtype=pd.ArrowDtype(pa.decimal128(19, 10)))
print(data)
輸出結果:
0 123456789.1234567890
1 -987654321.9876543210
2 99999999.9999999999
dtype: decimal128(19, 10)[pyarrow]
#### 內容解密:
pa.decimal128(19, 10)表示該十進位制數字的總位數為 19 位,其中小數點後有 10 位。- 使用字串來表示十進位制數字可以避免浮點數誤差的問題。
- 如果直接使用浮點數,則會產生誤差,如下所示:
data = pd.Series([
123456789.123456789,
-987654321.987654321,
99999999.9999999999,
], dtype=pd.ArrowDtype(pa.decimal128(19, 10)))
print(data)
輸出結果:
0 123456789.1234567910
1 -987654321.9876543283
2 100000000.0000000000
dtype: decimal128(19, 10)[pyarrow]
#### 內容解密:
- 直接使用浮點數會導致精確度的損失。
- 使用
decimal模組可以避免這個問題。
使用 decimal 模組
Python 的 decimal 模組提供了另一種方式來處理十進位制數字。
import decimal
# 建立十進位制數字
num1 = decimal.Decimal("99999999.9999999999")
num2 = decimal.Decimal("100000000.0")
# 比較兩個數字
print(num1 == num2) # False
# 進行算術運算
result = num1 + num2
print(result) # Decimal('199999999.9999999999')
#### 內容解密:
decimal.Decimal可以精確地表示十進位制數字。- 使用
decimal.Decimal可以避免浮點數誤差的問題。
NumPy 型別系統和物件型別的陷阱
在 pandas 中,預設的資料型別可能不是最優選擇。瞭解 NumPy 型別系統和物件型別的陷阱對於避免潛在問題非常重要。
使用範例
import pandas as pd
# 建立一個包含整數的 Series
data = pd.Series([0, 1, 2])
print(data.dtype) # int64
# 建立一個包含缺失值的 Series
data = pd.Series([0, None, 2])
print(data.dtype) # float64
#### 內容解密:
- 當 Series 中包含缺失值時,pandas 會將資料型別轉換為浮點數。
- 使用
dtype引數無法直接指定整數型別,因為缺失值無法表示為整數。