IO 工具(文字、CSV、HDF5 等)#
pandas I/O API 是一組頂層 reader
函數,存取方式類似 pandas.read_csv()
,通常會傳回一個 pandas 物件。對應的 writer
函數是物件方法,存取方式類似 DataFrame.to_csv()
。以下是包含可用 readers
和 writers
的表格。
格式類型 |
資料說明 |
讀取器 |
寫入器 |
---|---|---|---|
文字 |
|||
文字 |
固定寬度文字檔 |
||
文字 |
|||
文字 |
|||
文字 |
|||
文字 |
|||
文字 |
本機剪貼簿 |
||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
二進位 |
|||
SQL |
|||
SQL |
在此針對其中一些 IO 方法進行非正式效能比較。
注意
對於使用 StringIO
類別的範例,請務必使用 from io import StringIO
匯入 Python 3。
CSV 和文字檔案#
用於讀取文字檔案(又稱平面檔案)的強力函數為 read_csv()
。請參閱 食譜以取得一些進階策略。
剖析選項#
read_csv()
接受下列常見引數
基本#
- filepath_or_buffer各種
檔案路徑(
str
、pathlib.Path
或py:py._path.local.LocalPath
)、URL(包括 http、ftp 和 S3 位置)或任何具有read()
方法的物件(例如開啟的檔案或StringIO
)。- sepstr,預設為
','
,適用於read_csv()
,\t
適用於read_table()
要使用的分隔符號。如果 sep 為
None
,C 引擎無法自動偵測分隔符號,但 Python 解析引擎可以,這表示後者將會使用 Python 內建的偵測工具csv.Sniffer
自動偵測分隔符號。此外,長度超過 1 個字元且不同於'\s+'
的分隔符號將會被視為正規表示式,並強制使用 Python 解析引擎。請注意,正規表示式分隔符號容易忽略加了引號的資料。正規表示式範例:'\\r\\t'
。- delimiterstr,預設為
None
sep 的替代引數名稱。
- delim_whitespace布林值,預設為 False
指定是否將空白(例如
' '
或'\t'
)用作分隔符號。等同於設定sep='\s+'
。如果將此選項設定為True
,則不應傳入任何內容作為delimiter
參數。
欄位和索引位置及名稱#
- header整數或整數清單,預設為
'infer'
用作欄位名稱和資料開頭的行號。預設行為是推論欄位名稱:如果沒有傳遞名稱,則行為與
header=0
相同,且欄位名稱會從檔案的第一行推論,如果欄位名稱明確傳遞,則行為與header=None
相同。明確傳遞header=0
以便替換現有名稱。標頭可以是指定欄位上 MultiIndex 行位置的整數清單,例如
[0,1,3]
。未指定的介入列會略過(例如,此範例中的 2 會略過)。請注意,如果skip_blank_lines=True
,此參數會忽略註解行和空白行,因此 header=0 表示資料的第一行,而不是檔案的第一行。- names陣列類似,預設
None
要使用的欄位名稱清單。如果檔案不包含標頭列,則您應該明確傳遞
header=None
。此清單中不允許有重複項。- index_col整數、字串、整數/字串序列,或 False,選用,預設
None
用作
DataFrame
列標籤的欄位,以字串名稱或欄位索引提供。如果提供整數/字串序列,則使用 MultiIndex。注意
index_col=False
可用於強制 pandas 不 使用第一欄作為索引,例如當您有一個格式錯誤的檔案,每一行的結尾都有分隔符號時。None
的預設值指示 pandas 猜測。如果欄位標題列的欄位數等於資料檔案主體的欄位數,則會使用預設索引。如果較大,則會使用第一個欄位作為索引,以便主體中剩餘的欄位數等於標題中的欄位數。標題後的第 1 列用於確定欄位數,這些欄位數會進入索引。如果後續列包含的欄位少於第 1 列,則會以
NaN
填滿。這可透過
usecols
避免。這可確保欄位按原樣取得,並忽略尾隨資料。- usecols類清單或可呼叫,預設
None
傳回子集的欄位。如果類似清單,所有元素都必須是位置 (即文件欄位中的整數索引) 或字串,對應於使用者在
names
中提供的欄位名稱,或從文件標題列推論。如果給定names
,文件標題列將不會列入考量。例如,有效的類似清單usecols
參數會是[0, 1, 2]
或['foo', 'bar', 'baz']
。元素順序會被忽略,因此
usecols=[0, 1]
與[1, 0]
相同。若要從data
建立 DataFrame,並保留元素順序,請使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]
(欄位順序為['foo', 'bar']
) 或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]
(順序為['bar', 'foo']
)。如果可呼叫,可呼叫函式會針對欄位名稱進行評估,傳回可呼叫函式評估為 True 的名稱
In [1]: import pandas as pd In [2]: from io import StringIO In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [4]: pd.read_csv(StringIO(data)) Out[4]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"]) Out[5]: col1 col3 0 a 1 1 a 2 2 c 3
使用此參數時,在使用 c 引擎時,解析時間會快很多,而且記憶體用量會比較低。Python 引擎會先載入資料,然後再決定要捨棄哪些欄位。
一般解析設定#
- dtype類型名稱或欄位 -> 類型的字典,預設
None
資料或欄位的資料類型。例如:
{'a': np.float64, 'b': np.int32, 'c': 'Int64'}
使用str
或object
搭配適當的na_values
設定,以保留並非 dtype 解釋。如果指定轉換器,它們將會用於取代 dtype 轉換。1.5.0 版的新增功能: 新增對 defaultdict 的支援。指定 defaultdict 作為輸入,其中預設值會決定未明確列出的欄位的 dtype。
- dtype_backend{“numpy_nullable”, “pyarrow”},預設為 NumPy 支援的資料框
要使用的 dtype_backend,例如,資料框是否應具有 NumPy 陣列,當設定為 “numpy_nullable” 時,所有具有可為空實作的 dtype 都會使用可為空 dtype,如果設定為 “pyarrow”,則所有 dtype 都會使用 pyarrow。
dtype_backend 仍處於實驗階段。
2.0 版的新增功能。
- engine{
'c'
,'python'
,'pyarrow'
} 要使用的解析器引擎。C 和 pyarrow 引擎較快,而 python 引擎目前功能較為完整。目前僅 pyarrow 引擎支援多執行緒。
1.4.0 版的新增功能: 新增 “pyarrow” 引擎作為實驗性引擎,此引擎有一些功能不支援,或可能無法正確運作。
- convertersdict,預設為
None
用於轉換特定欄位中值的函式字典。金鑰可以是整數或欄位標籤。
- true_values清單,預設為
None
視為
True
的值。- false_values清單,預設
None
視為
False
的值。- skipinitialspace布林值,預設
False
略過分隔符號後的空白。
- skiprows類清單或整數,預設
None
要略過的行號(0 起始)或檔案開頭要略過的行數(整數)。
如果可呼叫,可呼叫函式會針對列索引進行評估,如果列應該略過則傳回 True,否則傳回 False
In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [7]: pd.read_csv(StringIO(data)) Out[7]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0) Out[8]: col1 col2 col3 0 a b 2
- skipfooter整數,預設
0
檔案底部要略過的行數(引擎為「c」時不支援)。
- nrows整數,預設
None
要讀取的檔案列數。對於讀取大型檔案的部分內容很有用。
- low_memory布林值,預設
True
在區塊中內部處理檔案,會降低剖析時的記憶體使用量,但可能會造成混合型推論。若要確保沒有混合型,請設定為
False
,或使用dtype
參數指定型別。請注意,整個檔案會讀取到單一DataFrame
中,不論如何,請使用chunksize
或iterator
參數以區塊方式傳回資料。(僅 C 剖析器有效)- memory_map布林值,預設為 False
如果為
filepath_or_buffer
提供檔案路徑,將檔案物件直接對應到記憶體,並直接從那裡存取資料。使用這個選項可以提升效能,因為不再有任何 I/O 負擔。
NA 和遺失資料處理#
- na_values純量、字串、類清單或字典,預設
None
可識別為 NA/NaN 的其他字串。如果傳遞字典,則為特定每欄 NA 值。請參閱下方的 na values const,以取得預設會詮釋為 NaN 的值清單。
- keep_default_na布林值,預設
True
在剖析資料時是否納入預設 NaN 值。視傳入
na_values
的情況而定,行為如下:如果
keep_default_na
為True
,且指定na_values
,則會將na_values
附加到用於剖析的預設 NaN 值。如果
keep_default_na
為True
,且未指定na_values
,則僅使用預設 NaN 值進行剖析。如果
keep_default_na
為False
,且指定na_values
,則僅使用指定在na_values
中的 NaN 值進行剖析。如果
keep_default_na
為False
,且未指定na_values
,則不會將任何字串剖析為 NaN。
請注意,如果將
na_filter
傳入為False
,則會略過keep_default_na
和na_values
參數。- na_filter布林值,預設
True
偵測遺失值標記(空字串和 na_values 的值)。在沒有任何 NA 的資料中,傳遞
na_filter=False
可以提升讀取大型檔案的效能。- verbose布林值,預設
False
指出放置在非數字欄位中的 NA 值數量。
- skip_blank_lines布林值,預設
True
如果
True
,略過空白行,而不是詮釋為 NaN 值。
日期時間處理#
- parse_dates布林值或整數或名稱清單或清單清單或字典,預設
False
。 如果
True
-> 嘗試剖析索引。如果
[1, 2, 3]
-> 嘗試將欄位 1、2、3 各自剖析為一個獨立的日期欄位。如果
[[1, 3]]
-> 結合欄位 1 和 3,並剖析為一個單一的日期欄位。如果
{'foo': [1, 3]}
-> 將欄位 1、3 剖析為日期,並將結果稱為「foo」。
注意
iso8601 格式日期有一個快速路徑。
- infer_datetime_format布林值,預設
False
如果
True
且啟用某一欄位的 parse_dates,嘗試推斷日期時間格式以加速處理。自 2.0.0 版開始已棄用: 此參數的嚴格版本現在為預設值,傳遞它不會產生任何效果。
- keep_date_col布林值,預設
False
如果
True
且 parse_dates 指定要合併多個欄位,則保留原始欄位。- date_parser函式,預設
None
用於將一連串字串欄位轉換為日期時間實例陣列的函式。預設使用
dateutil.parser.parser
進行轉換。pandas 會嘗試以三種不同的方式呼叫 date_parser,如果發生例外狀況,則進階到下一個:1) 傳遞一個或多個陣列 (由 parse_dates 定義) 作為參數;2) 將由 parse_dates 定義的欄位中的字串值 (按列) 串接為單一陣列並傳遞;3) 使用一個或多個字串 (對應於由 parse_dates 定義的欄位) 作為參數,針對每一列呼叫 date_parser 一次。自 2.0.0 版開始已棄用: 改用
date_format
,或讀入為object
,然後視需要套用to_datetime()
。- date_format字串或欄位 -> 格式的字典,預設
None
如果與
parse_dates
併用,將根據此格式解析日期。對於更複雜的內容,請讀入為object
,然後視需要套用to_datetime()
。2.0.0 版的新功能。
- dayfirst布林值,預設
False
DD/MM 格式日期,國際和歐洲格式。
- cache_dates布林值,預設 True
如果為 True,請使用唯一已轉換日期的快取來套用日期時間轉換。在解析重複的日期字串時可能會大幅提升速度,特別是帶有時區偏移的字串。
反覆運算#
- iterator布林值,預設
False
傳回
TextFileReader
物件以進行反覆運算或使用get_chunk()
取得區塊。- chunksize整數,預設
None
傳回
TextFileReader
物件以進行反覆運算。請參閱以下 反覆運算和區塊處理。
引用、壓縮和檔案格式#
- compression{
'infer'
,'gzip'
,'bz2'
,'zip'
,'xz'
,'zstd'
,None
,dict
}, 預設'infer'
用於對磁碟資料進行即時解壓縮。如果為「推論」,則在
filepath_or_buffer
為路徑且以「.gz」、「.bz2」、「.zip」、「.xz」、「.zst」結尾時,分別使用 gzip、bz2、zip、xz 或 zstandard,否則不進行解壓縮。如果使用「zip」,則 ZIP 檔案中只能包含一個要讀取的資料檔案。設定為None
以不進行解壓縮。也可以是一個字典,其中包含設定為 {'zip'
、'gzip'
、'bz2'
、'zstd'
} 之一的'method'
鍵,而其他鍵值對會轉發到zipfile.ZipFile
、gzip.GzipFile
、bz2.BZ2File
或zstandard.ZstdDecompressor
。舉例來說,可以傳遞以下內容以進行更快的壓縮並建立可重製的 gzip 檔案:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}
。在 1.2.0 版本中變更: 先前版本會將「gzip」的字典項目轉發到
gzip.open
。- thousandsstr,預設為
None
千位分隔符號。
- decimalstr,預設為
'.'
用於辨識小數點的字元。例如,對歐洲資料使用
','
。- float_precision字串,預設為 None
指定 C 引擎應對浮點值使用哪個轉換器。選項為
None
(一般轉換器)、high
(高精度轉換器)和round_trip
(往返轉換器)。- lineterminatorstr(長度 1),預設為
None
將檔案切割成多行的字元。僅適用於 C 剖析器。
- quotechar字串 (長度 1)
用於表示引述項目開始和結束的字元。引述項目可以包含分隔符號,而分隔符號將會被忽略。
- quoting整數或
csv.QUOTE_*
執行個體,預設0
根據
csv.QUOTE_*
常數控制欄位引述行為。使用QUOTE_MINIMAL
(0)、QUOTE_ALL
(1)、QUOTE_NONNUMERIC
(2) 或QUOTE_NONE
(3) 之一。- doublequote布林值,預設
True
當指定
quotechar
且quoting
不是QUOTE_NONE
時,指出是否要將欄位內部連續兩個quotechar
元素解釋為單一quotechar
元素。- escapechar字串 (長度 1),預設
None
引述為
QUOTE_NONE
時用於跳脫分隔符號的單一字元字串。- comment字串,預設
None
表示不應解析該行的剩餘部分。如果在行開頭找到,則將完全忽略該行。此參數必須是一個單一字元。與空行一樣(只要
skip_blank_lines=True
),完全註解的行會被header
參數忽略,但不會被skiprows
忽略。例如,如果comment='#'
,使用header=0
解析「#empty\na,b,c\n1,2,3」,將會導致「a,b,c」被視為標頭。- 編碼字串,預設
None
讀取/寫入時要使用的 UTF 編碼(例如
'utf-8'
)。Python 標準編碼清單。- 方言字串或
csv.Dialect
執行個體,預設None
如果提供此參數,它將覆寫下列參數的值(預設或非預設):
delimiter
、doublequote
、escapechar
、skipinitialspace
、quotechar
和quoting
。如果需要覆寫值,將會發出 ParserWarning。請參閱csv.Dialect
文件以取得更多詳細資料。
錯誤處理#
- on_bad_lines(‘error’, ‘warn’, ‘skip’), 預設為 ‘error’
指定在遇到錯誤行(欄位過多的行)時要執行的動作。允許的值為
‘error’,在遇到錯誤行時引發 ParserError。
‘warn’,在遇到錯誤行時印出警告並略過該行。
‘skip’,在遇到錯誤行時略過,不引發錯誤或警告。
1.3.0 版的新增功能。
指定欄位資料類型#
您可以指定整個 DataFrame
或個別欄位的資料類型
In [9]: import numpy as np
In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"
In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11
In [12]: df = pd.read_csv(StringIO(data), dtype=object)
In [13]: df
Out[13]:
a b c d
0 1 2 3 4
1 5 6 7 8
2 9 10 11 NaN
In [14]: df["a"][0]
Out[14]: '1'
In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})
In [16]: df.dtypes
Out[16]:
a int64
b object
c float64
d Int64
dtype: object
很幸運地,pandas 提供多種方法來確保您的欄位只包含一種 dtype
。如果您不熟悉這些概念,您可以參閱 這裡 以深入了解 dtypes,以及 這裡 以深入了解 pandas 中的 object
轉換。
例如,您可以使用 converters
參數 read_csv()
In [17]: data = "col_1\n1\n2\n'A'\n4.22"
In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})
In [19]: df
Out[19]:
col_1
0 1
1 2
2 'A'
3 4.22
In [20]: df["col_1"].apply(type).value_counts()
Out[20]:
col_1
<class 'str'> 4
Name: count, dtype: int64
或者,您可以在讀取資料後使用 to_numeric()
函數來強制轉換資料類型,
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
這將把所有有效的解析轉換為浮點數,並將無效的解析保留為 NaN
。
最終,您處理讀取包含混合資料類型的欄位的方式取決於您的特定需求。在上述情況中,如果您想將資料異常值標示為 NaN
,那麼 to_numeric()
可能會是您的最佳選擇。但是,如果您希望所有資料都強制轉換,無論類型為何,那麼使用 converters
參數 read_csv()
絕對值得一試。
注意
在某些情況下,讀取包含混合資料類型的欄位的異常資料將導致資料集不一致。如果您依賴 pandas 來推斷欄位的資料類型,那麼解析引擎將會推斷不同資料區塊的資料類型,而不是一次推斷整個資料集。因此,您可能會得到具有混合資料類型的欄位。例如,
In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))
In [26]: df = pd.DataFrame({"col_1": col_1})
In [27]: df.to_csv("foo.csv")
In [28]: mixed_df = pd.read_csv("foo.csv")
In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]:
col_1
<class 'int'> 737858
<class 'str'> 262144
Name: count, dtype: int64
In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')
將導致 mixed_df
包含特定區塊的欄位 int
dtype,以及其他區塊的 str
,這是因為讀取資料時資料類型混合所致。請務必注意,整體欄位將標示為 dtype
的 object
,這是用於資料類型混合的欄位。
設定 dtype_backend="numpy_nullable"
將導致每個欄位的 dtype 為可為空值。
In [31]: data = """a,b,c,d,e,f,g,h,i,j
....: 1,2.5,True,a,,,,,12-31-2019,
....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
....: """
....:
In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])
In [33]: df
Out[33]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 2019-12-31 <NA>
1 3 4.5 False b 6 7.5 True a 2019-12-31 <NA>
In [34]: df.dtypes
Out[34]:
a Int64
b Float64
c boolean
d string[python]
e Int64
f Float64
g boolean
h string[python]
i datetime64[ns]
j Int64
dtype: object
指定類別 dtype#
Categorical
欄位可透過指定 dtype='category'
或 dtype=CategoricalDtype(categories, ordered)
直接剖析。
In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"
In [36]: pd.read_csv(StringIO(data))
Out[36]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]:
col1 object
col2 object
col3 int64
dtype: object
In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]:
col1 category
col2 category
col3 category
dtype: object
個別欄位可使用字典規格剖析為 Categorical
In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]:
col1 category
col2 object
col3 int64
dtype: object
指定 dtype='category'
將導致未排序的 Categorical
,其 categories
為資料中觀察到的唯一值。若要進一步控制類別和順序,請先建立 CategoricalDtype
,並將其傳遞給該欄位的 dtype
。
In [40]: from pandas.api.types import CategoricalDtype
In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)
In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]:
col1 category
col2 object
col3 int64
dtype: object
使用 dtype=CategoricalDtype
時,dtype.categories
之外的「意外」值將視為遺漏值。
In [43]: dtype = CategoricalDtype(["a", "b", "d"]) # No 'c'
In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]:
0 a
1 a
2 NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']
這符合 Categorical.set_categories()
的行為。
注意
使用 dtype='category'
,結果類別將始終解析為字串(物件 dtype)。如果類別是數字,可以使用 to_numeric()
函數進行轉換,或適當地使用其他轉換器,例如 to_datetime()
。
當 dtype
是具有同質 categories
(所有數字、所有日期時間等)的 CategoricalDtype
時,會自動進行轉換。
In [45]: df = pd.read_csv(StringIO(data), dtype="category")
In [46]: df.dtypes
Out[46]:
col1 category
col2 category
col3 category
dtype: object
In [47]: df["col3"]
Out[47]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
命名和使用欄#
處理欄名稱#
檔案可能會有或沒有標題列。pandas 假設第一列應當用作欄名稱
In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [53]: pd.read_csv(StringIO(data))
Out[53]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
透過指定 names
參數搭配 header
,您可以指出要使用的其他名稱,以及是否要捨棄標題列(如果有)
In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]:
foo bar baz
0 1 2 3
1 4 5 6
2 7 8 9
In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]:
foo bar baz
0 a b c
1 1 2 3
2 4 5 6
3 7 8 9
如果標題在第一列以外的其他列,請將列號傳遞給 header
。這將略過前幾列
In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"
In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
注意
預設行為是推斷欄位名稱:如果沒有傳遞名稱,行為與 header=0
相同,欄位名稱會從檔案的第一個非空白行推斷,如果明確傳遞欄位名稱,行為與 header=None
相同。
重複名稱的剖析#
如果檔案或標頭包含重複名稱,預設情況下,pandas 會區分它們,以防止覆寫資料
In [59]: data = "a,b,a\n0,1,2\n3,4,5"
In [60]: pd.read_csv(StringIO(data))
Out[60]:
a b a.1
0 0 1 2
1 3 4 5
不再有重複資料,因為重複的欄位「X」、「X.1」、「X.N」會變成「X」、「X.1」、「X.N」。
過濾欄位 (usecols
)#
usecols
參數允許您使用欄位名稱、位置數字或可呼叫函數來選取檔案中任何欄位的子集
In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"
In [62]: pd.read_csv(StringIO(data))
Out[62]:
a b c d
0 1 2 3 foo
1 4 5 6 bar
2 7 8 9 baz
In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]:
b d
0 2 foo
1 5 bar
2 8 baz
In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]:
a c d
0 1 3 foo
1 4 6 bar
2 7 9 baz
In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]:
a c
0 1 3
1 4 6
2 7 9
usecols
參數也可以用來指定最終結果中不要使用的欄位
In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]:
b d
0 2 foo
1 5 bar
2 8 baz
在這種情況下,可呼叫函數指定我們從輸出中排除「a」和「c」欄位。
處理 Unicode 資料#
應將 encoding
參數用於編碼的 Unicode 資料,這會導致位元組字串在結果中解碼為 Unicode
In [86]: from io import BytesIO
In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"
In [88]: data = data.decode("utf8").encode("latin-1")
In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")
In [90]: df
Out[90]:
word length
0 Träumen 7
1 Grüße 5
In [91]: df["word"][1]
Out[91]: 'Grüße'
某些將所有字元編碼為多個位元組的格式(例如 UTF-16)在未指定編碼的情況下,根本無法正確剖析。Python 標準編碼完整清單。
索引欄和尾隨分隔符#
如果檔案的資料欄比欄位名稱多一欄,第一欄將會用作 DataFrame
的列名稱
In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [93]: pd.read_csv(StringIO(data))
Out[93]:
a b c
4 apple bat 5.7
8 orange cow 10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]:
a b c
index
4 apple bat 5.7
8 orange cow 10.0
通常,你可以使用 index_col
選項來達成此行為。
有些例外情況是檔案在每一行資料結尾準備了分隔符,造成剖析器混淆。若要明確停用索引欄推論並捨棄最後一欄,請傳遞 index_col=False
In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [98]: pd.read_csv(StringIO(data))
Out[98]:
a b c
4 apple bat NaN
8 orange cow NaN
In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]:
a b c
0 4 apple bat
1 8 orange cow
如果使用 usecols
選項來分析資料子集,index_col
規格會根據該子集,而非原始資料。
In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]:
b c
4 bat NaN
8 cow NaN
In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]:
b c
4 bat NaN
8 cow NaN
日期處理#
指定日期欄位#
為了更方便處理日期時間資料,read_csv()
使用關鍵字引數 parse_dates
和 date_format
,讓使用者可以指定多種欄位和日期/時間格式,以將輸入文字資料轉換為 datetime
物件。
最簡單的情況是傳入 parse_dates=True
In [104]: with open("foo.csv", mode="w") as f:
.....: f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
.....:
# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)
In [106]: df
Out[106]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)
我們常常需要分開儲存日期和時間資料,或分開儲存各種日期欄位。關鍵字 parse_dates
可用於指定要從中分析日期和/或時間的欄位組合。
你可以指定一個欄位清單給 parse_dates
,產生的日期欄位會加到輸出的前面(以免影響現有的欄位順序),而新的欄位名稱會是組成欄位名稱的串接。
In [108]: data = (
.....: "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
.....: "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
.....: "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
.....: "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
.....: "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
.....: "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
.....: )
.....:
In [109]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])
In [111]: df
Out[111]:
1_2 1_3 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
預設情況下,剖析器會移除組成日期欄位,但你可以選擇透過關鍵字 keep_date_col
來保留它們。
In [112]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
.....: )
.....:
In [113]: df
Out[113]:
1_2 1_3 0 ... 2 3 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD ... 19:00:00 18:56:00 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD ... 20:00:00 19:56:00 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD ... 21:00:00 20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD ... 21:00:00 21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD ... 22:00:00 21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD ... 23:00:00 22:56:00 -0.59
[6 rows x 7 columns]
請注意,如果您希望將多個欄位合併成單一日期欄位,則必須使用巢狀清單。換句話說,parse_dates=[1, 2]
表示第二和第三個欄位應分別解析為獨立的日期欄位,而 parse_dates=[[1, 2]]
表示這兩個欄位應解析為單一欄位。
您也可以使用字典來指定自訂名稱欄位
In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)
In [116]: df
Out[116]:
nominal actual 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
重要的是要記住,如果要將多個文字欄位解析成單一日期欄位,則會在資料前面加上一個新欄位。 index_col
規格是根據這組新欄位,而不是原始資料欄位
In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [118]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=date_spec, index_col=0
.....: ) # index is the nominal column
.....:
In [119]: df
Out[119]:
actual 0 4
nominal
1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
注意
如果欄位或索引包含無法解析的日期,則整個欄位或索引將會以物件資料類型不變地傳回。對於非標準的日期時間解析,請在 pd.read_csv
之後使用 to_datetime()
。
注意
read_csv 有一個快速路徑,可以用於解析 iso8601 格式的日期時間字串,例如「2000-01-01T00:01:02+00:00」和類似的變體。如果您能將資料儲存在這種格式的日期時間,載入時間會顯著加快,觀察到的速度約為 20 倍。
自 2.2.0 版起已棄用: 在 read_csv 中合併日期欄位已棄用。請改用 pd.to_datetime
於相關結果欄位。
日期解析函式#
最後,解析器允許您指定自訂 date_format
。在效能方面,您應該按順序嘗試這些解析日期的方法
如果您知道格式,請使用
date_format
,例如:date_format="%d/%m/%Y"
或date_format={column_name: "%d/%m/%Y"}
。如果您對不同的欄使用不同的格式,或想傳遞任何額外的選項(例如
utc
)給to_datetime
,則應將資料讀入為object
資料類型,然後使用to_datetime
。
剖析具有混合時區的 CSV#
pandas 無法原生表示具有混合時區的欄或索引。如果您的 CSV 檔案包含具有混合時區的欄,預設結果將會是具有字串的物件資料類型欄,即使使用 parse_dates
。若要將混合時區值剖析為日期時間欄,請讀入為 object
資料類型,然後呼叫 to_datetime()
,並使用 utc=True
。
In [120]: content = """\
.....: a
.....: 2000-01-01T00:00:00+05:00
.....: 2000-01-01T00:00:00+06:00"""
.....:
In [121]: df = pd.read_csv(StringIO(content))
In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [123]: df["a"]
Out[123]:
0 1999-12-31 19:00:00+00:00
1 1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]
推論日期時間格式#
以下是可猜測的一些日期時間字串範例(全部代表 2011 年 12 月 30 日下午 00:00:00)
“20111230”
“2011/12/30”
“20111230 00:00:00”
“12/30/2011 00:00:00”
“30/Dec/2011 00:00:00”
“30/December/2011 00:00:00”
請注意,格式推論會受到 dayfirst
的影響。如果 dayfirst=True
,它會猜測 “01/12/2011” 是 12 月 1 日。如果 dayfirst=False
(預設值),它會猜測 “01/12/2011” 是 1 月 12 日。
如果您嘗試解析一欄日期字串,pandas 會嘗試從第一個非 NaN 元素猜測格式,然後使用該格式解析該欄的其餘部分。如果 pandas 無法猜測格式(例如您的第一個字串是 '01 December US/Pacific 2000'
),則會提出警告,並且每一列將由 dateutil.parser.parse
個別解析。解析日期最安全的方法是明確設定 format=
。
In [124]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [125]: df
Out[125]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
如果您在同一欄中混合了日期時間格式,您可以傳遞 format='mixed'
In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")
In [127]: df = pd.read_csv(data)
In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')
In [129]: df
Out[129]:
date
0 2000-01-12
1 2000-01-13
或者,如果您的日期時間格式都是 ISO8601(可能格式不相同)
In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")
In [131]: df = pd.read_csv(data)
In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')
In [133]: df
Out[133]:
date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00
國際日期格式#
雖然美國日期格式傾向於 MM/DD/YYYY,但許多國際格式改用 DD/MM/YYYY。為方便起見,提供了一個 dayfirst
關鍵字
In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"
In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [136]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
將 CSV 寫入二進位檔案物件#
1.2.0 版的新功能。
df.to_csv(..., mode="wb")
允許將 CSV 寫入以二進位模式開啟的檔案物件。在大多數情況下,不需要指定 mode
,因為 Pandas 會自動偵測檔案物件是在文字模式還是二進位模式開啟。
In [139]: import io
In [140]: data = pd.DataFrame([0, 1, 2])
In [141]: buffer = io.BytesIO()
In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")
指定浮點數轉換方法#
可以使用參數 float_precision
,以便在使用 C 引擎解析時使用特定的浮點數轉換器。選項包括一般轉換器、高精度轉換器和來回轉換器(保證在寫入檔案後來回轉換值)。例如
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"
In [144]: data = "a,b,c\n1,2,{0}".format(val)
In [145]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision=None,
.....: )["c"][0] - float(val)
.....: )
.....:
Out[145]: 5.551115123125783e-17
In [146]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision="high",
.....: )["c"][0] - float(val)
.....: )
.....:
Out[146]: 5.551115123125783e-17
In [147]: abs(
.....: pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
.....: - float(val)
.....: )
.....:
Out[147]: 0.0
千位分隔符#
對於已使用千位分隔符號寫入的大數字,您可以將 thousands
關鍵字設定為長度為 1 的字串,以便正確解析整數
預設情況下,帶有千位分隔符號的數字將被解析為字串
In [148]: data = (
.....: "ID|level|category\n"
.....: "Patient1|123,000|x\n"
.....: "Patient2|23,000|y\n"
.....: "Patient3|1,234,018|z"
.....: )
.....:
In [149]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [150]: df = pd.read_csv("tmp.csv", sep="|")
In [151]: df
Out[151]:
ID level category
0 Patient1 123,000 x
1 Patient2 23,000 y
2 Patient3 1,234,018 z
In [152]: df.level.dtype
Out[152]: dtype('O')
thousands
關鍵字允許正確解析整數
In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")
In [154]: df
Out[154]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
In [155]: df.level.dtype
Out[155]: dtype('int64')
NA 值#
若要控制哪些值會被解析為遺漏值(由 NaN
表示),請在 na_values
中指定一個字串。如果您指定一個字串清單,則其中的所有值都會被視為遺漏值。如果您指定一個數字(一個 float
,例如 5.0
或一個 integer
,例如 5
),對應的等效值也會暗示一個遺漏值(在本例中,實際上 [5.0, 5]
被識別為 NaN
)。
若要完全覆寫被識別為遺漏值的預設值,請指定 keep_default_na=False
。
讓我們考慮一些範例
pd.read_csv("path_to_file.csv", na_values=[5])
在上面的範例中,5
和 5.0
將會被辨識為 NaN
,除了預設值之外。字串將會首先被解釋為數字 5
,然後再作為 NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])
在上面,只有空白欄位會被辨識為 NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])
在上面,NA
和 0
這兩個字串都是 NaN
。
pd.read_csv("path_to_file.csv", na_values=["Nope"])
預設值,除了字串 "Nope"
之外,會被辨識為 NaN
。
無窮大#
inf
類型的值將會被解析為 np.inf
(正無窮大),而 -inf
則為 -np.inf
(負無窮大)。這些值會忽略大小寫,這表示 Inf
也會被解析為 np.inf
。
布林值#
常見的值 True
、False
、TRUE
和 FALSE
都會被辨識為布林值。偶爾你可能會想要辨識其他值為布林值。若要執行此操作,請使用 true_values
和 false_values
選項,如下所示
In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"
In [157]: print(data)
a,b,c
1,Yes,2
3,No,4
In [158]: pd.read_csv(StringIO(data))
Out[158]:
a b c
0 1 Yes 2
1 3 No 4
In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]:
a b c
0 1 True 2
1 3 False 4
處理「錯誤」行#
有些檔案可能會出現格式錯誤的行,欄位太少或太多。欄位太少的行會在尾端欄位填入 NA 值。欄位太多的行預設會引發錯誤
In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
623 return parser
625 with parser:
--> 626 return parser.read(nrows)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
1916 nrows = validate_integer("nrows", nrows)
1917 try:
1918 # error: "ParserBase" has no attribute "read"
1919 (
1920 index,
1921 columns,
1922 col_dict,
-> 1923 ) = self._engine.read( # type: ignore[attr-defined]
1924 nrows
1925 )
1926 except Exception:
1927 self.close()
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
232 try:
233 if self.low_memory:
--> 234 chunks = self._reader.read_low_memory(nrows)
235 # destructive to chunks
236 data = _concatenate_chunks(chunks)
File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()
File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()
File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()
File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()
File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()
ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
你可以選擇略過錯誤行
In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]:
a b c
0 1 2 3
1 8 9 10
1.4.0 版的新功能。
或者如果 engine="python"
,傳遞一個可呼叫函式來處理錯誤行。錯誤行會是一個字串清單,由 sep
分割。
In [164]: external_list = []
In [165]: def bad_lines_func(line):
.....: external_list.append(line)
.....: return line[-3:]
.....:
In [166]: external_list
Out[166]: []
注意
可呼叫函式只會處理欄位太多的行。由其他錯誤造成的錯誤行會靜默略過。
In [167]: bad_lines_func = lambda line: print(line)
In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'
In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'
In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]:
name type
0 name a a is of type a
在這種情況下,行未被處理,因為這裡的「錯誤行」是由跳脫字元造成的。
你也可以使用 usecols
參數來消除某些行中出現但其他行中沒有的額外欄資料
In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
617 _validate_names(kwds.get("names", None))
619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
622 if chunksize or iterator:
623 return parser
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
1617 self.options["has_index_names"] = kwds["has_index_names"]
1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
1895 raise ValueError(msg)
1897 try:
-> 1898 return mapping[engine](f, **self.options)
1899 except Exception:
1900 if self.handles is not None:
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
152 # error: Cannot determine type of 'names'
153 if len(self.names) < len(usecols): # type: ignore[has-type]
154 # error: Cannot determine type of 'names'
--> 155 self._validate_usecols_names(
156 usecols,
157 self.names, # type: ignore[has-type]
158 )
160 # error: Cannot determine type of 'names'
161 self._validate_parse_dates_presence(self.names) # type: ignore[has-type]
File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
977 missing = [c for c in usecols if c not in names]
978 if len(missing) > 0:
--> 979 raise ValueError(
980 f"Usecols do not match columns, columns expected but not found: "
981 f"{missing}"
982 )
984 return usecols
ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]
如果你想要保留所有資料,包括欄位太多的行,你可以指定足夠數量的 names
。這可確保欄位不足的行會填入 NaN
。
In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]:
a b c d
0 name type NaN NaN
1 name a a is of type a NaN NaN
2 name b b is of type b" NaN NaN
方言#
dialect
關鍵字在指定檔案格式時提供更大的彈性。預設會使用 Excel 方言,但你可以指定方言名稱或 csv.Dialect
執行個體。
假設你的資料有未封閉的引號
In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"
In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f
預設情況下,read_csv
使用 Excel 方言,並將雙引號視為引號字元,這會導致它在找到結束雙引號之前找到換行符號時失敗。
我們可以使用 dialect
來解決這個問題
In [175]: import csv
In [176]: dia = csv.excel()
In [177]: dia.quoting = csv.QUOTE_NONE
In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]:
label1 label2 label3
index1 "a c e
index2 b d f
所有方言選項都可以透過關鍵字參數分別指定
In [179]: data = "a,b,c~1,2,3~4,5,6"
In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]:
a b c
0 1 2 3
1 4 5 6
另一個常見的方言選項是 skipinitialspace
,用於略過分隔符號後的任何空白
In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"
In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6
In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]:
a b c
0 1 2 3
1 4 5 6
剖析器會盡力「做正確的事」,而且不會脆弱。類型推論是一件相當重要的事。如果一個欄位可以在不改變內容的情況下強制轉換為整數資料類型,剖析器就會這麼做。任何非數字欄位都會像其他 pandas 物件一樣,以物件資料類型呈現。
引用和跳脫字元#
嵌入式欄位中的引號(和其他跳脫字元)可以用許多方式處理。其中一種方式是使用反斜線;若要正確剖析這些資料,您應該傳遞 escapechar
選項
In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'
In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5
In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]:
a b
0 hello, "Bob", nice to see you 5
具有固定寬度欄位的檔案#
雖然 read_csv()
會讀取分隔資料,但 read_fwf()
函數會處理具有已知且固定的欄位寬度的資料檔案。read_fwf
的函數參數在很大程度上與 read_csv
相同,但有兩個額外的參數,以及 delimiter
參數的不同用法
colspecs
:一對(元組)清單,提供每行固定寬度欄位的範圍,作為半開區間(即 [from, to[ )。字串值「infer」可用於指示剖析器嘗試從資料的前 100 列偵測欄位規格。未指定時,預設行為為推論。寬度
:欄位寬度的清單,如果間隔是連續的,可以使用此清單取代「colspecs」。分隔符
:固定寬度檔案中視為填補字元的字元。如果欄位的填補字元不是空白(例如「~」),可以使用此參數指定。
考慮一個典型的固定寬度資料檔案
In [187]: data1 = (
.....: "id8141 360.242940 149.910199 11950.7\n"
.....: "id1594 444.953632 166.985655 11788.4\n"
.....: "id1849 364.136849 183.628767 11806.2\n"
.....: "id1230 413.836124 184.375703 11916.8\n"
.....: "id1948 502.953953 173.237159 12468.3"
.....: )
.....:
In [188]: with open("bar.csv", "w") as f:
.....: f.write(data1)
.....:
若要將此檔案剖析成 DataFrame
,我們只需提供欄位規格給 read_fwf
函數,以及檔案名稱
# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)
In [191]: df
Out[191]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
請注意,當指定 header=None
參數時,剖析器會自動選取欄位名稱 X.<欄位編號>。或者,您也可以只提供連續欄位的欄位寬度
# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]
In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)
In [194]: df
Out[194]:
0 1 2 3
0 id8141 360.242940 149.910199 11950.7
1 id1594 444.953632 166.985655 11788.4
2 id1849 364.136849 183.628767 11806.2
3 id1230 413.836124 184.375703 11916.8
4 id1948 502.953953 173.237159 12468.3
剖析器會處理欄位周圍的額外空白,因此檔案中欄位之間可以有額外的分隔。
預設情況下,read_fwf
會嘗試使用檔案的前 100 列來推論檔案的 colspecs
。它只能在欄位已對齊且由提供的 分隔符
正確分隔時執行此動作(預設分隔符為空白)。
In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)
In [196]: df
Out[196]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
read_fwf
支援 dtype
參數,用於指定已剖析欄位的類型與推論的類型不同。
In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]:
1 float64
2 float64
3 float64
dtype: object
In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]:
0 object
1 float64
2 object
3 float64
dtype: object
索引#
具有「隱式」索引欄位的檔案#
考慮一個檔案,其標頭中的項目比資料欄位少一個
In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"
In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5
In [201]: with open("foo.csv", "w") as f:
.....: f.write(data)
.....:
在這種特殊情況下,read_csv
會假設第一個欄位要作為 DataFrame
的索引
In [202]: pd.read_csv("foo.csv")
Out[202]:
A B C
20090101 a 1 2
20090102 b 3 4
20090103 c 4 5
請注意,日期並未自動解析。在這種情況下,您需要像以前一樣執行
In [203]: df = pd.read_csv("foo.csv", parse_dates=True)
In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)
使用 MultiIndex
讀取索引#
假設您的資料由兩個欄位索引
In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'
In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
In [207]: with open("mindex_ex.csv", mode="w") as f:
.....: f.write(data)
.....:
read_csv
的 index_col
參數可以採用欄位號碼清單,將多個欄位轉換為 MultiIndex
,作為回傳物件的索引
In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])
In [209]: df
Out[209]:
zit xit
year indiv
1977 A 1.2 0.6
B 1.5 0.5
In [210]: df.loc[1977]
Out[210]:
zit xit
indiv
A 1.2 0.6
B 1.5 0.5
使用 MultiIndex
讀取欄位#
透過為 header
參數指定列位置清單,您可以讀取欄位的 MultiIndex
。指定非連續列會略過中間列。
In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))
In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))
In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)
In [214]: df.to_csv("mi.csv")
In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0
In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]:
c 1 2
d a b
a Unnamed: 2_level_2 Unnamed: 3_level_2
1 1.0 1.0
2 b 1.0 1.0
3 c 1.0 1.0
4 d 1.0 1.0
read_csv
也能解讀較常見的多欄位索引格式。
In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"
In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12
In [219]: with open("mi2.csv", "w") as fh:
.....: fh.write(data)
.....:
In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]:
a b c
q r s t u v
one 1 2 3 4 5 6
two 7 8 9 10 11 12
注意
如果未指定 index_col
(例如,您沒有索引,或使用 df.to_csv(..., index=False)
撰寫),則欄位索引中的任何 names
都會遺失。
自動「偵測」分隔符#
read_csv
能夠推斷分隔的檔案(不一定是逗號分隔),因為 pandas 使用 csv 模組的 csv.Sniffer
類別。為此,您必須指定 sep=None
。
In [221]: df = pd.DataFrame(np.random.randn(10, 4))
In [222]: df.to_csv("tmp2.csv", sep=":", index=False)
In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]:
0 1 2 3
0 0.469112 -0.282863 -1.509059 -1.135632
1 1.212112 -0.173215 0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929 1.071804
3 0.721555 -0.706771 -1.039575 0.271860
4 -0.424972 0.567020 0.276232 -1.087401
5 -0.673690 0.113648 -1.478427 0.524988
6 0.404705 0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312 0.844885
8 1.075770 -0.109050 1.643563 -1.469388
9 0.357021 -0.674600 -1.776904 -0.968914
讀取多個檔案以建立單一 DataFrame#
逐塊迭代檔案#
假設您希望逐漸迭代(可能是非常大的)檔案,而不是將整個檔案讀入記憶體中,例如以下
In [224]: df = pd.DataFrame(np.random.randn(10, 4))
In [225]: df.to_csv("tmp.csv", index=False)
In [226]: table = pd.read_csv("tmp.csv")
In [227]: table
Out[227]:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
透過為 read_csv
指定 chunksize
,回傳值將會是 TextFileReader
類型的可迭代物件
In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
.....: print(reader)
.....: for chunk in reader:
.....: print(chunk)
.....:
<pandas.io.parsers.readers.TextFileReader object at 0x7fac5f067a60>
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
0 1 2 3
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
0 1 2 3
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
版本 1.2 中已變更: read_csv/json/sas
在迭代檔案時會回傳內容管理員。
指定 iterator=True
也會回傳 TextFileReader
物件
In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
.....: print(reader.get_chunk(5))
.....:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
指定剖析器引擎#
Pandas 目前支援三種引擎,C 引擎、Python 引擎和一個實驗性的 pyarrow 引擎(需要 pyarrow
套件)。一般來說,pyarrow 引擎在較大的工作負載上執行速度最快,而且在其他大多數工作負載上,執行速度與 C 引擎相當。Python 引擎在大部分的工作負載上,執行速度往往比 pyarrow 和 C 引擎慢。但是,pyarrow 引擎的健壯性遠不如 C 引擎,而且與 Python 引擎相比,缺少一些功能。
在可能的情況下,pandas 會使用 C 解析器(指定為 engine='c'
),但如果指定了 C 不支援的選項,它可能會退回到 Python。
目前,C 和 pyarrow 引擎不支援的選項包括
sep
除了一個字元之外(例如正規表示式分隔符號)skipfooter
sep=None
與delim_whitespace=False
指定上述任何選項都會產生 ParserWarning
,除非使用 engine='python'
明確選擇 Python 引擎。
pyarrow 引擎不支援的選項,且不在上述清單中的選項包括
float_precision
chunksize
comment
nrows
thousands
memory_map
dialect
on_bad_lines
delim_whitespace
quoting
lineterminator
converters
decimal
iterator
dayfirst
infer_datetime_format
verbose
skipinitialspace
low_memory
使用 engine='pyarrow'
指定這些選項會引發 ValueError
。
讀取/寫入遠端檔案#
你可以傳入一個 URL,以讀取或寫入遠端檔案到 pandas 的許多 IO 函式中 - 以下範例顯示如何讀取 CSV 檔案
df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")
1.3.0 版的新增功能。
傳遞標頭鍵值對應的字典給 storage_options
關鍵字參數,如下所示,即可在 HTTP(s) 要求中傳送自訂標頭
headers = {"User-Agent": "pandas"}
df = pd.read_csv(
"https://download.bls.gov/pub/time.series/cu/cu.item",
sep="\t",
storage_options=headers
)
若已安裝,所有非本機檔案或 HTTP(s) 的 URL 都會由 fsspec 及其各種檔案系統實作(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS…)處理。其中一些實作需要安裝額外的套件,例如 S3 URL 需要 s3fs 函式庫
df = pd.read_json("s3://pandas-test/adatafile.json")
在處理遠端儲存系統時,您可能需要使用環境變數或特定位置的組態檔進行額外組態。例如,若要存取 S3 儲存區中的資料,您需要以 S3Fs 文件 中列出的數種方式之一定義憑證。對於多個儲存後端來說也是如此,您應該追蹤 fsimpl1 中內建於 fsspec
的實作,以及 fsimpl2 中未包含在主要 fsspec
發行版中的實作的連結。
您也可以直接將參數傳遞給後端驅動程式。由於 fsspec
未使用 AWS_S3_HOST
環境變數,因此我們可以直接定義包含 endpoint_url 的字典,並將物件傳遞給儲存選項參數
storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)
更多範例組態和文件可以在 S3Fs 文件 中找到。
如果您沒有 S3 憑證,您仍然可以透過指定匿名連線來存取公開資料,例如
1.2.0 版的新功能。
pd.read_csv(
"s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
"-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"anon": True},
)
fsspec
也允許使用複雜的 URL,以便存取壓縮檔案中的資料、檔案的本機快取,以及更多功能。若要快取上述範例,您會修改呼叫為
pd.read_csv(
"simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
"SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"s3": {"anon": True}},
)
我們在其中指定「anon」參數是針對實作的「s3」部分,而非快取實作。請注意,這只會在工作階段期間快取到暫時目錄,但您也可以指定永久儲存。
寫出資料#
寫入 CSV 格式#
Series
和 DataFrame
物件有一個執行個體方法 to_csv
,它允許將物件的內容儲存為逗號分隔值檔案。此函式會採用多個引數。只有第一個引數是必要的。
path_or_buf
:要寫入檔案的字串路徑或檔案物件。如果是檔案物件,則必須使用newline=''
開啟sep
:輸出檔案的欄位分隔符號(預設為「,」)na_rep
:遺失值的字串表示(預設為「」)float_format
:浮點數的格式字串columns
:要寫入的欄位(預設為 None)header
:是否寫出欄位名稱(預設為 True)index
:是否寫出列(索引)名稱(預設為 True)index_label
:索引欄位的欄位標籤(如果需要)。如果為 None(預設),且header
和index
為 True,則使用索引名稱。(如果DataFrame
使用 MultiIndex,則應提供一個序列)mode
:Python 寫入模式,預設為 'w'encoding
:一個字串,表示在 Python 3 以前的版本中,如果內容是非 ASCII,則要使用的編碼lineterminator
:表示行尾的字元序列(預設為os.linesep
)quoting
:設定 csv 模組中的引號規則(預設為 csv.QUOTE_MINIMAL)。請注意,如果您已設定float_format
,則浮點數會轉換為字串,而 csv.QUOTE_NONNUMERIC 會將它們視為非數字quotechar
:用於引號欄位的字元(預設為 '”')doublequote
:控制欄位中quotechar
的引號(預設為 True)escapechar
:當適當時,用來跳脫sep
和quotechar
的字元(預設為 None)chunksize
:一次寫入列數date_format
:datetime 物件的格式字串
寫入格式化字串#
DataFrame
物件有一個實例方法 to_string
,允許控制物件的字串表示。所有引數都是選填的
buf
預設為 None,例如 StringIO 物件columns
預設為 None,要寫入的欄位col_space
預設為 None,每欄的最小寬度。na_rep
預設為NaN
,NA 值的表示formatters
預設為 None,一個字典(按欄位),每個函式都接受單一引數並傳回格式化字串float_format
預設為 None,一個函式,接受單一(浮點數)引數並傳回格式化字串;套用於DataFrame
中的浮點數。sparsify
預設為 True,設定為 False 可讓具有階層索引的DataFrame
在每一列印出每個 MultiIndex 鍵。index_names
預設為 True,會印出索引名稱index
預設為 True,會印出索引(即列標籤)header
預設為 True,會印出欄標籤justify
預設為left
,會讓欄標題靠左或靠右對齊
Series
物件也有 to_string
方法,但只有 buf
、na_rep
、float_format
參數。另外還有 length
參數,如果設定為 True
,會另外輸出 Series 的長度。
JSON#
讀寫 JSON
格式檔案和字串。
寫入 JSON#
Series
或 DataFrame
可以轉換為有效的 JSON 字串。使用 to_json
搭配選用參數
path_or_buf
:寫入輸出的路徑名稱或緩衝區。此處可以是None
,如此會傳回 JSON 字串。orient
:系列
:預設為
index
允許的值為 {
split
,records
,index
}
DataFrame
:預設為
columns
允許的值為 {
split
,records
,index
,columns
,values
,table
}
JSON 字串的格式
分割
類似字典 {index -> [index], columns -> [columns], data -> [values]}
記錄
類似清單 [{column -> value}, … , {column -> value}]
索引
類似字典 {index -> {column -> value}}
欄
類似字典 {column -> {index -> value}}
值
僅值陣列
表格
遵守 JSON 表格架構
date_format
: 字串,日期轉換類型,時間戳記為「epoch」,ISO8601 為「iso」。double_precision
: 編碼浮點值時要使用的位數,預設為 10。force_ascii
:強制編碼字串為 ASCII,預設為 True。date_unit
:編碼的時間單位,控制時間戳記和 ISO8601 精確度。分別為秒、毫秒、微秒和奈秒的「s」、「ms」、「us」或「ns」。預設為「ms」。default_handler
:如果物件無法轉換為適合 JSON 的格式,則呼叫此處理常式。採用一個引數,即要轉換的物件,並傳回可序列化物件。lines
:如果為records
導向,則會將每筆記錄寫入 JSON 的每一行。mode
:字串,寫入路徑時的寫入模式。寫入為「w」,附加為「a」。預設為「w」。
注意 NaN
、NaT
和 None
會轉換為 null
,而 datetime
物件會根據 date_format
和 date_unit
參數轉換。
In [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [231]: json = dfj.to_json()
In [232]: json
Out[232]: '{"A":{"0":-0.1213062281,"1":0.6957746499,"2":0.9597255933,"3":-0.6199759194,"4":-0.7323393705},"B":{"0":-0.0978826728,"1":0.3417343559,"2":-1.1103361029,"3":0.1497483186,"4":0.6877383895}}'
導向選項#
對於結果 JSON 檔案/字串的格式,有許多不同的選項。考慮以下 DataFrame
和 Series
In [233]: dfjo = pd.DataFrame(
.....: dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
.....: columns=list("ABC"),
.....: index=list("xyz"),
.....: )
.....:
In [234]: dfjo
Out[234]:
A B C
x 1 4 7
y 2 5 8
z 3 6 9
In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")
In [236]: sjo
Out[236]:
x 15
y 16
z 17
Name: D, dtype: int64
欄位導向(DataFrame
的預設值)將資料序列化為巢狀 JSON 物件,欄位標籤作為主要索引
In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'
# Not available for Series
索引導向(Series
的預設值)類似於欄位導向,但索引標籤現在是主要的
In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'
In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'
記錄導向將資料序列化為欄位 -> 值記錄的 JSON 陣列,不包含索引標籤。這對於將 DataFrame
資料傳遞給繪圖函式庫(例如 JavaScript 函式庫 d3.js
)很有用
In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'
In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'
值導向是一個基礎選項,僅序列化為值的巢狀 JSON 陣列,不包含欄位和索引標籤
In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'
# Not available for Series
分割導向序列化為包含值、索引和欄位的單獨條目的 JSON 物件。名稱也包含在 Series
中
In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'
In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'
表格導向序列化為 JSON 表格架構,允許保留元資料,包括但不限於資料類型和索引名稱。
注意
任何編碼為 JSON 物件的導向選項在往返序列化期間都不會保留索引和欄位標籤的順序。如果您希望保留標籤順序,請使用 split
選項,因為它使用已排序的容器。
日期處理#
以 ISO 日期格式寫入
In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [246]: dfd["date"] = pd.Timestamp("20130101")
In [247]: dfd = dfd.sort_index(axis=1, ascending=False)
In [248]: json = dfd.to_json(date_format="iso")
In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
以 ISO 日期格式寫入,帶有微秒
In [250]: json = dfd.to_json(date_format="iso", date_unit="us")
In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Epoch 時間戳,以秒為單位
In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")
In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
寫入檔案,帶有日期索引和日期欄位
In [254]: dfj2 = dfj.copy()
In [255]: dfj2["date"] = pd.Timestamp("20130101")
In [256]: dfj2["ints"] = list(range(5))
In [257]: dfj2["bools"] = True
In [258]: dfj2.index = pd.date_range("20130101", periods=5)
In [259]: dfj2.to_json("test.json")
In [260]: with open("test.json") as fh:
.....: print(fh.read())
.....:
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}
備用行為#
如果 JSON 序列化器無法直接處理容器內容,它將以以下方式回退
如果 dtype 不受支援(例如
np.complex_
),則會呼叫default_handler
(如果提供的話)來處理每個值,否則會引發例外狀況。如果物件不受支援,它將嘗試執行以下動作
檢查物件是否已定義
toDict
方法,並呼叫它。toDict
方法應傳回dict
,然後將其序列化為 JSON。如果已提供
default_handler
,則呼叫它。透過遍歷物件內容將其轉換為
dict
。但是,這通常會失敗並產生OverflowError
或產生意外的結果。
一般來說,對於不受支援的物件或 dtype,最好的方法是提供 default_handler
。例如
>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json() # raises
RuntimeError: Unhandled numpy dtype 15
可以透過指定一個簡單的 default_handler
來處理
In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'
讀取 JSON#
將 JSON 字串讀取至 pandas 物件時,可以使用多個參數。如果未提供 typ
或其為 None
,剖析器將嘗試剖析 DataFrame
。若要明確強制剖析 Series
,請傳遞 typ=series
filepath_or_buffer
:**有效的** JSON 字串或檔案處理 / StringIO。字串可以是 URL。有效的 URL 架構包括 http、ftp、S3 和檔案。對於檔案 URL,預期會有主機。例如,本機檔案可以是 file://127.0.0.1/path/to/table.jsontyp
:要復原的物件類型(series 或 frame),預設為「frame」orient
:- 系列
預設為
index
允許的值為 {
split
,records
,index
}
- DataFrame
預設為
columns
允許的值為 {
split
,records
,index
,columns
,values
,table
}
JSON 字串的格式
分割
類似字典 {index -> [index], columns -> [columns], data -> [values]}
記錄
類似清單 [{column -> value}, … , {column -> value}]
索引
類似字典 {index -> {column -> value}}
欄
類似字典 {column -> {index -> value}}
值
僅值陣列
表格
遵守 JSON 表格架構
dtype
:如果為 True,則推斷資料類型,如果為欄位到資料類型的字典,則使用那些,如果為False
,則完全不推斷資料類型,預設為 True,僅套用於資料。convert_axes
:布林值,嘗試將軸轉換為適當的資料類型,預設為True
convert_dates
:要解析為日期的欄位清單;如果為True
,則嘗試解析類似日期的欄位,預設為True
。keep_default_dates
:布林值,預設True
。如果解析日期,則解析預設類似日期的欄位。precise_float
:布林值,預設False
。設定為啟用使用較高精準度 (strtod) 函式,當將字串解碼為雙倍值時。預設 (False
) 是使用快速但較不精準的內建功能。date_unit
:字串,轉換日期時要偵測的時間戳記單位。預設為 None。預設會偵測時間戳記精準度,如果不需要,則傳遞「s」、「ms」、「us」或「ns」之一,分別強制時間戳記精準度為秒、毫秒、微秒或奈秒。lines
:將檔案讀取為每行一個 json 物件。encoding
:用於解碼 py3 位元的編碼。chunksize
:與lines=True
搭配使用時,傳回一個pandas.api.typing.JsonReader
,每次反覆運算會讀取chunksize
行。engine
:"ujson"
(內建 JSON 解析器)或"pyarrow"
(會轉派到 pyarrow 的pyarrow.json.read_json
)。"pyarrow"
僅在lines=True
時可用。
如果 JSON 無法解析,解析器會引發 ValueError/TypeError/AssertionError
之一。
如果在編碼成 JSON 時使用了非預設的 orient
,請務必在此處傳遞相同的選項,以使解碼產生合理的結果,請參閱 方向選項 以取得概觀。
資料轉換#
convert_axes=True
、dtype=True
和 convert_dates=True
的預設值會嘗試將軸和所有資料解析成適當的類型,包括日期。如果您需要覆寫特定的資料類型,請將字典傳遞給 dtype
。僅當您需要在軸中保留類字串的數字(例如「1」、「2」)時,才應將 convert_axes
設為 False
。
注意
如果 convert_dates=True
且資料和/或欄位標籤顯示為「日期樣式」,則大型整數值可能會轉換為日期。確切的臨界值取決於指定的 date_unit
。「日期樣式」表示欄位標籤符合下列其中一個條件
以
'_at'
結尾以
'_time'
結尾以
'timestamp'
開頭為
'modified'
為
'date'
警告
在讀取 JSON 資料時,自動強制轉換為 dtypes 會有一些怪異之處
索引可以按照與序列化不同的順序重建,亦即,無法保證傳回的順序與序列化之前相同
如果可以安全地轉換,則
float
資料的欄位將會轉換為integer
,例如1.
的欄位重建時,bool 欄位將會轉換為
integer
因此,有時您可能想要透過 dtype
關鍵字參數指定特定的 dtypes。
從 JSON 字串讀取
In [262]: from io import StringIO
In [263]: pd.read_json(StringIO(json))
Out[263]:
date B A
0 1 0.403310 0.176444
1 1 0.301624 -0.154951
2 1 -1.369849 -2.179861
3 1 1.462696 -0.954208
4 1 -0.826591 -1.743161
從檔案讀取
In [264]: pd.read_json("test.json")
Out[264]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356 0 True
2013-01-02 0.695775 0.341734 1356 1 True
2013-01-03 0.959726 -1.110336 1356 2 True
2013-01-04 -0.619976 0.149748 1356 3 True
2013-01-05 -0.732339 0.687738 1356 4 True
不要轉換任何資料(但仍轉換軸和日期)
In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]:
A object
B object
date object
ints object
bools object
dtype: object
指定要轉換的 dtypes
In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]:
A float32
B float64
date int64
ints int64
bools int8
dtype: object
保留字串索引
In [267]: from io import StringIO
In [268]: si = pd.DataFrame(
.....: np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
.....: )
.....:
In [269]: si
Out[269]:
0 1 2 3
0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0
In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')
In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')
In [272]: json = si.to_json()
In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)
In [274]: sij
Out[274]:
0 1 2 3
0 0 0 0 0
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')
In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')
以奈秒寫入的日期需要以奈秒讀回
In [277]: from io import StringIO
In [278]: json = dfj2.to_json(date_unit="ns")
# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")
In [280]: dfju
Out[280]:
A B date ints bools
1356998400000000000 -0.121306 -0.097883 1356998400 0 True
1357084800000000000 0.695775 0.341734 1356998400 1 True
1357171200000000000 0.959726 -1.110336 1356998400 2 True
1357257600000000000 -0.619976 0.149748 1356998400 3 True
1357344000000000000 -0.732339 0.687738 1356998400 4 True
# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))
In [282]: dfju
Out[282]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 2013-01-01 0 True
2013-01-02 0.695775 0.341734 2013-01-01 1 True
2013-01-03 0.959726 -1.110336 2013-01-01 2 True
2013-01-04 -0.619976 0.149748 2013-01-01 3 True
2013-01-05 -0.732339 0.687738 2013-01-01 4 True
# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")
In [284]: dfju
Out[284]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356998400 0 True
2013-01-02 0.695775 0.341734 1356998400 1 True
2013-01-03 0.959726 -1.110336 1356998400 2 True
2013-01-04 -0.619976 0.149748 1356998400 3 True
2013-01-05 -0.732339 0.687738 1356998400 4 True
透過設定 dtype_backend
參數,您可以控制用於結果 DataFrame 的預設 dtypes。
In [285]: data = (
.....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
.....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
.....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
.....: )
.....:
In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")
In [287]: df
Out[287]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 12-31-2019 None
1 3 4.5 False b 6 7.5 True a 12-31-2019 None
In [288]: df.dtypes
Out[288]:
a int64[pyarrow]
b double[pyarrow]
c bool[pyarrow]
d string[pyarrow]
e int64[pyarrow]
f double[pyarrow]
g bool[pyarrow]
h string[pyarrow]
i string[pyarrow]
j null[pyarrow]
dtype: object
正規化#
pandas 提供了一個工具函式,用於取得字典或字典清單,並將此半結構化資料正規化成平面表格。
In [289]: data = [
.....: {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
.....: {"name": {"given": "Mark", "family": "Regner"}},
.....: {"id": 2, "name": "Faye Raker"},
.....: ]
.....:
In [290]: pd.json_normalize(data)
Out[290]:
id name.first name.last name.given name.family name
0 1.0 Coleen Volk NaN NaN NaN
1 NaN NaN NaN Mark Regner NaN
2 2.0 NaN NaN NaN NaN Faye Raker
In [291]: data = [
.....: {
.....: "state": "Florida",
.....: "shortname": "FL",
.....: "info": {"governor": "Rick Scott"},
.....: "county": [
.....: {"name": "Dade", "population": 12345},
.....: {"name": "Broward", "population": 40000},
.....: {"name": "Palm Beach", "population": 60000},
.....: ],
.....: },
.....: {
.....: "state": "Ohio",
.....: "shortname": "OH",
.....: "info": {"governor": "John Kasich"},
.....: "county": [
.....: {"name": "Summit", "population": 1234},
.....: {"name": "Cuyahoga", "population": 1337},
.....: ],
.....: },
.....: ]
.....:
In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]:
name population state shortname info.governor
0 Dade 12345 Florida FL Rick Scott
1 Broward 40000 Florida FL Rick Scott
2 Palm Beach 60000 Florida FL Rick Scott
3 Summit 1234 Ohio OH John Kasich
4 Cuyahoga 1337 Ohio OH John Kasich
max_level 參數提供更多控制權,用於結束正規化的層級。使用 max_level=1,下列程式片段會正規化,直到提供的字典的第 1 層巢狀層級。
In [293]: data = [
.....: {
.....: "CreatedBy": {"Name": "User001"},
.....: "Lookup": {
.....: "TextField": "Some text",
.....: "UserField": {"Id": "ID001", "Name": "Name001"},
.....: },
.....: "Image": {"a": "b"},
.....: }
.....: ]
.....:
In [294]: pd.json_normalize(data, max_level=1)
Out[294]:
CreatedBy.Name Lookup.TextField Lookup.UserField Image.a
0 User001 Some text {'Id': 'ID001', 'Name': 'Name001'} b
區隔符號分隔的 json#
pandas 能夠讀取和寫入區隔符號分隔的 json 檔案,這在使用 Hadoop 或 Spark 的資料處理管線中很常見。
對於區隔符號分隔的 json 檔案,pandas 也可以傳回一個反覆運算器,一次讀取 chunksize
行。這對於大型檔案或從串流中讀取資料很有用。
In [295]: from io import StringIO
In [296]: jsonl = """
.....: {"a": 1, "b": 2}
.....: {"a": 3, "b": 4}
.....: """
.....:
In [297]: df = pd.read_json(StringIO(jsonl), lines=True)
In [298]: df
Out[298]:
a b
0 1 2
1 3 4
In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'
# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
.....: reader
.....: for chunk in reader:
.....: print(chunk)
.....:
Empty DataFrame
Columns: []
Index: []
a b
0 1 2
a b
1 3 4
也可以使用 pyarrow 讀取器讀取區隔符號分隔的 json,方法是指定 engine="pyarrow"
。
In [301]: from io import BytesIO
In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")
In [303]: df
Out[303]:
a b
0 1 2
1 3 4
2.0.0 版的新功能。
表格架構#
表格架構是一個規範,用於將表格資料集描述為 JSON 物件。JSON 包含欄位名稱、類型和其他屬性的資訊。您可以使用 orient table
來建立一個 JSON 字串,其中包含兩個欄位,schema
和 data
。
In [304]: df = pd.DataFrame(
.....: {
.....: "A": [1, 2, 3],
.....: "B": ["a", "b", "c"],
.....: "C": pd.date_range("2016-01-01", freq="d", periods=3),
.....: },
.....: index=pd.Index(range(3), name="idx"),
.....: )
.....:
In [305]: df
Out[305]:
A B C
idx
0 1 a 2016-01-01
1 2 b 2016-01-02
2 3 c 2016-01-03
In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'
schema
欄位包含 fields
鍵,它本身包含一個欄位名稱至類型配對的清單,包括 Index
或 MultiIndex
(請參閱下方類型清單)。如果 (多重) 索引是唯一的,schema
欄位也會包含一個 primaryKey
欄位。
第二個欄位 data
包含序列化資料,其 records
方向。索引已包含,且任何日期時間皆為 ISO 8601 格式,這是表格架構規格所要求的。
表格架構規格中說明了支援類型清單。此表格顯示從 pandas 類型對應的資料
pandas 類型 |
表格架構類型 |
---|---|
int64 |
整數 |
float64 |
數字 |
bool |
布林 |
datetime64[ns] |
日期時間 |
timedelta64[ns] |
持續時間 |
categorical |
任何 |
物件 |
str |
關於已產生表格架構的幾個注意事項
schema
物件包含pandas_version
欄位。這包含 pandas 架構方言的版本,且會隨著每個修訂版而遞增。序列化時,所有日期都轉換為 UTC。即使是時區未指定的值,也會視為 UTC,偏移量為 0。
In [307]: from pandas.io.json import build_table_schema In [308]: s = pd.Series(pd.date_range("2016", periods=4)) In [309]: build_table_schema(s) Out[309]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
帶有時區的日期時間(序列化之前),會包含一個額外的欄位
tz
,其中包含時區名稱(例如'US/Central'
)。In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central")) In [311]: build_table_schema(s_tz) Out[311]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
週期在序列化之前會轉換為時間戳記,因此會轉換為 UTC 的行為相同。此外,週期會包含一個額外的欄位
freq
,其中包含週期的頻率,例如'A-DEC'
。In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4)) In [313]: build_table_schema(s_per) Out[313]: {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
類別使用
any
類型和enum
約束,列出可能的數值集合。此外,會包含ordered
欄位In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"])) In [315]: build_table_schema(s_cat) Out[315]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'any', 'constraints': {'enum': ['a', 'b']}, 'ordered': False}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
如果索引是唯一的,則包含標籤陣列的
primaryKey
欄位會包含在內In [316]: s_dupe = pd.Series([1, 2], index=[1, 1]) In [317]: build_table_schema(s_dupe) Out[317]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'pandas_version': '1.4.0'}
primaryKey
行為與 MultiIndexes 相同,但在這種情況下,primaryKey
是陣列In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)])) In [319]: build_table_schema(s_multi) Out[319]: {'fields': [{'name': 'level_0', 'type': 'string'}, {'name': 'level_1', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': FrozenList(['level_0', 'level_1']), 'pandas_version': '1.4.0'}
預設命名大致遵循這些規則
對於系列,會使用
object.name
。如果沒有,則名稱為values
對於
DataFrames
,會使用欄位名稱的字串化版本對於
Index
(非MultiIndex
),會使用index.name
,如果為 None,則會改用index
。對於
MultiIndex
,會使用mi.names
。如果任何層級沒有名稱,則會使用level_<i>
。
read_json
也接受 orient='table'
作為引數。這允許以可來回處理的方式保留資料類型和索引名稱等元資料。
In [320]: df = pd.DataFrame(
.....: {
.....: "foo": [1, 2, 3, 4],
.....: "bar": ["a", "b", "c", "d"],
.....: "baz": pd.date_range("2018-01-01", freq="d", periods=4),
.....: "qux": pd.Categorical(["a", "b", "c", "c"]),
.....: },
.....: index=pd.Index(range(4), name="idx"),
.....: )
.....:
In [321]: df
Out[321]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [322]: df.dtypes
Out[322]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
In [323]: df.to_json("test.json", orient="table")
In [324]: new_df = pd.read_json("test.json", orient="table")
In [325]: new_df
Out[325]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [326]: new_df.dtypes
Out[326]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
請注意,字面字串「index」作為 Index
的名稱無法進行來回傳遞,在 MultiIndex
中以 'level_'
開頭的名稱也無法進行來回傳遞。這些名稱在 DataFrame.to_json()
中預設用於表示遺失值,後續的讀取無法區分意圖。
In [327]: df.index.name = "index"
In [328]: df.to_json("test.json", orient="table")
In [329]: new_df = pd.read_json("test.json", orient="table")
In [330]: print(new_df.index.name)
None
在使用 orient='table'
與使用者定義的 ExtensionArray
時,產生的架構將包含各自 fields
元素中的額外 extDtype
鍵。這個額外的鍵並非標準,但確實能讓擴充型別進行 JSON 來回傳遞(例如 read_json(df.to_json(orient="table"), orient="table")
)。
如果已正確註冊 ExtensionDtype
,extDtype
鍵會載入擴充的名稱,pandas 會使用該名稱在登錄檔中進行查詢,並將序列化的資料重新轉換成自訂的資料型別。
HTML#
讀取 HTML 內容#
警告
我們強烈建議您閱讀以下有關 BeautifulSoup4/html5lib/lxml 剖析器問題的 HTML 表格剖析注意事項。
頂層 read_html()
函式可以接受 HTML 字串/檔案/URL,並將 HTML 表格剖析成 pandas DataFrames
清單。我們來看幾個範例。
注意
read_html
會傳回 DataFrame
物件的 list
,即使 HTML 內容只包含一個表格。
讀取沒有選項的 URL
In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[ Bank NameBank CityCity StateSt ... Acquiring InstitutionAI Closing DateClosing FundFund
0 Almena State Bank Almena KS ... Equity Bank October 23, 2020 10538
1 First City Bank of Florida Fort Walton Beach FL ... United Fidelity Bank, fsb October 16, 2020 10537
2 The First State Bank Barboursville WV ... MVB Bank, Inc. April 3, 2020 10536
3 Ericson State Bank Ericson NE ... Farmers and Merchants Bank February 14, 2020 10535
4 City National Bank of New Jersey Newark NJ ... Industrial Bank November 1, 2019 10534
.. ... ... ... ... ... ... ...
558 Superior Bank, FSB Hinsdale IL ... Superior Federal, FSB July 27, 2001 6004
559 Malta National Bank Malta OH ... North Valley Bank May 3, 2001 4648
560 First Alliance Bank & Trust Co. Manchester NH ... Southern New Hampshire Bank & Trust February 2, 2001 4647
561 National State Bank of Metropolis Metropolis IL ... Banterra Bank of Marion December 14, 2000 4646
562 Bank of Honolulu Honolulu HI ... Bank of the Orient October 13, 2000 4645
[563 rows x 7 columns]]
注意
上述 URL 的資料每週一變更,所以上述結果資料可能略有不同。
讀取 URL 並在 HTTP 要求中傳遞標頭
In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 Accept-Encoding: identity
1 Host: www.sump.org
2 User-Agent: Python-urllib/3.8
3 Connection: close]
In [324]: headers = {
In [325]: 'User-Agent':'Mozilla Firefox v14.0',
In [326]: 'Accept':'application/json',
In [327]: 'Connection':'keep-alive',
In [328]: 'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 User-Agent: Mozilla Firefox v14.0
1 AcceptEncoding: gzip, deflate, br
2 Accept: application/json
3 Connection: keep-alive
4 Auth: Bearer 2*/f3+fe68df*4]
注意
我們在上面看到,我們傳遞的標頭會反映在 HTTP 要求中。
讀取上述 URL 檔案的內容,並將其作為字串傳遞給 read_html
In [331]: html_str = """
.....: <table>
.....: <tr>
.....: <th>A</th>
.....: <th colspan="1">B</th>
.....: <th rowspan="1">C</th>
.....: </tr>
.....: <tr>
.....: <td>a</td>
.....: <td>b</td>
.....: <td>c</td>
.....: </tr>
.....: </table>
.....: """
.....:
In [332]: with open("tmp.html", "w") as f:
.....: f.write(html_str)
.....:
In [333]: df = pd.read_html("tmp.html")
In [334]: df[0]
Out[334]:
A B C
0 a b c
如果您願意,您甚至可以傳遞 StringIO
的執行個體
In [335]: dfs = pd.read_html(StringIO(html_str))
In [336]: dfs[0]
Out[336]:
A B C
0 a b c
注意
由於有太多網路存取函式會降低文件建置速度,以下範例不會由 IPython 評估器執行。如果您發現錯誤或無法執行的範例,請不要猶豫,請到 pandas GitHub 問題頁面 回報。
讀取 URL 並比對包含特定文字的表格
match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
指定標題列(預設 <th>
或 <td>
元素位於 <thead>
內部用於形成欄位索引,如果 <thead>
內部包含多列,則會建立多重索引);如果已指定,標題列會從資料中取出,扣除已剖析的標題元素(<th>
元素)。
dfs = pd.read_html(url, header=0)
指定索引欄位
dfs = pd.read_html(url, index_col=0)
指定要略過的列數
dfs = pd.read_html(url, skiprows=0)
使用清單指定要略過的列數(range
也可使用)
dfs = pd.read_html(url, skiprows=range(2))
指定 HTML 屬性
dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0])) # Should be True
指定應轉換為 NaN 的值
dfs = pd.read_html(url, na_values=["No Acquirer"])
指定是否保留預設的 NaN 值組
dfs = pd.read_html(url, keep_default_na=False)
為欄位指定轉換器。這對於具有前導零的數字文字資料很有用。預設情況下,數字欄位會轉換為數字類型,而前導零會遺失。為避免這種情況,我們可以將這些欄位轉換為字串。
url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
url_mcc,
match="Telekom Albania",
header=0,
converters={"MNC": str},
)
使用上述選項的某種組合
dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)
讀取 pandas to_html
輸出(會遺失一些浮點精度)
df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)
如果 lxml
後端是您提供的唯一剖析器,則在剖析失敗時會產生錯誤。如果您只有一個剖析器,您可以只提供字串,但建議的做法是傳遞一個包含一個字串的清單,例如,如果函式預期有字串序列。您可以使用
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])
或者您可以傳遞 flavor='lxml'
而沒有清單
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")
但是,如果您已安裝 bs4 和 html5lib,並傳遞 None
或 ['lxml', 'bs4']
,則剖析很可能會成功。請注意,一旦剖析成功,函式就會傳回。
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])
可使用 extract_links="all"
從儲存格中萃取連結和文字。
In [337]: html_table = """
.....: <table>
.....: <tr>
.....: <th>GitHub</th>
.....: </tr>
.....: <tr>
.....: <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
.....: </tr>
.....: </table>
.....: """
.....:
In [338]: df = pd.read_html(
.....: StringIO(html_table),
.....: extract_links="all"
.....: )[0]
.....:
In [339]: df
Out[339]:
(GitHub, None)
0 (pandas, https://github.com/pandas-dev/pandas)
In [340]: df[("GitHub", None)]
Out[340]:
0 (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object
In [341]: df[("GitHub", None)].str[1]
Out[341]:
0 https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object
1.5.0 版的新增功能。
寫入 HTML 檔案#
DataFrame
物件有一個實例方法 to_html
,用於將 DataFrame
的內容呈現為 HTML 表格。函式引數與上述 to_string
方法中的引數相同。
注意
為簡潔起見,這裡並未顯示 DataFrame.to_html
的所有可能選項。請參閱 DataFrame.to_html()
以取得完整的選項集。
注意
在支援 HTML 呈現的環境中,例如 Jupyter Notebook,display(HTML(...))`
會將原始 HTML 呈現到環境中。
In [342]: from IPython.display import display, HTML
In [343]: df = pd.DataFrame(np.random.randn(2, 2))
In [344]: df
Out[344]:
0 1
0 -0.345352 1.314232
1 0.690579 0.995761
In [345]: html = df.to_html()
In [346]: print(html) # raw html
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [347]: display(HTML(html))
<IPython.core.display.HTML object>
columns
引數會限制顯示的欄。
In [348]: html = df.to_html(columns=[0])
In [349]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
</tr>
</tbody>
</table>
In [350]: display(HTML(html))
<IPython.core.display.HTML object>
float_format
會採用 Python 可呼叫物件來控制浮點數值的精確度。
In [351]: html = df.to_html(float_format="{0:.10f}".format)
In [352]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.3453521949</td>
<td>1.3142323796</td>
</tr>
<tr>
<th>1</th>
<td>0.6905793352</td>
<td>0.9957609037</td>
</tr>
</tbody>
</table>
In [353]: display(HTML(html))
<IPython.core.display.HTML object>
bold_rows
預設會將列標籤設為粗體,但你可以關閉此功能。
In [354]: html = df.to_html(bold_rows=False)
In [355]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<td>1</td>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [356]: display(HTML(html))
<IPython.core.display.HTML object>
classes
引數提供將 CSS 類別套用至產生的 HTML 表格的功能。請注意,這些類別會附加到現有的 'dataframe'
類別。
In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
render_links
引數提供將超連結新增至包含 URL 的儲存格的功能。
In [358]: url_df = pd.DataFrame(
.....: {
.....: "name": ["Python", "pandas"],
.....: "url": ["https://www.python.org/", "https://pandas.dev.org.tw"],
.....: }
.....: )
.....:
In [359]: html = url_df.to_html(render_links=True)
In [360]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>name</th>
<th>url</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Python</td>
<td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
</tr>
<tr>
<th>1</th>
<td>pandas</td>
<td><a href="https://pandas.dev.org.tw" target="_blank">https://pandas.dev.org.tw</a></td>
</tr>
</tbody>
</table>
In [361]: display(HTML(html))
<IPython.core.display.HTML object>
最後,escape
參數允許您控制在結果 HTML 中是否轉義「<」、「>」和「&」字元(預設為 True
)。因此,若要取得未轉義字元的 HTML,請傳遞 escape=False
In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})
已轉義
In [363]: html = df.to_html()
In [364]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</td>
<td>2.396780</td>
</tr>
<tr>
<th>1</th>
<td><</td>
<td>0.014871</td>
</tr>
<tr>
<th>2</th>
<td>></td>
<td>3.357427</td>
</tr>
</tbody>
</table>
In [365]: display(HTML(html))
<IPython.core.display.HTML object>
未轉義
In [366]: html = df.to_html(escape=False)
In [367]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</td>
<td>2.396780</td>
</tr>
<tr>
<th>1</th>
<td><</td>
<td>0.014871</td>
</tr>
<tr>
<th>2</th>
<td>></td>
<td>3.357427</td>
</tr>
</tbody>
</table>
In [368]: display(HTML(html))
<IPython.core.display.HTML object>
注意
有些瀏覽器可能不會顯示前兩個 HTML 表格的呈現差異。
HTML 表格剖析陷阱#
用於剖析頂層 pandas io 函數 read_html
中 HTML 表格的函式庫存在一些版本問題。
lxml 的問題
優點
缺點
使用 lxml 作為後端時 BeautifulSoup4 遇到的問題
由於 BeautifulSoup4 本質上只是一個解析器後端的包裝器,因此上述問題也存在於這裡。
使用 html5lib 作為後端時 BeautifulSoup4 遇到的問題
優點
缺點
使用 html5lib 最大的缺點是它慢得像糖蜜。但是請考慮到網路上許多表格並不足以讓解析演算法執行時間產生影響。瓶頸更有可能是從 URL 透過網路讀取原始文字的過程中,也就是 IO(輸入輸出)。對於非常大的表格,這可能不成立。
LaTeX#
1.3.0 版的新增功能。
目前沒有從 LaTeX 讀取的方法,只有輸出方法。
寫入 LaTeX 檔案#
注意
DataFrame 和 Styler 物件目前有一個 to_latex
方法。我們建議使用 Styler.to_latex() 方法,而不是 DataFrame.to_latex(),因為前者在條件式樣方面具有更大的彈性,而後者可能會在未來被棄用。
檢閱 Styler.to_latex 的文件,其中提供了條件式樣的範例,並說明其關鍵字參數的操作。
對於簡單的應用,以下模式就足夠了。
In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])
In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
& c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}
若要在輸出前格式化值,請串接 Styler.format 方法。
In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
& c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}
XML#
讀取 XML#
1.3.0 版的新增功能。
頂層 read_xml()
函數可以接受 XML 字串/檔案/URL,並將節點和屬性解析成一個 pandas DataFrame
。
注意
由於沒有標準的 XML 結構,其中設計類型可能在許多方面有所不同,因此 read_xml
最適合用於較平坦、較淺的版本。如果 XML 文件是深度巢狀的,請使用 stylesheet
功能將 XML 轉換成較平坦的版本。
讓我們看幾個範例。
讀取 XML 字串
In [372]: from io import StringIO
In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
.....: <bookstore>
.....: <book category="cooking">
.....: <title lang="en">Everyday Italian</title>
.....: <author>Giada De Laurentiis</author>
.....: <year>2005</year>
.....: <price>30.00</price>
.....: </book>
.....: <book category="children">
.....: <title lang="en">Harry Potter</title>
.....: <author>J K. Rowling</author>
.....: <year>2005</year>
.....: <price>29.99</price>
.....: </book>
.....: <book category="web">
.....: <title lang="en">Learning XML</title>
.....: <author>Erik T. Ray</author>
.....: <year>2003</year>
.....: <price>39.95</price>
.....: </book>
.....: </bookstore>"""
.....:
In [374]: df = pd.read_xml(StringIO(xml))
In [375]: df
Out[375]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
讀取沒有選項的 URL
In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")
In [377]: df
Out[377]:
category title author year price cover
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00 None
1 children Harry Potter J K. Rowling 2005 29.99 None
2 web XQuery Kick Start Vaidyanathan Nagarajan 2003 49.99 None
3 web Learning XML Erik T. Ray 2003 39.95 paperback
讀取「books.xml」檔案的內容,並將其作為字串傳遞給 read_xml
In [378]: file_path = "books.xml"
In [379]: with open(file_path, "w") as f:
.....: f.write(xml)
.....:
In [380]: with open(file_path, "r") as f:
.....: df = pd.read_xml(StringIO(f.read()))
.....:
In [381]: df
Out[381]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
讀取「books.xml」的內容作為 StringIO
或 BytesIO
的執行個體,並將其傳遞給 read_xml
In [382]: with open(file_path, "r") as f:
.....: sio = StringIO(f.read())
.....:
In [383]: df = pd.read_xml(sio)
In [384]: df
Out[384]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
In [385]: with open(file_path, "rb") as f:
.....: bio = BytesIO(f.read())
.....:
In [386]: df = pd.read_xml(bio)
In [387]: df
Out[387]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
甚至可以從 AWS S3 儲存貯體讀取 XML,例如提供生物醫學和生命科學期刊的 NIH NCBI PMC 文章資料集
In [388]: df = pd.read_xml(
.....: "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
.....: xpath=".//journal-meta",
.....: )
.....:
In [389]: df
Out[389]:
journal-id journal-title issn publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN
使用 lxml 作為預設 parser
,您可以存取功能齊全的 XML 函式庫,它擴充了 Python 的 ElementTree API。其中一個強大的工具是能夠使用更具表達力的 XPath 選擇性或有條件地查詢節點
In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")
In [391]: df
Out[391]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
僅指定要解析的元素或屬性
In [392]: df = pd.read_xml(file_path, elems_only=True)
In [393]: df
Out[393]:
title author year price
0 Everyday Italian Giada De Laurentiis 2005 30.00
1 Harry Potter J K. Rowling 2005 29.99
2 Learning XML Erik T. Ray 2003 39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)
In [395]: df
Out[395]:
category
0 cooking
1 children
2 web
XML 文件可以有帶有前置詞和沒有前置詞的預設命名空間,兩者都用特殊屬性 xmlns
表示。為了在命名空間內容下解析節點,xpath
必須引用前置詞。
例如,以下 XML 包含帶有前置詞 doc
和 URI https://example.com
的命名空間。為了解析 doc:row
節點,必須使用 namespaces
。
In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <doc:data xmlns:doc="https://example.com">
.....: <doc:row>
.....: <doc:shape>square</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides>4.0</doc:sides>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>circle</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides/>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>triangle</doc:shape>
.....: <doc:degrees>180</doc:degrees>
.....: <doc:sides>3.0</doc:sides>
.....: </doc:row>
.....: </doc:data>"""
.....:
In [397]: df = pd.read_xml(StringIO(xml),
.....: xpath="//doc:row",
.....: namespaces={"doc": "https://example.com"})
.....:
In [398]: df
Out[398]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
類似地,XML 文件可以有一個沒有前置詞的預設命名空間。如果未指定暫時前置詞,則不會傳回節點並會引發 ValueError
。但將任何暫時名稱指定給正確的 URI 可讓節點解析。
In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <data xmlns="https://example.com">
.....: <row>
.....: <shape>square</shape>
.....: <degrees>360</degrees>
.....: <sides>4.0</sides>
.....: </row>
.....: <row>
.....: <shape>circle</shape>
.....: <degrees>360</degrees>
.....: <sides/>
.....: </row>
.....: <row>
.....: <shape>triangle</shape>
.....: <degrees>180</degrees>
.....: <sides>3.0</sides>
.....: </row>
.....: </data>"""
.....:
In [400]: df = pd.read_xml(StringIO(xml),
.....: xpath="//pandas:row",
.....: namespaces={"pandas": "https://example.com"})
.....:
In [401]: df
Out[401]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
但是,如果 XPath 未引用節點名稱(例如預設,/*
),則不需要 namespaces
。
注意
由於 xpath
識別要解析內容的父代,因此只會解析包含子節點或當前屬性的直接後代。因此,read_xml
不會解析孫子輩或其他後代的文字,也不會解析任何後代的屬性。若要擷取較低層級的內容,請將 xpath 調整至較低層級。例如,
In [402]: xml = """
.....: <data>
.....: <row>
.....: <shape sides="4">square</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="0">circle</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="3">triangle</shape>
.....: <degrees>180</degrees>
.....: </row>
.....: </data>"""
.....:
In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")
In [404]: df
Out[404]:
shape degrees
0 square 360
1 circle 360
2 triangle 180
顯示 shape
元素上的屬性 sides
未如預期般解析,因為此屬性位於 row
元素的子代,而非 row
元素本身。換句話說,sides
屬性是 row
元素的孫子輩後代。然而,xpath
目標為 row
元素,僅涵蓋其子代和屬性。
使用 lxml 作為解析器,您可以使用 XSLT 腳本壓平巢狀 XML 文件,該腳本也可以是字串/檔案/URL 類型。作為背景知識,XSLT 是一種特殊用途語言,以特殊的 XML 檔案撰寫,可以使用 XSLT 處理器將原始 XML 文件轉換成其他 XML、HTML,甚至是文字 (CSV、JSON 等)。
例如,考慮芝加哥「L」線乘車次數的這個有點巢狀的結構,其中車站和乘車次數元素在其自己的區段中封裝資料。使用以下 XSLT,lxml
可以將原始巢狀文件轉換成較平坦的輸出 (如下所示以供示範),以便更輕鬆地解析成 DataFrame
In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station id="40850" name="Library"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="41700" name="Washington/Wabash"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="40380" name="Clark/Lake"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: </response>"""
.....:
In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/response">
.....: <xsl:copy>
.....: <xsl:apply-templates select="row"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <xsl:copy>
.....: <station_id><xsl:value-of select="station/@id"/></station_id>
.....: <station_name><xsl:value-of select="station/@name"/></station_name>
.....: <xsl:copy-of select="month|rides/*"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station_id>40850</station_id>
.....: <station_name>Library</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>41700</station_id>
.....: <station_name>Washington/Wabash</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>40380</station_id>
.....: <station_name>Clark/Lake</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </row>
.....: </response>"""
.....:
In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)
In [409]: df
Out[409]:
station_id station_name ... avg_saturday_rides avg_sunday_holiday_rides
0 40850 Library ... 534.0 417.2
1 41700 Washington/Wabash ... 1909.8 1438.6
2 40380 Clark/Lake ... 1657.0 1453.8
[3 rows x 6 columns]
對於範圍從數百 MB 到 GB 的超大型 XML 檔案,pandas.read_xml()
支援使用 lxml 的 iterparse 和 etree 的 iterparse 來剖析如此龐大的檔案,這些方法對於在 XML 樹狀結構中進行迭代並擷取特定元素和屬性非常省記憶體,而且不會將整個樹狀結構保留在記憶體中。
1.5.0 版的新增功能。
若要使用此功能,您必須將實體 XML 檔案路徑傳遞至 read_xml
並使用 iterparse
參數。檔案不應壓縮或指向線上來源,而應儲存在本機磁碟中。此外,iterparse
應為字典,其中鍵為文件中的重複節點(成為列),而值則為重複節點的子節點(即子節點、孫節點)的任何元素或屬性的清單。由於此方法中未使用 XPath,因此子節點彼此之間不需要有相同的關聯性。以下顯示範例,說明如何讀取維基百科非常龐大(12 GB 以上)的最新文章資料轉儲。
In [1]: df = pd.read_xml(
... "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
... iterparse = {"page": ["title", "ns", "id"]}
... )
... df
Out[2]:
title ns id
0 Gettysburg Address 0 21450
1 Main Page 0 42950
2 Declaration by United Nations 0 8435
3 Constitution of the United States of America 0 8435
4 Declaration of Independence (Israel) 0 17858
... ... ... ...
3578760 Page:Black cat 1897 07 v2 n10.pdf/17 104 219649
3578761 Page:Black cat 1897 07 v2 n10.pdf/43 104 219649
3578762 Page:Black cat 1897 07 v2 n10.pdf/44 104 219649
3578763 The History of Tom Jones, a Foundling/Book IX 0 12084291
3578764 Page:Shakespeare of Stratford (1926) Yale.djvu/91 104 21450
[3578765 rows x 3 columns]
撰寫 XML#
1.3.0 版的新增功能。
DataFrame
物件有一個執行個體方法 to_xml
,它會將 DataFrame
的內容呈現為 XML 文件。
注意
此方法不支援 XML 的特殊屬性,包括 DTD、CData、XSD 架構、處理指令、註解等。僅支援根層級的命名空間。不過,stylesheet
允許在初始輸出後變更設計。
讓我們看幾個範例。
撰寫不帶選項的 XML
In [410]: geom_df = pd.DataFrame(
.....: {
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
撰寫具有新根目錄和列名的 XML
In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<objects>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</objects>
<objects>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</objects>
<objects>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</objects>
</geometry>
撰寫以屬性為中心的 XML
In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
<row index="0" shape="square" degrees="360" sides="4.0"/>
<row index="1" shape="circle" degrees="360"/>
<row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>
撰寫元素和屬性的混合
In [414]: print(
.....: geom_df.to_xml(
.....: index=False,
.....: attr_cols=['shape'],
.....: elem_cols=['degrees', 'sides'])
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<data>
<row shape="square">
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row shape="circle">
<degrees>360</degrees>
<sides/>
</row>
<row shape="triangle">
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
具有階層式欄位的任何 DataFrames
都會針對 XML 元素名稱進行扁平化,層級以底線分隔
In [415]: ext_geom_df = pd.DataFrame(
.....: {
.....: "type": ["polygon", "other", "polygon"],
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
.....: columns='type',
.....: values=['degrees', 'sides'],
.....: aggfunc='sum')
.....:
In [417]: pvt_df
Out[417]:
degrees sides
type other polygon other polygon
shape
circle 360.0 NaN 0.0 NaN
square NaN 360.0 NaN 4.0
triangle NaN 180.0 NaN 3.0
In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<shape>circle</shape>
<degrees_other>360.0</degrees_other>
<degrees_polygon/>
<sides_other>0.0</sides_other>
<sides_polygon/>
</row>
<row>
<shape>square</shape>
<degrees_other/>
<degrees_polygon>360.0</degrees_polygon>
<sides_other/>
<sides_polygon>4.0</sides_polygon>
</row>
<row>
<shape>triangle</shape>
<degrees_other/>
<degrees_polygon>180.0</degrees_polygon>
<sides_other/>
<sides_polygon>3.0</sides_polygon>
</row>
</data>
撰寫具有預設命名空間的 XML
In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data xmlns="https://example.com">
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
撰寫具有命名空間前綴的 XML
In [420]: print(
.....: geom_df.to_xml(namespaces={"doc": "https://example.com"},
.....: prefix="doc")
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<doc:data xmlns:doc="https://example.com">
<doc:row>
<doc:index>0</doc:index>
<doc:shape>square</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides>4.0</doc:sides>
</doc:row>
<doc:row>
<doc:index>1</doc:index>
<doc:shape>circle</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides/>
</doc:row>
<doc:row>
<doc:index>2</doc:index>
<doc:shape>triangle</doc:shape>
<doc:degrees>180</doc:degrees>
<doc:sides>3.0</doc:sides>
</doc:row>
</doc:data>
撰寫沒有宣告或美化列印的 XML
In [421]: print(
.....: geom_df.to_xml(xml_declaration=False,
.....: pretty_print=False)
.....: )
.....:
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>
撰寫 XML 並使用樣式表進行轉換
In [422]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/data">
.....: <geometry>
.....: <xsl:apply-templates select="row"/>
.....: </geometry>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <object index="{index}">
.....: <xsl:if test="shape!='circle'">
.....: <xsl:attribute name="type">polygon</xsl:attribute>
.....: </xsl:if>
.....: <xsl:copy-of select="shape"/>
.....: <property>
.....: <xsl:copy-of select="degrees|sides"/>
.....: </property>
.....: </object>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
<object index="0" type="polygon">
<shape>square</shape>
<property>
<degrees>360</degrees>
<sides>4.0</sides>
</property>
</object>
<object index="1">
<shape>circle</shape>
<property>
<degrees>360</degrees>
<sides/>
</property>
</object>
<object index="2" type="polygon">
<shape>triangle</shape>
<property>
<degrees>180</degrees>
<sides>3.0</sides>
</property>
</object>
</geometry>
XML 最終注意事項#
所有 XML 文件都遵守 W3C 規格。
etree
和lxml
剖析器都無法剖析格式不佳或不符合 XML 語法規則的任何標記文件。請注意,除非符合 XHTML 規格,否則 HTML 並非 XML 文件。不過,其他熱門標記類型,包括 KML、XAML、RSS、MusicML、MathML 都符合 XML 架構。基於上述原因,如果您的應用程式在 pandas 作業之前建置 XML,請使用適當的 DOM 函式庫,例如
etree
和lxml
,來建置必要的文件,而不是透過字串串接或 regex 調整。請務必記住,XML 是具有標記規則的特殊文字檔案。對於非常大的 XML 檔案(數百 MB 到 GB),XPath 和 XSLT 可能會變成記憶體密集型操作。請務必有足夠的可用 RAM 來讀寫大型 XML 檔案(大約是文字大小的 5 倍)。
由於 XSLT 是一種程式語言,請小心使用,因為此類指令碼可能會對您的環境構成安全風險,而且可能會執行大型或無限遞迴操作。務必在全面執行之前先針對小型片段測試指令碼。
etree 剖析器支援
read_xml
和to_xml
的所有功能,但複雜的 XPath 和任何 XSLT 除外。儘管功能有限,etree
仍然是一個可靠且有能力的剖析器和樹狀結構建立器。對於較大的檔案,其效能可能會在某種程度上落後lxml
,但在小型到中型的檔案上則相對不明顯。
Excel 檔案#
方法 read_excel()
可使用 openpyxl
Python 模組讀取 Excel 2007+ (.xlsx
) 檔案。Excel 2003 (.xls
) 檔案可使用 xlrd
讀取。二進位 Excel (.xlsb
) 檔案可使用 pyxlsb
讀取。所有格式都可使用 calamine 引擎讀取。實例方法 to_excel()
用於將 DataFrame
儲存至 Excel。一般而言,語意與使用 csv 資料類似。請參閱 範例手冊 以取得一些進階策略。
注意
當 engine=None
時,將使用下列邏輯來決定引擎
如果
path_or_buffer
是 OpenDocument 格式 (.odf、.ods、.odt),則會使用 odf。否則,如果
path_or_buffer
是 xls 格式,則會使用xlrd
。否則,如果
path_or_buffer
是 xlsb 格式,pyxlsb
會被使用。否則,
openpyxl
會被使用。
讀取 Excel 檔案#
在最基本的用例中,read_excel
會接受一個 Excel 檔案路徑,以及 sheet_name
,用於指出要分析哪個工作表。
在使用 engine_kwargs
參數時,pandas 會將這些參數傳遞給引擎。因此,了解 pandas 內部使用哪個函式非常重要。
對於引擎 openpyxl,pandas 使用
openpyxl.load_workbook()
來讀取 (.xlsx
) 和 (.xlsm
) 檔案。對於引擎 xlrd,pandas 使用
xlrd.open_workbook()
來讀取 (.xls
) 檔案。對於引擎 pyxlsb,pandas 使用
pyxlsb.open_workbook()
來讀取 (.xlsb
) 檔案。對於引擎 odf,pandas 使用
odf.opendocument.load()
來讀取 (.ods
) 檔案。對於引擎積碳,pandas 使用
python_calamine.load_workbook()
來讀取 (.xlsx
)、(.xlsm
)、(.xls
)、(.xlsb
)、(.ods
) 檔案。
# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")
ExcelFile
類別#
為了方便處理同一個檔案中的多個工作表,ExcelFile
類別可以用來包裝檔案,並傳遞給 read_excel
讀取多個工作表時,由於檔案只讀入記憶體一次,因此效能會有所提升。
xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")
ExcelFile
類別也可以用作內容管理員。
with pd.ExcelFile("path_to_file.xls") as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
sheet_names
屬性會產生檔案中工作表名稱的清單。
ExcelFile
的主要使用案例是使用不同的參數來剖析多個工作表。
data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)
請注意,如果所有工作表都使用相同的剖析參數,則可以將工作表名稱的清單傳遞給 read_excel
,而效能不會降低。
# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])
# equivalent using the read_excel function
data = pd.read_excel(
"path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)
ExcelFile
也可以使用 xlrd.book.Book
物件作為參數呼叫。這允許使用者控制 Excel 檔案的讀取方式。例如,可以透過呼叫 xlrd.open_workbook()
搭配 on_demand=True
來依需求載入工作表。
import xlrd
xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
指定工作表#
注意
第二個引數是 sheet_name
,不要與 ExcelFile.sheet_names
混淆。
注意
ExcelFile 的屬性 sheet_names
提供工作表清單的存取權。
引數
sheet_name
允許指定要讀取的工作表。預設
sheet_name
值為 0,表示讀取第一個工作表傳遞字串以參照工作簿中特定工作表的名稱。
傳遞整數以參照工作表的索引。索引遵循 Python 慣例,從 0 開始。
傳遞字串或整數的清單,以傳回指定工作表的字典。
傳遞
None
以傳回所有可用工作表的字典。
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])
使用工作表索引
# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])
使用所有預設值
# Returns a DataFrame
pd.read_excel("path_to_file.xls")
使用 None 取得所有工作表
# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)
使用清單取得多個工作表
# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])
read_excel
可以讀取多個工作表,方法是將 sheet_name
設定為工作表名稱清單、工作表位置清單,或 None
以讀取所有工作表。工作表可以透過工作表索引或工作表名稱指定,分別使用整數或字串。
讀取 MultiIndex
#
read_excel
可以讀取 MultiIndex
索引,方法是將欄位清單傳遞給 index_col
,以及將 MultiIndex
欄位傳遞給 header
,方法是將列清單傳遞給 header
。如果 index
或 columns
具有序列化層級名稱,則也會透過指定構成層級的列/欄位將其讀入。
例如,要讀取沒有名稱的 MultiIndex
索引
In [424]: df = pd.DataFrame(
.....: {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
.....: index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
.....: )
.....:
In [425]: df.to_excel("path_to_file.xlsx")
In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [427]: df
Out[427]:
a b
a c 1 5
d 2 6
b c 3 7
d 4 8
如果索引具有層級名稱,它們也會使用相同的參數進行剖析。
In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])
In [429]: df.to_excel("path_to_file.xlsx")
In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [431]: df
Out[431]:
a b
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
如果來源檔案同時具有 MultiIndex
索引和欄位,則應將指定每個索引和欄位的清單傳遞給 index_col
和 header
In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])
In [433]: df.to_excel("path_to_file.xlsx")
In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])
In [435]: df
Out[435]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
在 index_col
中指定的欄位中的遺失值將會向前填入,以允許與 to_excel
進行循環處理,以使用 merged_cells=True
。若要避免向前填入遺失值,請在讀取資料後使用 set_index
,而不是 index_col
。
剖析特定欄位#
使用者通常會插入欄位來在 Excel 中執行暫時運算,而您可能不想讀入這些欄位。 read_excel
會使用 usecols
關鍵字,讓您可以指定要剖析的欄位子集。
您可以將逗號分隔的 Excel 欄位和範圍指定為字串
pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")
如果 usecols
是整數清單,則假設它是檔案欄位索引,以進行剖析。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])
會忽略元素順序,因此 usecols=[0, 1]
與 [1, 0]
相同。
如果 usecols
是字串清單,假設每個字串對應到使用者在 names
中提供的欄位名稱,或從文件標題列推論。這些字串定義要解析哪些欄位
pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])
忽略元素順序,因此 usecols=['baz', 'joe']
與 ['joe', 'baz']
相同。
如果 usecols
可呼叫,可呼叫函式將針對欄位名稱評估,傳回可呼叫函式評估為 True
的名稱。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())
解析日期#
在讀取 Excel 檔案時,類似日期時間的值通常會自動轉換為適當的資料類型。但如果您有一欄字串看起來像日期(但實際上並未在 Excel 中設定為日期格式),您可以使用 parse_dates
關鍵字將這些字串解析為日期時間
pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])
儲存格轉換器#
可以透過 converters
選項轉換 Excel 儲存格的內容。例如,將一欄轉換為布林值
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})
此選項會處理遺失值,並將轉換器中的例外狀況視為遺失資料。轉換會逐一儲存格套用,而不是套用至整欄,因此陣列資料類型並非保證。例如,一欄包含遺失值的整數無法轉換為具有整數資料類型的陣列,因為 NaN 嚴格來說是浮點數。您可以手動遮罩遺失資料以復原整數資料類型
def cfun(x):
return int(x) if x else -1
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})
資料類型規格#
作為轉換器的替代方案,可以使用 dtype
關鍵字指定整欄的類型,該關鍵字會將字典對應欄位名稱至類型。若要詮釋沒有類型推論的資料,請使用類型 str
或 object
。
pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})
寫入 Excel 檔案#
將 Excel 檔案寫入磁碟#
若要將 DataFrame
物件寫入 Excel 檔案的工作表,可以使用 to_excel
執行個體方法。參數在很大程度上與上面所述的 to_csv
相同,第一個參數是 Excel 檔案的名稱,第二個參數(選用)是要寫入 DataFrame
的工作表名稱。例如
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
副檔名為 .xlsx
的檔案將使用 xlsxwriter
(如果可用)或 openpyxl
進行寫入。
將會以試圖模擬 REPL 輸出的方式寫入 DataFrame
。 index_label
將置於第二列,而非第一列。您可以透過將 to_excel()
中的 merge_cells
選項設定為 False
,將其置於第一列
df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)
為了將個別 DataFrames
寫入單一 Excel 檔案中的個別工作表,可以傳遞 ExcelWriter
。
with pd.ExcelWriter("path_to_file.xlsx") as writer:
df1.to_excel(writer, sheet_name="Sheet1")
df2.to_excel(writer, sheet_name="Sheet2")
在使用 engine_kwargs
參數時,pandas 會將這些參數傳遞給引擎。因此,了解 pandas 內部使用哪個函式非常重要。
對於 openpyxl 引擎,pandas 使用
openpyxl.Workbook()
來建立新工作表,並使用openpyxl.load_workbook()
將資料附加至現有工作表。openpyxl 引擎寫入 (.xlsx
) 和 (.xlsm
) 檔案。對於 xlsxwriter 引擎,pandas 使用
xlsxwriter.Workbook()
來寫入 (.xlsx
) 檔案。對於 odf 引擎,pandas 使用
odf.opendocument.OpenDocumentSpreadsheet()
來寫入 (.ods
) 檔案。
將 Excel 檔案寫入記憶體#
pandas 支援使用 ExcelWriter
將 Excel 檔案寫入類似緩衝區的物件,例如 StringIO
或 BytesIO
。
from io import BytesIO
bio = BytesIO()
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")
# Save the workbook
writer.save()
# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read()
注意
engine
是選用的,但建議使用。設定引擎會決定產生的工作簿版本。設定 engine='xlrd'
會產生 Excel 2003 格式的工作簿 (xls)。使用 'openpyxl'
或 'xlsxwriter'
會產生 Excel 2007 格式的工作簿 (xlsx)。如果省略,會產生 Excel 2007 格式的工作簿。
Excel 寫入器引擎#
pandas 選擇 Excel 寫入器有兩種方法
關鍵字引數
engine
檔案名稱副檔名(透過設定選項中指定的預設值)
預設情況下,pandas 會使用 XlsxWriter 處理 .xlsx
,openpyxl 處理 .xlsm
。如果您安裝了多個引擎,您可以透過 設定設定選項 io.excel.xlsx.writer
和 io.excel.xls.writer
來設定預設引擎。如果 Xlsxwriter 無法使用,pandas 會改用 openpyxl 處理 .xlsx
檔案。
若要指定要使用的寫入器,您可以傳遞引擎關鍵字引數給 to_excel
和 ExcelWriter
。內建引擎如下
openpyxl
:需要 2.4 或更高版本xlsxwriter
# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")
# Or via pandas configuration.
from pandas import options # noqa: E402
options.io.excel.xlsx.writer = "xlsxwriter"
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
樣式和格式化#
可以透過 DataFrame
的 to_excel
方法中的下列參數,來修改從 pandas 建立的 Excel 試算表的視覺效果
float_format
:浮點數的格式化字串(預設None
)。freeze_panes
:一個二元組,代表要凍結的最下排和最右欄。這些參數都是以 1 為基礎,因此 (1, 1) 會凍結第一排和第一欄(預設None
)。
使用 Xlsxwriter 引擎提供許多選項,用於控制使用 to_excel
方法建立的 Excel 工作表的格式。可以在 Xlsxwriter 文件中找到範例:https://xlsxwriter.readthedocs.io/working_with_pandas.html
OpenDocument 試算表#
Excel 檔案 的 io 方法也支援使用 odfpy 模組讀取和寫入 OpenDocument 試算表。讀取和寫入 OpenDocument 試算表的語意和功能與使用 engine='odf'
讀取和寫入 Excel 檔案 的功能相同。需要安裝選用的相依性套件「odfpy」。
read_excel()
方法可以讀取 OpenDocument 試算表
# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")
類似地,to_excel()
方法可以寫入 OpenDocument 試算表
# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")
二進位 Excel (.xlsb) 檔案#
方法 read_excel()
也可以使用 pyxlsb
模組來讀取二進位 Excel 檔案。讀取二進位 Excel 檔案的語意和功能大部分與使用 engine='pyxlsb'
讀取 Excel 檔案 相同。pyxlsb
無法辨識檔案中的日期時間類型,而會傳回浮點數(如果您需要辨識日期時間類型,可以使用 calamine)。
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")
注意
目前 pandas 只支援讀取二進位 Excel 檔案。不支援寫入。
Calamine(Excel 和 ODS 檔案)#
方法 read_excel()
可以使用 python-calamine
模組來讀取 Excel 檔案(.xlsx
、.xlsm
、.xls
、.xlsb
)和 OpenDocument 試算表(.ods
)。此模組是 Rust 函式庫 calamine 的繫結,而且在大部分情況下都比其他引擎快。需要安裝選用相依模組「python-calamine」。
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")
剪貼簿#
取得資料的便捷方法是使用 read_clipboard()
方法,它會取得剪貼簿緩衝區的內容,並將它們傳遞給 read_csv
方法。例如,您可以將以下文字複製到剪貼簿(在許多作業系統中為 CTRL-C)
A B C
x 1 4 p
y 2 5 q
z 3 6 r
然後透過呼叫將資料直接匯入 DataFrame
>>> clipdf = pd.read_clipboard()
>>> clipdf
A B C
x 1 4 p
y 2 5 q
z 3 6 r
to_clipboard
方法可寫入 DataFrame
的內容到剪貼簿。接著,您可以將剪貼簿內容貼到其他應用程式(在許多作業系統中為 CTRL-V)。以下說明如何將 DataFrame
寫入剪貼簿並讀回。
>>> df = pd.DataFrame(
... {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )
>>> df
A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
A B C
x 1 4 p
y 2 5 q
z 3 6 r
我們可以看到,我們取得了相同的內容,這是我們先前寫入剪貼簿的內容。
注意
您可能需要在 Linux 上安裝 xclip 或 xsel(搭配 PyQt5、PyQt4 或 qtpy)才能使用這些方法。
Pickling#
所有 pandas 物件都具備 to_pickle
方法,它使用 Python 的 cPickle
模組,使用 pickle 格式將資料結構儲存到磁碟。
In [436]: df
Out[436]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
In [437]: df.to_pickle("foo.pkl")
pandas
命名空間中的 read_pickle
函式可從檔案載入任何已封裝的 pandas 物件(或任何其他已封裝的物件)
In [438]: pd.read_pickle("foo.pkl")
Out[438]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
警告
read_pickle()
僅保證向後相容於幾個次要版本。
壓縮的 pickle 檔案#
read_pickle()
、DataFrame.to_pickle()
和 Series.to_pickle()
可以讀取和寫入壓縮的 pickle 檔案。壓縮類型 gzip
、bz2
、xz
、zstd
支援讀取和寫入。zip
檔案格式僅支援讀取,且必須只包含一個要讀取的資料檔案。
壓縮類型可以是明確的參數,或從檔案副檔名推斷。如果是「推斷」,則使用 gzip
、bz2
、zip
、xz
、zstd
,如果檔名結尾為 '.gz'
、'.bz2'
、'.zip'
、'.xz'
或 '.zst'
。
壓縮參數也可以是 dict
,用於將選項傳遞給壓縮協定。它必須有一個 'method'
鍵,設定為壓縮協定的名稱,該名稱必須為 {'zip'
, 'gzip'
, 'bz2'
, 'xz'
, 'zstd'
} 之一。所有其他鍵值對都會傳遞給基礎壓縮函式庫。
In [439]: df = pd.DataFrame(
.....: {
.....: "A": np.random.randn(1000),
.....: "B": "foo",
.....: "C": pd.date_range("20130101", periods=1000, freq="s"),
.....: }
.....: )
.....:
In [440]: df
Out[440]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
使用明確的壓縮類型
In [441]: df.to_pickle("data.pkl.compress", compression="gzip")
In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")
In [443]: rt
Out[443]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
從副檔名推斷壓縮類型
In [444]: df.to_pickle("data.pkl.xz", compression="infer")
In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")
In [446]: rt
Out[446]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
預設為「推斷」
In [447]: df.to_pickle("data.pkl.gz")
In [448]: rt = pd.read_pickle("data.pkl.gz")
In [449]: rt
Out[449]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
In [450]: df["A"].to_pickle("s1.pkl.bz2")
In [451]: rt = pd.read_pickle("s1.pkl.bz2")
In [452]: rt
Out[452]:
0 -0.317441
1 -1.236269
2 0.896171
3 -0.487602
4 -0.082240
...
995 -0.171092
996 1.786173
997 -0.575189
998 0.820750
999 -1.256530
Name: A, Length: 1000, dtype: float64
傳遞選項給壓縮協定以加速壓縮
In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})
msgpack#
pandas 對 msgpack
的支援已在版本 1.0.0 中移除。建議改用 pickle。
或者,您也可以使用 Arrow IPC 序列化格式,進行 pandas 物件的線上傳輸。如需 pyarrow 的文件,請參閱 此處。
HDF5 (PyTables)#
HDFStore
是類字典的物件,它使用出色的 PyTables 函式庫,使用高性能 HDF5 格式讀寫 pandas。請參閱 食譜,了解一些進階策略
警告
pandas 使用 PyTables 讀寫 HDF5 檔案,這允許使用 pickle 序列化物件資料類型資料。載入從不受信任來源接收的 pickle 資料可能不安全。
請參閱:https://docs.python.org/3/library/pickle.html,以取得更多資訊。
In [454]: store = pd.HDFStore("store.h5")
In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
物件可以寫入檔案,就像將鍵值對新增到字典一樣
In [456]: index = pd.date_range("1/1/2000", periods=8)
In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
# store.put('s', s) is an equivalent method
In [459]: store["s"] = s
In [460]: store["df"] = df
In [461]: store
Out[461]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
在目前或後續的 Python 課程中,您可以擷取儲存的物件
# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
刪除由金鑰指定的物件
# store.remove('df') is an equivalent method
In [464]: del store["df"]
In [465]: store
Out[465]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
關閉儲存區並使用內容管理員
In [466]: store.close()
In [467]: store
Out[467]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
In [468]: store.is_open
Out[468]: False
# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
.....: store.keys()
.....:
讀寫 API#
HDFStore
支援使用 read_hdf
進行讀取和使用 to_hdf
進行寫入的頂層 API,類似於 read_csv
和 to_csv
的運作方式。
In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})
In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)
In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]:
A B
3 3 3
4 4 4
HDFStore 預設不會捨棄所有遺失的列。可以透過設定 dropna=True
來變更此行為。
In [473]: df_with_missing = pd.DataFrame(
.....: {
.....: "col1": [0, np.nan, 2],
.....: "col2": [1, np.nan, np.nan],
.....: }
.....: )
.....:
In [474]: df_with_missing
Out[474]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")
In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [477]: df_with_missing.to_hdf(
.....: "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
.....: )
.....:
In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]:
col1 col2
0 0.0 1.0
2 2.0 NaN
固定格式#
上述範例顯示使用 put
進行儲存,這會將 HDF5 寫入 PyTables
的固定陣列格式,稱為 fixed
格式。這些類型的儲存區一旦寫入就無法附加(儘管您可以直接移除它們並重新寫入)。它們也不是可查詢的;它們必須完整擷取。它們也不支援具有非唯一欄位名稱的資料框。fixed
格式的儲存區提供非常快速的寫入和比 table
儲存區稍微快速的讀取。使用 put
或 to_hdf
或 format='fixed'
或 format='f'
時,預設會指定此格式。
警告
如果您嘗試使用 where
擷取,fixed
格式會產生 TypeError
In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")
In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")
File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
447 raise ValueError(
448 "key must be provided when HDF5 "
449 "file contains multiple datasets."
450 )
451 key = candidate_only_group._v_pathname
--> 452 return store.select(
453 key,
454 where=where,
455 start=start,
456 stop=stop,
457 columns=columns,
458 iterator=iterator,
459 chunksize=chunksize,
460 auto_close=auto_close,
461 )
462 except (ValueError, TypeError, LookupError):
463 if not isinstance(path_or_buf, HDFStore):
464 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
892 # create the iterator
893 it = TableIterator(
894 self,
895 s,
(...)
903 auto_close=auto_close,
904 )
--> 906 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
2026 where = self.where
2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
2030 self.close()
2031 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
889 def func(_start, _stop, _where):
--> 890 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
3270 def read(
3271 self,
3272 where=None,
(...)
3276 ) -> DataFrame:
3277 # start, stop applied to rows, so 0th axis only
-> 3278 self.validate_read(columns, where)
3279 select_axis = self.obj_type()._get_block_manager_axis(0)
3281 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
2917 raise TypeError(
2918 "cannot pass a column specification when reading "
2919 "a Fixed format store. this store must be selected in its entirety"
2920 )
2921 if where is not None:
-> 2922 raise TypeError(
2923 "cannot pass a where specification when reading "
2924 "from a Fixed format store. this store must be selected in its entirety"
2925 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
表格格式#
HDFStore
支援磁碟上的另一個 PyTables
格式,即 table
格式。在概念上,table
的形狀非常類似於 DataFrame,具有列和欄。table
可以附加在同一個或其他工作階段中。此外,還支援刪除和查詢類型作業。此格式由 format='table'
或 format='t'
指定給 append
或 put
或 to_hdf
。
此格式也可以設定為選項 pd.set_option('io.hdf.default_format','table')
,以啟用 put/append/to_hdf
預設儲存在 table
格式中。
In [481]: store = pd.HDFStore("store.h5")
In [482]: df1 = df[0:4]
In [483]: df2 = df[4:]
# append data (creates a table automatically)
In [484]: store.append("df", df1)
In [485]: store.append("df", df2)
In [486]: store
Out[486]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# select the entire object
In [487]: store.select("df")
Out[487]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'
注意
您也可以透過傳遞 format='table'
或 format='t'
給 put
作業來建立 table
。
階層式金鑰#
儲存庫的鍵可以指定為字串。這些鍵可以採用階層路徑名稱的格式(例如 foo/bar/bah
),這將產生一個子儲存庫的階層(或 PyTables 術語中的 群組
)。鍵可以不指定開頭的「/」,而且永遠都是絕對的(例如「foo」是指「/foo」)。移除操作可以移除子儲存庫中的所有內容,以及其下的內容,因此請小心。
In [489]: store.put("foo/bar/bah", df)
In [490]: store.append("food/orange", df)
In [491]: store.append("food/apple", df)
In [492]: store
Out[492]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']
# remove all nodes under this level
In [494]: store.remove("food")
In [495]: store
Out[495]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
您可以使用 walk
方法來瀏覽群組階層,它會為每個群組鍵產生一個元組,以及其內容的相對鍵。
In [496]: for (path, subgroups, subkeys) in store.walk():
.....: for subgroup in subgroups:
.....: print("GROUP: {}/{}".format(path, subgroup))
.....: for subkey in subkeys:
.....: key = "/".join([path, subkey])
.....: print("KEY: {}".format(key))
.....: print(store.get(key))
.....:
GROUP: /foo
KEY: /df
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
警告
階層鍵無法像上面所述的儲存在根節點下的項目那樣,以點號(屬性)存取的方式擷取。
In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah
File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
611 """allow attribute access to get stores"""
612 try:
--> 613 return self.get(name)
614 except (KeyError, ClosedFileError):
615 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
811 if group is None:
812 raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
1877 def _read_group(self, group: Node):
-> 1878 s = self._create_storer(group)
1879 s.infer_axes()
1880 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1750 tt = "generic_table"
1751 else:
-> 1752 raise TypeError(
1753 "cannot create a storer if the object is not existing "
1754 "nor a value are passed"
1755 )
1756 else:
1757 if isinstance(value, Series):
TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]:
/foo/bar/bah (Group) ''
children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]
請改用明確的字串鍵
In [499]: store["foo/bar/bah"]
Out[499]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
儲存類型#
在表格中儲存混合類型#
支援儲存混合資料型態。字串會使用附加欄位的最大大小,以固定寬度儲存。後續嘗試附加較長字串的動作會引發 ValueError
。
將 min_itemsize={`values`: size}
作為參數傳遞給附加,將會設定字串欄位較大的最小值。目前支援儲存 浮點數, 字串, 整數, 布林, datetime64
。對於字串欄位,將 nan_rep = 'nan'
傳遞給附加,將會變更磁碟上的預設 nan 表示法(會轉換為/從 np.nan
),預設為 nan
。
In [500]: df_mixed = pd.DataFrame(
.....: {
.....: "A": np.random.randn(8),
.....: "B": np.random.randn(8),
.....: "C": np.array(np.random.randn(8), dtype="float32"),
.....: "string": "string",
.....: "int": 1,
.....: "bool": True,
.....: "datetime64": pd.Timestamp("20010102"),
.....: },
.....: index=list(range(8)),
.....: )
.....:
In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan
In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})
In [503]: df_mixed1 = store.select("df_mixed")
In [504]: df_mixed1
Out[504]:
A B C ... int bool datetime64
0 0.013747 -1.166078 -1.292080 ... 1 True 1970-01-01 00:00:00.978393600
1 -0.712009 0.247572 1.526911 ... 1 True 1970-01-01 00:00:00.978393600
2 -0.645096 1.687406 0.288504 ... 1 True 1970-01-01 00:00:00.978393600
3 NaN NaN 0.097771 ... 1 True NaT
4 NaN NaN 1.536408 ... 1 True NaT
5 -0.023202 0.043702 0.926790 ... 1 True 1970-01-01 00:00:00.978393600
6 2.359782 0.088224 -0.676448 ... 1 True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724 ... 1 True 1970-01-01 00:00:00.978393600
[8 rows x 7 columns]
In [505]: df_mixed1.dtypes.value_counts()
Out[505]:
float64 2
float32 1
object 1
int64 1
bool 1
datetime64[ns] 1
Name: count, dtype: int64
# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]:
/df_mixed/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
"values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
"values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
"values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
"values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
"values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
byteorder := 'little'
chunkshape := (689,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
儲存多重索引資料框#
將多重索引 資料框
儲存為表格與儲存/從同質索引 資料框
中選取資料非常類似。
In [507]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: names=["foo", "bar"],
.....: )
.....:
In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [509]: df_mi
Out[509]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
In [510]: store.append("df_mi", df_mi)
In [511]: store.select("df_mi")
Out[511]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]:
A B C
foo bar
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
注意
index
關鍵字是保留字,不可用作層級名稱。
查詢#
查詢表格#
select
和 delete
作業有一個選用條件,可指定為僅選取/刪除資料的子集。這允許使用者擁有非常大的磁碟表格,並僅擷取部分資料。
查詢使用 Term
類別在幕後指定為布林表達式。
index
和columns
是資料框
支援的索引器。如果指定
data_columns
,這些資料欄可作為其他索引器使用。多重索引中的層級名稱,預設名稱為
level_0
、level_1
,…(如果未提供)。
有效的比較運算子為
=, ==, !=, >, >=, <, <=
有效的布林表達式與下列項目結合
|
:或&
:且(
和)
:用於分組
這些規則類似於布林表達式在 pandas 中用於索引的方式。
注意
=
會自動擴充為比較運算子==
~
是非運算子,但只能在非常有限的情況下使用如果傳遞一個表達式列表/元組,它們將通過
&
進行組合
以下為有效的表達式
'index >= date'
"columns = ['A', 'D']"
"columns in ['A', 'D']"
'columns = A'
'columns == A'
"~(columns = ['A', 'B'])"
'index > df.index[3] & string = "bar"'
'(index > df.index[3] & index <= df.index[6]) | string = "bar"'
"ts >= Timestamp('2012-02-01')"
"major_axis>=20130101"
indexers
位於子表達式的左側
columns
、major_axis
、ts
子表達式的右側(比較運算子之後)可以是
將會評估的函數,例如
Timestamp('2012-02-01')
字串,例如
"bar"
日期格式,例如
20130101
或"20130101"
清單,例如
"['A', 'B']"
在本地名稱空間中定義的變數,例如
date
注意
不建議透過將字串內插到查詢表達式中來傳遞字串給查詢。只要將感興趣的字串指定給變數,然後在表達式中使用該變數即可。例如,執行下列動作
string = "HolyMoly'"
store.select("df", "index == string")
而非執行下列動作
string = "HolyMoly'"
store.select('df', f'index == {string}')
後者不會運作,而且會引發 SyntaxError
。請注意,string
變數中有一個單引號後接一個雙引號。
如果您必須內插,請使用 '%r'
格式字串
store.select("df", "index == %r" % string)
這會為 string
加上引號。
以下是一些範例
In [513]: dfq = pd.DataFrame(
.....: np.random.randn(10, 4),
.....: columns=list("ABCD"),
.....: index=pd.date_range("20130101", periods=10),
.....: )
.....:
In [514]: store.append("dfq", dfq, format="table", data_columns=True)
使用布林表達式,搭配內嵌函數評估。
In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]:
A B
2013-01-05 -0.830545 -0.457071
2013-01-06 0.431186 1.049421
2013-01-07 0.617509 -0.811230
2013-01-08 0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10 0.361428 0.887304
使用內嵌欄位參考。
In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]:
A B C D
2013-01-02 0.658179 0.362814 -0.917897 0.010165
2013-01-03 0.905122 1.848731 -1.184241 0.932053
2013-01-05 -0.830545 -0.457071 1.565581 1.148032
2013-01-06 0.431186 1.049421 0.383309 0.595013
2013-01-07 0.617509 -0.811230 -2.088563 -1.393500
2013-01-08 0.947422 -0.671233 -0.847097 -1.187785
2013-01-10 0.361428 0.887304 0.266457 -0.399641
可以提供 columns
關鍵字,以選取要傳回的欄位清單,這等同於傳遞 'columns=list_of_columns_to_filter'
In [517]: store.select("df", "columns=['A', 'B']")
Out[517]:
A B
2000-01-01 0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03 0.816983 1.965656
2000-01-04 0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06 1.103675 1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977 0.465222
start
和 stop
參數可指定用於限制總搜尋空間。這些參數以資料表中列的總數表示。
注意
select
如果查詢表達式具有未知變數參考,將會引發 ValueError
。這通常表示您嘗試在不是資料列的欄位中選取資料。
select
如果查詢表達式無效,將會引發 SyntaxError
。
查詢 timedelta64[ns]#
您可以使用 timedelta64[ns]
類型儲存和查詢。可以在下列格式中指定條件:<float>(<unit>)
,其中浮點數可以有正負號(和小數),而單位可以是 timedelta 的 D,s,ms,us,ns
。以下是範例
In [518]: from datetime import timedelta
In [519]: dftd = pd.DataFrame(
.....: {
.....: "A": pd.Timestamp("20130101"),
.....: "B": [
.....: pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
.....: for i in range(10)
.....: ],
.....: }
.....: )
.....:
In [520]: dftd["C"] = dftd["A"] - dftd["B"]
In [521]: dftd
Out[521]:
A B C
0 2013-01-01 2013-01-01 00:00:10 -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10 -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10 -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10 -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10 -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10 -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10 -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10 -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10 -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50
In [522]: store.append("dftd", dftd, data_columns=True)
In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]:
A B C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10 -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10 -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10 -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10 -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10 -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50
查詢 MultiIndex#
可以透過使用層級名稱從 MultiIndex
中選取資料。
In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])
In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]:
A B C
foo bar
baz two -1.646063 -0.695847 -0.429156
如果 MultiIndex
層級名稱為 None
,層級會自動透過 level_n
關鍵字提供,其中 n
是您要從中選取的 MultiIndex
層級。
In [526]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: )
.....:
In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [528]: df_mi_2
Out[528]:
A B C
foo one -0.219582 1.186860 -1.437189
two 0.053768 1.872644 -1.469813
three -0.564201 0.876341 0.407749
bar one -0.232583 0.179812 0.922152
two -1.820952 -0.641360 2.133239
baz two -0.941248 -0.136307 -1.271305
three -0.099774 -0.061438 -0.845172
qux one 0.465793 0.756995 -0.541690
two -0.802241 0.877657 -2.553831
three 0.094899 -2.319519 0.293601
In [529]: store.append("df_mi_2", df_mi_2)
# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]:
A B C
foo two 0.053768 1.872644 -1.469813
索引#
在資料已存在於表格中之後(在 append/put
作業之後),您可以使用 create_table_index
為表格建立/修改索引。強烈建議建立表格索引。當您使用 select
,並將索引維度作為 where
時,這將大幅加速您的查詢。
注意
索引會自動建立在可索引的欄位和您指定的任何資料欄位上。您可以透過傳遞 index=False
給 append
來關閉此行為。
# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index
In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')
# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")
In [534]: i = store.root.df.table.cols.index.index
In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')
在將大量資料附加到儲存區時,通常會關閉每次附加的索引建立,然後在最後重新建立。
In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [538]: st = pd.HDFStore("appends.h5", mode="w")
In [539]: st.append("df", df_1, data_columns=["B"], index=False)
In [540]: st.append("df", df_2, data_columns=["B"], index=False)
In [541]: st.get_storer("df").table
Out[541]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
然後在附加完成後建立索引。
In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")
In [543]: st.get_storer("df").table
Out[543]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
autoindex := True
colindexes := {
"B": Index(9, fullshuffle, zlib(1)).is_csi=True}
In [544]: st.close()
請參閱 此處,了解如何針對現有儲存區建立完全排序索引 (CSI)。
透過資料欄位查詢#
您可以指定(並編制索引)某些欄位,以便執行查詢(除了 indexable
欄位,您隨時都可以查詢)。例如,假設您要在磁碟上執行此常見操作,並只傳回符合此查詢的畫面。您可以指定 data_columns = True
以強制所有欄位都成為 data_columns
。
In [545]: df_dc = df.copy()
In [546]: df_dc["string"] = "foo"
In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan
In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"
In [549]: df_dc["string2"] = "cool"
In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0
In [551]: df_dc
Out[551]:
A B C string string2
2000-01-01 0.858644 -0.851236 1.058006 foo cool
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-04 0.712795 -0.062433 0.736755 foo cool
2000-01-05 -0.298721 -1.988045 1.475308 NaN cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-07 -0.729161 -0.142928 -1.063038 foo cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
In [553]: store.select("df_dc", where="B > 0")
Out[553]:
A B C string string2
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]:
/df_dc/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2),
"C": Float64Col(shape=(), dflt=0.0, pos=3),
"string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
"string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
byteorder := 'little'
chunkshape := (1680,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}
將許多欄位變成 data columns
會造成效能降低,因此由使用者指定這些欄位。此外,您無法在第一次追加/放入操作後變更資料欄位(或可索引欄位)(當然,您可以直接讀取資料並建立新表格!)。
反覆運算器#
您可以傳遞 iterator=True
或 chunksize=number_in_a_chunk
給 select
和 select_as_multiple
以傳回結果的反覆運算器。預設值是每塊傳回 50,000 列。
In [557]: for df in store.select("df", chunksize=3):
.....: print(df)
.....:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
A B C
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
A B C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
注意
您也可以將反覆運算器與 read_hdf
搭配使用,這會在完成反覆運算後開啟,然後自動關閉儲存庫。
for df in pd.read_hdf("store.h5", "df", chunksize=3):
print(df)
請注意,chunksize 關鍵字適用於來源列。因此,如果您正在執行查詢,chunksize 會將表格中的總列數和套用的查詢細分,傳回大小可能不等的塊的反覆運算器。
以下是產生查詢並使用它建立大小相等的傳回塊的範例。
In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})
In [559]: dfeq
Out[559]:
number
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
In [560]: store.append("dfeq", dfeq, data_columns=["number"])
In [561]: def chunks(l, n):
.....: return [l[i: i + n] for i in range(0, len(l), n)]
.....:
In [562]: evens = [2, 4, 6, 8, 10]
In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")
In [564]: for c in chunks(coordinates, 2):
.....: print(store.select("dfeq", where=c))
.....:
number
1 2
3 4
number
5 6
7 8
number
9 10
進階查詢#
選取單一欄位#
若要擷取單一可索引或資料欄位,請使用 select_column
方法。例如,這將讓您能非常快速地取得索引。這些會傳回結果的 Series
,並以列號編制索引。這些目前不接受 where
選擇器。
In [565]: store.select_column("df_dc", "index")
Out[565]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
3 2000-01-04
4 2000-01-05
5 2000-01-06
6 2000-01-07
7 2000-01-08
Name: index, dtype: datetime64[ns]
In [566]: store.select_column("df_dc", "string")
Out[566]:
0 foo
1 foo
2 foo
3 foo
4 NaN
5 NaN
6 foo
7 bar
Name: string, dtype: object
選取座標#
有時您想要取得查詢的座標(又稱索引位置)。這會傳回結果位置的 Index
。這些座標也可以傳遞給後續的 where
作業。
In [567]: df_coord = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [568]: store.append("df_coord", df_coord)
In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")
In [570]: c
Out[570]:
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
...
990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
dtype='int64', length=268)
In [571]: store.select("df_coord", where=c)
Out[571]:
0 1
2002-01-02 0.007717 1.168386
2002-01-03 0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208 1.260503
... ... ...
2002-09-22 -1.139583 0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24 0.059018 1.775482
2002-09-25 1.242255 -0.055457
2002-09-26 0.410317 2.194489
[268 rows x 2 columns]
使用 where 遮罩選取#
有時您的查詢可能涉及建立要選取的列清單。通常,這個 mask
會是索引作業的結果 index
。此範例選取 datetimeindex 中為 5 的月份。
In [572]: df_mask = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [573]: store.append("df_mask", df_mask)
In [574]: c = store.select_column("df_mask", "index")
In [575]: where = c[pd.DatetimeIndex(c).month == 5].index
In [576]: store.select("df_mask", where=where)
Out[576]:
0 1
2000-05-01 1.479511 0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03 0.900321 0.049695
2000-05-04 0.614266 -1.077151
2000-05-05 0.233881 0.493246
... ... ...
2002-05-27 0.294122 0.457407
2002-05-28 -1.102535 1.215650
2002-05-29 -0.432911 0.753606
2002-05-30 -1.105212 2.311877
2002-05-31 2.567296 2.610691
[93 rows x 2 columns]
儲存器物件#
如果您想要檢查儲存的物件,請透過 get_storer
擷取。您可以用程式化方式使用它來取得物件中的列數。
In [577]: store.get_storer("df_dc").nrows
Out[577]: 8
多個表格查詢#
方法 append_to_multiple
和 select_as_multiple
可以一次執行多個表格的附加/選取作業。其概念是有一個表格(稱為選擇器表格),您會索引大部分/全部的欄位,並執行您的查詢。其他表格是資料表格,其索引與選擇器表格的索引相符。接著,您可以在選擇器表格上執行非常快速的查詢,但可以取得大量的資料。此方法類似於有一個非常寬的表格,但能進行更有效率的查詢。
方法 append_to_multiple
會根據 d
(一個將表格名稱對應到您在該表格中想要的「欄」清單的字典)將給定的單一 DataFrame 分割成多個表格。如果在清單位置使用 None
,該表格將擁有給定 DataFrame 中其餘未指定的欄。參數 selector
定義哪個表格是選擇器表格(您可以從中執行查詢)。參數 dropna
會從輸入的 DataFrame
刪除列,以確保表格同步。這表示如果要寫入的其中一個表格的列完全是 np.nan
,該列會從所有表格中刪除。
如果 dropna
為 False,使用者有責任同步表格。請記住,完全是 np.Nan
的列不會寫入 HDFStore,因此如果您選擇呼叫 dropna=False
,有些表格可能比其他表格有更多列,因此 select_as_multiple
可能無法運作或可能會傳回意外的結果。
In [578]: df_mt = pd.DataFrame(
.....: np.random.randn(8, 6),
.....: index=pd.date_range("1/1/2000", periods=8),
.....: columns=["A", "B", "C", "D", "E", "F"],
.....: )
.....:
In [579]: df_mt["foo"] = "bar"
In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan
# you can also create the tables individually
In [581]: store.append_to_multiple(
.....: {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
.....: )
.....:
In [582]: store
Out[582]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# individual tables were created
In [583]: store.select("df1_mt")
Out[583]:
A B
2000-01-01 0.162291 -0.430489
2000-01-02 NaN NaN
2000-01-03 0.429207 -1.099274
2000-01-04 1.869081 -1.466039
2000-01-05 0.092130 -1.726280
2000-01-06 0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342 0.557402
In [584]: store.select("df2_mt")
Out[584]:
C D E F foo
2000-01-01 -2.502042 0.668149 0.460708 1.834518 bar
2000-01-02 0.130441 -0.608465 0.439872 0.506364 bar
2000-01-03 -1.069546 1.236277 0.116634 -1.772519 bar
2000-01-04 0.137462 0.313939 0.748471 -0.943009 bar
2000-01-05 0.836517 2.049798 0.562167 0.189952 bar
2000-01-06 1.112750 -0.151596 1.503311 0.939470 bar
2000-01-07 -0.294348 0.335844 -0.794159 1.495614 bar
2000-01-08 0.860312 -0.538674 -0.541986 -1.759606 bar
# as a multiple
In [585]: store.select_as_multiple(
.....: ["df1_mt", "df2_mt"],
.....: where=["A>0", "B>0"],
.....: selector="df1_mt",
.....: )
.....:
Out[585]:
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []
從表格中刪除#
您可以透過指定 where
選擇性地從表格中刪除。在刪除列時,了解 PyTables
會透過清除列,然後移動後續資料來刪除列,非常重要。因此,刪除可能會根據資料的方向而成為非常昂貴的操作。為了獲得最佳效能,值得讓您要刪除的維度成為 indexables
的第一個維度。
資料依據 indexables
(以磁碟為準)排序。以下是簡單的用例。您儲存面板類型資料,日期在 major_axis
中,而 ID 在 minor_axis
中。然後資料會像這樣交錯排列
- date_1
id_1
id_2
.
id_n
- date_2
id_1
.
id_n
很明顯,對 major_axis
執行刪除作業會相當快速,因為會移除一個區塊,然後移動後續資料。另一方面,對 minor_axis
執行刪除作業會非常耗時。這種情況下,使用 where
選取所有資料,但遺失資料除外,然後重新寫入表格,肯定會快很多。
注意事項和警告#
壓縮#
PyTables
允許壓縮已儲存的資料。這適用於所有類型的儲存,不只是表格。兩個參數用於控制壓縮: complevel
和 complib
。
complevel
指定是否壓縮資料,以及壓縮程度。complevel=0
和complevel=None
會停用壓縮,而0<complevel<10
會啟用壓縮。complib
指定要使用的壓縮函式庫。如果未指定任何內容,則會使用預設函式庫zlib
。壓縮函式庫通常針對良好的壓縮率或速度進行最佳化,而結果將取決於資料類型。選擇哪種類型的壓縮取決於您的特定需求和資料。支援的壓縮函式庫清單zlib:預設的壓縮函式庫。在壓縮方面是經典之作,可達到良好的壓縮率,但速度稍慢。
lzo:快速壓縮和解壓縮。
bzip2:良好的壓縮率。
blosc:快速壓縮和解壓縮。
支援其他 blosc 壓縮器
blosc:blosclz 這是
blosc
的預設壓縮器blosc:lz4:一種精簡、非常流行且快速的壓縮器。
blosc:lz4hc:LZ4 的調整版本,以犧牲速度為代價產生更好的壓縮比。
blosc:snappy:在許多地方使用的流行壓縮器。
blosc:zlib:一種經典之作;比前述的稍慢,但可達到更好的壓縮比。
blosc:zstd:一種極為平衡的編解碼器;它提供上述其他編解碼器中最佳的壓縮比,而且速度相當快。
如果
complib
被定義為列出的函式庫以外的其他函式庫,則會發出ValueError
例外。
注意
如果使用 complib
選項指定的函式庫在您的平台上不存在,則壓縮會預設為 zlib
,而不會有其他動作。
啟用檔案中所有物件的壓縮
store_compressed = pd.HDFStore(
"store_compressed.h5", complevel=9, complib="blosc:blosclz"
)
或在壓縮未啟用的儲存區中進行即時壓縮(這只適用於資料表)
store.append("df", df, complib="zlib", complevel=5)
ptrepack#
PyTables
在資料表寫入後進行壓縮時,可提供更好的寫入效能,而不是在最一開始就開啟壓縮。您可以使用提供的 PyTables
工具 ptrepack
。此外,ptrepack
可以在事後變更壓縮層級。
ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5
此外 ptrepack in.h5 out.h5
將會重新封裝檔案,讓您可以重複使用先前刪除的空間。或者,您也可以直接移除檔案並重新寫入,或使用 copy
方法。
注意事項#
警告
HDFStore
不支援寫入執行緒安全。底層的 PyTables
只支援同時讀取(透過執行緒或程序)。如果您需要同時讀取和寫入,則需要在單一程序中的單一執行緒中序列化這些作業。否則,您的資料將會損毀。請參閱 (GH 2397) 以取得更多資訊。
如果您使用鎖定來管理多個程序之間的寫入存取權,您可能想在釋放寫入鎖定之前使用
fsync()
。為了方便,您可以使用store.flush(fsync=True)
來為您執行此操作。一旦建立
table
,欄位 (DataFrame) 會固定;只能附加完全相同的欄位請注意,時區 (例如
pytz.timezone('US/Eastern')
) 不一定會在所有時區版本中相同。因此,如果資料使用時區函式庫的一個版本定位到 HDFStore 中的特定時區,而該資料使用另一個版本更新,資料將會轉換為 UTC,因為這些時區不被視為相同。請使用相同版本的時區函式庫,或使用tz_convert
搭配已更新的時區定義。
警告
PyTables
會顯示 NaturalNameWarning
,如果欄位名稱無法用作屬性選擇器。自然識別碼僅包含字母、數字和底線,且不能以數字開頭。其他識別碼無法用於 where
子句,而且通常不是好主意。
資料類型#
HDFStore
會將物件資料類型對應到 PyTables
底層資料類型。這表示下列類型已知可行
類型 |
表示遺失值 |
---|---|
浮動小數點 : |
|
整數 : |
|
布林 |
|
|
|
|
|
類別 : 請參閱以下部分 |
|
物件 : |
|
unicode
欄位不支援,並且將會失敗。
類別資料#
您可以將包含 類別
資料類型的資料寫入 HDFStore
。查詢運作方式與物件陣列相同。但是,類別
資料類型資料以更有效率的方式儲存。
In [586]: dfcat = pd.DataFrame(
.....: {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
.....: )
.....:
In [587]: dfcat
Out[587]:
A B
0 a -1.520478
1 a -1.069391
2 b -0.551981
3 b 0.452407
4 c 0.409257
5 d 0.301911
6 b -0.640843
7 a -2.253022
In [588]: dfcat.dtypes
Out[588]:
A category
B float64
dtype: object
In [589]: cstore = pd.HDFStore("cats.h5", mode="w")
In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])
In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")
In [592]: result
Out[592]:
A B
2 b -0.551981
3 b 0.452407
4 c 0.409257
6 b -0.640843
In [593]: result.dtypes
Out[593]:
A category
B float64
dtype: object
字串欄位#
min_itemsize
HDFStore
的基礎實作對字串欄位使用固定的欄位寬度 (itemsize)。字串欄位 itemsize 計算為傳遞給 HDFStore
的資料長度 (針對該欄位) 的最大值,在第一次附加時。後續附加可能會引入一個字串,其長度大於欄位可以容納的長度,這時會引發例外狀況 (否則這些欄位可能會在沒有提示的情況下被截斷,導致資訊遺失)。未來我們可能會放寬這個限制,並允許使用者指定的截斷發生。
在首次建立表格時傳遞 min_itemsize
以先驗指定特定字串欄位的最小長度。 min_itemsize
可以是整數,或將欄位名稱對應到整數的字典。您可以傳遞 values
作為金鑰,以允許所有可索引或資料欄位擁有此 min_itemsize。
傳遞 min_itemsize
字典會導致自動將所有傳遞的欄位建立為資料欄位。
注意
如果您未傳遞任何 data_columns
,則 min_itemsize
將會是傳遞的任何字串長度的最大值
In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))
In [595]: dfs
Out[595]:
A B
0 foo bar
1 foo bar
2 foo bar
3 foo bar
4 foo bar
# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)
In [597]: store.get_storer("dfs").table
Out[597]:
/dfs/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
byteorder := 'little'
chunkshape := (963,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})
In [599]: store.get_storer("dfs2").table
Out[599]:
/dfs2/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
"A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
byteorder := 'little'
chunkshape := (1598,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"A": Index(6, mediumshuffle, zlib(1)).is_csi=False}
nan_rep
字串欄位會使用 nan_rep
字串表示法序列化 np.nan
(遺失值)。這預設為字串值 nan
。您可能會無意間將實際 nan
值變成遺失值。
In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})
In [601]: dfss
Out[601]:
A
0 foo
1 bar
2 nan
In [602]: store.append("dfss", dfss)
In [603]: store.select("dfss")
Out[603]:
A
0 foo
1 bar
2 NaN
# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")
In [605]: store.select("dfss2")
Out[605]:
A
0 foo
1 bar
2 nan
效能#
tables
格式與fixed
儲存相比,寫入效能較差。好處是可以附加/刪除和查詢(可能是大量的資料)。與一般儲存相比,寫入時間通常較長。查詢時間可能相當快,特別是在索引軸上。您可以傳遞
chunksize=<int>
給append
,指定寫入區塊大小(預設為 50000)。這將大幅降低寫入時的記憶體使用量。您可以傳遞
expectedrows=<int>
給第一個append
,設定PyTables
預期的總列數。這將最佳化讀取/寫入效能。重複列可以寫入表格,但在選取時會被濾除(選取最後的項目;因此表格在主要、次要配對上是唯一的)
如果您嘗試儲存將由 PyTables 序列化(而非儲存為原生類型)的類型,將會引發
PerformanceWarning
。請參閱 此處 以取得更多資訊和一些解決方案。
Feather#
Feather 為資料框提供二進制欄位序列化。其設計用於有效率地讀取和寫入資料框,並簡化跨資料分析語言分享資料。
Feather 設計用於忠實地序列化和反序列化資料框,支援所有 pandas 資料類型,包括擴充資料類型,例如類別和帶時區的日期時間。
幾個注意事項
此格式不會寫入
Index
或MultiIndex
給DataFrame
,如果提供非預設值,將會引發錯誤。您可以使用.reset_index()
來儲存索引,或使用.reset_index(drop=True)
來忽略它。不支援重複的欄位名稱和非字串欄位名稱
實際物件 dtype 欄位中的 Python 物件不受支援。在嘗試序列化時,這會產生有用的錯誤訊息。
請參閱 完整文件。
In [606]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.Categorical(list("abc")),
.....: "g": pd.date_range("20130101", periods=3),
.....: "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "i": pd.date_range("20130101", periods=3, freq="ns"),
.....: }
.....: )
.....:
In [607]: df
Out[607]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
In [608]: df.dtypes
Out[608]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
寫入 feather 檔案。
In [609]: df.to_feather("example.feather")
從 feather 檔案讀取。
In [610]: result = pd.read_feather("example.feather")
In [611]: result
Out[611]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
# we preserve dtypes
In [612]: result.dtypes
Out[612]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
Parquet#
Apache Parquet 提供資料框的分割二進制欄位序列化。其設計目的是提高資料框的讀寫效率,並簡化跨資料分析語言的資料共用。Parquet 可使用各種壓縮技術,在維持良好讀取效能的同時,盡可能縮小檔案大小。
Parquet 的設計目的在於忠實地序列化和反序列化 DataFrame
,支援所有 pandas 資料類型,包括擴充資料類型,例如帶有時區的日期時間。
有幾個注意事項。
不支援重複的欄位名稱和非字串欄位名稱。
pyarrow
引擎總是將索引寫入輸出,但fastparquet
僅寫入非預設索引。這個額外的欄位可能會造成非 pandas 使用者預料之外的問題。無論底層引擎為何,您都可以使用index
參數強制包含或省略索引。如果指定索引層級名稱,則必須是字串。
在
pyarrow
引擎中,非字串類型的類別資料類型可以序列化至 parquet,但會反序列化為其原始資料類型。pyarrow
引擎會保留字串類型的類別資料類型的ordered
旗標。fastparquet
不会保留ordered
旗標。不支援的類型包括
Interval
和實際的 Python 物件類型。這些類型在嘗試序列化時會產生有用的錯誤訊息。Period
類型在 pyarrow >= 0.16.0 中受支援。pyarrow
引擎會保留延伸資料類型,例如可為空的整數和字串資料類型(需要 pyarrow >= 0.16.0,且需要延伸類型來實作必要的協定,請參閱 延伸類型文件)。
您可以指定 engine
來引導序列化。這可以是 pyarrow
、fastparquet
或 auto
之一。如果未指定引擎,則會檢查 pd.options.io.parquet.engine
選項;如果這也是 auto
,則會嘗試 pyarrow
,並回退到 fastparquet
。
參閱文件 pyarrow 和 fastparquet。
注意
這些引擎非常相似,應可讀取/寫入幾乎相同的 Parquet 格式檔案。 pyarrow>=8.0.0
支援時間差資料,fastparquet>=0.1.4
支援時區感知日期時間。這些函式庫的不同之處在於具有不同的基礎相依性(fastparquet
使用 numba
,而 pyarrow
使用 C 函式庫)。
In [613]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.date_range("20130101", periods=3),
.....: "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "h": pd.Categorical(list("abc")),
.....: "i": pd.Categorical(list("abc"), ordered=True),
.....: }
.....: )
.....:
In [614]: df
Out[614]:
a b c d e f g h i
0 a 1 3 4.0 True 2013-01-01 2013-01-01 00:00:00-05:00 a a
1 b 2 4 5.0 False 2013-01-02 2013-01-02 00:00:00-05:00 b b
2 c 3 5 6.0 True 2013-01-03 2013-01-03 00:00:00-05:00 c c
In [615]: df.dtypes
Out[615]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
寫入 Parquet 檔案。
In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")
In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")
從 Parquet 檔案讀取。
In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")
In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")
In [620]: result.dtypes
Out[620]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
透過設定 dtype_backend
參數,您可以控制用於結果 DataFrame 的預設 dtypes。
In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")
In [622]: result.dtypes
Out[622]:
a string[pyarrow]
b int64[pyarrow]
c uint8[pyarrow]
d double[pyarrow]
e bool[pyarrow]
f timestamp[ns][pyarrow]
g timestamp[ns, tz=US/Eastern][pyarrow]
h dictionary<values=string, indices=int32, order...
i dictionary<values=string, indices=int32, order...
dtype: object
注意
請注意,fastparquet
不支援此功能。
僅讀取 Parquet 檔案的特定欄位。
In [623]: result = pd.read_parquet(
.....: "example_fp.parquet",
.....: engine="fastparquet",
.....: columns=["a", "b"],
.....: )
.....:
In [624]: result = pd.read_parquet(
.....: "example_pa.parquet",
.....: engine="pyarrow",
.....: columns=["a", "b"],
.....: )
.....:
In [625]: result.dtypes
Out[625]:
a object
b int64
dtype: object
處理索引#
將 DataFrame
序列化為 Parquet 可能包含隱含索引,作為輸出檔案中的一個或多個欄位。因此,此程式碼
In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [627]: df.to_parquet("test.parquet", engine="pyarrow")
如果您使用 pyarrow
進行序列化,則會建立一個具有三個欄位的 Parquet 檔案:a
、b
和 __index_level_0__
。如果您使用 fastparquet
,則索引 可能會或可能不會 寫入檔案。
這個意外的多餘欄位會導致一些資料庫(例如 Amazon Redshift)拒絕這個檔案,因為目標資料表中不存在該欄位。
如果您要在寫入時略過資料框的索引,請將 index=False
傳遞給 to_parquet()
In [628]: df.to_parquet("test.parquet", index=False)
這會建立一個僅包含兩個預期欄位(a
和 b
)的 Parquet 檔案。如果您的 DataFrame
有自訂索引,當您將這個檔案載入 DataFrame
時,您不會取回它。
傳遞 index=True
將永遠寫入索引,即使這不是基礎引擎的預設行為。
分割 Parquet 檔案#
Parquet 支援根據一個或多個欄位的數值分割資料。
In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})
In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)
path
指定將儲存資料的父目錄。 partition_cols
是將用來分割資料集的欄位名稱。欄位會依據指定的順序分割。分割區隔是由分割欄位中的唯一數值決定的。上述範例會建立一個分割的資料集,其可能如下所示
test
├── a=0
│ ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│ └── ...
└── a=1
├── e6ab24a4f45147b49b54a662f0c412a3.parquet
└── ...
ORC#
類似於 parquet 格式,ORC 格式 是資料框的二進制欄位序列化。它旨在讓讀取資料框更有效率。pandas 提供 ORC 格式的讀取器和寫入器,read_orc()
和 to_orc()
。這需要 pyarrow 函式庫。
警告
強烈建議使用 conda 安裝 pyarrow,因為 pyarrow 發生了一些問題。
to_orc()
需要 pyarrow>=7.0.0。read_orc()
和to_orc()
尚未支援 Windows,您可以在 安裝選用相依性 中找到有效的環境。關於支援的 dtypes,請參閱 Arrow 中支援的 ORC 功能。
目前,當資料框轉換為 ORC 檔案時,日期時間欄位中的時區不會保留。
In [631]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(4.0, 7.0, dtype="float64"),
.....: "d": [True, False, True],
.....: "e": pd.date_range("20130101", periods=3),
.....: }
.....: )
.....:
In [632]: df
Out[632]:
a b c d e
0 a 1 4.0 True 2013-01-01
1 b 2 5.0 False 2013-01-02
2 c 3 6.0 True 2013-01-03
In [633]: df.dtypes
Out[633]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
寫入 orc 檔案。
In [634]: df.to_orc("example_pa.orc", engine="pyarrow")
從 orc 檔案讀取。
In [635]: result = pd.read_orc("example_pa.orc")
In [636]: result.dtypes
Out[636]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
僅讀取 orc 檔案的特定欄位。
In [637]: result = pd.read_orc(
.....: "example_pa.orc",
.....: columns=["a", "b"],
.....: )
.....:
In [638]: result.dtypes
Out[638]:
a object
b int64
dtype: object
SQL 查詢#
模組 pandas.io.sql
提供一組查詢包裝器,用於簡化資料擷取並減少對特定資料庫 API 的依賴性。
在可用的情況下,使用者可能首先想要選擇 Apache Arrow ADBC 驅動程式。這些驅動程式應提供最佳效能、空值處理和類型偵測。
2.2.0 版的新增功能: 新增對 ADBC 驅動程式的原生支援
如需 ADBC 驅動程式及其開發狀態的完整清單,請參閱 ADBC 驅動程式實作狀態 文件。
在 ADBC 驅動程式不可用或可能缺少功能的情況下,使用者應選擇在資料庫驅動程式程式庫旁安裝 SQLAlchemy。此類驅動程式的範例包括 PostgreSQL 的 psycopg2 或 MySQL 的 pymysql。對於 SQLite,它預設包含在 Python 的標準程式庫中。您可以在 SQLAlchemy 文件 中找到每個 SQL 方言支援的驅動程式概觀。
如果未安裝 SQLAlchemy,您可以使用 sqlite3.Connection
取代 SQLAlchemy 引擎、連線或 URI 字串。
另請參閱一些 食譜範例,以了解一些進階策略。
主要功能為
|
將 SQL 資料庫表格讀取到 DataFrame 中。 |
|
將 SQL 查詢讀入 DataFrame。 |
|
將 SQL 查詢或資料庫表格讀入 DataFrame。 |
|
將儲存在 DataFrame 中的記錄寫入 SQL 資料庫。 |
注意
函數 read_sql()
是 read_sql_table()
和 read_sql_query()
的方便包裝器(以及向後相容性),並且會根據提供的輸入(資料庫表格名稱或 SQL 查詢)委派給特定函數。如果表格名稱有特殊字元,不需要加上引號。
在以下範例中,我們使用 SQlite SQL 資料庫引擎。您可以使用暫時的 SQLite 資料庫,其中資料儲存在「記憶體」中。
若要使用 ADBC 驅動程式連線,您將需要使用套件管理員安裝 adbc_driver_sqlite
。安裝後,您可以使用 ADBC 驅動程式提供的 DBAPI 介面連線至您的資料庫。
import adbc_driver_sqlite.dbapi as sqlite_dbapi
# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
df = pd.read_sql_table("data", conn)
若要連線到 SQLAlchemy,請使用 create_engine()
函數,以資料庫 URI 建立引擎物件。您只需要為每個要連線的資料庫建立一次引擎。如需有關 create_engine()
和 URI 格式化的詳細資訊,請參閱下列範例和 SQLAlchemy 文件
In [639]: from sqlalchemy import create_engine
# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")
如果您想管理自己的連線,可以傳遞其中一個連線。下列範例會使用 Python 內容管理員開啟與資料庫的連線,此管理員會在區塊完成後自動關閉連線。請參閱 SQLAlchemy 文件,以了解如何處理資料庫連線。
with engine.connect() as conn, conn.begin():
data = pd.read_sql_table("data", conn)
警告
當您開啟與資料庫的連線時,您也有責任關閉連線。讓連線保持開啟的副作用可能包括鎖定資料庫或其他中斷行為。
寫入資料框架#
假設下列資料在 DataFrame
data
中,我們可以使用 to_sql()
將其插入資料庫。
id |
Date |
Col_1 |
Col_2 |
Col_3 |
---|---|---|---|---|
26 |
2012-10-18 |
X |
25.7 |
True |
42 |
2012-10-19 |
Y |
-12.4 |
False |
63 |
2012-10-20 |
Z |
5.73 |
True |
In [641]: import datetime
In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]
In [643]: d = [
.....: (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
.....: (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
.....: (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
.....: ]
.....:
In [644]: data = pd.DataFrame(d, columns=c)
In [645]: data
Out[645]:
id Date Col_1 Col_2 Col_3
0 26 2010-10-18 X 27.50 True
1 42 2010-10-19 Y -12.50 False
2 63 2010-10-20 Z 5.73 True
In [646]: data.to_sql("data", con=engine)
Out[646]: 3
在某些資料庫中,寫入大型資料框架可能會導致錯誤,因為封包大小限制已超過。這可以用在呼叫 to_sql
時設定 chunksize
參數來避免。例如,下列會將 data
以每次 1000 列的批次寫入資料庫
In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3
SQL 資料類型#
確保 SQL 資料庫間一致的資料類型管理具有挑戰性。並非每個 SQL 資料庫都提供相同的類型,即使它們確實如此,特定類型的實作方式也可能有所不同,對類型如何保留產生微妙的影響。
為了最佳保留資料庫類型,建議使用者在可行的情況下使用 ADBC 驅動程式。Arrow 類型系統提供更廣泛的類型,比歷史悠久的 pandas/NumPy 類型系統更接近資料庫類型。為了說明,請注意不同資料庫和 pandas 後端中可用的類型(非詳盡清單)
numpy/pandas |
arrow |
postgres |
sqlite |
---|---|---|---|
int16/Int16 |
int16 |
SMALLINT |
INTEGER |
int32/Int32 |
int32 |
INTEGER |
INTEGER |
int64/Int64 |
int64 |
BIGINT |
INTEGER |
float32 |
float32 |
REAL |
REAL |
float64 |
float64 |
DOUBLE PRECISION |
REAL |
物件 |
string |
TEXT |
TEXT |
bool |
|
BOOLEAN |
|
datetime64[ns] |
timestamp(us) |
TIMESTAMP |
|
datetime64[ns,tz] |
timestamp(us,tz) |
TIMESTAMPTZ |
|
date32 |
DATE |
||
month_day_nano_interval |
INTERVAL |
||
二進位 |
BINARY |
BLOB |
|
decimal128 |
DECIMAL [1] |
||
list |
ARRAY [1] |
||
struct |
|
註腳
如果您有興趣在 DataFrame 的整個生命週期中盡可能地保留資料庫類型,建議使用者利用 read_sql()
的 dtype_backend="pyarrow"
參數
# for roundtripping
with pg_dbapi.connect(uri) as conn:
df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")
這將防止您的資料轉換為傳統的 pandas/NumPy 類型系統,後者通常會以無法來回轉換的方式轉換 SQL 類型。
如果沒有 ADBC 驅動程式,to_sql()
會根據資料的 dtype 將資料對應到適當的 SQL 資料類型。當您有 dtype 為 object
的欄位時,pandas 會嘗試推斷資料類型。
您隨時可以使用 dtype
參數指定任何欄位的所需 SQL 類型,來覆寫預設類型。此參數需要一個將欄位名稱對應到 SQLAlchemy 類型 (或 sqlite3 回退模式的字串) 的字典。例如,指定對字串欄位使用 sqlalchemy String
類型,而不是預設的 Text
類型
In [648]: from sqlalchemy.types import String
In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3
注意
由於不同資料庫風格對 timedelta 的支援有限,類型為 timedelta64
的欄位會以整數值 (以奈秒為單位) 寫入資料庫,並會產生警告。唯一的例外是在使用 ADBC PostgreSQL 驅動程式時,此時 timedelta 會以 INTERVAL
寫入資料庫
注意
類別的 category
資料型別的欄位會轉換為密集表示,就像使用 np.asarray(categorical)
所取得的一樣(例如,字串類別會產生字串陣列)。因此,讀回資料庫表格時不會產生類別。
日期時間資料型別#
使用 ADBC 或 SQLAlchemy,to_sql()
可以寫入時區無感知或時區感知的日期時間資料。然而,儲存在資料庫中的結果資料最終取決於所使用資料庫系統的日期時間資料支援資料型別。
下表列出一些常見資料庫的日期時間資料支援資料型別。其他資料庫方言的日期時間資料可能會有不同的資料型別。
資料庫 |
SQL 日期時間型別 |
時區支援 |
---|---|---|
SQLite |
|
否 |
MySQL |
|
否 |
PostgreSQL |
|
是 |
寫入時區感知資料到不支援時區的資料庫時,資料會寫入為時區無感知時間戳記,其為相對於時區的當地時間。
read_sql_table()
也能讀取時區感知或未感知的日期時間資料。在讀取 TIMESTAMP WITH TIME ZONE
型別時,pandas 會將資料轉換為 UTC。
插入方法#
參數 method
控制所使用的 SQL 插入子句。可能的數值為
None
:使用標準 SQLINSERT
子句(每列一個)。'multi'
:在單一INSERT
子句中傳遞多個數值。它使用一種特殊的 SQL 語法,並非所有後端都支援。這通常會為分析資料庫(例如Presto 和Redshift)提供更好的效能,但如果資料表包含許多欄,則傳統 SQL 後端的效能會較差。如需更多資訊,請查看 SQLAlchemy 文件。具有簽章
(pd_table, conn, keys, data_iter)
的可呼叫項:這可用於根據特定後端方言功能實作效能更高的插入方法。
使用 PostgreSQL COPY 子句 的可呼叫項範例
# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO
def psql_insert_copy(table, conn, keys, data_iter):
"""
Execute SQL statement inserting data
Parameters
----------
table : pandas.io.sql.SQLTable
conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
keys : list of str
Column names
data_iter : Iterable that iterates the values to be inserted
"""
# gets a DBAPI connection that can provide a cursor
dbapi_conn = conn.connection
with dbapi_conn.cursor() as cur:
s_buf = StringIO()
writer = csv.writer(s_buf)
writer.writerows(data_iter)
s_buf.seek(0)
columns = ', '.join(['"{}"'.format(k) for k in keys])
if table.schema:
table_name = '{}.{}'.format(table.schema, table.name)
else:
table_name = table.name
sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
table_name, columns)
cur.copy_expert(sql=sql, file=s_buf)
讀取資料表#
read_sql_table()
會讀取資料庫資料表,提供資料表名稱和要讀取的子集欄(如果有的話)。
注意
為了使用 read_sql_table()
,必須安裝 ADBC 驅動程式或 SQLAlchemy 選用相依項。
In [650]: pd.read_sql_table("data", engine)
Out[650]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
注意
ADBC 驅動程式會將資料庫類型直接對應回 arrow 類型。對於其他驅動程式,請注意 pandas 會從查詢輸出推論欄位 dtypes,而不是從實體資料庫架構中查詢資料類型。例如,假設 userid
是表格中的整數欄位。那麼,直覺上,select userid ...
會傳回整數值序列,而 select cast(userid as text) ...
會傳回物件值 (str) 序列。因此,如果查詢輸出為空,則所有結果欄位都會傳回為物件值 (因為它們最為一般)。如果您預見您的查詢有時會產生空結果,您可能想要在之後明確進行類型轉換,以確保 dtype 完整性。
您也可以將欄位名稱指定為 DataFrame
索引,並指定要讀取的欄位子集。
In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]:
index Date Col_1 Col_2 Col_3
id
26 0 2010-10-18 X 27.50 True
42 1 2010-10-19 Y -12.50 False
63 2 2010-10-20 Z 5.73 True
In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]:
Col_1 Col_2
0 X 27.50
1 Y -12.50
2 Z 5.73
而且您可以明確強制將欄位解析為日期
In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
如果需要,您可以明確指定格式字串,或傳遞給 pandas.to_datetime()
的引數字典
pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
"data",
engine,
parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)
您可以使用 has_table()
檢查表格是否存在
架構支援#
透過 read_sql_table()
和 to_sql()
函式中 schema
關鍵字支援從不同的架構讀取和寫入。但請注意,這取決於資料庫類型 (sqlite 沒有架構)。例如
df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")
查詢#
您可以在 read_sql_query()
函數中使用原始 SQL 進行查詢。在此情況下,您必須使用適合您資料庫的 SQL 變體。使用 SQLAlchemy 時,您也可以傳遞與資料庫無關的 SQLAlchemy 表達式語言建構。
In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.50 1
1 1 42 2010-10-19 00:00:00.000000 Y -12.50 0
2 2 63 2010-10-20 00:00:00.000000 Z 5.73 1
當然,您可以指定更「複雜」的查詢。
In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]:
id Col_1 Col_2
0 42 Y -12.5
read_sql_query()
函數支援 chunksize
參數。指定此參數將傳回查詢結果的區塊迭代器
In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))
In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
.....: print(chunk)
.....:
a b c
0 -0.395347 -0.822726 -0.363777
1 1.676124 -0.908102 -1.391346
2 -1.094269 0.278380 1.205899
3 1.503443 0.932171 -0.709459
4 -0.645944 -1.351389 0.132023
a b c
0 0.210427 0.192202 0.661949
1 1.690629 -1.046044 0.618697
2 -0.013863 1.314289 1.951611
3 -1.485026 0.304662 1.194757
4 -0.446717 0.528496 -0.657575
a b c
0 -0.876654 0.336252 0.172668
1 0.337684 -0.411202 -0.828394
2 -0.244413 1.094948 0.087183
3 1.125934 -1.480095 1.205944
4 -0.451849 0.452214 -2.208192
a b c
0 -2.061019 0.044184 -0.017118
1 1.248959 -0.675595 -1.908296
2 -0.125934 1.491974 0.648726
3 0.391214 0.438609 1.634248
4 1.208707 -1.535740 1.620399
引擎連線範例#
若要使用 SQLAlchemy 連線,請使用 create_engine()
函數從資料庫 URI 建立引擎物件。您只需要針對要連線的每個資料庫建立一次引擎。
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")
engine = create_engine("oracle://scott:[email protected]:1521/sidname")
engine = create_engine("mssql+pyodbc://mydsn")
# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")
# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")
有關更多資訊,請參閱 SQLAlchemy 文件 中的範例
進階 SQLAlchemy 查詢#
您可以使用 SQLAlchemy 建構描述您的查詢。
使用 sqlalchemy.text()
以後端中立的方式指定查詢參數
In [659]: import sqlalchemy as sa
In [660]: pd.read_sql(
.....: sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
.....: )
.....:
Out[660]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.5 1
如果您有資料庫的 SQLAlchemy 描述,則可以使用 SQLAlchemy 表達式來表達 where 條件
In [661]: metadata = sa.MetaData()
In [662]: data_table = sa.Table(
.....: "data",
.....: metadata,
.....: sa.Column("index", sa.Integer),
.....: sa.Column("Date", sa.DateTime),
.....: sa.Column("Col_1", sa.String),
.....: sa.Column("Col_2", sa.Float),
.....: sa.Column("Col_3", sa.Boolean),
.....: )
.....:
In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []
您可以使用 sqlalchemy.bindparam()
將 SQLAlchemy 表達式與傳遞給 read_sql()
的參數結合起來
In [664]: import datetime as dt
In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))
In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]:
index Date Col_1 Col_2 Col_3
0 1 2010-10-19 Y -12.50 False
1 2 2010-10-20 Z 5.73 True
Sqlite 回退#
支援使用 sqlite 而無須使用 SQLAlchemy。此模式需要一個 Python 資料庫轉接器,以符合 Python DB-API。
您可以這樣建立連線
import sqlite3
con = sqlite3.connect(":memory:")
然後執行以下查詢
data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)
Google BigQuery#
pandas-gbq
套件提供可讀寫 Google BigQuery 的功能。
pandas 會與這個外部套件整合。如果已安裝 pandas-gbq
,您可以使用 pandas 方法 pd.read_gbq
和 DataFrame.to_gbq
,這些方法會呼叫 pandas-gbq
中的相關函式。
完整的說明文件可以在 這裡 找到。
Stata 格式#
寫入 Stata 格式#
方法 DataFrame.to_stata()
會將 DataFrame 寫入 .dta 檔案。這個檔案的格式版本永遠是 115(Stata 12)。
In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [668]: df.to_stata("stata.dta")
Stata 資料檔案的資料類型支援有限;只有少於 244 個字元的字串、int8
、int16
、int32
、float32
和 float64
可以儲存在 .dta
檔案中。此外,Stata 保留特定值來表示遺失資料。對於特定資料類型,匯出 Stata 中不在允許範圍內的非遺失值會將變數重新設定為下一個較大的大小。例如,int8
值在 Stata 中限制在 -127 和 100 之間,因此值大於 100 的變數會觸發轉換為 int16
。浮點資料類型中的 nan
值會儲存為基本遺失資料類型(Stata 中的 .
)。
注意
無法匯出整數資料類型的遺失資料值。
Stata 寫入器優雅地處理其他資料類型,包括 int64
、bool
、uint8
、uint16
、uint32
,方法是轉換為可表示資料的最小的支援類型。例如,類型為 uint8
的資料會轉換為 int8
(如果所有值都小於 100(Stata 中非遺失 int8
資料的上限),或者,如果值超出此範圍,則變數會轉換為 int16
。
警告
如果 int64
值大於 2**53,則從 int64
轉換為 float64
可能會導致精度損失。
警告
StataWriter
和 DataFrame.to_stata()
僅支援包含最多 244 個字元的固定寬度字串,這是 dta 檔案格式版本 115 強制的限制。嘗試寫入字串長度超過 244 個字元的 Stata dta 檔案會引發 ValueError
。
從 Stata 格式讀取#
頂層函式 read_stata
會讀取 dta 檔案,並傳回 DataFrame
或 pandas.api.typing.StataReader
,可使用它來遞增讀取檔案。
In [669]: pd.read_stata("stata.dta")
Out[669]:
index A B
0 0 -0.165614 0.490482
1 1 -0.637829 0.067091
2 2 -0.242577 1.348038
3 3 0.647699 -0.644937
4 4 0.625771 0.918376
5 5 0.401781 -1.488919
6 6 -0.981845 -0.046882
7 7 -0.306796 0.877025
8 8 -0.336606 0.624747
9 9 -1.582600 0.806340
指定 chunksize
會產生一個 pandas.api.typing.StataReader
執行個體,可用於一次從檔案中讀取 chunksize
行。 StataReader
物件可用作反覆運算器。
In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
.....: for df in reader:
.....: print(df.shape)
.....:
(3, 3)
(3, 3)
(3, 3)
(1, 3)
若要進行更精細的控制,請使用 iterator=True
並在每次呼叫 read()
時指定 chunksize
。
In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
.....: chunk1 = reader.read(5)
.....: chunk2 = reader.read(5)
.....:
目前 index
會作為一欄來擷取。
參數 convert_categoricals
表示是否應讀取值標籤,並使用這些標籤從中建立 Categorical
變數。值標籤也可以透過函數 value_labels
擷取,此函數需要在使用前呼叫 read()
。
參數 convert_missing
表示是否應保留 Stata 中的遺失值表示方式。如果為 False
(預設值),遺失值會表示為 np.nan
。如果為 True
,遺失值會使用 StataMissingValue
物件表示,而包含遺失值的欄會具有 object
資料類型。
注意
read_stata()
和 StataReader
支援 .dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。
注意
設定 preserve_dtypes=False
將上轉至標準 pandas 資料類型:所有整數類型為 int64
,浮點資料為 float64
。預設情況下,匯入時會保留 Stata 資料類型。
注意
所有 StataReader
物件,無論是透過 read_stata()
(使用 iterator=True
或 chunksize
)建立,或是手動實例化,都必須用作內容管理員(例如 with
陳述式)。雖然 close()
方法可用,但並不支援使用。它不是公開 API 的一部分,未來將在不預警的情況下移除。
類別資料#
Categorical
資料可以匯出至 Stata 資料檔案,作為值標籤資料。匯出的資料包含底層類別代碼(作為整數資料值)和類別(作為值標籤)。Stata 沒有明確等同於 Categorical
的項目,且在匯出時會遺失有關變數是否已排序的資訊。
警告
Stata 僅支援字串值標籤,因此在匯出資料時會對類別呼叫 str
。匯出具有非字串類別的 Categorical
變數會產生警告,且如果類別的 str
表示法不是唯一的,可能會導致資訊遺失。
標籤資料也可以從 Stata 資料檔匯入為 Categorical
變數,方法是使用關鍵字引數 convert_categoricals
(預設為 True
)。關鍵字引數 order_categoricals
(預設為 True
)決定匯入的 Categorical
變數是否為順序變數。
注意
匯入類別資料時,Stata 資料檔中變數的值不會保留,因為 Categorical
變數永遠使用介於 -1
和 n-1
之間的整數資料型別,其中 n
是類別數。如果需要 Stata 資料檔中的原始值,可以透過設定 convert_categoricals=False
來匯入原始資料(但不會匯入變數標籤)。原始值可以與匯入的類別資料配對,因為原始 Stata 資料值與匯入的類別變數的類別代碼之間有簡單的對應關係:遺漏值指定為代碼 -1
,最小的原始值指定為 0
,第二小的原始值指定為 1
,依此類推,直到最大的原始值指定為代碼 n-1
。
注意
Stata 支援部分標籤的序列。這些序列對某些資料值有值標籤,但不是全部。匯入部分標籤的序列會產生 Categorical
,其中有標籤的值為字串類別,沒有標籤的值為數字類別。
SAS 格式#
頂層函式 read_sas()
可以讀取(但無法寫入)SAS XPORT (.xpt) 和 SAS7BDAT (.sas7bdat) 格式檔案。
SAS 檔案僅包含兩種值類型:ASCII 文字和浮點值(通常為 8 位元組,但有時會被截斷)。對於 xport 檔案,沒有自動類型轉換為整數、日期或類別。對於 SAS7BDAT 檔案,格式代碼可能允許日期變數自動轉換為日期。預設情況下,會讀取整個檔案並以 DataFrame
的形式傳回。
指定 chunksize
或使用 iterator=True
來取得讀取器物件(XportReader
或 SAS7BDATReader
)以遞增方式讀取檔案。讀取器物件也具有包含有關檔案及其變數的其他資訊的屬性。
讀取 SAS7BDAT 檔案
df = pd.read_sas("sas_data.sas7bdat")
取得反覆運算器並一次讀取 100,000 行 XPORT 檔案
def do_something(chunk):
pass
with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
for chunk in rdr:
do_something(chunk)
xport 檔案格式的 規格 可從 SAS 網站取得。
沒有 SAS7BDAT 格式的官方文件。
SPSS 格式#
頂層函式 read_spss()
可以讀取(但無法寫入)SPSS SAV (.sav) 和 ZSAV (.zsav) 格式檔案。
SPSS 檔案包含欄位名稱。預設會讀取整個檔案,類別欄位會轉換成 pd.Categorical
,並傳回包含所有欄位的 DataFrame
。
指定 usecols
參數以取得欄位的子集。指定 convert_categoricals=False
以避免將類別欄位轉換成 pd.Categorical
。
讀取 SPSS 檔案
df = pd.read_spss("spss_data.sav")
從 SPSS 檔案中萃取 usecols
中包含的欄位子集,並避免將類別欄位轉換成 pd.Categorical
df = pd.read_spss(
"spss_data.sav",
usecols=["foo", "bar"],
convert_categoricals=False,
)
有關 SAV 和 ZSAV 檔案格式的更多資訊,請參閱 此處。
其他檔案格式#
pandas 本身僅支援與一組有限的檔案格式進行 IO,這些檔案格式可清楚對應到其表格資料模型。若要將其他檔案格式讀入和寫入 pandas,我們建議使用廣大社群中的這些套件。
netCDF#
xarray 提供受 pandas DataFrame
啟發的資料結構,用於處理多維資料集,重點在於 netCDF 檔案格式,以及與 pandas 之間的輕鬆轉換。
效能考量#
這是使用 pandas 0.24.2 進行各種 IO 方法的非正式比較。計時取決於機器,應忽略細微差異。
In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})
In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A 1000000 non-null float64
B 1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB
以下測試函數將用於比較多種 IO 方法的效能
import numpy as np
import os
sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
def test_sql_write(df):
if os.path.exists("test.sql"):
os.remove("test.sql")
sql_db = sqlite3.connect("test.sql")
df.to_sql(name="test_table", con=sql_db)
sql_db.close()
def test_sql_read():
sql_db = sqlite3.connect("test.sql")
pd.read_sql_query("select * from test_table", sql_db)
sql_db.close()
def test_hdf_fixed_write(df):
df.to_hdf("test_fixed.hdf", key="test", mode="w")
def test_hdf_fixed_read():
pd.read_hdf("test_fixed.hdf", "test")
def test_hdf_fixed_write_compress(df):
df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")
def test_hdf_fixed_read_compress():
pd.read_hdf("test_fixed_compress.hdf", "test")
def test_hdf_table_write(df):
df.to_hdf("test_table.hdf", key="test", mode="w", format="table")
def test_hdf_table_read():
pd.read_hdf("test_table.hdf", "test")
def test_hdf_table_write_compress(df):
df.to_hdf(
"test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
)
def test_hdf_table_read_compress():
pd.read_hdf("test_table_compress.hdf", "test")
def test_csv_write(df):
df.to_csv("test.csv", mode="w")
def test_csv_read():
pd.read_csv("test.csv", index_col=0)
def test_feather_write(df):
df.to_feather("test.feather")
def test_feather_read():
pd.read_feather("test.feather")
def test_pickle_write(df):
df.to_pickle("test.pkl")
def test_pickle_read():
pd.read_pickle("test.pkl")
def test_pickle_write_compress(df):
df.to_pickle("test.pkl.compress", compression="xz")
def test_pickle_read_compress():
pd.read_pickle("test.pkl.compress", compression="xz")
def test_parquet_write(df):
df.to_parquet("test.parquet")
def test_parquet_read():
pd.read_parquet("test.parquet")
寫入時,速度前三名的函數為 test_feather_write
、test_hdf_fixed_write
和 test_hdf_fixed_write_compress
。
In [4]: %timeit test_sql_write(df)
3.29 s ± 43.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit test_hdf_fixed_write(df)
19.4 ms ± 560 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: %timeit test_hdf_fixed_write_compress(df)
19.6 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [7]: %timeit test_hdf_table_write(df)
449 ms ± 5.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [8]: %timeit test_hdf_table_write_compress(df)
448 ms ± 11.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [9]: %timeit test_csv_write(df)
3.66 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [10]: %timeit test_feather_write(df)
9.75 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [11]: %timeit test_pickle_write(df)
30.1 ms ± 229 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [12]: %timeit test_pickle_write_compress(df)
4.29 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [13]: %timeit test_parquet_write(df)
67.6 ms ± 706 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
讀取時,速度前三名的函數為 test_feather_read
、test_pickle_read
和 test_hdf_fixed_read
。
In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
檔案 test.pkl.compress
、test.parquet
和 test.feather
在磁碟上佔用的空間最小 (以位元組為單位)。
29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983 Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144 Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf
註解和空行#
忽略行註解和空行#
如果指定
comment
參數,則會忽略完全註解的行。預設情況下,也會忽略完全空白的行。如果
skip_blank_lines=False
,則read_csv
就不會忽略空白行警告
忽略的行可能會造成行號的模糊性;參數
header
使用列號(忽略註解/空白行),而skiprows
使用行號(包含註解/空白行)如果
header
和skiprows
都已指定,header
會相對於skiprows
的結尾。例如註解#
有時,檔案中可能會包含註解或元資料
預設情況下,剖析器會在輸出中包含註解
我們可以使用
comment
關鍵字來抑制註解