常见问题 (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.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.6 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         5800
dtype: int64

# total memory usage of dataframe
In [9]: df.memory_usage().sum()
Out[9]: 290928

默认情况下,DataFrame 索引的内存使用情况会显示在返回的 Series 中,可以通过传递 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字节)。

另请参阅 分类内存使用

在 pandas 中使用 if/truth 语句#

pandas 遵循 NumPy 的惯例,当你尝试将某些东西转换为 bool 时会引发一个错误。这发生在 if 语句中或使用布尔运算时:andornot。不清楚以下代码的结果应该是什么:

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

/home/pandas/pandas/core/generic.py in ?(self)
   1494     @final
   1495     def __bool__(self) -> NoReturn:
-> 1496         raise ValueError(
   1497             f"The truth value of a {type(self).__name__} is ambiguous. "
   1498             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1499         )

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 /home/pandas/pandas/core/indexes/base.py:3578, in Index.get_loc(self, key)
   3577 try:
-> 3578     return self._engine.get_loc(casted_key)
   3579 except KeyError as err:

File index.pyx:168, in pandas._libs.index.IndexEngine.get_loc()

File 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 /home/pandas/pandas/core/frame.py:10389, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
  10375 from pandas.core.apply import frame_apply
  10377 op = frame_apply(
  10378     self,
  10379     func=func,
   (...)
  10387     kwargs=kwargs,
  10388 )
> 10389 return op.apply().__finalize__(self, method="apply")

File /home/pandas/pandas/core/apply.py:910, in FrameApply.apply(self)
    907 elif self.raw:
    908     return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
--> 910 return self.apply_standard()

File /home/pandas/pandas/core/apply.py:1061, in FrameApply.apply_standard(self)
   1059 def apply_standard(self):
   1060     if self.engine == "python":
-> 1061         results, res_index = self.apply_series_generator()
   1062     else:
   1063         results, res_index = self.apply_series_numba()

File /home/pandas/pandas/core/apply.py:1077, in FrameApply.apply_series_generator(self)
   1074 results = {}
   1076 for i, v in enumerate(series_gen):
-> 1077     results[i] = self.func(v, *self.args, **self.kwargs)
   1078     if isinstance(results[i], ABCSeries):
   1079         # If we have a view on v, we need to make a copy because
   1080         #  series_generator will swap out the underlying data
   1081         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 /home/pandas/pandas/core/series.py:5086, in Series.pop(self, item)
   5055 def pop(self, item: Hashable) -> Any:
   5056     """
   5057     Return item and drops from series. Raise KeyError if not found.
   5058 
   (...)
   5084     dtype: int64
   5085     """
-> 5086     return super().pop(item=item)

File /home/pandas/pandas/core/generic.py:835, in NDFrame.pop(self, item)
    834 def pop(self, item: Hashable) -> Series | Any:
--> 835     result = self[item]
    836     del self[item]
    838     return result

File /home/pandas/pandas/core/series.py:903, in Series.__getitem__(self, key)
    898     key = unpack_1tuple(key)
    900 elif key_is_scalar:
    901     # Note: GH#50617 in 3.0 we changed int key to always be treated as
    902     #  a label, matching DataFrame behavior.
--> 903     return self._get_value(key)
    905 # Convert generator to list before going through hashable part
    906 # (We will iterate through the generator there to check for slices)
    907 if is_iterator(key):

File /home/pandas/pandas/core/series.py:990, in Series._get_value(self, label, takeable)
    987     return self._values[label]
    989 # Similar to Index.get_value, but we do not fall back to positional
--> 990 loc = self.index.get_loc(label)
    992 if is_integer(loc):
    993     return self._values[loc]

File /home/pandas/pandas/core/indexes/base.py:3585, in Index.get_loc(self, key)
   3580     if isinstance(casted_key, slice) or (
   3581         isinstance(casted_key, abc.Iterable)
   3582         and any(isinstance(x, slice) for x in casted_key)
   3583     ):
   3584         raise InvalidIndexError(key) from err
-> 3585     raise KeyError(key) from err
   3586 except TypeError:
   3587     # If we have a listlike key, _check_indexing_error will raise
   3588     #  InvalidIndexError. Otherwise we fall through and re-raise
   3589     #  the TypeError.
   3590     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 可以用以下方式表示:

  • 一个 掩码数组 解决方案:一个数据数组和一个布尔值数组,指示一个值是否存在或缺失。

  • 使用一个特殊的哨兵值、位模式或一组哨兵值来表示跨数据类型的 NA

特殊值 np.nan (Not-A-Number) 被选为 NumPy 类型的 NA 值,并且有一些 API 函数如 DataFrame.isna()DataFrame.notna() 可以在不同的数据类型中用于检测 NA 值。然而,这种选择的一个缺点是将缺失的整数数据强制转换为浮点类型,如 对整数 NA 的支持 所示。

NA 类型提升为 NumPy 类型#

当通过 reindex() 或其他方式将 NA 引入现有的 SeriesDataFrame 时,布尔和整数类型将被提升为不同的 dtype 以存储 NA。提升总结在此表中:

Typeclass

用于存储 NAs 的 dtype 推广

floating

no change

object

no change

integer

转换为 float64

boolean

转换为 object

对整数 NA 的支持#

在没有从底层内置高性能 NA 支持的情况下,主要受害者是无法在整数数组中表示 NAs。例如:

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语言只有少数几种内置数据类型:integernumeric``(浮点数)、``characterbooleanNA 类型是通过为每种类型保留特殊的位模式来实现的,用作缺失值。虽然使用完整的NumPy类型层次结构来实现这一点是可能的,但这将是一个更大的权衡(尤其是对于8位和16位数据类型)和实现工作。

然而,R NA 语义现在可以通过使用掩码的 NumPy 类型(如 Int64Dtype)或 PyArrow 类型(ArrowDtype)来实现。

与 NumPy 的区别#

对于 SeriesDataFrame 对象,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 数组传递给 SeriesDataFrame 构造函数之前,将其转换为本地系统字节顺序,使用类似以下的方法:

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 关于字节顺序的文档