常见问题解答 (FAQ)#
DataFrame 内存使用情况#
调用 DataFrame 的 info() 方法时会显示 DataFrame(包括索引)的内存使用情况。配置选项 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.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: 284.1+ KB
符号 + 表示实际内存使用可能更高,因为 pandas 不计算 dtype=object 的列中值的内存使用情况。
传递 memory_usage='deep' 将启用更准确的内存使用报告,该报告会考虑所包含对象的完整使用情况。这是可选的,因为进行这种更深入的内省可能会很耗时。
In [7]: df.info(memory_usage="deep")
<class 'pandas.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: 420.8 KB
默认情况下,显示选项设置为 True,但可以通过在调用 info() 时传递 memory_usage 参数来显式覆盖。
可以通过调用 memory_usage() 方法找到每列的内存使用情况。这将返回一个 Series,其中索引由列名表示,每列的内存使用情况以字节为单位显示。对于上面的 DataFrame,可以使用 memory_usage() 方法找到每列的内存使用情况以及总内存使用情况。
In [8]: df.memory_usage()
Out[8]:
Index 132
int64 40000
float64 40000
datetime64[ns] 40000
timedelta64[ns] 40000
complex128 80000
object 40000
bool 5000
categorical 5800
dtype: int64
# total memory usage of dataframe
In [9]: df.memory_usage().sum()
Out[9]: np.int64(290932)
默认情况下,返回的 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 5800
dtype: int64
info() 方法显示的内存使用情况利用 memory_usage() 方法来确定 DataFrame 的内存使用情况,同时还会将输出格式化为人类可读的单位(基于 2 的表示法;即 1KB = 1024 字节)。
另请参阅 Categorical 内存使用情况。
在 pandas 中使用 if/truth 语句#
pandas 遵循 NumPy 的约定,在尝试将某些内容转换为 bool 时会引发错误。这会在 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)
1511 @final
1512 def __bool__(self) -> NoReturn:
-> 1513 raise ValueError(
1514 f"The truth value of a {type(self).__name__} is ambiguous. "
1515 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1516 )
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,该 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]: np.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:3641, in Index.get_loc(self, key)
3640 try:
-> 3641 return self._engine.get_loc(casted_key)
3642 except KeyError as err:
File ~/work/pandas/pandas/pandas/_libs/index.pyx:168, in pandas._libs.index.IndexEngine.get_loc()
File ~/work/pandas/pandas/pandas/_libs/index.pyx:197, in pandas._libs.index.IndexEngine.get_loc()
File pandas/_libs/hashtable_class_helper.pxi:7668, in pandas._libs.hashtable.PyObjectHashTable.get_item()
File pandas/_libs/hashtable_class_helper.pxi:7676, 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:12419, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
12405 raise ValueError(f"Unknown engine '{engine}'")
12407 op = frame_apply(
12408 self,
12409 func=func,
(...) 12417 kwargs=kwargs,
12418 )
> 12419 return op.apply().__finalize__(self, method="apply")
12420 elif hasattr(engine, "__pandas_udf__"):
12421 if result_type is not None:
File ~/work/pandas/pandas/pandas/core/apply.py:1015, in FrameApply.apply(self)
1012 elif self.raw:
1013 return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
-> 1015 return self.apply_standard()
File ~/work/pandas/pandas/pandas/core/apply.py:1167, in FrameApply.apply_standard(self)
1165 def apply_standard(self):
1166 if self.engine == "python":
-> 1167 results, res_index = self.apply_series_generator()
1168 else:
1169 results, res_index = self.apply_series_numba()
File ~/work/pandas/pandas/pandas/core/apply.py:1183, in FrameApply.apply_series_generator(self)
1180 results = {}
1182 for i, v in enumerate(series_gen):
-> 1183 results[i] = self.func(v, *self.args, **self.kwargs)
1184 if isinstance(results[i], ABCSeries):
1185 # If we have a view on v, we need to make a copy because
1186 # series_generator will swap out the underlying data
1187 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:5821, in Series.pop(self, item)
5790 def pop(self, item: Hashable) -> Any:
5791 """
5792 Return item and drops from series. Raise KeyError if not found.
5793
(...) 5819 dtype: int64
5820 """
-> 5821 return maybe_unbox_numpy_scalar(super().pop(item=item))
File ~/work/pandas/pandas/pandas/core/generic.py:839, in NDFrame.pop(self, item)
838 def pop(self, item: Hashable) -> Series | Any:
--> 839 result = self[item]
840 del self[item]
842 return result
File ~/work/pandas/pandas/pandas/core/series.py:959, in Series.__getitem__(self, key)
954 key = unpack_1tuple(key)
956 elif key_is_scalar:
957 # Note: GH#50617 in 3.0 we changed int key to always be treated as
958 # a label, matching DataFrame behavior.
--> 959 return self._get_value(key)
961 # Convert generator to list before going through hashable part
962 # (We will iterate through the generator there to check for slices)
963 if is_iterator(key):
File ~/work/pandas/pandas/pandas/core/series.py:1046, in Series._get_value(self, label, takeable)
1043 return self._values[label]
1045 # Similar to Index.get_value, but we do not fall back to positional
-> 1046 loc = self.index.get_loc(label)
1048 if is_integer(loc):
1049 return self._values[loc]
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3648, in Index.get_loc(self, key)
3643 if isinstance(casted_key, slice) or (
3644 isinstance(casted_key, abc.Iterable)
3645 and any(isinstance(x, slice) for x in casted_key)
3646 ):
3647 raise InvalidIndexError(key) from err
-> 3648 raise KeyError(key) from err
3649 except TypeError:
3650 # If we have a listlike key, _check_indexing_error will raise
3651 # InvalidIndexError. Otherwise we fall through and re-raise
3652 # the TypeError.
3653 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 类型的缺失值表示#
对于 NumPy 类型的 NA 表示,使用 np.nan#
由于 NumPy 和 Python 总体上从根本上不支持 NA(缺失),NA 可以表示为
一种掩码数组解决方案:一个数据数组和一个布尔值数组,指示某个值是否存在或缺失。
使用特殊的哨兵值、位模式或一组哨兵值来表示跨数据类型的
NA。
选择特殊值 np.nan(非数字)作为 NumPy 类型的 NA 值,并且有 API 函数,如 DataFrame.isna() 和 DataFrame.notna(),可以在跨数据类型使用这些函数来检测 NA 值。然而,这个选择有一个缺点,就是将缺失的整数数据强制转换为浮点类型,如 整数 NA 支持 中所示。
NumPy 类型的 NA 类型提升#
通过 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 提供的可空整数扩展 dtype 之一。
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
要解决此问题,您应该在使用 Series 或 DataFrame 构造函数之前,使用类似以下方法将底层 NumPy 数组转换为本地系统字节序:
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 关于字节序的文档。