常見問題集 (FAQ)#
DataFrame 記憶體使用量#
呼叫 DataFrame
(包含索引)的記憶體使用量時,會顯示 info()
。設定選項 display.memory_usage
(請參閱 選項清單),指定呼叫 info()
方法時,是否顯示 DataFrame
記憶體使用量。
例如,呼叫 info()
時,會顯示以下 DataFrame
的記憶體使用量:
In [1]: dtypes = [
...: "int64",
...: "float64",
...: "datetime64[ns]",
...: "timedelta64[ns]",
...: "complex128",
...: "object",
...: "bool",
...: ]
...:
In [2]: n = 5000
In [3]: data = {t: np.random.randint(100, size=n).astype(t) for t in dtypes}
In [4]: df = pd.DataFrame(data)
In [5]: df["categorical"] = df["object"].astype("category")
In [6]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 int64 5000 non-null int64
1 float64 5000 non-null float64
2 datetime64[ns] 5000 non-null datetime64[ns]
3 timedelta64[ns] 5000 non-null timedelta64[ns]
4 complex128 5000 non-null complex128
5 object 5000 non-null object
6 bool 5000 non-null bool
7 categorical 5000 non-null category
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 288.2+ KB
+
符號表示實際記憶體使用量可能會更高,因為 pandas 沒有計算 dtype=object
欄位中值的記憶體使用量。
傳遞 memory_usage='deep'
將啟用更精確的記憶體使用率報告,說明所包含物件的完整使用率。這是可選的,因為執行此更深入的內省可能會很昂貴。
In [7]: df.info(memory_usage="deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 int64 5000 non-null int64
1 float64 5000 non-null float64
2 datetime64[ns] 5000 non-null datetime64[ns]
3 timedelta64[ns] 5000 non-null timedelta64[ns]
4 complex128 5000 non-null complex128
5 object 5000 non-null object
6 bool 5000 non-null bool
7 categorical 5000 non-null category
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 424.7 KB
預設顯示選項設定為 True
,但可以在呼叫 info()
時傳遞 memory_usage
參數時明確覆寫。
可以透過呼叫 memory_usage()
方法來找出每一個欄位的記憶體使用率。這會傳回一個 Series
,其索引由欄位名稱表示,並以位元組顯示每一個欄位的記憶體使用率。對於上述 DataFrame
,可以使用 memory_usage()
方法找出每一個欄位的記憶體使用率和總記憶體使用率
In [8]: df.memory_usage()
Out[8]:
Index 128
int64 40000
float64 40000
datetime64[ns] 40000
timedelta64[ns] 40000
complex128 80000
object 40000
bool 5000
categorical 9968
dtype: int64
# total memory usage of dataframe
In [9]: df.memory_usage().sum()
Out[9]: 295096
預設會在傳回的 Series
中顯示 DataFrame
索引的記憶體使用率,可以透過傳遞 index=False
參數來抑制索引的記憶體使用率
In [10]: df.memory_usage(index=False)
Out[10]:
int64 40000
float64 40000
datetime64[ns] 40000
timedelta64[ns] 40000
complex128 80000
object 40000
bool 5000
categorical 9968
dtype: int64
由 info()
方法顯示的記憶體使用量使用 memory_usage()
方法來確定 DataFrame
的記憶體使用量,同時也以人類可讀的單位(2 進位制表示法;即 1KB = 1024 位元組)來格式化輸出。
另請參閱 類別記憶體使用量。
將 if/真值陳述式與 pandas 一起使用#
當您嘗試將某個東西轉換為 bool
時,pandas 會遵循 NumPy 慣例,會產生錯誤。這會在 if
陳述式中或使用布林運算時發生:and
、or
和 not
。下列程式碼的結果為何並不清楚
>>> if pd.Series([False, True, False]):
... pass
它應該是 True
,因為它不是零長度,還是 False
,因為有 False
值?這並不清楚,因此 pandas 會產生 ValueError
In [11]: if pd.Series([False, True, False]):
....: print("I was true")
....:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-11-5c782b38cd2f> in ?()
----> 1 if pd.Series([False, True, False]):
2 print("I was 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 Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
您需要明確選擇您想對 DataFrame
執行的動作,例如使用 any()
、all()
或 empty()
。或者,您可能想比較 pandas 物件是否為 None
In [12]: if pd.Series([False, True, False]) is not None:
....: print("I was not None")
....:
I was not None
以下是檢查任何值是否為 True
的方法
In [13]: if pd.Series([False, True, False]).any():
....: print("I am any")
....:
I am any
位元布林#
位元布林運算子(如 ==
和 !=
)會傳回布林 Series
,與標量比較時會執行逐元素比較。
In [14]: s = pd.Series(range(5))
In [15]: s == 4
Out[15]:
0 False
1 False
2 False
3 False
4 True
dtype: bool
請參閱 布林比較 以取得更多範例。
使用 in
運算子#
在 Series
上使用 Python in
運算子會測試索引中的成員資格,而非值中的成員資格。
In [16]: s = pd.Series(range(5), index=list("abcde"))
In [17]: 2 in s
Out[17]: False
In [18]: 'b' in s
Out[18]: True
如果這種行為令人驚訝,請記住,在 Python 字典中使用 in
會測試鍵,而不是值,而 Series
類似於字典。若要測試值中的成員資格,請使用 isin()
方法。
In [19]: s.isin([2])
Out[19]:
a False
b False
c True
d False
e False
dtype: bool
In [20]: s.isin([2]).any()
Out[20]: True
對於 DataFrame
,同樣地,in
適用於欄位軸,測試欄位名稱清單中的成員資格。
使用使用者定義函數 (UDF) 方法進行變異#
本節適用於採用 UDF 的 pandas 方法。特別是 DataFrame.apply()
、DataFrame.aggregate()
、DataFrame.transform()
和 DataFrame.filter()
方法。
在程式設計中,一般規則是不應在迭代容器時變異它。變異會使迭代器失效,導致意外的行為。請考慮以下範例
In [21]: values = [0, 1, 2, 3, 4, 5]
In [22]: n_removed = 0
In [23]: for k, value in enumerate(values):
....: idx = k - n_removed
....: if value % 2 == 1:
....: del values[idx]
....: n_removed += 1
....: else:
....: values[idx] = value + 1
....:
In [24]: values
Out[24]: [1, 4, 5]
可能會預期結果為 [1, 3, 5]
。在使用採用 UDF 的 pandas 方法時,pandas 內部通常會在 DataFrame
或其他 pandas 物件上進行反覆運算。因此,如果 UDF 會改變 DataFrame
(變更),就會產生預期之外的行為。
以下是使用 DataFrame.apply()
的類似範例
In [25]: def f(s):
....: s.pop("a")
....: return s
....:
In [26]: df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
In [27]: df.apply(f, axis="columns")
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3805, in Index.get_loc(self, key)
3804 try:
-> 3805 return self._engine.get_loc(casted_key)
3806 except KeyError as err:
File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()
File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()
File pandas/_libs/hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()
File pandas/_libs/hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'a'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
Cell In[27], line 1
----> 1 df.apply(f, axis="columns")
File ~/work/pandas/pandas/pandas/core/frame.py:10361, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
10347 from pandas.core.apply import frame_apply
10349 op = frame_apply(
10350 self,
10351 func=func,
(...)
10359 kwargs=kwargs,
10360 )
> 10361 return op.apply().__finalize__(self, method="apply")
File ~/work/pandas/pandas/pandas/core/apply.py:916, in FrameApply.apply(self)
913 elif self.raw:
914 return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
--> 916 return self.apply_standard()
File ~/work/pandas/pandas/pandas/core/apply.py:1063, in FrameApply.apply_standard(self)
1061 def apply_standard(self):
1062 if self.engine == "python":
-> 1063 results, res_index = self.apply_series_generator()
1064 else:
1065 results, res_index = self.apply_series_numba()
File ~/work/pandas/pandas/pandas/core/apply.py:1081, in FrameApply.apply_series_generator(self)
1078 with option_context("mode.chained_assignment", None):
1079 for i, v in enumerate(series_gen):
1080 # ignore SettingWithCopy here in case the user mutates
-> 1081 results[i] = self.func(v, *self.args, **self.kwargs)
1082 if isinstance(results[i], ABCSeries):
1083 # If we have a view on v, we need to make a copy because
1084 # series_generator will swap out the underlying data
1085 results[i] = results[i].copy(deep=False)
Cell In[25], line 2, in f(s)
1 def f(s):
----> 2 s.pop("a")
3 return s
File ~/work/pandas/pandas/pandas/core/series.py:5382, in Series.pop(self, item)
5357 def pop(self, item: Hashable) -> Any:
5358 """
5359 Return item and drops from series. Raise KeyError if not found.
5360
(...)
5380 dtype: int64
5381 """
-> 5382 return super().pop(item=item)
File ~/work/pandas/pandas/pandas/core/generic.py:946, in NDFrame.pop(self, item)
945 def pop(self, item: Hashable) -> Series | Any:
--> 946 result = self[item]
947 del self[item]
949 return result
File ~/work/pandas/pandas/pandas/core/series.py:1112, in Series.__getitem__(self, key)
1109 return self._values[key]
1111 elif key_is_scalar:
-> 1112 return self._get_value(key)
1114 # Convert generator to list before going through hashable part
1115 # (We will iterate through the generator there to check for slices)
1116 if is_iterator(key):
File ~/work/pandas/pandas/pandas/core/series.py:1228, in Series._get_value(self, label, takeable)
1225 return self._values[label]
1227 # Similar to Index.get_value, but we do not fall back to positional
-> 1228 loc = self.index.get_loc(label)
1230 if is_integer(loc):
1231 return self._values[loc]
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3812, in Index.get_loc(self, key)
3807 if isinstance(casted_key, slice) or (
3808 isinstance(casted_key, abc.Iterable)
3809 and any(isinstance(x, slice) for x in casted_key)
3810 ):
3811 raise InvalidIndexError(key)
-> 3812 raise KeyError(key) from err
3813 except TypeError:
3814 # If we have a listlike key, _check_indexing_error will raise
3815 # InvalidIndexError. Otherwise we fall through and re-raise
3816 # the TypeError.
3817 self._check_indexing_error(key)
KeyError: 'a'
若要解決這個問題,可以建立一份副本,這樣變更就不會套用在反覆運算的容器上。
In [28]: values = [0, 1, 2, 3, 4, 5]
In [29]: n_removed = 0
In [30]: for k, value in enumerate(values.copy()):
....: idx = k - n_removed
....: if value % 2 == 1:
....: del values[idx]
....: n_removed += 1
....: else:
....: values[idx] = value + 1
....:
In [31]: values
Out[31]: [1, 3, 5]
In [32]: def f(s):
....: s = s.copy()
....: s.pop("a")
....: return s
....:
In [33]: df = pd.DataFrame({"a": [1, 2, 3], 'b': [4, 5, 6]})
In [34]: df.apply(f, axis="columns")
Out[34]:
b
0 4
1 5
2 6
NumPy 型別的遺失值表示#
np.nan
作為 NumPy 型別的 NA
表示#
由於 NumPy 和 Python 本身從一開始就缺乏 NA
(遺失)支援,因此 NA
可以用下列方式表示
遮罩陣列解決方案:資料陣列和布林值陣列,用來指出值是否存在或遺失。
使用特殊哨兵值、位元模式或一組哨兵值,來表示不同 dtypes 中的
NA
。
特殊值 np.nan
(非數字)被選為 NumPy 類型的 NA
值,而且有 API 函式,例如 DataFrame.isna()
和 DataFrame.notna()
,可用於不同 dtypes 中偵測 NA 值。然而,這個選擇的缺點是會將遺失的整數資料強制轉換為浮點類型,如 支援整數 NA 中所示。
NA
類型提升,適用於 NumPy 類型#
當透過 Series
或 DataFrame
中的 reindex()
或其他方法,將 NA 引入現有的 Series
或 DataFrame
時,布林值和整數類型會提升為不同的 dtype,以儲存 NA。提升的內容總結在這個表格中
類型類別 |
用於儲存 NA 的提升 dtype |
---|---|
|
不變 |
|
不變 |
|
轉換為 |
|
轉換為 |
支援整數 NA
#
由於 NumPy 從頭開始建置的效能優異 NA
支援缺席,主要損失是無法在整數陣列中表示 NA。例如
In [35]: s = pd.Series([1, 2, 3, 4, 5], index=list("abcde"))
In [36]: s
Out[36]:
a 1
b 2
c 3
d 4
e 5
dtype: int64
In [37]: s.dtype
Out[37]: dtype('int64')
In [38]: s2 = s.reindex(["a", "b", "c", "f", "u"])
In [39]: s2
Out[39]:
a 1.0
b 2.0
c 3.0
f NaN
u NaN
dtype: float64
In [40]: s2.dtype
Out[40]: dtype('float64')
此權衡取捨主要是出於記憶體和效能考量,也讓產生的 Series
繼續保持「數字」。
如果您需要表示可能遺失值的整數,請使用 pandas 或 pyarrow 提供的可為空整數擴充資料類型之一
In [41]: s_int = pd.Series([1, 2, 3, 4, 5], index=list("abcde"), dtype=pd.Int64Dtype())
In [42]: s_int
Out[42]:
a 1
b 2
c 3
d 4
e 5
dtype: Int64
In [43]: s_int.dtype
Out[43]: Int64Dtype()
In [44]: s2_int = s_int.reindex(["a", "b", "c", "f", "u"])
In [45]: s2_int
Out[45]:
a 1
b 2
c 3
f <NA>
u <NA>
dtype: Int64
In [46]: s2_int.dtype
Out[46]: Int64Dtype()
In [47]: s_int_pa = pd.Series([1, 2, None], dtype="int64[pyarrow]")
In [48]: s_int_pa
Out[48]:
0 1
1 2
2 <NA>
dtype: int64[pyarrow]
請參閱 可為空整數資料類型 和 PyArrow 功能 以取得更多資訊。
為何不讓 NumPy 像 R?#
許多人建議 NumPy 應僅模擬更特定於領域的統計程式語言 R 中存在的 NA
支援。部分原因是 NumPy 類型階層
類型類別 |
資料類型 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
相較之下,R 語言只有少數內建資料類型:integer
、numeric
(浮點數)、character
和 boolean
。NA
類型是透過保留特殊位元模式來實作,作為每個類型的遺失值。雖然使用完整的 NumPy 類型階層來執行此動作是可行的,但這將會是一個更重大的權衡(特別是對於 8 位元和 16 位元資料類型)和實作承諾。
不過,現在可以使用遮罩 NumPy 類型(例如 Int64Dtype
或 PyArrow 類型(ArrowDtype
))來取得 R NA
語意。
與 NumPy 的差異#
對於 Series
和 DataFrame
物件,var()
以 N-1
標準化以產生 總體變異數的無偏估計,而 NumPy 的 numpy.var()
以 N 標準化,用以測量樣本的變異數。請注意,cov()
在 pandas 和 NumPy 中都以 N-1
標準化。
執行緒安全性#
pandas 並非 100% 執行緒安全。已知問題與 copy()
方法有關。如果您大量複製在執行緒間共用的 DataFrame
物件,我們建議在執行資料複製的執行緒內保留鎖定。
請參閱 此連結 以取得更多資訊。
位元組順序問題#
偶爾您可能必須處理在位元組順序與執行 Python 的機器不同的機器上建立的資料。此問題的常見症狀是類似下列的錯誤
Traceback
...
ValueError: Big-endian buffer not supported on little-endian compiler
若要處理此問題,您應該在將基礎 NumPy 陣列傳遞給 Series
或 DataFrame
建構函式之前,將其轉換為原生系統位元組順序,方法類似於下列
In [49]: x = np.array(list(range(10)), ">i4") # big endian
In [50]: newx = x.byteswap().view(x.dtype.newbyteorder()) # force native byteorder
In [51]: s = pd.Series(newx)
請參閱 NumPy 位元組順序文件,以取得更多詳細資訊。