Pandas 是資料科學領域不可或缺的工具,但資料型別管理不當會導致效能瓶頸。尤其在處理布林值和字串時,預設的 object dtype 容易造成資料混亂和效能下降。為此,建議使用 pd.BooleanDtype 和 pd.StringDtype 確保資料型別一致性,並搭配 astype 和 convert_dtypes 方法最佳化 DataFrame 的資料型別,有效提升資料處理效能。此外,CSV 檔案的讀寫效率也至關重要。Pandas 提供了 to_csv 和 read_csv 方法,可透過指定分隔符號、處理參照以及搭配 gzip 壓縮等技巧,有效管理大型 CSV 檔案,降低記憶體使用並提升 I/O 效能。實務上,建議先預覽資料並分析其結構與型別,再根據需求調整資料型別,以達到最佳效能。
資料型別的挑戰與解決方案
在 pandas 中,資料型別的管理是資料處理的基礎。然而,歷史上的布林資料型別卻帶來了一些令人困惑的問題。讓我們從一個簡單的例子開始:
pd.Series([True, False])
輸出結果為:
0 True
1 False
dtype: bool
看起來一切正常,但當我們引入缺失值時,情況就變得複雜了:
pd.Series([True, False, None])
輸出結果為:
0 True
1 False
2 None
dtype: object
這裡出現了 object 資料型別,這是 pandas 中最不理想的資料型別之一。因為它允許任何有效的數值被儲存,從而喪失了資料型別的強制性。
pd.Series([True, False, None, "one of these things", ["is not like"], ["the other"]])
輸出結果為:
0 True
1 False
2 None
3 one of these things
4 [is not like]
5 [the other]
dtype: object
這種混亂的情況可以透過使用 pd.BooleanDtype 來避免:
pd.Series([True, False, None], dtype=pd.BooleanDtype())
輸出結果為:
0 True
1 False
2 <NA>
dtype: boolean
內容解密:
- 使用
pd.BooleanDtype的好處:明確指定布林資料型別,避免資料型別被降級為object。 - 缺失值的處理:在
pd.BooleanDtype中,缺失值被表示為<NA>,保持了資料的一致性。 - 資料型別的重要性:正確的資料型別能夠提高資料處理的效率和準確性。
另一個值得注意的問題是,預設情況下,pandas 將字串資料型別視為 object:
pd.Series(["foo", "bar", "baz"])
輸出結果為:
0 foo
1 bar
2 baz
dtype: object
這同樣會導致資料型別的不嚴謹性。使用 pd.StringDtype() 可以解決這個問題:
ser = pd.Series(["foo", "bar", "baz"], dtype=pd.StringDtype())
ser.iloc[2] = 42
輸出結果會報錯:
TypeError: Cannot set non-string value '42' into a StringArray.
內容解密:
pd.StringDtype()的作用:強制執行字串資料型別,確保資料的一致性和準確性。- 錯誤處理:嘗試將非字串值賦給字串型別的 Series 會引發
TypeError,從而避免了資料汙染。
資料型別管理的最佳實踐
在實際應用中,我們應該盡量避免使用 object 資料型別,因為它會導致效能下降和程式碼維護困難。對於 DataFrame,我們可以使用 astype 方法或 convert_dtypes 方法來轉換資料型別:
df = pd.DataFrame([
["foo", 1, 123.45],
["bar", 2, 333.33],
["baz", 3, 999.99],
], columns=list("abc"))
df.astype({
"a": pd.StringDtype(),
"b": pd.Int64Dtype(),
"c": pd.Float64Dtype(),
})
或者:
df.convert_dtypes(dtype_backend="numpy_nullable")
內容解密:
astype方法的使用:明確指定每列的資料型別,提高資料處理的效率和準確性。convert_dtypes方法的使用:自動轉換 DataFrame 的資料型別,簡化程式碼並提高可讀性。
pandas I/O 系統詳解:CSV 檔案讀寫實務
CSV(逗號分隔值)是一種常見的資料交換格式,儘管缺乏官方標準定義,但大多數開發者和使用者仍將其視為純文字檔案,每行代表一筆資料記錄,各欄位間以特定分隔符號區隔。最常用的分隔符號是逗號,但也常見使用豎線(|)、波浪符號(~)或反引號(`)等其他字元。
CSV 格式的挑戰
CSV 格式簡單易寫,但也因此導致讀取時的困難。CSV 檔案不包含任何後設資料(如分隔符號、參照規則等),也未提供資料型別的相關資訊。這使得 CSV 讀取器必須自行推斷資料結構,不僅增加效能開銷,也容易導致資料誤判。此外,作為純文字格式,CSV 在儲存效率上遠不如 Apache Parquet 等二進位格式。
使用 pandas 讀寫 CSV 檔案
首先,建立一個簡單的 DataFrame 物件,並使用 convert_dtypes 方法最佳化資料型別:
import pandas as pd
df = pd.DataFrame([
["Paul", "McCartney", 1942],
["John", "Lennon", 1940],
["Richard", "Starkey", 1940],
["George", "Harrison", 1943],
], columns=["first", "last", "birth"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")
print(df)
輸出結果:
first last birth
0 Paul McCartney 1942
1 John Lennon 1940
2 Richard Starkey 1940
3 George Harrison 1943
將 DataFrame 寫入 CSV 檔案
使用 to_csv 方法將 DataFrame 寫入 CSV 檔案,為了示範方便,我們使用 io.StringIO 物件暫存檔案內容:
import io
buf = io.StringIO()
df.to_csv(buf)
print(buf.getvalue())
輸出結果:
,first,last,birth
0,Paul,McCartney,1942
1,John,Lennon,1940
2,Richard,Starkey,1940
3,George,Harrison,1943
從 CSV 檔案讀取資料
使用 read_csv 方法讀取 CSV 資料,並指定 dtype_backend="numpy_nullable" 以最佳化資料型別:
buf.seek(0)
df_read = pd.read_csv(buf, dtype_backend="numpy_nullable", index_col=0)
print(df_read)
輸出結果:
first last birth
0 Paul McCartney 1942
1 John Lennon 1940
2 Richard Starkey 1940
3 George Harrison 1943
注意到預設情況下,read_csv 會將原始 DataFrame 的索引視為普通欄位讀入。若要還原始索引,可使用 index_col=0 引數指定第一欄為索引。
處理參照和分隔符號
當資料欄位中包含分隔符號時,pandas 能夠正確處理參照規則。以下範例展示包含逗號的姓名資料:
df_quoted = pd.DataFrame([
["McCartney, Paul", 1942],
["Lennon, John", 1940],
["Starkey, Richard", 1940],
["Harrison, George", 1943],
], columns=["name", "birth"])
df_quoted = df_quoted.convert_dtypes(dtype_backend="numpy_nullable")
print(df_quoted)
輸出結果:
name birth
0 McCartney, Paul 1942
1 Lennon, John 1940
2 Starkey, Richard 1940
3 Harrison, George 1943
使用 to_csv 方法匯出時,pandas 自動處理參照規則,確保資料正確性。
內容解密:
to_csv方法用於將 DataFrame 資料匯出為 CSV 格式,可透過index=False引數避免匯出索引欄位。read_csv方法用於讀取 CSV 資料,並可透過dtype_backend="numpy_nullable"引數最佳化資料型別。index_col=0引數用於指定 CSV 第一欄為索引欄位,以還原始 DataFrame 結構。- 參照規則確保資料中包含分隔符號時仍能正確解析。
使用pandas高效處理CSV檔案
在資料分析的過程中,CSV檔案是一種常見的資料儲存格式。然而,當面對大型CSV檔案時,直接讀取可能會導致記憶體耗盡或效能瓶頸。本章節將介紹如何使用pandas來高效地處理大型CSV檔案。
CSV檔案的寫入與壓縮
首先,讓我們看看如何使用pandas將DataFrame寫入CSV檔案。預設情況下,pandas會使用逗號作為分隔符,並且會對包含特殊字元的欄位進行參照處理。
import pandas as pd
import io
# 建立範例DataFrame
data = {
"name": ["McCartney, Paul", "Lennon, John", "Starkey, Richard", "Harrison, George"],
"birth": [1942, 1940, 1940, 1943]
}
df = pd.DataFrame(data)
# 將DataFrame寫入CSV
buf = io.StringIO()
df.to_csv(buf, index=False)
print(buf.getvalue())
輸出結果:
name,birth
"McCartney, Paul",1942
"Lennon, John",1940
"Starkey, Richard",1940
"Harrison, George",1943
內容解密:
- 使用
io.StringIO()建立一個記憶體中的字串緩衝區。 df.to_csv(buf, index=False)將DataFrame寫入緩衝區,不包含索引欄位。print(buf.getvalue())輸出緩衝區中的內容。
除了使用逗號作為分隔符外,我們還可以指定其他字元作為分隔符。
buf = io.StringIO()
df.to_csv(buf, index=False, sep="|")
print(buf.getvalue())
輸出結果:
name|birth
McCartney, Paul|1942
Lennon, John|1940
Starkey, Richard|1940
Harrison, George|1943
內容解密:
sep="|"指定使用豎線作為分隔符。
對於大型CSV檔案,壓縮可以有效地減少儲存空間。pandas支援多種壓縮格式,包括gzip。
# 建立較大的DataFrame
df_large = pd.DataFrame({
"col1": ["a"] * 1000,
"col2": ["b"] * 1000,
"col3": ["c"] * 1000,
})
# 比較未壓縮與壓縮的檔案大小
buf_uncompressed = io.StringIO()
df_large.to_csv(buf_uncompressed, index=False)
print(f"未壓縮大小:{len(buf_uncompressed.getvalue())} 位元組")
buf_compressed = io.BytesIO()
df_large.to_csv(buf_compressed, index=False, compression="gzip")
print(f"壓縮大小:{len(buf_compressed.getvalue())} 位元組")
輸出結果:
未壓縮大小:6015 位元組
壓縮大小:69 位元組
內容解密:
- 使用
compression="gzip"啟用gzip壓縮。 - 比較未壓縮與壓縮後的檔案大小,可以看出壓縮顯著減少了檔案大小。
高效讀取大型CSV檔案
當處理大型CSV檔案時,直接讀取可能會導致記憶體不足。以下是一些提高讀取效率的方法。
預覽資料
首先,我們可以預覽檔案的前幾行,以瞭解資料的結構和型別。
df_preview = pd.read_csv("data/diamonds.csv", nrows=1000)
print(df_preview.head())
輸出結果:
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75
分析資料型別
接下來,我們可以分析預覽資料的資料型別,以確定是否可以使用更節省記憶體的型別。
print(df_preview.info())
輸出結果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 10 columns):
# Column Non-Null Count Dtype
---
---
---
---
-
---
-
---
---
---
--
0 carat 1000 non-null Float64
1 cut 1000 non-null string
2 color 1000 non-null string
3 clarity 1000 non-null string
4 depth 1000 non-null Float64
5 table 1000 non-null Float64
6 price 1000 non-null Int64
7 x 1000 non-null Float64
8 y 1000 non-null Float64
9 z 1000 non-null Float64
dtypes: Float64(6), Int64(1), string(3)
memory usage: 85.1 KB
調整資料型別
透過分析price欄位的統計資訊,我們發現其範圍在326至2898之間,可以使用Int16型別來儲存,從而節省記憶體。
print(df_preview["price"].describe())
輸出結果:
count 1000.00000
mean 2476.54000
std 839.57562
min 326.00000
25% 2777.00000
50% 2818.00000
75% 2856.00000
max 2898.00000
Name: price, dtype: Float64
使用合適的資料型別讀取大型CSV檔案
根據前面的分析,我們可以使用更合適的資料型別來讀取大型CSV檔案,以減少記憶體使用。
df_large = pd.read_csv("data/diamonds.csv", dtype={"price": "Int16"})
透過上述方法,我們可以更高效地處理大型CSV檔案,避免記憶體不足的問題。