基本必要功能#

在此我們將討論許多 pandas 資料結構中常見的基本必要功能。首先,讓我們建立一些範例物件,就像我們在 10 分鐘學會 pandas 區段中所做的一樣

In [1]: index = pd.date_range("1/1/2000", periods=8)

In [2]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [3]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])

頭部和尾部#

若要檢視 Series 或 DataFrame 物件的小範例,請使用 head()tail() 方法。要顯示的元素預設數量為五個,但您可以傳遞自訂數字。

In [4]: long_series = pd.Series(np.random.randn(1000))

In [5]: long_series.head()
Out[5]: 
0   -1.157892
1   -1.344312
2    0.844885
3    1.075770
4   -0.109050
dtype: float64

In [6]: long_series.tail(3)
Out[6]: 
997   -0.289388
998   -1.020544
999    0.589993
dtype: float64

屬性和基礎資料#

pandas 物件有許多屬性,讓您可以存取元資料

  • shape:提供物件的軸向度,與 ndarray 一致

  • 軸標籤
    • Seriesindex(僅軸)

    • DataFrameindex(列)和 columns

請注意,這些屬性可以安全地指定!

In [7]: df[:2]
Out[7]: 
                   A         B         C
2000-01-01 -0.173215  0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929

In [8]: df.columns = [x.lower() for x in df.columns]

In [9]: df
Out[9]: 
                   a         b         c
2000-01-01 -0.173215  0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
2000-01-03  1.071804  0.721555 -0.706771
2000-01-04 -1.039575  0.271860 -0.424972
2000-01-05  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427
2000-01-07  0.524988  0.404705  0.577046
2000-01-08 -1.715002 -1.039268 -0.370647

pandas 物件 (IndexSeriesDataFrame) 可以視為陣列的容器,其中包含實際資料並執行實際運算。對於許多類型而言,基礎陣列是 numpy.ndarray。但是,pandas 和第三方程式庫可能會延伸 NumPy 的類型系統,以新增對自訂陣列的支援(請參閱 資料類型)。

若要取得 IndexSeries 內的實際資料,請使用 .array 屬性

In [10]: s.array
Out[10]: 
<NumpyExtensionArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

In [11]: s.index.array
Out[11]: 
<NumpyExtensionArray>
['a', 'b', 'c', 'd', 'e']
Length: 5, dtype: object

array 將永遠是 ExtensionArrayExtensionArray 的確切細節以及 pandas 使用它們的原因有點超出本簡介的範圍。請參閱 dtypes 以了解更多資訊。

如果您知道需要 NumPy 陣列,請使用 to_numpy()numpy.asarray()

In [12]: s.to_numpy()
Out[12]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

In [13]: np.asarray(s)
Out[13]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

當 Series 或 Index 由 ExtensionArray 支援時,to_numpy() 可能涉及複製資料和強制轉換值。請參閱 dtypes 以了解更多資訊。

to_numpy() 提供對結果 numpy.ndarraydtype 的一些控制。例如,考慮帶有時區的日期時間。NumPy 沒有表示支援時區的日期時間的資料類型,因此有兩個可能實用的表示方式

  1. 具有 Timestamp 物件的物件資料類型 numpy.ndarray,每個物件都有正確的 tz

  2. 一個 datetime64[ns] -dtype numpy.ndarray,其中值已轉換為 UTC,時區已捨棄

時區可以使用 dtype=object 保留

In [14]: ser = pd.Series(pd.date_range("2000", periods=2, tz="CET"))

In [15]: ser.to_numpy(dtype=object)
Out[15]: 
array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'),
       Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object)

或使用 dtype='datetime64[ns]' 捨棄

In [16]: ser.to_numpy(dtype="datetime64[ns]")
Out[16]: 
array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00.000000000'],
      dtype='datetime64[ns]')

取得 DataFrame 內部的「原始資料」可能稍微複雜一點。當 DataFrame 僅對所有欄位使用單一資料類型時,DataFrame.to_numpy() 會傳回基礎資料

In [17]: df.to_numpy()
Out[17]: 
array([[-0.1732,  0.1192, -1.0442],
       [-0.8618, -2.1046, -0.4949],
       [ 1.0718,  0.7216, -0.7068],
       [-1.0396,  0.2719, -0.425 ],
       [ 0.567 ,  0.2762, -1.0874],
       [-0.6737,  0.1136, -1.4784],
       [ 0.525 ,  0.4047,  0.577 ],
       [-1.715 , -1.0393, -0.3706]])

如果 DataFrame 包含同質類型資料,則 ndarray 實際上可以原地修改,而變更會反映在資料結構中。對於異質資料(例如 DataFrame 的某些欄位並非全部相同 dtype),則不會如此。值屬性本身與軸標籤不同,無法指定。

注意

使用異質資料時,結果 ndarray 的 dtype 會選擇為容納所有相關資料。例如,如果涉及字串,結果將為物件 dtype。如果只有浮點數和整數,結果陣列將為浮點數 dtype。

過去,pandas 建議使用 Series.valuesDataFrame.values 從 Series 或 DataFrame 中萃取資料。您仍然可以在舊程式碼庫和網路上找到這些參照。未來,我們建議避免使用 .values 而改用 .array.to_numpy().values 有下列缺點

  1. 當您的 Series 包含 延伸類型 時,無法清楚得知 Series.values 會傳回 NumPy 陣列或延伸陣列。 Series.array 永遠會傳回 ExtensionArray,而且絕不會複製資料。 Series.to_numpy() 永遠會傳回 NumPy 陣列,但可能會付出複製/強制轉換數值為代價。

  2. 當您的 DataFrame 包含混合資料類型時,DataFrame.values 可能會涉及複製資料並強制轉換數值為共用 dtype,這是一個相對昂貴的操作。 DataFrame.to_numpy() 是一個方法,它清楚說明傳回的 NumPy 陣列可能不是 DataFrame 中相同資料的檢視。

加速運算#

pandas 支援使用 numexpr 函式庫和 bottleneck 函式庫來加速特定類型的二進位數字和布林運算。

這些函式庫在處理大型資料集時特別有用,並且提供大幅度的加速。 numexpr 使用智慧分塊、快取和多核心。 bottleneck 是一組專門的 cython 常式,在處理具有 nans 的陣列時特別快。

以下是範例(使用 100 個欄 x 100,000 個列 DataFrames

運算

0.11.0 (毫秒)

先前版本 (毫秒)

與先前的比率

df1 > df2

13.32

125.35

0.1063

df1 * df2

21.71

36.63

0.5928

df1 + df2

22.04

36.50

0.6039

強烈建議您安裝這兩個函式庫。請參閱區段 建議的相依性 以取得更多安裝資訊。

這兩個預設都已啟用,您可以透過設定選項來控制

pd.set_option("compute.use_bottleneck", False)
pd.set_option("compute.use_numexpr", False)

彈性的二元運算#

在 pandas 資料結構之間的二元運算中,有兩個重要的關注重點

  • 高維(例如 DataFrame)和低維(例如 Series)物件之間的廣播行為。

  • 運算中的遺失資料。

我們將示範如何獨立管理這些問題,儘管它們可以同時處理。

比對/廣播行為#

DataFrame 具有方法 add()sub()mul()div() 和相關函數 radd()rsub(),… 用於執行二元運算。對於廣播行為,Series 輸入是主要關注點。使用這些函數,您可以使用關鍵字 axis 來匹配 indexcolumns

In [18]: df = pd.DataFrame(
   ....:     {
   ....:         "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
   ....:         "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
   ....:         "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
   ....:     }
   ....: )
   ....: 

In [19]: df
Out[19]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [20]: row = df.iloc[1]

In [21]: column = df["two"]

In [22]: df.sub(row, axis="columns")
Out[22]: 
        one       two     three
a  1.051928 -0.139606       NaN
b  0.000000  0.000000  0.000000
c  0.352192 -0.433754  1.277825
d       NaN -1.632779 -0.562782

In [23]: df.sub(row, axis=1)
Out[23]: 
        one       two     three
a  1.051928 -0.139606       NaN
b  0.000000  0.000000  0.000000
c  0.352192 -0.433754  1.277825
d       NaN -1.632779 -0.562782

In [24]: df.sub(column, axis="index")
Out[24]: 
        one  two     three
a -0.377535  0.0       NaN
b -1.569069  0.0 -1.962513
c -0.783123  0.0 -0.250933
d       NaN  0.0 -0.892516

In [25]: df.sub(column, axis=0)
Out[25]: 
        one  two     three
a -0.377535  0.0       NaN
b -1.569069  0.0 -1.962513
c -0.783123  0.0 -0.250933
d       NaN  0.0 -0.892516

此外,您可以將 MultiIndexed DataFrame 的層級與 Series 對齊。

In [26]: dfmi = df.copy()

In [27]: dfmi.index = pd.MultiIndex.from_tuples(
   ....:     [(1, "a"), (1, "b"), (1, "c"), (2, "a")], names=["first", "second"]
   ....: )
   ....: 

In [28]: dfmi.sub(column, axis=0, level="second")
Out[28]: 
                   one       two     three
first second                              
1     a      -0.377535  0.000000       NaN
      b      -1.569069  0.000000 -1.962513
      c      -0.783123  0.000000 -0.250933
2     a            NaN -1.493173 -2.385688

Series 和 Index 也支援 divmod() 內建函數。此函數同時進行取整除和模數運算,傳回與左側相同類型的二元組。例如

In [29]: s = pd.Series(np.arange(10))

In [30]: s
Out[30]: 
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [31]: div, rem = divmod(s, 3)

In [32]: div
Out[32]: 
0    0
1    0
2    0
3    1
4    1
5    1
6    2
7    2
8    2
9    3
dtype: int64

In [33]: rem
Out[33]: 
0    0
1    1
2    2
3    0
4    1
5    2
6    0
7    1
8    2
9    0
dtype: int64

In [34]: idx = pd.Index(np.arange(10))

In [35]: idx
Out[35]: Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')

In [36]: div, rem = divmod(idx, 3)

In [37]: div
Out[37]: Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64')

In [38]: rem
Out[38]: Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64')

我們也可以執行逐元素 divmod()

In [39]: div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6])

In [40]: div
Out[40]: 
0    0
1    0
2    0
3    1
4    1
5    1
6    1
7    1
8    1
9    1
dtype: int64

In [41]: rem
Out[41]: 
0    0
1    1
2    2
3    0
4    0
5    1
6    1
7    2
8    2
9    3
dtype: int64

遺失資料/使用填補值進行運算#

在 Series 和 DataFrame 中,算術函數有輸入填補值的選項,也就是當某個位置最多只有一個值遺失時,用來替換的值。例如,在新增兩個 DataFrame 物件時,您可能希望將 NaN 視為 0,除非兩個 DataFrame 都遺失該值,否則結果將會是 NaN(稍後,如果您願意,可以使用 fillna 將 NaN 替換為其他值)。

In [42]: df2 = df.copy()

In [43]: df2.loc["a", "three"] = 1.0

In [44]: df
Out[44]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [45]: df2
Out[45]: 
        one       two     three
a  1.394981  1.772517  1.000000
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [46]: df + df2
Out[46]: 
        one       two     three
a  2.789963  3.545034       NaN
b  0.686107  3.824246 -0.100780
c  1.390491  2.956737  2.454870
d       NaN  0.558688 -1.226343

In [47]: df.add(df2, fill_value=0)
Out[47]: 
        one       two     three
a  2.789963  3.545034  1.000000
b  0.686107  3.824246 -0.100780
c  1.390491  2.956737  2.454870
d       NaN  0.558688 -1.226343

彈性比較#

Series 和 DataFrame 有二元比較方法 eqneltgtlege,其行為類似於上面所述的二元算術運算

In [48]: df.gt(df2)
Out[48]: 
     one    two  three
a  False  False  False
b  False  False  False
c  False  False  False
d  False  False  False

In [49]: df2.ne(df)
Out[49]: 
     one    two  three
a  False  False   True
b  False  False  False
c  False  False  False
d   True  False  False

這些運算會產生與左側輸入相同類型的 pandas 物件,其資料型態為 bool。這些 boolean 物件可以用於索引運算,請參閱 布林索引 一節。

布林簡化#

您可以套用下列簡約:emptyany()all()bool(),以提供一種總結布林結果的方法。

In [50]: (df > 0).all()
Out[50]: 
one      False
two       True
three    False
dtype: bool

In [51]: (df > 0).any()
Out[51]: 
one      True
two      True
three    True
dtype: bool

您可以簡約成最終布林值。

In [52]: (df > 0).any().any()
Out[52]: True

您可以透過 empty 屬性來測試 pandas 物件是否為空。

In [53]: df.empty
Out[53]: False

In [54]: pd.DataFrame(columns=list("ABC")).empty
Out[54]: True

警告

確認 pandas 物件的真值會引發錯誤,因為測試其為空或其值是模稜兩可的。

In [55]: if df:
   ....:     print(True)
   ....: 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-55-318d08b2571a> in ?()
----> 1 if df:
      2     print(True)

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   1574     @final
   1575     def __nonzero__(self) -> NoReturn:
-> 1576         raise ValueError(
   1577             f"The truth value of a {type(self).__name__} is ambiguous. "
   1578             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1579         )

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
In [56]: df and df2
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-56-b241b64bb471> in ?()
----> 1 df and df2

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   1574     @final
   1575     def __nonzero__(self) -> NoReturn:
-> 1576         raise ValueError(
   1577             f"The truth value of a {type(self).__name__} is ambiguous. "
   1578             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1579         )

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

請參閱 gotchas 以取得更詳細的說明。

比較物件是否相等#

您可能會發現,計算相同結果的方法不只一種。舉一個簡單的例子,請考慮 df + dfdf * 2。要測試這兩個計算是否產生相同的結果,根據上面顯示的工具,您可能會想像使用 (df + df == df * 2).all()。但事實上,這個表達式為 False

In [57]: df + df == df * 2
Out[57]: 
     one   two  three
a   True  True  False
b   True  True   True
c   True  True   True
d  False  True   True

In [58]: (df + df == df * 2).all()
Out[58]: 
one      False
two       True
three    False
dtype: bool

請注意,布林 DataFrame df + df == df * 2 包含一些 False 值!這是因為 NaN 無法比較為相等

In [59]: np.nan == np.nan
Out[59]: False

因此,NDFrame(例如 Series 和 DataFrames)有一個 equals() 方法用於測試相等性,將對應位置的 NaN 視為相等。

In [60]: (df + df).equals(df * 2)
Out[60]: True

請注意,Series 或 DataFrame 索引需要按相同順序排列,相等性才能為 True

In [61]: df1 = pd.DataFrame({"col": ["foo", 0, np.nan]})

In [62]: df2 = pd.DataFrame({"col": [np.nan, 0, "foo"]}, index=[2, 1, 0])

In [63]: df1.equals(df2)
Out[63]: False

In [64]: df1.equals(df2.sort_index())
Out[64]: True

比較類陣列物件#

當將 pandas 資料結構與純量值進行比較時,您可以方便地執行逐元素比較

In [65]: pd.Series(["foo", "bar", "baz"]) == "foo"
Out[65]: 
0     True
1    False
2    False
dtype: bool

In [66]: pd.Index(["foo", "bar", "baz"]) == "foo"
Out[66]: array([ True, False, False])

pandas 也可以處理相同長度的不同類陣列物件之間的逐元素比較

In [67]: pd.Series(["foo", "bar", "baz"]) == pd.Index(["foo", "bar", "qux"])
Out[67]: 
0     True
1     True
2    False
dtype: bool

In [68]: pd.Series(["foo", "bar", "baz"]) == np.array(["foo", "bar", "qux"])
Out[68]: 
0     True
1     True
2    False
dtype: bool

嘗試比較不同長度的 IndexSeries 物件會引發 ValueError

In [69]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[69], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])

File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
     72             return NotImplemented
     74 other = item_from_zerodim(other)
---> 76 return method(self, other)

File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
     38 @unpack_zerodim_and_defer("__eq__")
     39 def __eq__(self, other):
---> 40     return self._cmp_method(other, operator.eq)

File ~/work/pandas/pandas/pandas/core/series.py:6105, in Series._cmp_method(self, other, op)
   6102 res_name = ops.get_op_result_name(self, other)
   6104 if isinstance(other, Series) and not self._indexed_same(other):
-> 6105     raise ValueError("Can only compare identically-labeled Series objects")
   6107 lvalues = self._values
   6108 rvalues = extract_array(other, extract_numpy=True, extract_range=True)

ValueError: Can only compare identically-labeled Series objects

In [70]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[70], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])

File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
     72             return NotImplemented
     74 other = item_from_zerodim(other)
---> 76 return method(self, other)

File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
     38 @unpack_zerodim_and_defer("__eq__")
     39 def __eq__(self, other):
---> 40     return self._cmp_method(other, operator.eq)

File ~/work/pandas/pandas/pandas/core/series.py:6105, in Series._cmp_method(self, other, op)
   6102 res_name = ops.get_op_result_name(self, other)
   6104 if isinstance(other, Series) and not self._indexed_same(other):
-> 6105     raise ValueError("Can only compare identically-labeled Series objects")
   6107 lvalues = self._values
   6108 rvalues = extract_array(other, extract_numpy=True, extract_range=True)

ValueError: Can only compare identically-labeled Series objects

合併重疊的資料集#

偶爾會遇到的問題是合併兩個類似的資料集,其中一個資料集中的值優先於另一個資料集。一個範例是兩個資料系列代表一個特定的經濟指標,其中一個被認為是「較高品質」。然而,較低品質的系列可能延伸到更早的歷史或具有更完整的資料涵蓋範圍。因此,我們想要合併兩個 DataFrame 物件,其中一個 DataFrame 中的遺失值有條件地填入另一個 DataFrame 中標籤相同的值。實作此操作的函數為 combine_first(),我們說明如下

In [71]: df1 = pd.DataFrame(
   ....:     {"A": [1.0, np.nan, 3.0, 5.0, np.nan], "B": [np.nan, 2.0, 3.0, np.nan, 6.0]}
   ....: )
   ....: 

In [72]: df2 = pd.DataFrame(
   ....:     {
   ....:         "A": [5.0, 2.0, 4.0, np.nan, 3.0, 7.0],
   ....:         "B": [np.nan, np.nan, 3.0, 4.0, 6.0, 8.0],
   ....:     }
   ....: )
   ....: 

In [73]: df1
Out[73]: 
     A    B
0  1.0  NaN
1  NaN  2.0
2  3.0  3.0
3  5.0  NaN
4  NaN  6.0

In [74]: df2
Out[74]: 
     A    B
0  5.0  NaN
1  2.0  NaN
2  4.0  3.0
3  NaN  4.0
4  3.0  6.0
5  7.0  8.0

In [75]: df1.combine_first(df2)
Out[75]: 
     A    B
0  1.0  NaN
1  2.0  2.0
2  3.0  3.0
3  5.0  4.0
4  3.0  6.0
5  7.0  8.0

一般 DataFrame 合併#

上述 combine_first() 方法呼叫更通用的 DataFrame.combine()。此方法採用另一個 DataFrame 和一個組合器函數,對齊輸入 DataFrame,然後將組合器函數傳遞給成對的 Series(即名稱相同的欄)。

因此,例如,要複製上述 combine_first()

In [76]: def combiner(x, y):
   ....:     return np.where(pd.isna(x), y, x)
   ....: 

In [77]: df1.combine(df2, combiner)
Out[77]: 
     A    B
0  1.0  NaN
1  2.0  2.0
2  3.0  3.0
3  5.0  4.0
4  3.0  6.0
5  7.0  8.0

描述性統計資料#

有許多方法可計算描述性統計資料和其他相關運算,例如 SeriesDataFrame。大多數都是聚合(因此產生較低維度的結果),例如 sum()mean()quantile(),但有些方法(例如 cumsum()cumprod())會產生相同大小的物件。一般來說,這些方法會採用引數,就像 ndarray.{sum, std, …} 一樣,但軸可以透過名稱或整數指定

  • Series:不需要軸引數

  • DataFrame:「索引」(axis=0,預設值)、「欄位」(axis=1)

例如

In [78]: df
Out[78]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [79]: df.mean(0)
Out[79]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [80]: df.mean(1)
Out[80]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

所有這些方法都有 skipna 選項,用來表示是否要排除遺失的資料(預設為 True

In [81]: df.sum(0, skipna=False)
Out[81]: 
one           NaN
two      5.442353
three         NaN
dtype: float64

In [82]: df.sum(axis=1, skipna=True)
Out[82]: 
a    3.167498
b    2.204786
c    3.401050
d   -0.333828
dtype: float64

結合廣播/算術行為,可以非常簡潔地描述各種統計程序,例如標準化(將資料呈現為平均值為零且標準差為 1)

In [83]: ts_stand = (df - df.mean()) / df.std()

In [84]: ts_stand.std()
Out[84]: 
one      1.0
two      1.0
three    1.0
dtype: float64

In [85]: xs_stand = df.sub(df.mean(1), axis=0).div(df.std(1), axis=0)

In [86]: xs_stand.std(1)
Out[86]: 
a    1.0
b    1.0
c    1.0
d    1.0
dtype: float64

請注意,cumsum()cumprod() 等方法會保留 NaN 值的位置。這與 expanding()rolling() 有些不同,因為 NaN 行為進一步由 min_periods 參數決定。

In [87]: df.cumsum()
Out[87]: 
        one       two     three
a  1.394981  1.772517       NaN
b  1.738035  3.684640 -0.050390
c  2.433281  5.163008  1.177045
d       NaN  5.442353  0.563873

以下是常見函數的快速參考摘要表。每個函數也有一個選用的 level 參數,只有在物件有 階層索引 時才會套用。

函數

說明

count

非 NA 觀測值數目

sum

值總和

mean

值平均值

median

數值的算術中位數

min

最小值

max

最大值

mode

眾數

abs

絕對值

prod

數值的乘積

std

貝塞爾校正樣本標準差

var

無偏方差

sem

平均數的標準誤差

skew

樣本偏度(3 次矩)

kurt

樣本峰度(4 次矩)

quantile

樣本分位數(% 的值)

cumsum

累積和

cumprod

累積乘積

cummax

累積最大值

cummin

累積最小值

請注意,有些 NumPy 方法(例如 meanstdsum)會在預設情況下排除 Series 輸入中的 NA

In [88]: np.mean(df["one"])
Out[88]: 0.8110935116651192

In [89]: np.mean(df["one"].to_numpy())
Out[89]: nan

Series.nunique() 會傳回 Series 中唯一非 NA 值的數量

In [90]: series = pd.Series(np.random.randn(500))

In [91]: series[20:500] = np.nan

In [92]: series[10:20] = 5

In [93]: series.nunique()
Out[93]: 11

資料摘要:describe#

有一個方便的 describe() 函數,它會計算 Series 或 DataFrame 欄位的各種摘要統計資料(當然會排除 NA)

In [94]: series = pd.Series(np.random.randn(1000))

In [95]: series[::2] = np.nan

In [96]: series.describe()
Out[96]: 
count    500.000000
mean      -0.021292
std        1.015906
min       -2.683763
25%       -0.699070
50%       -0.069718
75%        0.714483
max        3.160915
dtype: float64

In [97]: frame = pd.DataFrame(np.random.randn(1000, 5), columns=["a", "b", "c", "d", "e"])

In [98]: frame.iloc[::2] = np.nan

In [99]: frame.describe()
Out[99]: 
                a           b           c           d           e
count  500.000000  500.000000  500.000000  500.000000  500.000000
mean     0.033387    0.030045   -0.043719   -0.051686    0.005979
std      1.017152    0.978743    1.025270    1.015988    1.006695
min     -3.000951   -2.637901   -3.303099   -3.159200   -3.188821
25%     -0.647623   -0.576449   -0.712369   -0.691338   -0.691115
50%      0.047578   -0.021499   -0.023888   -0.032652   -0.025363
75%      0.729907    0.775880    0.618896    0.670047    0.649748
max      2.740139    2.752332    3.004229    2.728702    3.240991

您可以選擇要包含在輸出中的特定百分位數

In [100]: series.describe(percentiles=[0.05, 0.25, 0.75, 0.95])
Out[100]: 
count    500.000000
mean      -0.021292
std        1.015906
min       -2.683763
5%        -1.645423
25%       -0.699070
50%       -0.069718
75%        0.714483
95%        1.711409
max        3.160915
dtype: float64

預設情況下,總是會包含中位數。

對於非數值的 Series 物件,describe() 會提供唯一值數目和最常出現值的簡單摘要

In [101]: s = pd.Series(["a", "a", "b", "b", "a", "a", np.nan, "c", "d", "a"])

In [102]: s.describe()
Out[102]: 
count     9
unique    4
top       a
freq      5
dtype: object

請注意,在混合型 DataFrame 物件上,describe() 會限制摘要,僅包含數值欄,或若沒有數值欄,則僅包含類別欄

In [103]: frame = pd.DataFrame({"a": ["Yes", "Yes", "No", "No"], "b": range(4)})

In [104]: frame.describe()
Out[104]: 
              b
count  4.000000
mean   1.500000
std    1.290994
min    0.000000
25%    0.750000
50%    1.500000
75%    2.250000
max    3.000000

此行為可透過提供類型清單作為 include/exclude 參數來控制。特殊值 all 也可用

In [105]: frame.describe(include=["object"])
Out[105]: 
          a
count     4
unique    2
top     Yes
freq      2

In [106]: frame.describe(include=["number"])
Out[106]: 
              b
count  4.000000
mean   1.500000
std    1.290994
min    0.000000
25%    0.750000
50%    1.500000
75%    2.250000
max    3.000000

In [107]: frame.describe(include="all")
Out[107]: 
          a         b
count     4  4.000000
unique    2       NaN
top     Yes       NaN
freq      2       NaN
mean    NaN  1.500000
std     NaN  1.290994
min     NaN  0.000000
25%     NaN  0.750000
50%     NaN  1.500000
75%     NaN  2.250000
max     NaN  3.000000

該功能仰賴 select_dtypes。請參閱該處以取得關於可接受輸入的詳細資料。

最小/最大值的索引#

Series 和 DataFrame 上的 idxmin()idxmax() 函數會計算具有最小和最大對應值的索引標籤

In [108]: s1 = pd.Series(np.random.randn(5))

In [109]: s1
Out[109]: 
0    1.118076
1   -0.352051
2   -1.242883
3   -1.277155
4   -0.641184
dtype: float64

In [110]: s1.idxmin(), s1.idxmax()
Out[110]: (3, 0)

In [111]: df1 = pd.DataFrame(np.random.randn(5, 3), columns=["A", "B", "C"])

In [112]: df1
Out[112]: 
          A         B         C
0 -0.327863 -0.946180 -0.137570
1 -0.186235 -0.257213 -0.486567
2 -0.507027 -0.871259 -0.111110
3  2.000339 -2.430505  0.089759
4 -0.321434 -0.033695  0.096271

In [113]: df1.idxmin(axis=0)
Out[113]: 
A    2
B    3
C    1
dtype: int64

In [114]: df1.idxmax(axis=1)
Out[114]: 
0    C
1    A
2    C
3    A
4    C
dtype: object

當有多個列 (或欄) 與最小或最大值相符時,idxmin()idxmax() 會傳回第一個相符的索引

In [115]: df3 = pd.DataFrame([2, 1, 1, 3, np.nan], columns=["A"], index=list("edcba"))

In [116]: df3
Out[116]: 
     A
e  2.0
d  1.0
c  1.0
b  3.0
a  NaN

In [117]: df3["A"].idxmin()
Out[117]: 'd'

注意

idxminidxmax 在 NumPy 中稱為 argminargmax

值計數 (直方圖)/眾數#

系列方法 value_counts() 計算 1D 值陣列的直方圖。它也可以用作常規陣列上的函數

In [118]: data = np.random.randint(0, 7, size=50)

In [119]: data
Out[119]: 
array([6, 6, 2, 3, 5, 3, 2, 5, 4, 5, 4, 3, 4, 5, 0, 2, 0, 4, 2, 0, 3, 2,
       2, 5, 6, 5, 3, 4, 6, 4, 3, 5, 6, 4, 3, 6, 2, 6, 6, 2, 3, 4, 2, 1,
       6, 2, 6, 1, 5, 4])

In [120]: s = pd.Series(data)

In [121]: s.value_counts()
Out[121]: 
6    10
2    10
4     9
3     8
5     8
0     3
1     2
Name: count, dtype: int64

方法 value_counts() 可用於計算多個欄位之間的組合。預設會使用所有欄位,但可以使用 subset 參數選擇子集。

In [122]: data = {"a": [1, 2, 3, 4], "b": ["x", "x", "y", "y"]}

In [123]: frame = pd.DataFrame(data)

In [124]: frame.value_counts()
Out[124]: 
a  b
1  x    1
2  x    1
3  y    1
4  y    1
Name: count, dtype: int64

類似地,您可以取得系列或資料框中值最常發生的值,即眾數

In [125]: s5 = pd.Series([1, 1, 3, 3, 3, 5, 5, 7, 7, 7])

In [126]: s5.mode()
Out[126]: 
0    3
1    7
dtype: int64

In [127]: df5 = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randint(0, 7, size=50),
   .....:         "B": np.random.randint(-10, 15, size=50),
   .....:     }
   .....: )
   .....: 

In [128]: df5.mode()
Out[128]: 
     A   B
0  1.0  -9
1  NaN  10
2  NaN  13

離散化和分位數#

連續值可以使用 cut()(根據值劃分區間)和 qcut()(根據樣本分位數劃分區間)函數離散化

In [129]: arr = np.random.randn(20)

In [130]: factor = pd.cut(arr, 4)

In [131]: factor
Out[131]: 
[(-0.251, 0.464], (-0.968, -0.251], (0.464, 1.179], (-0.251, 0.464], (-0.968, -0.251], ..., (-0.251, 0.464], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251]]
Length: 20
Categories (4, interval[float64, right]): [(-0.968, -0.251] < (-0.251, 0.464] < (0.464, 1.179] <
                                           (1.179, 1.893]]

In [132]: factor = pd.cut(arr, [-5, -1, 0, 1, 5])

In [133]: factor
Out[133]: 
[(0, 1], (-1, 0], (0, 1], (0, 1], (-1, 0], ..., (-1, 0], (-1, 0], (-1, 0], (-1, 0], (-1, 0]]
Length: 20
Categories (4, interval[int64, right]): [(-5, -1] < (-1, 0] < (0, 1] < (1, 5]]

qcut() 計算樣本分位數。例如,我們可以將一些常態分佈資料切成相等大小的四分位數,如下所示

In [134]: arr = np.random.randn(30)

In [135]: factor = pd.qcut(arr, [0, 0.25, 0.5, 0.75, 1])

In [136]: factor
Out[136]: 
[(0.569, 1.184], (-2.278, -0.301], (-2.278, -0.301], (0.569, 1.184], (0.569, 1.184], ..., (-0.301, 0.569], (1.184, 2.346], (1.184, 2.346], (-0.301, 0.569], (-2.278, -0.301]]
Length: 30
Categories (4, interval[float64, right]): [(-2.278, -0.301] < (-0.301, 0.569] < (0.569, 1.184] <
                                           (1.184, 2.346]]

我們也可以傳遞無限值來定義區間

In [137]: arr = np.random.randn(20)

In [138]: factor = pd.cut(arr, [-np.inf, 0, np.inf])

In [139]: factor
Out[139]: 
[(-inf, 0.0], (0.0, inf], (0.0, inf], (-inf, 0.0], (-inf, 0.0], ..., (-inf, 0.0], (-inf, 0.0], (-inf, 0.0], (0.0, inf], (0.0, inf]]
Length: 20
Categories (2, interval[float64, right]): [(-inf, 0.0] < (0.0, inf]]

函數應用#

若要將您自己的或其他函式庫的函數套用至 pandas 物件,您應該知道以下三種方法。適當的方法取決於您的函數預期是對整個 DataFrameSeries、按列或按行,或逐元素操作。

  1. 表格函數應用: pipe()

  2. 列或欄函數應用: apply()

  3. 聚合 API: agg()transform()

  4. 應用逐元素函數: map()

表格函數應用#

資料框序列 可以傳遞到函數中。但是,如果函數需要在鏈中呼叫,請考慮使用 pipe() 方法。

首先一些設定

In [140]: def extract_city_name(df):
   .....:     """
   .....:     Chicago, IL -> Chicago for city_name column
   .....:     """
   .....:     df["city_name"] = df["city_and_code"].str.split(",").str.get(0)
   .....:     return df
   .....: 

In [141]: def add_country_name(df, country_name=None):
   .....:     """
   .....:     Chicago -> Chicago-US for city_name column
   .....:     """
   .....:     col = "city_name"
   .....:     df["city_and_country"] = df[col] + country_name
   .....:     return df
   .....: 

In [142]: df_p = pd.DataFrame({"city_and_code": ["Chicago, IL"]})

extract_city_nameadd_country_name 是取得和傳回 資料框 的函數。

現在比較下列

In [143]: add_country_name(extract_city_name(df_p), country_name="US")
Out[143]: 
  city_and_code city_name city_and_country
0   Chicago, IL   Chicago        ChicagoUS

等於

In [144]: df_p.pipe(extract_city_name).pipe(add_country_name, country_name="US")
Out[144]: 
  city_and_code city_name city_and_country
0   Chicago, IL   Chicago        ChicagoUS

pandas 鼓勵使用第二種風格,稱為方法鏈。 pipe 讓您可以輕鬆地在方法鏈中使用您自己的函式或其他函式庫的函式,以及 pandas 的方法。

在上面的範例中,函式 extract_city_nameadd_country_name 各自預期 DataFrame 作為第一個位置引數。如果您想要套用的函式將資料視為,例如,第二個引數,該怎麼辦?在這種情況下,提供 pipe 一個 (callable, data_keyword) 的元組。 .pipe 會將 DataFrame 路由到元組中指定的引數。

例如,我們可以使用 statsmodels 來擬合迴歸。其 API 預期第一個是公式,第二個是 DataFramedata。我們傳遞函式、關鍵字對 (sm.ols, 'data')pipe

In [147]: import statsmodels.formula.api as sm

In [148]: bb = pd.read_csv("data/baseball.csv", index_col="id")

In [149]: (
   .....:     bb.query("h > 0")
   .....:     .assign(ln_h=lambda df: np.log(df.h))
   .....:     .pipe((sm.ols, "data"), "hr ~ ln_h + year + g + C(lg)")
   .....:     .fit()
   .....:     .summary()
   .....: )
   .....:
Out[149]:
<class 'statsmodels.iolib.summary.Summary'>
"""
                           OLS Regression Results
==============================================================================
Dep. Variable:                     hr   R-squared:                       0.685
Model:                            OLS   Adj. R-squared:                  0.665
Method:                 Least Squares   F-statistic:                     34.28
Date:                Tue, 22 Nov 2022   Prob (F-statistic):           3.48e-15
Time:                        05:34:17   Log-Likelihood:                -205.92
No. Observations:                  68   AIC:                             421.8
Df Residuals:                      63   BIC:                             432.9
Df Model:                           4
Covariance Type:            nonrobust
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept   -8484.7720   4664.146     -1.819      0.074   -1.78e+04     835.780
C(lg)[T.NL]    -2.2736      1.325     -1.716      0.091      -4.922       0.375
ln_h           -1.3542      0.875     -1.547      0.127      -3.103       0.395
year            4.2277      2.324      1.819      0.074      -0.417       8.872
g               0.1841      0.029      6.258      0.000       0.125       0.243
==============================================================================
Omnibus:                       10.875   Durbin-Watson:                   1.999
Prob(Omnibus):                  0.004   Jarque-Bera (JB):               17.298
Skew:                           0.537   Prob(JB):                     0.000175
Kurtosis:                       5.225   Cond. No.                     1.49e+07
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.49e+07. This might indicate that there are
strong multicollinearity or other numerical problems.
"""

pipe 方法的靈感來自 unix 管道,最近則是 dplyrmagrittr,它們引入了流行的 (%>%) (讀取管道) 算子,用於 R。這裡 pipe 的實作非常乾淨,而且在 Python 中感覺很自在。我們鼓勵您查看 pipe() 的原始碼。

列或欄位明智函式應用#

使用 apply() 方法,可以沿著 DataFrame 的軸線應用任意函式,此方法與描述性統計方法一樣,會取得選用的 axis 參數

In [145]: df.apply(lambda x: np.mean(x))
Out[145]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [146]: df.apply(lambda x: np.mean(x), axis=1)
Out[146]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

In [147]: df.apply(lambda x: x.max() - x.min())
Out[147]: 
one      1.051928
two      1.632779
three    1.840607
dtype: float64

In [148]: df.apply(np.cumsum)
Out[148]: 
        one       two     three
a  1.394981  1.772517       NaN
b  1.738035  3.684640 -0.050390
c  2.433281  5.163008  1.177045
d       NaN  5.442353  0.563873

In [149]: df.apply(np.exp)
Out[149]: 
        one       two     three
a  4.034899  5.885648       NaN
b  1.409244  6.767440  0.950858
c  2.004201  4.385785  3.412466
d       NaN  1.322262  0.541630

apply() 方法也會根據字串方法名稱進行分派。

In [150]: df.apply("mean")
Out[150]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [151]: df.apply("mean", axis=1)
Out[151]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

傳遞給 apply() 的函式傳回類型會影響 DataFrame.apply 預設行為的最終輸出類型

  • 如果套用的函式傳回 Series,最終輸出會是 DataFrame。欄位會與套用函式傳回的 Series 索引相符。

  • 如果套用的函式傳回任何其他類型,最終輸出會是 Series

可以使用 result_type 覆寫此預設行為,此參數會接受三個選項:reducebroadcastexpand。這些選項會決定清單類似傳回值如何擴充(或不擴充)到 DataFrame

apply() 搭配一些巧思,可以用來回答許多關於資料集的問題。例如,假設我們想要擷取每欄最大值發生的日期

In [152]: tsdf = pd.DataFrame(
   .....:     np.random.randn(1000, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=1000),
   .....: )
   .....: 

In [153]: tsdf.apply(lambda x: x.idxmax())
Out[153]: 
A   2000-08-06
B   2001-01-18
C   2001-07-18
dtype: datetime64[ns]

您也可以將其他引數和關鍵字引數傳遞給 apply() 方法。

In [154]: def subtract_and_divide(x, sub, divide=1):
   .....:     return (x - sub) / divide
   .....: 

In [155]: df_udf = pd.DataFrame(np.ones((2, 2)))

In [156]: df_udf.apply(subtract_and_divide, args=(5,), divide=3)
Out[156]: 
          0         1
0 -1.333333 -1.333333
1 -1.333333 -1.333333

另一個有用的功能是可以傳遞 Series 方法,對每一欄或每一列執行一些 Series 操作。

In [157]: tsdf = pd.DataFrame(
   .....:     np.random.randn(10, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=10),
   .....: )
   .....: 

In [158]: tsdf.iloc[3:7] = np.nan

In [159]: tsdf
Out[159]: 
                   A         B         C
2000-01-01 -0.158131 -0.232466  0.321604
2000-01-02 -1.810340 -3.105758  0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08 -0.653602  0.178875  1.008298
2000-01-09  1.007996  0.462824  0.254472
2000-01-10  0.307473  0.600337  1.643950

In [160]: tsdf.apply(pd.Series.interpolate)
Out[160]: 
                   A         B         C
2000-01-01 -0.158131 -0.232466  0.321604
2000-01-02 -1.810340 -3.105758  0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 -1.098598 -0.889659  0.092225
2000-01-05 -0.987349 -0.622526  0.321243
2000-01-06 -0.876100 -0.355392  0.550262
2000-01-07 -0.764851 -0.088259  0.779280
2000-01-08 -0.653602  0.178875  1.008298
2000-01-09  1.007996  0.462824  0.254472
2000-01-10  0.307473  0.600337  1.643950

最後,apply() 會取得一個引數 raw,其預設值為 False,會在套用函數前將每一列或每一行轉換成 Series。當設定為 True 時,傳遞的函數將會收到一個 ndarray 物件,如果您不需要索引功能,這會對效能帶來正面的影響。

聚合 API#

聚合 API 允許以簡潔的方式表達多個聚合運算。此 API 在 pandas 物件中很相似,請參閱 groupby APIwindow APIresample API。聚合的進入點為 DataFrame.aggregate() 或別名 DataFrame.agg()

我們將使用上面類似的起始框架

In [161]: tsdf = pd.DataFrame(
   .....:     np.random.randn(10, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=10),
   .....: )
   .....: 

In [162]: tsdf.iloc[3:7] = np.nan

In [163]: tsdf
Out[163]: 
                   A         B         C
2000-01-01  1.257606  1.004194  0.167574
2000-01-02 -0.749892  0.288112 -0.757304
2000-01-03 -0.207550 -0.298599  0.116018
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.814347 -0.257623  0.869226
2000-01-09 -0.250663 -1.206601  0.896839
2000-01-10  2.169758 -1.333363  0.283157

使用單一函數等於 apply()。您也可以傳遞命名方法為字串。這些將傳回聚合輸出的 Series

In [164]: tsdf.agg(lambda x: np.sum(x))
Out[164]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

In [165]: tsdf.agg("sum")
Out[165]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

# these are equivalent to a ``.sum()`` because we are aggregating
# on a single function
In [166]: tsdf.sum()
Out[166]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

Series 上的單一聚合,這將傳回一個純量值

In [167]: tsdf["A"].agg("sum")
Out[167]: 3.033606102414146

使用多個函數聚合#

您可以傳遞多個聚合引數作為清單。傳遞函數的每個結果將會是結果 DataFrame 中的一列。這些自然會從聚合函數命名。

In [168]: tsdf.agg(["sum"])
Out[168]: 
            A         B        C
sum  3.033606 -1.803879  1.57551

多個函數會產生多列

In [169]: tsdf.agg(["sum", "mean"])
Out[169]: 
             A         B         C
sum   3.033606 -1.803879  1.575510
mean  0.505601 -0.300647  0.262585

Series 上,多個函數會傳回 Series,索引為函數名稱

In [170]: tsdf["A"].agg(["sum", "mean"])
Out[170]: 
sum     3.033606
mean    0.505601
Name: A, dtype: float64

傳遞 lambda 函式會產生一個名為 row 的 <lambda>

In [171]: tsdf["A"].agg(["sum", lambda x: x.mean()])
Out[171]: 
sum         3.033606
<lambda>    0.505601
Name: A, dtype: float64

傳遞一個命名函式會產生該名稱的 row

In [172]: def mymean(x):
   .....:     return x.mean()
   .....: 

In [173]: tsdf["A"].agg(["sum", mymean])
Out[173]: 
sum       3.033606
mymean    0.505601
Name: A, dtype: float64

使用 dict 進行聚合#

傳遞一個包含欄位名稱的字典給一個純量或純量清單,到 DataFrame.agg,可讓您自訂要將哪些函式套用至哪些欄位。請注意,結果並非以任何特定順序呈現,您可以使用 OrderedDict 來保證順序。

In [174]: tsdf.agg({"A": "mean", "B": "sum"})
Out[174]: 
A    0.505601
B   -1.803879
dtype: float64

傳遞一個類似清單的物件會產生 DataFrame 輸出。您會取得所有聚合器的矩陣式輸出。輸出將包含所有獨特的函式。未針對特定欄位註記的函式將會是 NaN

In [175]: tsdf.agg({"A": ["mean", "min"], "B": "sum"})
Out[175]: 
             A         B
mean  0.505601       NaN
min  -0.749892       NaN
sum        NaN -1.803879

自訂說明#

使用 .agg() 可以輕鬆建立一個自訂說明函式,類似內建的 說明函式

In [176]: from functools import partial

In [177]: q_25 = partial(pd.Series.quantile, q=0.25)

In [178]: q_25.__name__ = "25%"

In [179]: q_75 = partial(pd.Series.quantile, q=0.75)

In [180]: q_75.__name__ = "75%"

In [181]: tsdf.agg(["count", "mean", "std", "min", q_25, "median", q_75, "max"])
Out[181]: 
               A         B         C
count   6.000000  6.000000  6.000000
mean    0.505601 -0.300647  0.262585
std     1.103362  0.887508  0.606860
min    -0.749892 -1.333363 -0.757304
25%    -0.239885 -0.979600  0.128907
median  0.303398 -0.278111  0.225365
75%     1.146791  0.151678  0.722709
max     2.169758  1.004194  0.896839

轉換 API#

transform() 方法會傳回一個物件,其索引與原始物件相同(大小相同)。此 API 可讓您一次提供多個作業,而非逐一執行。其 API 與 .agg API 非常類似。

我們建立一個類似於上述區段所使用的框架。

In [182]: tsdf = pd.DataFrame(
   .....:     np.random.randn(10, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=10),
   .....: )
   .....: 

In [183]: tsdf.iloc[3:7] = np.nan

In [184]: tsdf
Out[184]: 
                   A         B         C
2000-01-01 -0.428759 -0.864890 -0.675341
2000-01-02 -0.168731  1.338144 -1.279321
2000-01-03 -1.621034  0.438107  0.903794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374 -1.240447 -0.201052
2000-01-09 -0.157795  0.791197 -1.144209
2000-01-10 -0.030876  0.371900  0.061932

轉換整個框架。 .transform() 允許輸入函數為:NumPy 函數、字串函數名稱或使用者定義函數。

In [185]: tsdf.transform(np.abs)
Out[185]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

In [186]: tsdf.transform("abs")
Out[186]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

In [187]: tsdf.transform(lambda x: x.abs())
Out[187]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

這裡 transform() 收到單一函數;這等同於 ufunc 應用程式。

In [188]: np.abs(tsdf)
Out[188]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

傳遞單一函數給 .transform(),其中 Series 將會回傳單一 Series

In [189]: tsdf["A"].transform(np.abs)
Out[189]: 
2000-01-01    0.428759
2000-01-02    0.168731
2000-01-03    1.621034
2000-01-04         NaN
2000-01-05         NaN
2000-01-06         NaN
2000-01-07         NaN
2000-01-08    0.254374
2000-01-09    0.157795
2000-01-10    0.030876
Freq: D, Name: A, dtype: float64

使用多個函數轉換#

傳遞多個函數將會產生欄位多重索引的資料框架。第一個層級將會是原始框架欄位名稱;第二個層級將會是轉換函數的名稱。

In [190]: tsdf.transform([np.abs, lambda x: x + 1])
Out[190]: 
                   A                   B                   C          
            absolute  <lambda>  absolute  <lambda>  absolute  <lambda>
2000-01-01  0.428759  0.571241  0.864890  0.135110  0.675341  0.324659
2000-01-02  0.168731  0.831269  1.338144  2.338144  1.279321 -0.279321
2000-01-03  1.621034 -0.621034  0.438107  1.438107  0.903794  1.903794
2000-01-04       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-08  0.254374  1.254374  1.240447 -0.240447  0.201052  0.798948
2000-01-09  0.157795  0.842205  0.791197  1.791197  1.144209 -0.144209
2000-01-10  0.030876  0.969124  0.371900  1.371900  0.061932  1.061932

傳遞多個函數給 Series 將會產生資料框架。產生的欄位名稱將會是轉換函數。

In [191]: tsdf["A"].transform([np.abs, lambda x: x + 1])
Out[191]: 
            absolute  <lambda>
2000-01-01  0.428759  0.571241
2000-01-02  0.168731  0.831269
2000-01-03  1.621034 -0.621034
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.254374  1.254374
2000-01-09  0.157795  0.842205
2000-01-10  0.030876  0.969124

使用字典轉換#

傳遞函數字典將允許每個欄位進行選擇性轉換。

In [192]: tsdf.transform({"A": np.abs, "B": lambda x: x + 1})
Out[192]: 
                   A         B
2000-01-01  0.428759  0.135110
2000-01-02  0.168731  2.338144
2000-01-03  1.621034  1.438107
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.254374 -0.240447
2000-01-09  0.157795  1.791197
2000-01-10  0.030876  1.371900

傳遞清單字典將會產生具有這些選擇性轉換的多重索引資料框架。

In [193]: tsdf.transform({"A": np.abs, "B": [lambda x: x + 1, "sqrt"]})
Out[193]: 
                   A         B          
            absolute  <lambda>      sqrt
2000-01-01  0.428759  0.135110       NaN
2000-01-02  0.168731  2.338144  1.156782
2000-01-03  1.621034  1.438107  0.661897
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.254374 -0.240447       NaN
2000-01-09  0.157795  1.791197  0.889493
2000-01-10  0.030876  1.371900  0.609836

套用逐元素函數#

由於並非所有函數都可以向量化(接受 NumPy 陣列並回傳另一個陣列或值),因此資料框架上的方法 map() 以及 Series 上類似的 map() 接受任何接受單一值並回傳單一值的 Python 函數。例如

In [194]: df4 = df.copy()

In [195]: df4
Out[195]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [196]: def f(x):
   .....:     return len(str(x))
   .....: 

In [197]: df4["one"].map(f)
Out[197]: 
a    18
b    19
c    18
d     3
Name: one, dtype: int64

In [198]: df4.map(f)
Out[198]: 
   one  two  three
a   18   17      3
b   19   18     20
c   18   18     16
d    3   19     19

Series.map() 具有附加功能;它可用於輕鬆「連結」或「對應」由次要序列定義的值。這與 合併/連接功能 息息相關

In [199]: s = pd.Series(
   .....:     ["six", "seven", "six", "seven", "six"], index=["a", "b", "c", "d", "e"]
   .....: )
   .....: 

In [200]: t = pd.Series({"six": 6.0, "seven": 7.0})

In [201]: s
Out[201]: 
a      six
b    seven
c      six
d    seven
e      six
dtype: object

In [202]: s.map(t)
Out[202]: 
a    6.0
b    7.0
c    6.0
d    7.0
e    6.0
dtype: float64

重新編制索引和變更標籤#

reindex() 是 pandas 中的基本資料對齊方法。它用於實作幾乎所有其他依賴標籤對齊功能的功能。重新編制索引表示將資料調整為符合沿特定軸線的一組標籤。這會達成以下幾件事

  • 重新排序現有資料以符合一組新標籤

  • 在不存在該標籤資料的標籤位置插入遺失值 (NA) 標記

  • 如果已指定,使用邏輯填入遺失標籤的資料(與時間序列資料的工作高度相關)

以下是一個簡單的範例

In [203]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [204]: s
Out[204]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
e   -1.326508
dtype: float64

In [205]: s.reindex(["e", "b", "f", "d"])
Out[205]: 
e   -1.326508
b    1.328614
f         NaN
d   -0.385845
dtype: float64

在此,f 標籤未包含在序列中,因此在結果中顯示為 NaN

使用資料框時,您可以同時重新編制索引和欄位

In [206]: df
Out[206]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [207]: df.reindex(index=["c", "f", "b"], columns=["three", "two", "one"])
Out[207]: 
      three       two       one
c  1.227435  1.478369  0.695246
f       NaN       NaN       NaN
b -0.050390  1.912123  0.343054

請注意,包含實際軸線標籤的 Index 物件可以在物件之間共用。因此,如果我們有一個序列和一個資料框,可以執行下列動作

In [208]: rs = s.reindex(df.index)

In [209]: rs
Out[209]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
dtype: float64

In [210]: rs.index is df.index
Out[210]: True

這表示重新編制索引的序列索引與資料框索引為同一個 Python 物件。

DataFrame.reindex() 也支援「軸式」呼叫慣例,您可以在其中指定單一 labels 參數和它適用的 axis

In [211]: df.reindex(["c", "f", "b"], axis="index")
Out[211]: 
        one       two     three
c  0.695246  1.478369  1.227435
f       NaN       NaN       NaN
b  0.343054  1.912123 -0.050390

In [212]: df.reindex(["three", "two", "one"], axis="columns")
Out[212]: 
      three       two       one
a       NaN  1.772517  1.394981
b -0.050390  1.912123  0.343054
c  1.227435  1.478369  0.695246
d -0.613172  0.279344       NaN

另請參閱

多重索引/進階索引 是執行重新索引更簡潔的方法。

注意

在撰寫效能敏感的程式碼時,有充分的理由花時間成為重新索引的忍者:許多作業在預先對齊的資料上執行速度較快。新增兩個未對齊的資料框會在內部觸發重新索引步驟。對於探索性分析,您幾乎不會注意到差異(因為 reindex 已經過大幅最佳化),但當 CPU 週期很重要時,在這裡和那裡灑幾行明確的 reindex 呼叫可能會產生影響。

重新索引以與另一個物件對齊#

您可能希望取得一個物件並重新索引其軸,以標示為與另一個物件相同。雖然此語法很直接,但很冗長,這是一個很常見的作業,因此 reindex_like() 方法可讓這個作業更簡單

In [213]: df2 = df.reindex(["a", "b", "c"], columns=["one", "two"])

In [214]: df3 = df2 - df2.mean()

In [215]: df2
Out[215]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

In [216]: df3
Out[216]: 
        one       two
a  0.583888  0.051514
b -0.468040  0.191120
c -0.115848 -0.242634

In [217]: df.reindex_like(df2)
Out[217]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

使用 align 彼此對齊物件#

align() 方法是同時對齊兩個物件最快的途徑。它支援 join 參數(與 合併和連接 相關)

  • join='outer':採用索引的聯集(預設)

  • join='left':使用呼叫物件的索引

  • join='right':使用傳遞物件的索引

  • join='inner':取索引的交集

傳回一個包含重新編製索引的兩個 Series 的元組

In [218]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [219]: s1 = s[:4]

In [220]: s2 = s[1:]

In [221]: s1.align(s2)
Out[221]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e         NaN
 dtype: float64,
 a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e    1.114285
 dtype: float64)

In [222]: s1.align(s2, join="inner")
Out[222]: 
(b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64,
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

In [223]: s1.align(s2, join="left")
Out[223]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64,
 a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

對於資料框,預設會對索引和欄位套用 join 方法

In [224]: df.align(df2, join="inner")
Out[224]: 
(        one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369,
         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

您也可以傳遞一個 axis 選項,只針對指定的軸進行對齊

In [225]: df.align(df2, join="inner", axis=0)
Out[225]: 
(        one       two     three
 a  1.394981  1.772517       NaN
 b  0.343054  1.912123 -0.050390
 c  0.695246  1.478369  1.227435,
         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

如果您傳遞一個 Series 給 DataFrame.align(),您可以使用 axis 參數選擇在資料框的索引或欄位上對齊兩個物件

In [226]: df.align(df2.iloc[0], axis=1)
Out[226]: 
(        one     three       two
 a  1.394981       NaN  1.772517
 b  0.343054 -0.050390  1.912123
 c  0.695246  1.227435  1.478369
 d       NaN -0.613172  0.279344,
 one      1.394981
 three         NaN
 two      1.772517
 Name: a, dtype: float64)

重新編製索引時的填補#

reindex() 有一個選用參數 method,它是一個填補方法,可以從下列表格中選擇

方法

動作

pad / ffill

向前填補值

bfill / backfill

向後填補值

nearest

從最近的索引值填補

我們在一個簡單的 Series 上說明這些填補方法

In [227]: rng = pd.date_range("1/3/2000", periods=8)

In [228]: ts = pd.Series(np.random.randn(8), index=rng)

In [229]: ts2 = ts.iloc[[0, 3, 6]]

In [230]: ts
Out[230]: 
2000-01-03    0.183051
2000-01-04    0.400528
2000-01-05   -0.015083
2000-01-06    2.395489
2000-01-07    1.414806
2000-01-08    0.118428
2000-01-09    0.733639
2000-01-10   -0.936077
Freq: D, dtype: float64

In [231]: ts2
Out[231]: 
2000-01-03    0.183051
2000-01-06    2.395489
2000-01-09    0.733639
Freq: 3D, dtype: float64

In [232]: ts2.reindex(ts.index)
Out[232]: 
2000-01-03    0.183051
2000-01-04         NaN
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07         NaN
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [233]: ts2.reindex(ts.index, method="ffill")
Out[233]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

In [234]: ts2.reindex(ts.index, method="bfill")
Out[234]: 
2000-01-03    0.183051
2000-01-04    2.395489
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    0.733639
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [235]: ts2.reindex(ts.index, method="nearest")
Out[235]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

這些方法需要索引依序遞增或遞減。

請注意,使用 ffillmethod='nearest' 除外)或 interpolate 也可以得到相同的結果。

In [236]: ts2.reindex(ts.index).ffill()
Out[236]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

reindex() 如果索引不是單調遞增或遞減,則會引發 ValueError。 fillna()interpolate() 對於索引順序不會執行任何檢查。

重新編制索引時填補的限制#

limittolerance 參數提供了重新編制索引時填補的額外控制。Limit 指定連續匹配的最大計數

In [237]: ts2.reindex(ts.index, method="ffill", limit=1)
Out[237]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

相反地,tolerance 指定索引和索引器值之間的最大距離

In [238]: ts2.reindex(ts.index, method="ffill", tolerance="1 day")
Out[238]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

請注意,當用於 DatetimeIndexTimedeltaIndexPeriodIndex 時,tolerance 如果可能,將強制轉換為 Timedelta。這允許您使用適當的字串指定容差。

從軸中刪除標籤#

reindex 密切相關的方法是 drop() 函數。它會從軸中移除一組標籤

In [239]: df
Out[239]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [240]: df.drop(["a", "d"], axis=0)
Out[240]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

In [241]: df.drop(["one"], axis=1)
Out[241]: 
        two     three
a  1.772517       NaN
b  1.912123 -0.050390
c  1.478369  1.227435
d  0.279344 -0.613172

請注意,下列方法也可以使用,但較不直觀/簡潔

In [242]: df.reindex(df.index.difference(["a", "d"]))
Out[242]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

重新命名/對應標籤#

rename() 方法允許您根據某些對應(字典或 Series)或任意函數重新標記軸。

In [243]: s
Out[243]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
dtype: float64

In [244]: s.rename(str.upper)
Out[244]: 
A   -0.186646
B   -1.692424
C   -0.303893
D   -1.425662
E    1.114285
dtype: float64

如果您傳遞函數,則在使用任何標籤呼叫函數時,函數必須傳回一個值(且必須產生一組唯一值)。也可以使用字典或 Series

In [245]: df.rename(
   .....:     columns={"one": "foo", "two": "bar"},
   .....:     index={"a": "apple", "b": "banana", "d": "durian"},
   .....: )
   .....: 
Out[245]: 
             foo       bar     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

如果對應中不包含欄位/索引標籤,則不會重新命名。請注意,對應中的額外標籤不會擲回錯誤。

DataFrame.rename() 也支援「軸式」呼叫慣例,您可以在其中指定單一 mapper 和要套用對應的 axis

In [246]: df.rename({"one": "foo", "two": "bar"}, axis="columns")
Out[246]: 
        foo       bar     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [247]: df.rename({"a": "apple", "b": "banana", "d": "durian"}, axis="index")
Out[247]: 
             one       two     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

最後,rename() 也接受純量或類清單,用於變更 Series.name 屬性。

In [248]: s.rename("scalar-name")
Out[248]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
Name: scalar-name, dtype: float64

方法 DataFrame.rename_axis()Series.rename_axis() 允許變更 MultiIndex 的特定名稱(與標籤相反)。

In [249]: df = pd.DataFrame(
   .....:     {"x": [1, 2, 3, 4, 5, 6], "y": [10, 20, 30, 40, 50, 60]},
   .....:     index=pd.MultiIndex.from_product(
   .....:         [["a", "b", "c"], [1, 2]], names=["let", "num"]
   .....:     ),
   .....: )
   .....: 

In [250]: df
Out[250]: 
         x   y
let num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [251]: df.rename_axis(index={"let": "abc"})
Out[251]: 
         x   y
abc num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [252]: df.rename_axis(index=str.upper)
Out[252]: 
         x   y
LET NUM       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

迭代#

pandas 物件的基本迭代行為取決於類型。在 Series 上進行迭代時,它被視為陣列,而基本迭代會產生值。資料框遵循類似字典的慣例,在物件的「鍵」上進行迭代。

簡而言之,基本迭代 (for i in object) 會產生

  • Series:值

  • DataFrame:欄位標籤

因此,例如,在 DataFrame 上進行迭代會提供欄位名稱

In [253]: df = pd.DataFrame(
   .....:     {"col1": np.random.randn(3), "col2": np.random.randn(3)}, index=["a", "b", "c"]
   .....: )
   .....: 

In [254]: for col in df:
   .....:     print(col)
   .....: 
col1
col2

pandas 物件也有類似字典的 items() 方法,用於在 (鍵、值) 對上進行迭代。

若要迭代 DataFrame 的列,可以使用下列方法

  • iterrows():將 DataFrame 的列作為 (索引、Series) 對進行迭代。這會將列轉換為 Series 物件,這可能會變更資料類型,並造成一些效能影響。

  • itertuples():以值的名稱元組形式對 DataFrame 的列進行反覆運算。這比 iterrows() 快很多,而且在大部分情況下,用它來對 DataFrame 的值進行反覆運算會更好。

警告

反覆運算 pandas 物件通常會很慢。在很多情況下,手動對列進行反覆運算並非必要,而且可以用下列方法之一來避免

  • 尋找向量化的解決方案:很多運算可以使用內建方法或 NumPy 函數、(布林)索引等來執行

  • 當你有一個無法一次對完整的 DataFrame/Series 執行的函數時,最好使用 apply(),而不是對值進行反覆運算。請參閱 函數應用 的文件。

  • 如果你需要對值進行反覆運算,但效能很重要,請考慮使用 cython 或 numba 來撰寫內部迴圈。請參閱 提升效能 章節,瞭解此方法的一些範例。

警告

絕不應該修改你正在反覆運算的內容。這無法保證在所有情況下都能運作。根據資料類型,反覆運算器會傳回一個副本,而不是一個檢視,寫入它不會產生任何效果!

例如,在下列情況中,設定值不會產生任何效果

In [255]: df = pd.DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]})

In [256]: for index, row in df.iterrows():
   .....:     row["a"] = 10
   .....: 

In [257]: df
Out[257]: 
   a  b
0  1  a
1  2  b
2  3  c

項目#

與字典式介面一致,items() 遍歷鍵值對

  • Series: (索引,純量值) 對

  • DataFrame: (欄,Series) 對

例如

In [258]: for label, ser in df.items():
   .....:     print(label)
   .....:     print(ser)
   .....: 
a
0    1
1    2
2    3
Name: a, dtype: int64
b
0    a
1    b
2    c
Name: b, dtype: object

iterrows#

iterrows() 讓您能以 Series 物件遍歷 DataFrame 的列。它傳回一個迭代器,產生每個索引值以及包含每列資料的 Series

In [259]: for row_index, row in df.iterrows():
   .....:     print(row_index, row, sep="\n")
   .....: 
0
a    1
b    a
Name: 0, dtype: object
1
a    2
b    b
Name: 1, dtype: object
2
a    3
b    c
Name: 2, dtype: object

注意

由於 iterrows() 為每列傳回一個 Series,它不會保留列中的資料型態 (資料型態會保留在 DataFrame 的欄中)。例如,

In [260]: df_orig = pd.DataFrame([[1, 1.5]], columns=["int", "float"])

In [261]: df_orig.dtypes
Out[261]: 
int        int64
float    float64
dtype: object

In [262]: row = next(df_orig.iterrows())[1]

In [263]: row
Out[263]: 
int      1.0
float    1.5
Name: 0, dtype: float64

傳回為 Series 的 row 中的所有值現在都向上轉型為浮點數,欄 x 中的原始整數值也是如此

In [264]: row["int"].dtype
Out[264]: dtype('float64')

In [265]: df_orig["int"].dtype
Out[265]: dtype('int64')

若要在遍歷列的同時保留資料型態,建議使用 itertuples(),它會傳回值的 namedtuple,而且通常比 iterrows() 快很多。

例如,一種人為的方式來轉置 DataFrame 會是

In [266]: df2 = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})

In [267]: print(df2)
   x  y
0  1  4
1  2  5
2  3  6

In [268]: print(df2.T)
   0  1  2
x  1  2  3
y  4  5  6

In [269]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})

In [270]: print(df2_t)
   0  1  2
x  1  2  3
y  4  5  6

itertuples#

方法 itertuples() 會傳回一個迭代器,為 DataFrame 中的每一列產生一個命名元組。元組的第一個元素會是該列對應的索引值,而其餘的值則是該列的值。

例如

In [271]: for row in df.itertuples():
   .....:     print(row)
   .....: 
Pandas(Index=0, a=1, b='a')
Pandas(Index=1, a=2, b='b')
Pandas(Index=2, a=3, b='c')

此方法不會將列轉換為 Series 物件;它只會在命名元組中傳回值。因此,itertuples() 會保留值的資料類型,而且通常比 iterrows() 快。

注意

如果欄位名稱不是有效的 Python 識別字、重複或以底線開頭,則會將其重新命名為位置名稱。如果欄位數目很多 (>255),則會傳回一般元組。

.dt 存取器#

如果 Series 是 datetime/period 類型的 Series,則它有一個存取器可以簡潔地傳回 Series 的 datetime 類型屬性。這會傳回一個 Series,其索引與現有的 Series 相同。

# datetime
In [272]: s = pd.Series(pd.date_range("20130101 09:10:12", periods=4))

In [273]: s
Out[273]: 
0   2013-01-01 09:10:12
1   2013-01-02 09:10:12
2   2013-01-03 09:10:12
3   2013-01-04 09:10:12
dtype: datetime64[ns]

In [274]: s.dt.hour
Out[274]: 
0    9
1    9
2    9
3    9
dtype: int32

In [275]: s.dt.second
Out[275]: 
0    12
1    12
2    12
3    12
dtype: int32

In [276]: s.dt.day
Out[276]: 
0    1
1    2
2    3
3    4
dtype: int32

這會產生類似這樣的良好表達式

In [277]: s[s.dt.day == 2]
Out[277]: 
1   2013-01-02 09:10:12
dtype: datetime64[ns]

您可以輕鬆產生 tz 感知的轉換

In [278]: stz = s.dt.tz_localize("US/Eastern")

In [279]: stz
Out[279]: 
0   2013-01-01 09:10:12-05:00
1   2013-01-02 09:10:12-05:00
2   2013-01-03 09:10:12-05:00
3   2013-01-04 09:10:12-05:00
dtype: datetime64[ns, US/Eastern]

In [280]: stz.dt.tz
Out[280]: <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>

您也可以串聯這些類型的運算

In [281]: s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[281]: 
0   2013-01-01 04:10:12-05:00
1   2013-01-02 04:10:12-05:00
2   2013-01-03 04:10:12-05:00
3   2013-01-04 04:10:12-05:00
dtype: datetime64[ns, US/Eastern]

您也可以使用 Series.dt.strftime() 將日期時間值格式化為字串,它支援與標準 strftime() 相同的格式。

# DatetimeIndex
In [282]: s = pd.Series(pd.date_range("20130101", periods=4))

In [283]: s
Out[283]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
3   2013-01-04
dtype: datetime64[ns]

In [284]: s.dt.strftime("%Y/%m/%d")
Out[284]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object
# PeriodIndex
In [285]: s = pd.Series(pd.period_range("20130101", periods=4))

In [286]: s
Out[286]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [287]: s.dt.strftime("%Y/%m/%d")
Out[287]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object

.dt 存取器適用於期間和時間增量資料類型。

# period
In [288]: s = pd.Series(pd.period_range("20130101", periods=4, freq="D"))

In [289]: s
Out[289]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [290]: s.dt.year
Out[290]: 
0    2013
1    2013
2    2013
3    2013
dtype: int64

In [291]: s.dt.day
Out[291]: 
0    1
1    2
2    3
3    4
dtype: int64
# timedelta
In [292]: s = pd.Series(pd.timedelta_range("1 day 00:00:05", periods=4, freq="s"))

In [293]: s
Out[293]: 
0   1 days 00:00:05
1   1 days 00:00:06
2   1 days 00:00:07
3   1 days 00:00:08
dtype: timedelta64[ns]

In [294]: s.dt.days
Out[294]: 
0    1
1    1
2    1
3    1
dtype: int64

In [295]: s.dt.seconds
Out[295]: 
0    5
1    6
2    7
3    8
dtype: int32

In [296]: s.dt.components
Out[296]: 
   days  hours  minutes  seconds  milliseconds  microseconds  nanoseconds
0     1      0        0        5             0             0            0
1     1      0        0        6             0             0            0
2     1      0        0        7             0             0            0
3     1      0        0        8             0             0            0

注意

如果您使用非日期時間值存取,Series.dt 會引發 TypeError

向量化字串方法#

Series 具有一組字串處理方法,讓您輕鬆對陣列的每個元素進行操作。或許最重要的是,這些方法會自動排除遺漏/NA 值。這些方法可透過 Series 的 str 屬性存取,而且通常具有與等效 (純量) 內建字串方法相符的名稱。例如

In [297]: s = pd.Series(
   .....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   .....: )
   .....: 

In [298]: s.str.lower()
Out[298]: 
0       a
1       b
2       c
3    aaba
4    baca
5    <NA>
6    caba
7     dog
8     cat
dtype: string

也提供了強大的模式比對方法,但請注意,模式比對通常預設使用 正規表示式 (在某些情況下總是使用它們)。

注意

在 pandas 1.0 之前,字串方法僅適用於 object -dtype Series。pandas 1.0 新增了專門用於字串的 StringDtype。請參閱 文字資料類型 以取得更多資訊。

請參閱 向量化字串方法 以取得完整說明。

排序#

pandas 支援三種排序:依索引標籤排序、依欄位值排序,以及依兩者的組合排序。

依索引#

Series.sort_index()DataFrame.sort_index() 方法用於依 pandas 物件的索引層級排序。

In [299]: df = pd.DataFrame(
   .....:     {
   .....:         "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
   .....:         "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
   .....:         "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
   .....:     }
   .....: )
   .....: 

In [300]: unsorted_df = df.reindex(
   .....:     index=["a", "d", "c", "b"], columns=["three", "two", "one"]
   .....: )
   .....: 

In [301]: unsorted_df
Out[301]: 
      three       two       one
a       NaN -1.152244  0.562973
d -0.252916 -0.109597       NaN
c  1.273388 -0.167123  0.640382
b -0.098217  0.009797 -1.299504

# DataFrame
In [302]: unsorted_df.sort_index()
Out[302]: 
      three       two       one
a       NaN -1.152244  0.562973
b -0.098217  0.009797 -1.299504
c  1.273388 -0.167123  0.640382
d -0.252916 -0.109597       NaN

In [303]: unsorted_df.sort_index(ascending=False)
Out[303]: 
      three       two       one
d -0.252916 -0.109597       NaN
c  1.273388 -0.167123  0.640382
b -0.098217  0.009797 -1.299504
a       NaN -1.152244  0.562973

In [304]: unsorted_df.sort_index(axis=1)
Out[304]: 
        one     three       two
a  0.562973       NaN -1.152244
d       NaN -0.252916 -0.109597
c  0.640382  1.273388 -0.167123
b -1.299504 -0.098217  0.009797

# Series
In [305]: unsorted_df["three"].sort_index()
Out[305]: 
a         NaN
b   -0.098217
c    1.273388
d   -0.252916
Name: three, dtype: float64

依索引排序也支援 key 參數,此參數會接收一個可呼叫函式,並套用至要排序的索引。對於 MultiIndex 物件,會逐層將 key 套用至 level 指定的層級。

In [306]: s1 = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3], "c": [2, 3, 4]}).set_index(
   .....:     list("ab")
   .....: )
   .....: 

In [307]: s1
Out[307]: 
     c
a b   
B 1  2
a 2  3
C 3  4
In [308]: s1.sort_index(level="a")
Out[308]: 
     c
a b   
B 1  2
C 3  4
a 2  3

In [309]: s1.sort_index(level="a", key=lambda idx: idx.str.lower())
Out[309]: 
     c
a b   
a 2  3
B 1  2
C 3  4

如需依值進行關鍵字排序的資訊,請參閱 值排序

依值#

Series.sort_values() 方法用於依值排序 SeriesDataFrame.sort_values() 方法用於依欄位或列值排序 DataFrameDataFrame.sort_values() 的選用 by 參數可用於指定一個或多個欄位,以決定排序順序。

In [310]: df1 = pd.DataFrame(
   .....:     {"one": [2, 1, 1, 1], "two": [1, 3, 2, 4], "three": [5, 4, 3, 2]}
   .....: )
   .....: 

In [311]: df1.sort_values(by="two")
Out[311]: 
   one  two  three
0    2    1      5
2    1    2      3
1    1    3      4
3    1    4      2

by 參數可以接收欄位名稱清單,例如:

In [312]: df1[["one", "two", "three"]].sort_values(by=["one", "two"])
Out[312]: 
   one  two  three
2    1    2      3
1    1    3      4
3    1    4      2
0    2    1      5

這些方法透過 na_position 參數對 NA 值進行特殊處理

In [313]: s[2] = np.nan

In [314]: s.sort_values()
Out[314]: 
0       A
3    Aaba
1       B
4    Baca
6    CABA
8     cat
7     dog
2    <NA>
5    <NA>
dtype: string

In [315]: s.sort_values(na_position="first")
Out[315]: 
2    <NA>
5    <NA>
0       A
3    Aaba
1       B
4    Baca
6    CABA
8     cat
7     dog
dtype: string

排序也支援 key 參數,該參數採用可呼叫函式來套用至要排序的值。

In [316]: s1 = pd.Series(["B", "a", "C"])
In [317]: s1.sort_values()
Out[317]: 
0    B
2    C
1    a
dtype: object

In [318]: s1.sort_values(key=lambda x: x.str.lower())
Out[318]: 
1    a
0    B
2    C
dtype: object

key 會給予 Series 值,並應傳回 Series 或具有相同形狀的陣列,其中包含轉換後的數值。對於 DataFrame 物件,會逐欄套用 key,因此 key 仍應預期 Series 並傳回 Series,例如:

In [319]: df = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3]})
In [320]: df.sort_values(by="a")
Out[320]: 
   a  b
0  B  1
2  C  3
1  a  2

In [321]: df.sort_values(by="a", key=lambda col: col.str.lower())
Out[321]: 
   a  b
1  a  2
0  B  1
2  C  3

每個欄位的名稱或類型可用於對不同欄位套用不同的函式。

依索引和值#

傳遞給 DataFrame.sort_values()by 參數的字串可以指欄位或索引層級名稱。

# Build MultiIndex
In [322]: idx = pd.MultiIndex.from_tuples(
   .....:     [("a", 1), ("a", 2), ("a", 2), ("b", 2), ("b", 1), ("b", 1)]
   .....: )
   .....: 

In [323]: idx.names = ["first", "second"]

# Build DataFrame
In [324]: df_multi = pd.DataFrame({"A": np.arange(6, 0, -1)}, index=idx)

In [325]: df_multi
Out[325]: 
              A
first second   
a     1       6
      2       5
      2       4
b     2       3
      1       2
      1       1

依「second」(索引)和「A」(欄位)排序

In [326]: df_multi.sort_values(by=["second", "A"])
Out[326]: 
              A
first second   
b     1       1
      1       2
a     1       6
b     2       3
a     2       4
      2       5

注意

如果字串同時符合欄位名稱和索引層級名稱,則會發出警告,且欄位優先。這將導致未來版本中出現歧義錯誤。

searchsorted#

Series 具有 searchsorted() 方法,其運作方式類似於 numpy.ndarray.searchsorted()

In [327]: ser = pd.Series([1, 2, 3])

In [328]: ser.searchsorted([0, 3])
Out[328]: array([0, 2])

In [329]: ser.searchsorted([0, 4])
Out[329]: array([0, 3])

In [330]: ser.searchsorted([1, 3], side="right")
Out[330]: array([1, 3])

In [331]: ser.searchsorted([1, 3], side="left")
Out[331]: array([0, 2])

In [332]: ser = pd.Series([3, 1, 2])

In [333]: ser.searchsorted([0, 3], sorter=np.argsort(ser))
Out[333]: array([0, 2])

最小值/最大值#

Series 具有 nsmallest()nlargest() 方法,可傳回最小值或最大值 \(n\)。對於大型 Series,這會比對整個 Series 進行排序,然後對結果呼叫 head(n) 快很多。

In [334]: s = pd.Series(np.random.permutation(10))

In [335]: s
Out[335]: 
0    2
1    0
2    3
3    7
4    1
5    5
6    9
7    6
8    8
9    4
dtype: int64

In [336]: s.sort_values()
Out[336]: 
1    0
4    1
0    2
2    3
9    4
5    5
7    6
3    7
8    8
6    9
dtype: int64

In [337]: s.nsmallest(3)
Out[337]: 
1    0
4    1
0    2
dtype: int64

In [338]: s.nlargest(3)
Out[338]: 
6    9
8    8
3    7
dtype: int64

DataFrame 也有 nlargestnsmallest 方法。

In [339]: df = pd.DataFrame(
   .....:     {
   .....:         "a": [-2, -1, 1, 10, 8, 11, -1],
   .....:         "b": list("abdceff"),
   .....:         "c": [1.0, 2.0, 4.0, 3.2, np.nan, 3.0, 4.0],
   .....:     }
   .....: )
   .....: 

In [340]: df.nlargest(3, "a")
Out[340]: 
    a  b    c
5  11  f  3.0
3  10  c  3.2
4   8  e  NaN

In [341]: df.nlargest(5, ["a", "c"])
Out[341]: 
    a  b    c
5  11  f  3.0
3  10  c  3.2
4   8  e  NaN
2   1  d  4.0
6  -1  f  4.0

In [342]: df.nsmallest(3, "a")
Out[342]: 
   a  b    c
0 -2  a  1.0
1 -1  b  2.0
6 -1  f  4.0

In [343]: df.nsmallest(5, ["a", "c"])
Out[343]: 
   a  b    c
0 -2  a  1.0
1 -1  b  2.0
6 -1  f  4.0
2  1  d  4.0
4  8  e  NaN

依據 MultiIndex 欄位排序#

當欄位是 MultiIndex 時,您必須明確指定排序,並完整指定所有層級到 by

In [344]: df1.columns = pd.MultiIndex.from_tuples(
   .....:     [("a", "one"), ("a", "two"), ("b", "three")]
   .....: )
   .....: 

In [345]: df1.sort_values(by=("a", "two"))
Out[345]: 
    a         b
  one two three
0   2   1     5
2   1   2     3
1   1   3     4
3   1   4     2

複製#

pandas 物件上的 copy() 方法會複製底層資料(但不會複製軸索引,因為它們是不可變的),並傳回一個新的物件。請注意,很少需要複製物件。例如,只有少數方法可以就地變更 DataFrame

  • 插入、刪除或修改欄位。

  • 指定給 indexcolumns 屬性。

  • 對於同質資料,直接透過 values 屬性或進階索引修改值。

明確來說,沒有任何 pandas 方法會產生修改資料的副作用;幾乎每個方法都會傳回一個新的物件,讓原始物件保持不變。如果資料有變更,那是因為您明確這樣做。

dtypes#

在大部分情況下,pandas 會使用 NumPy 陣列和 dtypes 來處理 Series 或 DataFrame 的個別欄位。NumPy 提供對 floatintbooltimedelta64[ns]datetime64[ns] 的支援(請注意,NumPy 不支援具時區感知功能的日期時間)。

pandas 和第三方程式庫在幾個地方擴充了 NumPy 的類型系統。本節說明 pandas 內部所做的擴充。請參閱 擴充類型,了解如何撰寫與 pandas 相容的擴充。請參閱 生態系統頁面,取得已實作擴充的第三方程式庫清單。

下表列出所有 pandas 擴充類型。對於需要 dtype 參數的方法,可以指定字串,如指示所示。請參閱各文件部分,以進一步了解每種類型。

資料類型

資料類型

純量

陣列

字串別名

支援時區的日期時間

DatetimeTZDtype

時間戳記

arrays.DatetimeArray

'datetime64[ns, <tz>]'

類別

CategoricalDtype

(無)

類別

'category'

週期 (時間跨度)

PeriodDtype

週期

arrays.PeriodArray 'Period[<freq>]'

'period[<freq>]',

稀疏

SparseDtype

(無)

arrays.SparseArray

'Sparse', 'Sparse[int]', 'Sparse[float]'

區間

IntervalDtype

區間

arrays.IntervalArray

'interval', 'Interval', 'Interval[<numpy_dtype>]', 'Interval[datetime64[ns, <tz>]]', 'Interval[timedelta64[<freq>]]'

可為空整數

Int64Dtype, …

(無)

arrays.IntegerArray

「Int8」「Int16」「Int32」「Int64」「UInt8」「UInt16」「UInt32」「UInt64」

可為空值浮點數

Float64Dtype, …

(無)

arrays.FloatingArray

「Float32」「Float64」

字串

StringDtype

str

arrays.StringArray

「字串」

布林值(含 NA)

BooleanDtype

bool

arrays.BooleanArray

「布林值」

pandas 有兩種儲存字串的方式。

  1. object 資料型態,可儲存任何 Python 物件,包括字串。

  2. StringDtype,專門用於儲存字串。

一般建議使用 StringDtype。如需更多資訊,請參閱 文字資料型態

最後,可以使用 object 資料型態儲存任意物件,但應盡量避免(以提升效能並與其他函式庫和方法相容。請參閱 物件轉換)。

DataFrame 的便利 dtypes 屬性會傳回一個 Series,其中包含每欄的資料型態。

In [346]: dft = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.rand(3),
   .....:         "B": 1,
   .....:         "C": "foo",
   .....:         "D": pd.Timestamp("20010102"),
   .....:         "E": pd.Series([1.0] * 3).astype("float32"),
   .....:         "F": False,
   .....:         "G": pd.Series([1] * 3, dtype="int8"),
   .....:     }
   .....: )
   .....: 

In [347]: dft
Out[347]: 
          A  B    C          D    E      F  G
0  0.035962  1  foo 2001-01-02  1.0  False  1
1  0.701379  1  foo 2001-01-02  1.0  False  1
2  0.281885  1  foo 2001-01-02  1.0  False  1

In [348]: dft.dtypes
Out[348]: 
A          float64
B            int64
C           object
D    datetime64[s]
E          float32
F             bool
G             int8
dtype: object

Series 物件上,請使用 dtype 屬性。

In [349]: dft["A"].dtype
Out[349]: dtype('float64')

如果 pandas 物件包含多個資料類型資料在單一欄位中,欄位的資料類型將會被選取來容納所有資料類型(object是最通用的)。

# these ints are coerced to floats
In [350]: pd.Series([1, 2, 3, 4, 5, 6.0])
Out[350]: 
0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    6.0
dtype: float64

# string data forces an ``object`` dtype
In [351]: pd.Series([1, 2, 3, 6.0, "foo"])
Out[351]: 
0      1
1      2
2      3
3    6.0
4    foo
dtype: object

DataFrame 中每個類型的欄位數量,可以透過呼叫 DataFrame.dtypes.value_counts() 來找到。

In [352]: dft.dtypes.value_counts()
Out[352]: 
float64          1
int64            1
object           1
datetime64[s]    1
float32          1
bool             1
int8             1
Name: count, dtype: int64

數值資料類型會傳播,並且可以在 DataFrame 中共存。如果傳遞資料類型(直接透過 dtype 關鍵字、傳遞的 ndarray 或傳遞的 Series),它將會在 DataFrame 作業中保留。此外,不同的數值資料類型不會合併。以下範例將會讓您體驗一下。

In [353]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=["A"], dtype="float32")

In [354]: df1
Out[354]: 
          A
0  0.224364
1  1.890546
2  0.182879
3  0.787847
4 -0.188449
5  0.667715
6 -0.011736
7 -0.399073

In [355]: df1.dtypes
Out[355]: 
A    float32
dtype: object

In [356]: df2 = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Series(np.random.randn(8), dtype="float16"),
   .....:         "B": pd.Series(np.random.randn(8)),
   .....:         "C": pd.Series(np.random.randint(0, 255, size=8), dtype="uint8"),  # [0,255] (range of uint8)
   .....:     }
   .....: )
   .....: 

In [357]: df2
Out[357]: 
          A         B    C
0  0.823242  0.256090   26
1  1.607422  1.426469   86
2 -0.333740 -0.416203   46
3 -0.063477  1.139976  212
4 -1.014648 -1.193477   26
5  0.678711  0.096706    7
6 -0.040863 -1.956850  184
7 -0.357422 -0.714337  206

In [358]: df2.dtypes
Out[358]: 
A    float16
B    float64
C      uint8
dtype: object

預設#

預設整數類型為 int64,浮點數類型為 float64不論平台(32 位元或 64 位元)。以下都將產生 int64 資料類型。

In [359]: pd.DataFrame([1, 2], columns=["a"]).dtypes
Out[359]: 
a    int64
dtype: object

In [360]: pd.DataFrame({"a": [1, 2]}).dtypes
Out[360]: 
a    int64
dtype: object

In [361]: pd.DataFrame({"a": 1}, index=list(range(2))).dtypes
Out[361]: 
a    int64
dtype: object

請注意,Numpy 會在建立陣列時選擇平台相關類型。以下在 32 位元平台上產生 int32

In [362]: frame = pd.DataFrame(np.array([1, 2]))

向上轉換#

類型在與其他類型合併時可能會向上轉換,表示它們從目前的類型(例如 intfloat)進行提升。

In [363]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2

In [364]: df3
Out[364]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2 -0.150862 -0.416203   46.0
3  0.724370  1.139976  212.0
4 -1.203098 -1.193477   26.0
5  1.346426  0.096706    7.0
6 -0.052599 -1.956850  184.0
7 -0.756495 -0.714337  206.0

In [365]: df3.dtypes
Out[365]: 
A    float32
B    float64
C    float64
dtype: object

DataFrame.to_numpy() 將會傳回資料類型的最小公分母,表示可以容納結果同質 NumPy 陣列中所有類型的資料類型。這可能會強制進行一些向上轉換

In [366]: df3.to_numpy().dtype
Out[366]: dtype('float64')

astype#

你可以使用 astype() 方法來明確地將資料類型從一種轉換為另一種。預設情況下,這些方法會傳回一個拷貝,即使資料類型沒有改變(傳遞 copy=False 來變更此行為)。此外,如果 astype 操作無效,它們會引發例外狀況。

向上轉換總是根據NumPy 規則。如果兩個不同的資料類型參與一個操作,則較一般的資料類型將會用作操作的結果。

In [367]: df3
Out[367]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2 -0.150862 -0.416203   46.0
3  0.724370  1.139976  212.0
4 -1.203098 -1.193477   26.0
5  1.346426  0.096706    7.0
6 -0.052599 -1.956850  184.0
7 -0.756495 -0.714337  206.0

In [368]: df3.dtypes
Out[368]: 
A    float32
B    float64
C    float64
dtype: object

# conversion of dtypes
In [369]: df3.astype("float32").dtypes
Out[369]: 
A    float32
B    float32
C    float32
dtype: object

使用 astype() 將資料子集轉換為指定的類型。

In [370]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})

In [371]: dft[["a", "b"]] = dft[["a", "b"]].astype(np.uint8)

In [372]: dft
Out[372]: 
   a  b  c
0  1  4  7
1  2  5  8
2  3  6  9

In [373]: dft.dtypes
Out[373]: 
a    uint8
b    uint8
c    int64
dtype: object

透過傳遞字典給 astype(),將某些欄位轉換為特定的資料類型。

In [374]: dft1 = pd.DataFrame({"a": [1, 0, 1], "b": [4, 5, 6], "c": [7, 8, 9]})

In [375]: dft1 = dft1.astype({"a": np.bool_, "c": np.float64})

In [376]: dft1
Out[376]: 
       a  b    c
0   True  4  7.0
1  False  5  8.0
2   True  6  9.0

In [377]: dft1.dtypes
Out[377]: 
a       bool
b      int64
c    float64
dtype: object

注意

當嘗試使用 astype()loc() 將子集欄位轉換為指定類型時,會發生向上轉型。

loc() 嘗試將我們指定給目前資料類型的內容放入,而 [] 會覆寫它們,採用右側的資料類型。因此,下列程式碼會產生意外的結果。

In [378]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})

In [379]: dft.loc[:, ["a", "b"]].astype(np.uint8).dtypes
Out[379]: 
a    uint8
b    uint8
dtype: object

In [380]: dft.loc[:, ["a", "b"]] = dft.loc[:, ["a", "b"]].astype(np.uint8)

In [381]: dft.dtypes
Out[381]: 
a    int64
b    int64
c    int64
dtype: object

物件轉換#

pandas 提供各種函式,嘗試強制將 object 資料類型轉換為其他類型。如果資料已經是正確的類型,但儲存在 object 陣列中,可以使用 DataFrame.infer_objects()Series.infer_objects() 方法將其軟轉換為正確的類型。

In [382]: import datetime

In [383]: df = pd.DataFrame(
   .....:     [
   .....:         [1, 2],
   .....:         ["a", "b"],
   .....:         [datetime.datetime(2016, 3, 2), datetime.datetime(2016, 3, 2)],
   .....:     ]
   .....: )
   .....: 

In [384]: df = df.T

In [385]: df
Out[385]: 
   0  1                    2
0  1  a  2016-03-02 00:00:00
1  2  b  2016-03-02 00:00:00

In [386]: df.dtypes
Out[386]: 
0    object
1    object
2    object
dtype: object

由於資料已轉置,因此原始推論將所有欄位儲存為物件,而 infer_objects 會修正此問題。

In [387]: df.infer_objects().dtypes
Out[387]: 
0             int64
1            object
2    datetime64[ns]
dtype: object

下列函式可供一維物件陣列或純量使用,以執行物件的強制轉換為指定類型

  • to_numeric()(轉換為數字資料類型)

    In [388]: m = ["1.1", 2, 3]
    
    In [389]: pd.to_numeric(m)
    Out[389]: array([1.1, 2. , 3. ])
    
  • to_datetime()(轉換為日期時間物件)

    In [390]: import datetime
    
    In [391]: m = ["2016-07-09", datetime.datetime(2016, 3, 2)]
    
    In [392]: pd.to_datetime(m)
    Out[392]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)
    
  • to_timedelta()(轉換為時間增量物件)

    In [393]: m = ["5us", pd.Timedelta("1day")]
    
    In [394]: pd.to_timedelta(m)
    Out[394]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)
    

若要強制轉換,我們可以傳入 errors 參數,它指定 pandas 應如何處理無法轉換為所需資料類型或物件的元素。預設為 errors='raise',表示在轉換過程中遇到的任何錯誤都將引發。但是,如果 errors='coerce',這些錯誤將被忽略,而 pandas 會將有問題的元素轉換為 pd.NaT(對於日期時間和時間增量)或 np.nan(對於數字)。如果您讀取的資料大部分為所需資料類型(例如數字、日期時間),但偶爾會混雜不符合的元素,而您希望將其表示為遺失值,這可能會很有用

In [395]: import datetime

In [396]: m = ["apple", datetime.datetime(2016, 3, 2)]

In [397]: pd.to_datetime(m, errors="coerce")
Out[397]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)

In [398]: m = ["apple", 2, 3]

In [399]: pd.to_numeric(m, errors="coerce")
Out[399]: array([nan,  2.,  3.])

In [400]: m = ["apple", pd.Timedelta("1day")]

In [401]: pd.to_timedelta(m, errors="coerce")
Out[401]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)

除了物件轉換之外,to_numeric() 提供另一個參數 downcast,它提供將新(或已有的)數字資料向下轉換為較小資料類型的選項,這可以節省記憶體

In [402]: m = ["1", 2, 3]

In [403]: pd.to_numeric(m, downcast="integer")  # smallest signed int dtype
Out[403]: array([1, 2, 3], dtype=int8)

In [404]: pd.to_numeric(m, downcast="signed")  # same as 'integer'
Out[404]: array([1, 2, 3], dtype=int8)

In [405]: pd.to_numeric(m, downcast="unsigned")  # smallest unsigned int dtype
Out[405]: array([1, 2, 3], dtype=uint8)

In [406]: pd.to_numeric(m, downcast="float")  # smallest float dtype
Out[406]: array([1., 2., 3.], dtype=float32)

由於這些方法僅適用於一維陣列、清單或純量;因此無法直接用於多維物件(例如資料框)。不過,透過 apply(),我們可以有效率地對每一欄套用函數

In [407]: import datetime

In [408]: df = pd.DataFrame([["2016-07-09", datetime.datetime(2016, 3, 2)]] * 2, dtype="O")

In [409]: df
Out[409]: 
            0                    1
0  2016-07-09  2016-03-02 00:00:00
1  2016-07-09  2016-03-02 00:00:00

In [410]: df.apply(pd.to_datetime)
Out[410]: 
           0          1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02

In [411]: df = pd.DataFrame([["1.1", 2, 3]] * 2, dtype="O")

In [412]: df
Out[412]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [413]: df.apply(pd.to_numeric)
Out[413]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [414]: df = pd.DataFrame([["5us", pd.Timedelta("1day")]] * 2, dtype="O")

In [415]: df
Out[415]: 
     0                1
0  5us  1 days 00:00:00
1  5us  1 days 00:00:00

In [416]: df.apply(pd.to_timedelta)
Out[416]: 
                       0      1
0 0 days 00:00:00.000005 1 days
1 0 days 00:00:00.000005 1 days

陷阱#

integer 型別資料執行選取作業時,資料很容易向上轉型為 floating。在未引入 nans 的情況下,輸入資料的 dtype 會保留。另請參閱 整數 NA 的支援

In [417]: dfi = df3.astype("int32")

In [418]: dfi["E"] = 1

In [419]: dfi
Out[419]: 
   A  B    C  E
0  1  0   26  1
1  3  1   86  1
2  0  0   46  1
3  0  1  212  1
4 -1 -1   26  1
5  1  0    7  1
6  0 -1  184  1
7  0  0  206  1

In [420]: dfi.dtypes
Out[420]: 
A    int32
B    int32
C    int32
E    int64
dtype: object

In [421]: casted = dfi[dfi > 0]

In [422]: casted
Out[422]: 
     A    B    C  E
0  1.0  NaN   26  1
1  3.0  1.0   86  1
2  NaN  NaN   46  1
3  NaN  1.0  212  1
4  NaN  NaN   26  1
5  1.0  NaN    7  1
6  NaN  NaN  184  1
7  NaN  NaN  206  1

In [423]: casted.dtypes
Out[423]: 
A    float64
B    float64
C      int32
E      int64
dtype: object

浮點數 dtype 則不變。

In [424]: dfa = df3.copy()

In [425]: dfa["A"] = dfa["A"].astype("float32")

In [426]: dfa.dtypes
Out[426]: 
A    float32
B    float64
C    float64
dtype: object

In [427]: casted = dfa[df2 > 0]

In [428]: casted
Out[428]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2       NaN       NaN   46.0
3       NaN  1.139976  212.0
4       NaN       NaN   26.0
5  1.346426  0.096706    7.0
6       NaN       NaN  184.0
7       NaN       NaN  206.0

In [429]: casted.dtypes
Out[429]: 
A    float32
B    float64
C    float64
dtype: object

根據 dtype 選取欄#

select_dtypes() 方法根據欄的 dtype 實作子集合。

首先,我們來建立一個具有許多不同 dtype 的 DataFrame

In [430]: df = pd.DataFrame(
   .....:     {
   .....:         "string": list("abc"),
   .....:         "int64": list(range(1, 4)),
   .....:         "uint8": np.arange(3, 6).astype("u1"),
   .....:         "float64": np.arange(4.0, 7.0),
   .....:         "bool1": [True, False, True],
   .....:         "bool2": [False, True, False],
   .....:         "dates": pd.date_range("now", periods=3),
   .....:         "category": pd.Series(list("ABC")).astype("category"),
   .....:     }
   .....: )
   .....: 

In [431]: df["tdeltas"] = df.dates.diff()

In [432]: df["uint64"] = np.arange(3, 6).astype("u8")

In [433]: df["other_dates"] = pd.date_range("20130101", periods=3)

In [434]: df["tz_aware_dates"] = pd.date_range("20130101", periods=3, tz="US/Eastern")

In [435]: df
Out[435]: 
  string  int64  uint8  ...  uint64  other_dates            tz_aware_dates
0      a      1      3  ...       3   2013-01-01 2013-01-01 00:00:00-05:00
1      b      2      4  ...       4   2013-01-02 2013-01-02 00:00:00-05:00
2      c      3      5  ...       5   2013-01-03 2013-01-03 00:00:00-05:00

[3 rows x 12 columns]

以及 dtype

In [436]: df.dtypes
Out[436]: 
string                                object
int64                                  int64
uint8                                  uint8
float64                              float64
bool1                                   bool
bool2                                   bool
dates                         datetime64[ns]
category                            category
tdeltas                      timedelta64[ns]
uint64                                uint64
other_dates                   datetime64[ns]
tz_aware_dates    datetime64[ns, US/Eastern]
dtype: object

select_dtypes() 有兩個參數 includeexclude,讓你可以表示「給我具有這些 dtype 的欄」(include) 和/或「給我沒有這些 dtype 的欄」(exclude)。

例如,選取 bool 欄位

In [437]: df.select_dtypes(include=[bool])
Out[437]: 
   bool1  bool2
0   True  False
1  False   True
2   True  False

您也可以在 NumPy 資料類型階層 中傳遞資料類型的名稱

In [438]: df.select_dtypes(include=["bool"])
Out[438]: 
   bool1  bool2
0   True  False
1  False   True
2   True  False

select_dtypes() 也適用於一般資料類型。

例如,選取所有數字和布林欄位,同時排除無符號整數

In [439]: df.select_dtypes(include=["number", "bool"], exclude=["unsignedinteger"])
Out[439]: 
   int64  float64  bool1  bool2 tdeltas
0      1      4.0   True  False     NaT
1      2      5.0  False   True  1 days
2      3      6.0   True  False  1 days

若要選取字串欄位,您必須使用 object 資料類型

In [440]: df.select_dtypes(include=["object"])
Out[440]: 
  string
0      a
1      b
2      c

若要查看一般 dtype(例如 numpy.number)的所有子資料類型,您可以定義一個傳回子資料類型樹狀結構的函式

In [441]: def subdtypes(dtype):
   .....:     subs = dtype.__subclasses__()
   .....:     if not subs:
   .....:         return dtype
   .....:     return [dtype, [subdtypes(dt) for dt in subs]]
   .....: 

所有 NumPy 資料類型都是 numpy.generic 的子類別

In [442]: subdtypes(np.generic)
Out[442]: 
[numpy.generic,
 [[numpy.number,
   [[numpy.integer,
     [[numpy.signedinteger,
       [numpy.int8,
        numpy.int16,
        numpy.int32,
        numpy.int64,
        numpy.longlong,
        numpy.timedelta64]],
      [numpy.unsignedinteger,
       [numpy.uint8,
        numpy.uint16,
        numpy.uint32,
        numpy.uint64,
        numpy.ulonglong]]]],
    [numpy.inexact,
     [[numpy.floating,
       [numpy.float16, numpy.float32, numpy.float64, numpy.longdouble]],
      [numpy.complexfloating,
       [numpy.complex64, numpy.complex128, numpy.clongdouble]]]]]],
  [numpy.flexible,
   [[numpy.character, [numpy.bytes_, numpy.str_]],
    [numpy.void, [numpy.record]]]],
  numpy.bool_,
  numpy.datetime64,
  numpy.object_]]

注意

pandas 也定義了資料類型 categorydatetime64[ns, tz],這些資料類型未整合到一般的 NumPy 階層中,而且不會顯示在上述函式中。