7.6 Pandas 中的数据操作
原文:Operating on Data in Pandas
译者:飞龙
本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。
NumPy 的一个重要部分是能够执行快速的逐元素运算,包括基本算术(加法,减法,乘法等),和更复杂的运算(三角函数,指数函数和对数函数等)。Pandas 从 NumPy 继承了大部分功能,我们在“NumPy 数组上的计算:通用函数”中介绍的ufunc
对此至关重要。
Pandas 包含一些有用的调整,但是:对于一元操作,如取负和三角函数,这些ufunc
将保留输出中的索引和列标签,对于二元操作,如加法和乘法,将对象传递给ufunc
时,Pandas 将自动对齐索引。这意味着,保留数据的上下文并组合来自不同来源的数据 - 这两个在原始的 NumPy 数组中可能容易出错的任务 - 对于 Pandas 来说基本上是万无一失的。我们还将看到,在一维Series
结构和二维DataFrame
结构之间有明确定义的操作。
通用函数:索引保留
因为 Pandas 为兼容 NumPy 而设计,所以任何 NumPy ufunc
都可以用于 Pandas Series
和DataFrame
对象。让我们首先定义一个简单的Series
和DataFrame
来演示它:
import pandas as pd
import numpy as np
rng = np.random.RandomState(42)
ser = pd.Series(rng.randint(0, 10, 4))
ser
'''
0 6
1 3
2 7
3 4
dtype: int64
'''
df = pd.DataFrame(rng.randint(0, 10, (3, 4)),
columns=['A', 'B', 'C', 'D'])
df
A | B | C | D | |
0 | 6 | 9 | 2 | 6 |
1 | 7 | 4 | 3 | 7 |
2 | 7 | 2 | 5 | 4 |
如果我们在这些对象中的任何一个上应用 NumPy ufunc
,结果将是保留索引的另一个 Pandas 对象:
np.exp(ser)
'''
0 403.428793
1 20.085537
2 1096.633158
3 54.598150
dtype: float64
'''
或者,对于稍微复杂的计算:
np.sin(df * np.pi / 4)
A | B | C | D | |
0 | -1.000000 | 7.071068e-01 | 1.000000 | -1.000000e+00 |
1 | -0.707107 | 1.224647e-16 | 0.707107 | -7.071068e-01 |
2 | -0.707107 | 1.000000e+00 | -0.707107 | 1.224647e-16 |
“NumPy 数组上的计算:通用函数”中讨论的任何ufunc
都可以以类似的方式使用。
通用函数:索引对齐
对于两个Series
或DataFrame
对象的二元操作,Pandas 将在执行操作的过程中对齐索引。这在处理不完整数据时非常方便,我们将在后面的一些示例中看到。
序列中的索引对齐
例如,假设我们正在组合两个不同的数据源,并且按照面积,找到美国前三的州,并且按人口找到美国前三的州:
area = pd.Series({'Alaska': 1723337, 'Texas': 695662,
'California': 423967}, name='area')
population = pd.Series({'California': 38332521, 'Texas': 26448193,
'New York': 19651127}, name='population')
让我们看看,当我们将这些分开以计算人口密度时会发生什么:
population / area
'''
Alaska NaN
California 90.413926
New York NaN
Texas 38.018740
dtype: float64
'''
所得数组包含两个输入数组的索引的并集,可以使用这些索引上的标准 Python 集合算法来确定:
area.index | population.index
# Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')
任何没有条目的项目都标为NaN
(非数字),这就是 Pandas 标记缺失数据的方式(请在“处理缺失数据”中参阅缺失数据的进一步讨论)。对于 Python 的任何内置算术表达式,索引匹配是以这种方式实现的;默认情况下,任何缺失值都使用NaN
填充:
A = pd.Series([2, 4, 6], index=[0, 1, 2])
B = pd.Series([1, 3, 5], index=[1, 2, 3])
A + B
'''
0 NaN
1 5.0
2 9.0
3 NaN
dtype: float64
'''
如果使用NaN
值不是所需的行为,则可以使用适当的对象方法代替运算符来修改填充值。例如,调用A.add(B)
相当于调用A + B
,但对于A
或``B`中的任何可能会缺失的元素,可以显式指定的填充值:
A.add(B, fill_value=0)
'''
0 2.0
1 5.0
2 9.0
3 5.0
dtype: float64
'''
数据帧中的索引对齐
在DataFrame
s上执行操作时,列和索引都会发生类似的对齐:
A = pd.DataFrame(rng.randint(0, 20, (2, 2)),
columns=list('AB'))
A
A | B | |
0 | 1 | 11 |
1 | 5 | 1 |
B = pd.DataFrame(rng.randint(0, 10, (3, 3)),
columns=list('BAC'))
B
B | A | C | |
0 | 4 | 0 | 9 |
1 | 5 | 8 | 0 |
2 | 9 | 2 | 6 |
A + B
A | B | C | |
0 | 1.0 | 15.0 | NaN |
1 | 13.0 | 6.0 | NaN |
2 | NaN | NaN | NaN |
请注意,索引是正确对齐的,无论它们在两个对象中的顺序如何,并且结果中的索引都是有序的。与Series
的情况一样,我们可以使用相关对象的算术方法,并传递任何所需的fill_value
来替代缺失的条目。这里我们将填充A
中所有值的均值(通过首先堆叠A
的行来计算):
fill = A.stack().mean()
A.add(B, fill_value=fill)
A | B | C | |
0 | 1.0 | 15.0 | 13.5 |
1 | 13.0 | 6.0 | 4.5 |
2 | 6.5 | 13.5 | 10.5 |
下表列出了 Python 运算符及其等效的 Pandas 对象方法:
Python 运算符 | Pandas 方法 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
通用函数:数据帧和序列之间的操作
执行DataFrame
和Series
之间的操作时,与之相似,索引和列是保持对齐的。DataFrame
和Series
之间的操作,类似于二维和一维 NumPy 数组之间的操作。考虑一个常见的操作,我们计算二维数组与其中一行的差:
A = rng.randint(10, size=(3, 4))
A
'''
array([[3, 8, 2, 4],
[2, 6, 4, 8],
[6, 1, 3, 8]])
'''
A - A[0]
'''
array([[ 0, 0, 0, 0],
[-1, -2, 2, 4],
[ 3, -7, 1, 4]])
'''
根据 NumPy 的广播规则(参见“数据计算:广播”),二维数组与其中一行之间的减法是逐行应用的。
在 Pandas 中,按照惯例,默认情况下逐行操作:
df = pd.DataFrame(A, columns=list('QRST'))
df - df.iloc[0]
Q | R | S | T | |
0 | 0 | 0 | 0 | 0 |
1 | -1 | -2 | 2 | 4 |
2 | 3 | -7 | 1 | 4 |
如果你希望逐列操作,则可以使用前面提到的对象方法,同时指定axis
关键字:
df.subtract(df['R'], axis=0)
Q | R | S | T | |
0 | -5 | 0 | -6 | -4 |
1 | -4 | 0 | -2 | 2 |
2 | 5 | 0 | 2 | 7 |
请注意,这些DataFrame
或Series
操作,如上面讨论的操作,将自动对齐两个元素之间的索引:
halfrow = df.iloc[0, ::2]
halfrow
'''
Q 3
S 2
Name: 0, dtype: int64
'''
df - halfrow
Q | R | S | T | |
0 | 0.0 | NaN | 0.0 | NaN |
1 | -1.0 | NaN | 2.0 | NaN |
2 | 3.0 | NaN | 1.0 | NaN |
索引和列的保留和对齐意味着,Pandas 中的数据操作将始终维护数据上下文,这可以防止在处理原始 NumPy 数组中的异构和/或未对齐数据时,可能出现的愚蠢错误。