基本功能#

在这里,我们讨论了许多 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 一致

  • 轴标签
    • 系列*索引*(仅轴)

    • DataFrame: index (行) 和 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 的类型系统以添加对自定义数组的支持(参见 dtypes)。

要获取 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 将始终是一个 ExtensionArray。关于什么是 ExtensionArray 以及为什么 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 没有表示带时区日期时间的 dtype,因此有两种可能有用的表示方法:

  1. 一个对象类型的 numpy.ndarray ,包含 Timestamp 对象,每个对象都有正确的 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),情况将不是这样。values 属性本身,与轴标签不同,不能被赋值。

备注

在处理异构数据时,结果 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 的数组时特别快。

强烈建议您安装这两个库。有关更多安装信息,请参见章节 推荐依赖

这两个默认情况下都是启用的,你可以通过设置选项来控制这一点:

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 中,算术函数有一个输入 fill_value 的选项,即在最多一个位置的值缺失时替换的值。例如,当添加两个 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 有二进制比较方法 eq, ne, lt, gt, le, 和 ge,其行为类似于上述二进制算术操作:

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 对象,其 dtype 为 bool。这些 boolean 对象可以在索引操作中使用,请参见关于 布尔索引 的部分。

布尔归约#

你可以应用这些简化:emptyany()all()

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)

/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 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

/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 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

因此,NDFrames(如 Series 和 DataFrames)有一个 equals() 方法用于测试相等性,相应位置中的 NaNs 被视为相等。

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 /home/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 /home/pandas/pandas/core/arraylike.py:41, in OpsMixin.__eq__(self, other)
     39 @unpack_zerodim_and_defer("__eq__")
     40 def __eq__(self, other):
---> 41     return self._cmp_method(other, operator.eq)

File /home/pandas/pandas/core/series.py:5813, in Series._cmp_method(self, other, op)
   5810 res_name = ops.get_op_result_name(self, other)
   5812 if isinstance(other, Series) and not self._indexed_same(other):
-> 5813     raise ValueError("Can only compare identically-labeled Series objects")
   5815 lvalues = self._values
   5816 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 /home/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 /home/pandas/pandas/core/arraylike.py:41, in OpsMixin.__eq__(self, other)
     39 @unpack_zerodim_and_defer("__eq__")
     40 def __eq__(self, other):
---> 41     return self._cmp_method(other, operator.eq)

File /home/pandas/pandas/core/series.py:5813, in Series._cmp_method(self, other, op)
   5810 res_name = ops.get_op_result_name(self, other)
   5812 if isinstance(other, Series) and not self._indexed_same(other):
-> 5813     raise ValueError("Can only compare identically-labeled Series objects")
   5815 lvalues = self._values
   5816 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(),会产生相同大小的对象。一般来说,这些方法接受一个 axis 参数,就像 ndarray.{sum, std, …} 一样,但可以通过名称或整数指定轴:

  • 系列:不需要轴参数

  • DataFrame:“index”(axis=0,默认),“columns”(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(axis=0)
Out[79]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

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

所有这些方法都有一个 skipna 选项,用于指示是否排除缺失数据(默认为 True):

In [81]: df.sum(axis=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(axis=1), axis=0).div(df.std(axis=1), axis=0)

In [86]: xs_stand.std(axis=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 输入中的 NAs:

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() 函数,它计算一个 Series 或一个 DataFrame 列的各种汇总统计数据(当然不包括 NAs):

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。有关接受的输入的详细信息,请参阅那里。

最小/最大值索引#

idxmin()idxmax() 函数在 Series 和 DataFrame 上计算具有最小和最大对应值的索引标签:

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() Series 方法计算一维数组值的直方图。它也可以作为常规数组上的函数使用:

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

同样地,你可以得到一个 Series 或 DataFrame 中最常出现的值,即众数:

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

离散化和分位数化#

连续值可以使用 :func:`cut`(基于值的箱子)和 :func:`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. Aggregation API: agg()transform()

  4. 逐元素应用函数: map()

表级函数应用#

DataFramesSeries 可以传递给函数。然而,如果函数需要在链中调用,考虑使用 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 是接受并返回 DataFrames 的函数。

现在比较以下内容:

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 首先需要一个公式,然后是一个 DataFrame 作为第二个参数,data。我们将函数、关键字对 (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.
"""

管道方法受到 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 API窗口 API,和 重采样 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 函数将产生一个名为 <lambda> 的行:

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

传递一个命名函数将生成该行的名称:

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

使用字典进行聚合#

将列名的字典传递给标量或标量列表,到 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

Transform 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

使用多个函数进行转换#

传递多个函数将生成一个列多索引的 DataFrame。第一层将是原始帧的列名;第二层将是转换函数的名称。

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 将生成一个 DataFrame。生成的列名将是转换函数的名称。

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

传递一个字典列表将生成一个具有这些选择性变换的多索引 DataFrame。

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 数组并返回另一个数组或值),DataFrame 上的方法 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 中基本的数据对齐方法。它用于实现几乎所有依赖于标签对齐功能的其他特性。reindex 意味着使数据符合沿特定轴的给定标签集。这完成了几件事情:

  • 重新排序现有数据以匹配新的一组标签

  • 在不存在该标签数据的标签位置插入缺失值(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 标签不包含在 Series 中,因此在结果中显示为 NaN

使用 DataFrame,您可以同时重新索引索引和列:

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 对象可以在对象之间 共享。因此,如果我们有一个 Series 和一个 DataFrame,可以执行以下操作:

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

这意味着重新索引的 Series 的索引与 DataFrame 的索引是同一个 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

参见

MultiIndex / 高级索引 是一种更简洁的重新索引方式。

备注

在编写对性能敏感的代码时,花一些时间成为一个重索引忍者是有充分理由的:许多操作在预对齐的数据上更快。将两个未对齐的 DataFrame 相加会内部触发一个重索引步骤。对于探索性分析,你几乎不会注意到差异(因为 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)

对于 DataFrame,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(),你可以选择将两个对象对齐在 DataFrame 的索引或列上,使用 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,这是一个从以下表格中选择的填充方法:

方法

行动

ffill

向前填充值

bfill

向后填充值

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

这些方法要求索引是**有序**的,即递增或递减。

注意,使用 ffill <missing_data.fillna>`(除了 ``method=’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

相比之下,容差指定了索引和索引器值之间的最大距离:

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

重命名 / 映射标签#

The rename() method allows you to relabel an axis based on some mapping (a dict or Series) or an arbitrary function.

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

如果你传递一个函数,它必须在用任何标签调用时返回一个值(并且必须生成一组唯一的值)。也可以使用字典或系列:

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 时,它被视为类数组,基本迭代产生值。DataFrames 遵循类似字典的约定,迭代对象的“键”。

简而言之,基本的迭代 (for i in object) 产生:

  • 系列:值

  • 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 对象,可能会改变数据类型并带来一些性能影响。

  • 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#

与类似字典的接口一致,items() 遍历键值对:

  • 系列:(索引,标量值)对

  • 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() 允许你遍历 DataFrame 的行,作为 Series 对象。它返回一个迭代器,产生每个索引值以及包含每行数据的 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

row 中的所有值,作为一个 Series 返回,现在都被转换为浮点数,包括列 x 中的原始整数值:

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

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

在迭代行时保留数据类型,最好使用 itertuples() ,它返回值的命名元组,并且通常比 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 对象;它只是返回 namedtuple 中的值。因此,itertuples() 保留了值的数据类型,并且通常比 iterrows() 更快。

备注

如果列名是无效的 Python 标识符、重复或以下划线开头,它们将被重命名为位置名称。如果有大量的列(>255),则返回常规元组。

.dt 访问器#

Series 有一个访问器,可以简洁地返回 的类似日期时间的属性,如果它是一个类似日期时间/周期的 Series。这将返回一个 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]

你可以轻松地生成时区感知的转换:

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]: zoneinfo.ZoneInfo(key='US/Eastern')

你也可以将这些类型的操作链接起来:

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 和 timedelta 数据类型。

# 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 对象,该键按级别应用于由 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() 方法用于按其值对 Series 进行排序。DataFrame.sort_values() 方法用于按其列或行值对 DataFrame 进行排序。DataFrame.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 对象,键按列应用,因此键仍应期望一个 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])

最小 / 最大值#

Seriesnsmallest()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 数组和数据类型来表示 Series 或 DataFrame 的单个列。NumPy 提供对 floatintbooltimedelta64[ns]datetime64[ns] 的支持(注意 NumPy 不支持带时区的日期时间)。

pandas 和第三方库在某些地方 扩展 了 NumPy 的类型系统。本节描述了 pandas 内部所做的扩展。请参阅 扩展类型 了解如何编写与 pandas 兼容的扩展。请参阅 生态系统页面 获取已实现扩展的第三方库列表。

下表列出了所有 pandas 扩展类型。对于需要 dtype 参数的方法,可以按指示使用字符串指定。请参阅各自的文档部分以了解更多每种类型的信息。

数据类型

数据类型

Scalar

数组

字符串别名

时区感知的日期时间

DatetimeTZDtype

时间戳

arrays.DatetimeArray

'datetime64[ns, <tz>]'

分类

CategoricalDtype

(无)

Categorical

'category'

周期(时间段)

PeriodDtype

Period

arrays.PeriodArray 'Period[<freq>]'

'period[<freq>]',

稀疏

SparseDtype

(无)

arrays.SparseArray

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

区间

IntervalDtype

Interval

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

'string'

布尔值(含NA)

BooleanDtype

bool

arrays.BooleanArray

'boolean'

pandas 有两种存储字符串的方式。

  1. object dtype,它可以保存任何 Python 对象,包括字符串。

  2. StringDtype,专门用于字符串。

一般来说,我们推荐使用 StringDtype。更多信息请参见 文本数据类型

最后,可以使用 object dtype 存储任意对象,但应尽可能避免(出于性能和与其他库及方法的互操作性考虑。请参见 对象转换)。

一个方便的 dtypes 属性用于 DataFrame 返回一个包含每列数据类型的 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.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]))

upcasting#

类型在与其它类型结合时可能会被 向上转换 ,这意味着它们从当前类型(例如从 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    float32
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    float32
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 dtype 的类型强制转换为其他类型。在数据已经是正确类型但存储在 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[us]
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[us]', freq=None)
    
  • to_timedelta() (转换为 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 应如何处理无法转换为所需 dtype 或对象的元素。默认情况下,errors='raise',这意味着在转换过程中遇到的任何错误都将被引发。然而,如果 errors='coerce',这些错误将被忽略,pandas 会将有问题的元素转换为 ``pd.NaT``(对于 datetime 和 timedelta)或 ``np.nan``(对于数值)。如果你读取的数据大部分是所需 dtype(例如数值、datetime),但偶尔会有不符合要求的元素混入,你希望将其表示为缺失值,这可能会有用:

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[us]', 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,它提供了将新(或已有的)数值数据向下转换为较小的 dtype 的选项,这样可以节省内存:

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)

由于这些方法仅适用于一维数组、列表或标量;它们不能直接用于多维对象,如 DataFrame。然而,通过 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

gotchas#

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

虽然浮点数类型保持不变。

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

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

In [426]: dfa.dtypes
Out[426]: 
A    float32
B    float64
C    float32
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    float32
dtype: object

基于 dtype 选择列#

select_dtypes() 方法实现了基于列的 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  float64  bool1  bool2                      dates category tdeltas  uint64 other_dates            tz_aware_dates
0      a      1      3      4.0   True  False 2024-08-26 03:53:46.285506        A     NaT       3  2013-01-01 2013-01-01 00:00:00-05:00
1      b      2      4      5.0  False   True 2024-08-27 03:53:46.285506        B  1 days       4  2013-01-02 2013-01-02 00:00:00-05:00
2      c      3      5      6.0   True  False 2024-08-28 03:53:46.285506        C  1 days       5  2013-01-03 2013-01-03 00:00:00-05:00

以及数据类型:

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,它们允许你说“给我带有这些数据类型的列”(include)和/或“给我不带有这些数据类型的列”(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 dtype:

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

要查看一个通用 dtypenumpy.number 的所有子 dtypes,你可以定义一个返回子 dtypes 树的函数:

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 层次结构中,并且不会通过上述函数显示出来。