IO 工具(文字、CSV、HDF5 等)#

pandas I/O API 是一組頂層 reader 函數,存取方式類似 pandas.read_csv(),通常會傳回一個 pandas 物件。對應的 writer 函數是物件方法,存取方式類似 DataFrame.to_csv()。以下是包含可用 readerswriters 的表格。

格式類型

資料說明

讀取器

寫入器

文字

CSV

read_csv

to_csv

文字

固定寬度文字檔

read_fwf

文字

JSON

read_json

to_json

文字

HTML

read_html

to_html

文字

LaTeX

Styler.to_latex

文字

XML

read_xml

to_xml

文字

本機剪貼簿

read_clipboard

to_clipboard

二進位

MS Excel

read_excel

to_excel

二進位

OpenDocument

read_excel

二進位

HDF5 格式

read_hdf

to_hdf

二進位

Feather 格式

read_feather

to_feather

二進位

Parquet 格式

read_parquet

to_parquet

二進位

ORC 格式

read_orc

to_orc

二進位

Stata

read_stata

to_stata

二進位

SAS

read_sas

二進位

SPSS

read_spss

二進位

Python Pickle 格式

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery

read_gbq

to_gbq

在此針對其中一些 IO 方法進行非正式效能比較。

注意

對於使用 StringIO 類別的範例,請務必使用 from io import StringIO 匯入 Python 3。

CSV 和文字檔案#

用於讀取文字檔案(又稱平面檔案)的強力函數為 read_csv()。請參閱 食譜以取得一些進階策略。

剖析選項#

read_csv() 接受下列常見引數

基本#

filepath_or_buffer各種

檔案路徑(strpathlib.Pathpy: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'}使用 strobject 搭配適當的 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 中,不論如何,請使用 chunksizeiterator 參數以區塊方式傳回資料。(僅 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_naTrue,且指定 na_values,則會將 na_values 附加到用於剖析的預設 NaN 值。

  • 如果 keep_default_naTrue,且未指定 na_values,則僅使用預設 NaN 值進行剖析。

  • 如果 keep_default_naFalse,且指定 na_values,則僅使用指定在 na_values 中的 NaN 值進行剖析。

  • 如果 keep_default_naFalse,且未指定 na_values,則不會將任何字串剖析為 NaN。

請注意,如果將 na_filter 傳入為 False,則會略過 keep_default_nana_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.ZipFilegzip.GzipFilebz2.BZ2Filezstandard.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

當指定 quotecharquoting 不是 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

如果提供此參數,它將覆寫下列參數的值(預設或非預設):delimiterdoublequoteescapecharskipinitialspacequotecharquoting。如果需要覆寫值,將會發出 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,這是因為讀取資料時資料類型混合所致。請務必注意,整體欄位將標示為 dtypeobject,這是用於資料類型混合的欄位。

設定 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」欄位。

註解和空行#

忽略行註解和空行#

如果指定 comment 參數,則會忽略完全註解的行。預設情況下,也會忽略完全空白的行。

In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c
  
# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
   a  b  c
0  1  2  3
1  4  5  6

如果 skip_blank_lines=False,則 read_csv 就不會忽略空白行

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
     a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0

警告

忽略的行可能會造成行號的模糊性;參數 header 使用列號(忽略註解/空白行),而 skiprows 使用行號(包含註解/空白行)

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
   A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
   a  b  c
0  1  2  3

如果 headerskiprows 都已指定,header 會相對於 skiprows 的結尾。例如

In [76]: data = (
   ....:     "# empty\n"
   ....:     "# second empty line\n"
   ....:     "# third emptyline\n"
   ....:     "X,Y,Z\n"
   ....:     "1,2,3\n"
   ....:     "A,B,C\n"
   ....:     "1,2.,4.\n"
   ....:     "5.,NaN,10.0\n"
   ....: )
   ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0


In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
     A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0

註解#

有時,檔案中可能會包含註解或元資料

In [79]: data = (
   ....:     "ID,level,category\n"
   ....:     "Patient1,123000,x # really unpleasant\n"
   ....:     "Patient2,23000,y # wouldn't take his medicine\n"
   ....:     "Patient3,1234018,z # awesome"
   ....: )
   ....: 

In [80]: with open("tmp.csv", "w") as fh:
   ....:     fh.write(data)
   ....: 

In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome

預設情況下,剖析器會在輸出中包含註解

In [82]: df = pd.read_csv("tmp.csv")

In [83]: df
Out[83]: 
         ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome

我們可以使用 comment 關鍵字來抑制註解

In [84]: df = pd.read_csv("tmp.csv", comment="#")

In [85]: df
Out[85]: 
         ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 

處理 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_datesdate_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。在效能方面,您應該按順序嘗試這些解析日期的方法

  1. 如果您知道格式,請使用 date_format,例如:date_format="%d/%m/%Y"date_format={column_name: "%d/%m/%Y"}

  2. 如果您對不同的欄使用不同的格式,或想傳遞任何額外的選項(例如 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

預設的 NaN 識別值為 ['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', ']

讓我們考慮一些範例

pd.read_csv("path_to_file.csv", na_values=[5])

在上面的範例中,55.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"])

在上面,NA0 這兩個字串都是 NaN

pd.read_csv("path_to_file.csv", na_values=["Nope"])

預設值,除了字串 "Nope" 之外,會被辨識為 NaN

無窮大#

inf 類型的值將會被解析為 np.inf(正無窮大),而 -inf 則為 -np.inf(負無窮大)。這些值會忽略大小寫,這表示 Inf 也會被解析為 np.inf

布林值#

常見的值 TrueFalseTRUEFALSE 都會被辨識為布林值。偶爾你可能會想要辨識其他值為布林值。若要執行此操作,請使用 true_valuesfalse_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_csvindex_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#

最好使用 concat() 來合併多個檔案。請參閱 範例手冊 以取得範例。

逐塊迭代檔案#

假設您希望逐漸迭代(可能是非常大的)檔案,而不是將整個檔案讀入記憶體中,例如以下

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=Nonedelim_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 格式#

SeriesDataFrame 物件有一個執行個體方法 to_csv,它允許將物件的內容儲存為逗號分隔值檔案。此函式會採用多個引數。只有第一個引數是必要的。

  • path_or_buf:要寫入檔案的字串路徑或檔案物件。如果是檔案物件,則必須使用 newline='' 開啟

  • sep:輸出檔案的欄位分隔符號(預設為「,」)

  • na_rep:遺失值的字串表示(預設為「」)

  • float_format:浮點數的格式字串

  • columns:要寫入的欄位(預設為 None)

  • header:是否寫出欄位名稱(預設為 True)

  • index:是否寫出列(索引)名稱(預設為 True)

  • index_label:索引欄位的欄位標籤(如果需要)。如果為 None(預設),且 headerindex 為 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:當適當時,用來跳脫 sepquotechar 的字元(預設為 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 方法,但只有 bufna_repfloat_format 參數。另外還有 length 參數,如果設定為 True,會另外輸出 Series 的長度。

JSON#

讀寫 JSON 格式檔案和字串。

寫入 JSON#

SeriesDataFrame 可以轉換為有效的 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」。

注意 NaNNaTNone 會轉換為 null,而 datetime 物件會根據 date_formatdate_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 檔案/字串的格式,有許多不同的選項。考慮以下 DataFrameSeries

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.json

  • typ:要復原的物件類型(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=Truedtype=Trueconvert_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 字串,其中包含兩個欄位,schemadata

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 鍵,它本身包含一個欄位名稱至類型配對的清單,包括 IndexMultiIndex(請參閱下方類型清單)。如果 (多重) 索引是唯一的,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"))。

如果已正確註冊 ExtensionDtypeextDtype 鍵會載入擴充的名稱,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>&amp;</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td>&lt;</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>&gt;</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 非常快。

    • lxml 需要 Cython 才能正確安裝。

  • 缺點

    • lxml 保證其剖析結果,除非提供 嚴格有效的標記

    • 有鑑於上述情況,我們選擇允許您(使用者)使用 lxml 後端,但 此後端將使用 html5lib,如果 lxml 無法剖析

    • 因此,強烈建議您同時安裝 BeautifulSoup4html5lib,這樣即使 lxml 失敗,您仍然可以獲得有效的結果(前提是其他所有內容都是有效的)。

使用 lxml 作為後端時 BeautifulSoup4 遇到的問題

  • 由於 BeautifulSoup4 本質上只是一個解析器後端的包裝器,因此上述問題也存在於這裡。

使用 html5lib 作為後端時 BeautifulSoup4 遇到的問題

  • 優點

    • html5lib 遠比 lxml 寬容,因此可以更明智地處理實際標記,而不會只是在不通知您的情況下刪除元素。

    • html5lib 會自動從無效標記中產生有效的 HTML5 標記。這對於解析 HTML 表格非常重要,因為它可以保證文件有效。但是,這並不表示它是「正確的」,因為修復標記的過程沒有單一的定義。

    • html5lib 是純 Python,除了它自己的安裝之外,不需要任何其他建置步驟。

  • 缺點

    • 使用 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」的內容作為 StringIOBytesIO 的執行個體,並將其傳遞給 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 的 iterparseetree 的 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 規格etreelxml 剖析器都無法剖析格式不佳或不符合 XML 語法規則的任何標記文件。請注意,除非符合 XHTML 規格,否則 HTML 並非 XML 文件。不過,其他熱門標記類型,包括 KML、XAML、RSS、MusicML、MathML 都符合 XML 架構

  • 基於上述原因,如果您的應用程式在 pandas 作業之前建置 XML,請使用適當的 DOM 函式庫,例如 etreelxml,來建置必要的文件,而不是透過字串串接或 regex 調整。請務必記住,XML 是具有標記規則的特殊文字檔案。

  • 對於非常大的 XML 檔案(數百 MB 到 GB),XPath 和 XSLT 可能會變成記憶體密集型操作。請務必有足夠的可用 RAM 來讀寫大型 XML 檔案(大約是文字大小的 5 倍)。

  • 由於 XSLT 是一種程式語言,請小心使用,因為此類指令碼可能會對您的環境構成安全風險,而且可能會執行大型或無限遞迴操作。務必在全面執行之前先針對小型片段測試指令碼。

  • etree 剖析器支援 read_xmlto_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。如果 indexcolumns 具有序列化層級名稱,則也會透過指定構成層級的列/欄位將其讀入。

例如,要讀取沒有名稱的 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_colheader

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 關鍵字指定整欄的類型,該關鍵字會將字典對應欄位名稱至類型。若要詮釋沒有類型推論的資料,請使用類型 strobject

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 輸出的方式寫入 DataFrameindex_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 檔案寫入類似緩衝區的物件,例如 StringIOBytesIO

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 寫入器有兩種方法

  1. 關鍵字引數 engine

  2. 檔案名稱副檔名(透過設定選項中指定的預設值)

預設情況下,pandas 會使用 XlsxWriter 處理 .xlsxopenpyxl 處理 .xlsm。如果您安裝了多個引擎,您可以透過 設定設定選項 io.excel.xlsx.writerio.excel.xls.writer 來設定預設引擎。如果 Xlsxwriter 無法使用,pandas 會改用 openpyxl 處理 .xlsx 檔案。

若要指定要使用的寫入器,您可以傳遞引擎關鍵字引數給 to_excelExcelWriter。內建引擎如下

  • 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")

樣式和格式化#

可以透過 DataFrameto_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

警告

載入從不可信來源收到的已封裝資料可能不安全。

請參閱:https://docs.python.org/3/library/pickle.html

警告

read_pickle() 僅保證向後相容於幾個次要版本。

壓縮的 pickle 檔案#

read_pickle()DataFrame.to_pickle()Series.to_pickle() 可以讀取和寫入壓縮的 pickle 檔案。壓縮類型 gzipbz2xzzstd 支援讀取和寫入。zip 檔案格式僅支援讀取,且必須只包含一個要讀取的資料檔案。

壓縮類型可以是明確的參數,或從檔案副檔名推斷。如果是「推斷」,則使用 gzipbz2zipxzzstd,如果檔名結尾為 '.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_csvto_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 儲存區稍微快速的讀取。使用 putto_hdfformat='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' 指定給 appendputto_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 關鍵字是保留字,不可用作層級名稱。

查詢#

查詢表格#

selectdelete 作業有一個選用條件,可指定為僅選取/刪除資料的子集。這允許使用者擁有非常大的磁碟表格,並僅擷取部分資料。

查詢使用 Term 類別在幕後指定為布林表達式。

  • indexcolumns資料框 支援的索引器。

  • 如果指定 data_columns,這些資料欄可作為其他索引器使用。

  • 多重索引中的層級名稱,預設名稱為 level_0level_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 位於子表達式的左側

columnsmajor_axists

子表達式的右側(比較運算子之後)可以是

  • 將會評估的函數,例如 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

startstop 參數可指定用於限制總搜尋空間。這些參數以資料表中列的總數表示。

注意

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=Falseappend 來關閉此行為。

# 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=Truechunksize=number_in_a_chunkselectselect_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_multipleselect_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 選取所有資料,但遺失資料除外,然後重新寫入表格,肯定會快很多。

警告

請注意,HDF5 不會自動回收 h5 檔案中的空間。因此,重複刪除(或移除節點)然後再新增,會導致檔案大小增加

若要重新整理和清理檔案,請使用 ptrepack

注意事項和警告#

壓縮#

PyTables 允許壓縮已儲存的資料。這適用於所有類型的儲存,不只是表格。兩個參數用於控制壓縮: complevelcomplib

  • complevel 指定是否壓縮資料,以及壓縮程度。 complevel=0complevel=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 底層資料類型。這表示下列類型已知可行

類型

表示遺失值

浮動小數點 : float64, float32, float16

np.nan

整數 : int64, int32, int8, uint64,uint32, uint8

布林

datetime64[ns]

NaT

timedelta64[ns]

NaT

類別 : 請參閱以下部分

物件 : 字串

np.nan

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 資料類型,包括擴充資料類型,例如類別和帶時區的日期時間。

幾個注意事項

  • 此格式不會寫入 IndexMultiIndexDataFrame,如果提供非預設值,將會引發錯誤。您可以使用 .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 來引導序列化。這可以是 pyarrowfastparquetauto 之一。如果未指定引擎,則會檢查 pd.options.io.parquet.engine 選項;如果這也是 auto,則會嘗試 pyarrow,並回退到 fastparquet

參閱文件 pyarrowfastparquet

注意

這些引擎非常相似,應可讀取/寫入幾乎相同的 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 檔案:ab__index_level_0__。如果您使用 fastparquet,則索引 可能會或可能不會 寫入檔案。

這個意外的多餘欄位會導致一些資料庫(例如 Amazon Redshift)拒絕這個檔案,因為目標資料表中不存在該欄位。

如果您要在寫入時略過資料框的索引,請將 index=False 傳遞給 to_parquet()

In [628]: df.to_parquet("test.parquet", index=False)

這會建立一個僅包含兩個預期欄位(ab)的 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 字串。

另請參閱一些 食譜範例,以了解一些進階策略。

主要功能為

read_sql_table(table_name, con[, schema, ...])

將 SQL 資料庫表格讀取到 DataFrame 中。

read_sql_query(sql, con[, index_col, ...])

將 SQL 查詢讀入 DataFrame。

read_sql(sql, con[, index_col, ...])

將 SQL 查詢或資料庫表格讀入 DataFrame。

DataFrame.to_sql(name, con, *[, schema, ...])

將儲存在 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

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

COMPOSITE TYPE

[1]

註腳

如果您有興趣在 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

TEXT

MySQL

TIMESTAMPDATETIME

PostgreSQL

TIMESTAMPTIMESTAMP WITH TIME ZONE

寫入時區感知資料到不支援時區的資料庫時,資料會寫入為時區無感知時間戳記,其為相對於時區的當地時間。

read_sql_table() 也能讀取時區感知或未感知的日期時間資料。在讀取 TIMESTAMP WITH TIME ZONE 型別時,pandas 會將資料轉換為 UTC。

插入方法#

參數 method 控制所使用的 SQL 插入子句。可能的數值為

  • None:使用標準 SQL INSERT 子句(每列一個)。

  • 'multi':在單一 INSERT 子句中傳遞多個數值。它使用一種特殊的 SQL 語法,並非所有後端都支援。這通常會為分析資料庫(例如PrestoRedshift)提供更好的效能,但如果資料表包含許多欄,則傳統 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_gbqDataFrame.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 個字元的字串、int8int16int32float32float64 可以儲存在 .dta 檔案中。此外,Stata 保留特定值來表示遺失資料。對於特定資料類型,匯出 Stata 中不在允許範圍內的非遺失值會將變數重新設定為下一個較大的大小。例如,int8 值在 Stata 中限制在 -127 和 100 之間,因此值大於 100 的變數會觸發轉換為 int16。浮點資料類型中的 nan 值會儲存為基本遺失資料類型(Stata 中的 .)。

注意

無法匯出整數資料類型的遺失資料值。

Stata 寫入器優雅地處理其他資料類型,包括 int64booluint8uint16uint32,方法是轉換為可表示資料的最小的支援類型。例如,類型為 uint8 的資料會轉換為 int8(如果所有值都小於 100(Stata 中非遺失 int8 資料的上限),或者,如果值超出此範圍,則變數會轉換為 int16

警告

如果 int64 值大於 2**53,則從 int64 轉換為 float64 可能會導致精度損失。

警告

StataWriterDataFrame.to_stata() 僅支援包含最多 244 個字元的固定寬度字串,這是 dta 檔案格式版本 115 強制的限制。嘗試寫入字串長度超過 244 個字元的 Stata dta 檔案會引發 ValueError

從 Stata 格式讀取#

頂層函式 read_stata 會讀取 dta 檔案,並傳回 DataFramepandas.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=Truechunksize)建立,或是手動實例化,都必須用作內容管理員(例如 with 陳述式)。雖然 close() 方法可用,但並不支援使用。它不是公開 API 的一部分,未來將在不預警的情況下移除。

類別資料#

Categorical 資料可以匯出至 Stata 資料檔案,作為值標籤資料。匯出的資料包含底層類別代碼(作為整數資料值)和類別(作為值標籤)。Stata 沒有明確等同於 Categorical 的項目,且在匯出時會遺失有關變數是否已排序的資訊。

警告

Stata 僅支援字串值標籤,因此在匯出資料時會對類別呼叫 str。匯出具有非字串類別的 Categorical 變數會產生警告,且如果類別的 str 表示法不是唯一的,可能會導致資訊遺失。

標籤資料也可以從 Stata 資料檔匯入為 Categorical 變數,方法是使用關鍵字引數 convert_categoricals(預設為 True)。關鍵字引數 order_categoricals(預設為 True)決定匯入的 Categorical 變數是否為順序變數。

注意

匯入類別資料時,Stata 資料檔中變數的值不會保留,因為 Categorical 變數永遠使用介於 -1n-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 來取得讀取器物件(XportReaderSAS7BDATReader)以遞增方式讀取檔案。讀取器物件也具有包含有關檔案及其變數的其他資訊的屬性。

讀取 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_writetest_hdf_fixed_writetest_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_readtest_pickle_readtest_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.compresstest.parquettest.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