Python 数据分析(PYDA)第三版(二)(3)https://developer.aliyun.com/article/1482376
函数应用和映射
NumPy ufuncs(逐元素数组方法)也适用于 pandas 对象:
In [223]: frame = pd.DataFrame(np.random.standard_normal((4, 3)), .....: columns=list("bde"), .....: index=["Utah", "Ohio", "Texas", "Oregon"]) In [224]: frame Out[224]: b d e Utah -0.204708 0.478943 -0.519439 Ohio -0.555730 1.965781 1.393406 Texas 0.092908 0.281746 0.769023 Oregon 1.246435 1.007189 -1.296221 In [225]: np.abs(frame) Out[225]: b d e Utah 0.204708 0.478943 0.519439 Ohio 0.555730 1.965781 1.393406 Texas 0.092908 0.281746 0.769023 Oregon 1.246435 1.007189 1.296221
另一个频繁的操作是将一个一维数组上的函数应用于每列或每行。DataFrame 的apply
方法正是这样做的:
In [226]: def f1(x): .....: return x.max() - x.min() In [227]: frame.apply(f1) Out[227]: b 1.802165 d 1.684034 e 2.689627 dtype: float64
这里的函数f
计算 Series 的最大值和最小值之间的差异,对frame
中的每列调用一次。结果是一个具有frame
列作为其索引的 Series。
如果将axis="columns"
传递给apply
,则该函数将每行调用一次。将其视为"跨列应用"是一种有用的方式:
In [228]: frame.apply(f1, axis="columns") Out[228]: Utah 0.998382 Ohio 2.521511 Texas 0.676115 Oregon 2.542656 dtype: float64
许多最常见的数组统计(如sum
和mean
)都是 DataFrame 方法,因此不需要使用apply
。
传递给apply
的函数不必返回标量值;它也可以返回具有多个值的 Series:
In [229]: def f2(x): .....: return pd.Series([x.min(), x.max()], index=["min", "max"]) In [230]: frame.apply(f2) Out[230]: b d e min -0.555730 0.281746 -1.296221 max 1.246435 1.965781 1.393406
也可以使用逐元素 Python 函数。假设您想要从frame
中的每个浮点值计算格式化字符串。您可以使用applymap
来实现:
In [231]: def my_format(x): .....: return f"{x:.2f}" In [232]: frame.applymap(my_format) Out[232]: b d e Utah -0.20 0.48 -0.52 Ohio -0.56 1.97 1.39 Texas 0.09 0.28 0.77 Oregon 1.25 1.01 -1.30
applymap
的命名原因是 Series 有一个map
方法,用于应用逐元素函数:
In [233]: frame["e"].map(my_format) Out[233]: Utah -0.52 Ohio 1.39 Texas 0.77 Oregon -1.30 Name: e, dtype: object
排序和排名
按某个标准对数据集进行排序是另一个重要的内置操作。要按行或列标签的字典顺序排序,请使用sort_index
方法,该方法返回一个新的排序对象:
In [234]: obj = pd.Series(np.arange(4), index=["d", "a", "b", "c"]) In [235]: obj Out[235]: d 0 a 1 b 2 c 3 dtype: int64 In [236]: obj.sort_index() Out[236]: a 1 b 2 c 3 d 0 dtype: int64
对于 DataFrame,您可以在任一轴上按索引排序:
In [237]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)), .....: index=["three", "one"], .....: columns=["d", "a", "b", "c"]) In [238]: frame Out[238]: d a b c three 0 1 2 3 one 4 5 6 7 In [239]: frame.sort_index() Out[239]: d a b c one 4 5 6 7 three 0 1 2 3 In [240]: frame.sort_index(axis="columns") Out[240]: a b c d three 1 2 3 0 one 5 6 7 4
默认情况下,数据按升序排序,但也可以按降序排序:
In [241]: frame.sort_index(axis="columns", ascending=False) Out[241]: d c b a three 0 3 2 1 one 4 7 6 5
要按值对 Series 进行排序,请使用其sort_values
方法:
In [242]: obj = pd.Series([4, 7, -3, 2]) In [243]: obj.sort_values() Out[243]: 2 -3 3 2 0 4 1 7 dtype: int64
默认情况下,任何缺失值都按顺序排在 Series 的末尾:
In [244]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) In [245]: obj.sort_values() Out[245]: 4 -3.0 5 2.0 0 4.0 2 7.0 1 NaN 3 NaN dtype: float64
缺失值也可以通过使用na_position
选项将其排序到开头:
In [246]: obj.sort_values(na_position="first") Out[246]: 1 NaN 3 NaN 4 -3.0 5 2.0 0 4.0 2 7.0 dtype: float64
在对 DataFrame 进行排序时,可以使用一个或多个列中的数据作为排序键。为此,请将一个或多个列名传递给sort_values
:
In [247]: frame = pd.DataFrame({"b": [4, 7, -3, 2], "a": [0, 1, 0, 1]}) In [248]: frame Out[248]: b a 0 4 0 1 7 1 2 -3 0 3 2 1 In [249]: frame.sort_values("b") Out[249]: b a 2 -3 0 3 2 1 0 4 0 1 7 1
要按多个列排序,请传递一个名称列表:
In [250]: frame.sort_values(["a", "b"]) Out[250]: b a 2 -3 0 0 4 0 3 2 1 1 7 1
排名从数组中的最低值开始,为数组中的每个有效数据点分配从 1 到数据点数量的等级。Series 和 DataFrame 的rank
方法是要查看的地方;默认情况下,rank
通过为每个组分配平均等级来打破平局:
In [251]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) In [252]: obj.rank() Out[252]: 0 6.5 1 1.0 2 6.5 3 4.5 4 3.0 5 2.0 6 4.5 dtype: float64
排名也可以根据它们在数据中观察到的顺序进行分配:
In [253]: obj.rank(method="first") Out[253]: 0 6.0 1 1.0 2 7.0 3 4.0 4 3.0 5 2.0 6 5.0 dtype: float64
在这里,与使用条目 0 和 2 的平均等级 6.5 不同,它们分别设置为 6 和 7,因为标签 0 在数据中位于标签 2 之前。
您也可以按降序排名:
In [254]: obj.rank(ascending=False) Out[254]: 0 1.5 1 7.0 2 1.5 3 3.5 4 5.0 5 6.0 6 3.5 dtype: float64
请参阅表 5.6 以获取可用的平局破解方法列表。
DataFrame 可以在行或列上计算排名:
In [255]: frame = pd.DataFrame({"b": [4.3, 7, -3, 2], "a": [0, 1, 0, 1], .....: "c": [-2, 5, 8, -2.5]}) In [256]: frame Out[256]: b a c 0 4.3 0 -2.0 1 7.0 1 5.0 2 -3.0 0 8.0 3 2.0 1 -2.5 In [257]: frame.rank(axis="columns") Out[257]: b a c 0 3.0 2.0 1.0 1 3.0 1.0 2.0 2 1.0 2.0 3.0 3 3.0 2.0 1.0
表 5.6:排名的平局破解方法
方法 | 描述 |
"average" |
默认:为相等组中的每个条目分配平均等级 |
"min" |
使用整个组的最小等级 |
"max" |
使用整个组的最大等级 |
"first" |
按数据中值出现的顺序分配等级 |
"dense" |
类似于method="min" ,但等级总是在组之间增加 1,而不是在组中相等元素的数量之间增加 |
具有重复标签的轴索引
到目前为止,我们看过的几乎所有示例都具有唯一的轴标签(索引值)。虽然许多 pandas 函数(如reindex
)要求标签是唯一的,但这并非强制要求。让我们考虑一个具有重复索引的小 Series:
In [258]: obj = pd.Series(np.arange(5), index=["a", "a", "b", "b", "c"]) In [259]: obj Out[259]: a 0 a 1 b 2 b 3 c 4 dtype: int64
索引的is_unique
属性可以告诉您其标签是否唯一:
In [260]: obj.index.is_unique Out[260]: False
数据选择是与重复不同的主要行为之一。索引具有多个条目的标签返回一个 Series,而单个条目返回一个标量值:
In [261]: obj["a"] Out[261]: a 0 a 1 dtype: int64 In [262]: obj["c"] Out[262]: 4
这可能会使您的代码变得更加复杂,因为根据标签是否重复,索引的输出类型可能会有所不同。
相同的逻辑也适用于 DataFrame 中的行(或列)索引:
In [263]: df = pd.DataFrame(np.random.standard_normal((5, 3)), .....: index=["a", "a", "b", "b", "c"]) In [264]: df Out[264]: 0 1 2 a 0.274992 0.228913 1.352917 a 0.886429 -2.001637 -0.371843 b 1.669025 -0.438570 -0.539741 b 0.476985 3.248944 -1.021228 c -0.577087 0.124121 0.302614 In [265]: df.loc["b"] Out[265]: 0 1 2 b 1.669025 -0.438570 -0.539741 b 0.476985 3.248944 -1.021228 In [266]: df.loc["c"] Out[266]: 0 -0.577087 1 0.124121 2 0.302614 Name: c, dtype: float64
5.3 总结和计算描述性统计
pandas 对象配备了一组常见的数学和统计方法。其中大多数属于减少或摘要统计的类别,这些方法从 Series 中提取单个值(如总和或均值),或者从 DataFrame 的行或列中提取一系列值。与 NumPy 数组上找到的类似方法相比,它们内置了对缺失数据的处理。考虑一个小的 DataFrame:
In [267]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5], .....: [np.nan, np.nan], [0.75, -1.3]], .....: index=["a", "b", "c", "d"], .....: columns=["one", "two"]) In [268]: df Out[268]: one two a 1.40 NaN b 7.10 -4.5 c NaN NaN d 0.75 -1.3
调用 DataFrame 的sum
方法会返回一个包含列和的 Series:
In [269]: df.sum() Out[269]: one 9.25 two -5.80 dtype: float64
传递axis="columns"
或axis=1
会跨列求和:
In [270]: df.sum(axis="columns") Out[270]: a 1.40 b 2.60 c 0.00 d -0.55 dtype: float64
当整行或整列包含所有 NA 值时,总和为 0,而如果任何值不是 NA,则结果为 NA。可以使用skipna
选项禁用此功能,在这种情况下,行或列中的任何 NA 值都会使相应的结果为 NA:
In [271]: df.sum(axis="index", skipna=False) Out[271]: one NaN two NaN dtype: float64 In [272]: df.sum(axis="columns", skipna=False) Out[272]: a NaN b 2.60 c NaN d -0.55 dtype: float64
一些聚合,如mean
,需要至少一个非 NA 值才能产生一个值结果,因此我们有:
In [273]: df.mean(axis="columns") Out[273]: a 1.400 b 1.300 c NaN d -0.275 dtype: float64
请参见表 5.7 以获取每种减少方法的常见选项列表。
表 5.7:减少方法的选项
方法 | 描述 |
axis |
要减少的轴;DataFrame 的行为“index”,列为“columns” |
skipna |
排除缺失值;默认为True |
level |
如果轴是分层索引(MultiIndex),则按级别减少 |
一些方法,如idxmin
和idxmax
,返回间接统计信息,如达到最小值或最大值的索引值:
In [274]: df.idxmax() Out[274]: one b two d dtype: object
其他方法是累积:
In [275]: df.cumsum() Out[275]: one two a 1.40 NaN b 8.50 -4.5 c NaN NaN d 9.25 -5.8
一些方法既不是减少也不是累积。describe
就是一个例子,一次生成多个摘要统计信息:
In [276]: df.describe() Out[276]: one two count 3.000000 2.000000 mean 3.083333 -2.900000 std 3.493685 2.262742 min 0.750000 -4.500000 25% 1.075000 -3.700000 50% 1.400000 -2.900000 75% 4.250000 -2.100000 max 7.100000 -1.300000
对于非数字数据,describe
会生成替代的摘要统计信息:
In [277]: obj = pd.Series(["a", "a", "b", "c"] * 4) In [278]: obj.describe() Out[278]: count 16 unique 3 top a freq 8 dtype: object
请参见表 5.8 以获取摘要统计和相关方法的完整列表。
表 5.8:描述性和摘要统计
方法 | 描述 |
count |
非 NA 值的数量 |
describe |
计算一组摘要统计信息 |
min, max |
计算最小值和最大值 |
argmin, argmax |
计算获得最小值或最大值的索引位置(整数),分别;在 DataFrame 对象上不可用 |
idxmin, idxmax |
计算获得最小值或最大值的索引标签 |
quantile |
计算从 0 到 1 范围的样本分位数(默认值:0.5) |
sum |
值的总和 |
mean |
值的均值 |
median |
值的算术中位数(50%分位数) |
mad |
与均值的平均绝对偏差 |
prod |
所有值的乘积 |
var |
值的样本方差 |
std |
值的样本标准差 |
skew |
值的样本偏度(第三时刻) |
kurt |
值的样本峰度(第四时刻) |
cumsum |
值的累积和 |
cummin, cummax |
值的累积最小值或最大值,分别 |
cumprod |
值的累积乘积 |
diff |
计算第一个算术差异(对时间序列有用) |
pct_change |
计算百分比变化 |
相关性和协方差
一些摘要统计信息,如相关性和协方差,是从一对参数计算得出的。让我们考虑一些股票价格和成交量的 DataFrame,最初从 Yahoo! Finance 获取,并在本书的附带数据集中以二进制 Python pickle 文件的形式提供:
In [279]: price = pd.read_pickle("examples/yahoo_price.pkl") In [280]: volume = pd.read_pickle("examples/yahoo_volume.pkl")
现在我计算价格的百分比变化,这是一个时间序列操作,将在第十一章:时间序列中进一步探讨:
In [281]: returns = price.pct_change() In [282]: returns.tail() Out[282]: AAPL GOOG IBM MSFT Date 2016-10-17 -0.000680 0.001837 0.002072 -0.003483 2016-10-18 -0.000681 0.019616 -0.026168 0.007690 2016-10-19 -0.002979 0.007846 0.003583 -0.002255 2016-10-20 -0.000512 -0.005652 0.001719 -0.004867 2016-10-21 -0.003930 0.003011 -0.012474 0.042096
Series 的corr
方法计算两个 Series 中重叠的、非 NA、按索引对齐的值的相关性。相关地,cov
计算协方差:
In [283]: returns["MSFT"].corr(returns["IBM"]) Out[283]: 0.49976361144151166 In [284]: returns["MSFT"].cov(returns["IBM"]) Out[284]: 8.870655479703549e-05
另一方面,DataFrame 的corr
和cov
方法分别返回完整的相关性或协方差矩阵作为 DataFrame:
In [285]: returns.corr() Out[285]: AAPL GOOG IBM MSFT AAPL 1.000000 0.407919 0.386817 0.389695 GOOG 0.407919 1.000000 0.405099 0.465919 IBM 0.386817 0.405099 1.000000 0.499764 MSFT 0.389695 0.465919 0.499764 1.000000 In [286]: returns.cov() Out[286]: AAPL GOOG IBM MSFT AAPL 0.000277 0.000107 0.000078 0.000095 GOOG 0.000107 0.000251 0.000078 0.000108 IBM 0.000078 0.000078 0.000146 0.000089 MSFT 0.000095 0.000108 0.000089 0.000215
使用 DataFrame 的corrwith
方法,您可以计算 DataFrame 的列或行与另一个 Series 或 DataFrame 之间的成对相关性。传递一个 Series 会返回一个 Series,其中计算了每列的相关值:
In [287]: returns.corrwith(returns["IBM"]) Out[287]: AAPL 0.386817 GOOG 0.405099 IBM 1.000000 MSFT 0.499764 dtype: float64
传递一个 DataFrame 会计算匹配列名的相关性。在这里,我计算了百分比变化与成交量的相关性:
In [288]: returns.corrwith(volume) Out[288]: AAPL -0.075565 GOOG -0.007067 IBM -0.204849 MSFT -0.092950 dtype: float64
传递axis="columns"
会逐行执行操作。在所有情况下,在计算相关性之前,数据点都会按标签对齐。
唯一值、值计数和成员资格
另一类相关方法提取一维 Series 中包含的值的信息。为了说明这些方法,考虑以下示例:
In [289]: obj = pd.Series(["c", "a", "d", "a", "a", "b", "b", "c", "c"])
第一个函数是unique
,它为您提供 Series 中唯一值的数组:
In [290]: uniques = obj.unique() In [291]: uniques Out[291]: array(['c', 'a', 'd', 'b'], dtype=object)
唯一的值不一定按它们首次出现的顺序返回,也不按排序顺序返回,但如果需要的话可以在之后排序(uniques.sort()
)。相关地,value_counts
计算包含值频率的 Series:
In [292]: obj.value_counts() Out[292]: c 3 a 3 b 2 d 1 Name: count, dtype: int64
Series 按值降序排序以方便起见。value_counts
也作为顶级 pandas 方法可用,可与 NumPy 数组或其他 Python 序列一起使用:
In [293]: pd.value_counts(obj.to_numpy(), sort=False) Out[293]: c 3 a 3 d 1 b 2 Name: count, dtype: int64
isin
执行矢量化的成员检查,并且在将数据集过滤到 Series 或 DataFrame 中的值子集时可能很有用:
In [294]: obj Out[294]: 0 c 1 a 2 d 3 a 4 a 5 b 6 b 7 c 8 c dtype: object In [295]: mask = obj.isin(["b", "c"]) In [296]: mask Out[296]: 0 True 1 False 2 False 3 False 4 False 5 True 6 True 7 True 8 True dtype: bool In [297]: obj[mask] Out[297]: 0 c 5 b 6 b 7 c 8 c dtype: object
与isin
相关的是Index.get_indexer
方法,它从可能不同的值的数组中为另一个不同值的数组提供索引数组:
In [298]: to_match = pd.Series(["c", "a", "b", "b", "c", "a"]) In [299]: unique_vals = pd.Series(["c", "b", "a"]) In [300]: indices = pd.Index(unique_vals).get_indexer(to_match) In [301]: indices Out[301]: array([0, 2, 1, 1, 0, 2])
有关这些方法的参考,请参见表 5.9。
表 5.9:唯一值、值计数和成员资格方法
方法 | 描述 |
isin |
计算一个布尔数组,指示每个 Series 或 DataFrame 值是否包含在传递的值序列中 |
get_indexer |
为数组中的每个值计算整数索引,以便将其对齐到另一个不同值的数组;有助于数据对齐和连接类型操作 |
unique |
计算 Series 中唯一值的数组,按观察顺序返回 |
value_counts |
返回一个 Series,其唯一值作为索引,频率作为值,按降序计数排序 |
在某些情况下,您可能希望在 DataFrame 中的多个相关列上计算直方图。以下是一个示例:
In [302]: data = pd.DataFrame({"Qu1": [1, 3, 4, 3, 4], .....: "Qu2": [2, 3, 1, 2, 3], .....: "Qu3": [1, 5, 2, 4, 4]}) In [303]: data Out[303]: Qu1 Qu2 Qu3 0 1 2 1 1 3 3 5 2 4 1 2 3 3 2 4 4 4 3 4
我们可以计算单列的值计数,如下所示:
In [304]: data["Qu1"].value_counts().sort_index() Out[304]: Qu1 1 1 3 2 4 2 Name: count, dtype: int64
要为所有列计算此值,请将pandas.value_counts
传递给 DataFrame 的apply
方法:
In [305]: result = data.apply(pd.value_counts).fillna(0) In [306]: result Out[306]: Qu1 Qu2 Qu3 1 1.0 1.0 1.0 2 0.0 2.0 1.0 3 2.0 2.0 0.0 4 2.0 0.0 2.0 5 0.0 0.0 1.0
在这里,结果中的行标签是所有列中出现的不同值。这些值是每列中这些值的相应计数。
还有一个DataFrame.value_counts
方法,但它计算考虑 DataFrame 的每一行作为元组的计数,以确定每个不同行的出现次数:
In [307]: data = pd.DataFrame({"a": [1, 1, 1, 2, 2], "b": [0, 0, 1, 0, 0]}) In [308]: data Out[308]: a b 0 1 0 1 1 0 2 1 1 3 2 0 4 2 0 In [309]: data.value_counts() Out[309]: a b 1 0 2 2 0 2 1 1 1 Name: count, dtype: int64
在这种情况下,结果具有一个表示不同行的索引作为层次索引,这是我们将在第八章:数据整理:连接、合并和重塑中更详细地探讨的一个主题。
5.4 结论
在下一章中,我们将讨论使用 pandas 读取(或加载)和写入数据集的工具。之后,我们将深入探讨使用 pandas 进行数据清洗、整理、分析和可视化的工具。