【翻译】《利用Python进行数据分析·第2版》第5章(下)pandas入门
作者:SeanCheney Python爱好者社区专栏作者
简书专栏:https://www.jianshu.com/u/130f76596b02
前文传送门:
【翻译】《利用Python进行数据分析·第2版》第1章 准备工作
【翻译】《利用Python进行数据分析·第2版》第2章(上)Python语法基础,IPython和Jupyter
【翻译】《利用Python进行数据分析·第2版》第2章(中)Python语法基础,IPython和Jupyter
【翻译】《利用Python进行数据分析·第2版》第2章(下)Python语法基础,IPython和Jupyter
【翻译】《利用Python进行数据分析·第2版》第3章(上)Python的数据结构、函数和文件
【翻译】《利用Python进行数据分析·第2版》第3章(中)Python的数据结构、函数和文件
【翻译】《利用Python进行数据分析·第2版》第3章(下)Python的数据结构、函数和文件
【翻译】《利用Python进行数据分析·第2版》第4章(上)NumPy基础:数组和矢量计算
【翻译】《利用Python进行数据分析·第2版》第4章(中)NumPy基础:数组和矢量计算
【翻译】《利用Python进行数据分析·第2版》第4章(下)NumPy基础:数组和矢量计算
【翻译】《利用Python进行数据分析·第2版》第5章(上)pandas入门
【翻译】《利用Python进行数据分析·第2版》第5章(中)pandas入门
DataFrame和Series之间的运算
跟不同维度的NumPy数组一样,DataFrame和Series之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差:
In [175]: arr = np.arange(12.).reshape((3, 4)) In [176]: arr Out[176]: array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]) In [177]: arr[0] Out[177]: array([ 0., 1., 2., 3.]) In [178]: arr - arr[0] Out[178]: array([[ 0., 0., 0., 0.], [ 4., 4., 4., 4.], [ 8., 8., 8., 8.]])
当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting),附录A将对此进行详细讲解。DataFrame和Series之间的运算差不多也是如此:
In [179]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), .....: columns=list('bde'), .....: index=['Utah', 'Ohio', 'Texas', 'Oregon']) In [180]: series = frame.iloc[0] In [181]: frame Out[181]: b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 In [182]: series Out[182]: b 0.0 d 1.0 e 2.0 Name: Utah, dtype: float64
默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:
In [183]: frame - series Out[183]: b d e Utah 0.0 0.0 0.0 Ohio 3.0 3.0 3.0 Texas 6.0 6.0 6.0 Oregon 9.0 9.0 9.0
如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:
In [184]: series2 = pd.Series(range(3), index=['b', 'e', 'f']) In [185]: frame + series2 Out[185]: b d e f Utah 0.0 NaN 3.0 NaN Ohio 3.0 NaN 6.0 NaN Texas 6.0 NaN 9.0 NaN Oregon 9.0 NaN 12.0 NaN
如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:
In [186]: series3 = frame['d'] In [187]: frame Out[187]: b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 In [188]: series3 Out[188]: Utah 1.0 Ohio 4.0 Texas 7.0 Oregon 10.0 Name: d, dtype: float64 In [189]: frame.sub(series3, axis='index') Out[189]: b d e Utah -1.0 0.0 1.0 Ohio -1.0 0.0 1.0 Texas -1.0 0.0 1.0 Oregon -1.0 0.0 1.0
传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的行索引(axis='index' or axis=0)并进行广播。
函数应用和映射
NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象:
In [190]: frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), .....: index=['Utah', 'Ohio', 'Texas', 'Oregon']) In [191]: frame Out[191]: 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 [192]: np.abs(frame) Out[192]: 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 [193]: f = lambda x: x.max() - x.min() In [194]: frame.apply(f) Out[194]: b 1.802165 d 1.684034 e 2.689627 dtype: float64
这里的函数f,计算了一个Series的最大和虽小的差,在frane的每列都执行了一次。结果是一个Series,使用frame的列作为索引。
如果传递axis='columns'到apply,这个函数会在每行执行:
In [195]: frame.apply(f, axis='columns') Out[195]: Utah 0.998382 Ohio 2.521511 Texas 0.676115 Oregon 2.542656 dtype: float64
许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。
传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:
In [196]: def f(x): .....: return pd.Series([x.min(), x.max()], index=['min', 'max']) In [197]: frame.apply(f) Out[197]: b d e min -0.555730 0.281746 -1.296221 max 1.246435 1.965781 1.393406
元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:
In [198]: format = lambda x: '%.2f' % x In [199]: frame.applymap(format) Out[199]: 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 [200]: frame['e'].map(format) Out[200]: Utah -0.52 Ohio 1.39 Texas 0.77 Oregon -1.30 Name: e, dtype: object
排序和排名
根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象:
In [201]: obj = pd.Series(range(4), index=['d', 'a', 'b', 'c']) In [202]: obj.sort_index() Out[202]: a 1 b 2 c 3 d 0 dtype: int64
对于DataFrame,则可以根据任意一个轴上的索引进行排序:
In [203]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)), .....: index=['three', 'one'], .....: columns=['d', 'a', 'b', 'c']) In [204]: frame.sort_index() Out[204]: d a b c one 4 5 6 7 three 0 1 2 3 In [205]: frame.sort_index(axis=1) Out[205]: a b c d three 1 2 3 0 one 5 6 7 4
数据默认是按升序排序的,但也可以降序排序:
In [206]: frame.sort_index(axis=1, ascending=False) Out[206]: d c b a three 0 3 2 1 one 4 7 6 5
若要按值对Series进行排序,可使用其sort_values方法:
In [207]: obj = pd.Series([4, 7, -3, 2]) In [208]: obj.sort_values() Out[208]: 2 -3 3 2 0 4 1 7 dtype: int64
在排序时,任何缺失值默认都会被放到Series的末尾:
In [209]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) In [210]: obj.sort_values() Out[210]: 4 -3.0 5 2.0 0 4.0 2 7.0 1 NaN 3 NaN dtype: float64
当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的:
In [211]: frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]}) In [212]: frame Out[212]: a b 0 0 4 1 1 7 2 0 -3 3 1 2 In [213]: frame.sort_values(by='b') Out[213]: a b 2 0 -3 3 1 2 0 0 4 1 1 7
要根据多个列进行排序,传入名称的列表即可:
In [214]: frame.sort_values(by=['a', 'b']) Out[214]: a b 2 0 -3 0 0 4 3 1 2 1 1 7
排名会从1开始一直到数组中有效数据的数量。接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的:
In [215]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) In [216]: obj.rank() Out[216]: 0 6.5 1 1.0 2 6.5 3 4.5 4 3.0 5 2.0 6 4.5 dtype: float64
也可以根据值在原数据中出现的顺序给出排名:
In [217]: obj.rank(method='first') Out[217]: 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的前面。
你也可以按降序进行排名:
# Assign tie values the maximum rank in the group In [218]: obj.rank(ascending=False, method='max') Out[218]: 0 2.0 1 7.0 2 2.0 3 4.0 4 5.0 5 6.0 6 4.0 dtype: float64
表5-6列出了所有用于破坏平级关系的method选项。DataFrame可以在行或列上计算排名:
In [219]: frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], .....: 'c': [-2, 5, 8, -2.5]}) In [220]: frame Out[220]: a b c 0 0 4.3 -2.0 1 1 7.0 5.0 2 0 -3.0 8.0 3 1 2.0 -2.5 In [221]: frame.rank(axis='columns') Out[221]: a b c 0 2.0 3.0 1.0 1 1.0 3.0 2.0 2 2.0 1.0 3.0 3 2.0 3.0 1.0
表5-6 排名时用于破坏平级关系的方法
带有重复标签的轴索引
直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的Series:
In [222]: obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c']) In [223]: obj Out[223]: a 0 a 1 b 2 b 3 c 4 dtype: int64
索引的is_unique属性可以告诉你它的值是否是唯一的:
In [224]: obj.index.is_unique Out[224]: False
对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值:
In [225]: obj['a'] Out[225]: a 0 a 1 dtype: int64 In [226]: obj['c'] Out[226]: 4
这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。
对DataFrame的行进行索引时也是如此:
In [227]: df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b']) In [228]: df Out[228]: 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 In [229]: df.loc['b'] Out[229]: 0 1 2 b 1.669025 -0.438570 -0.539741 b 0.476985 3.248944 -1.021228
5.3 汇总和计算描述统计
pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。看一个简单的DataFrame:
In [230]: 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 [231]: df Out[231]: one two a 1.40 NaN b 7.10 -4.5 c NaN NaN d 0.75 -1.3
调用DataFrame的sum方法将会返回一个含有列的和的Series:
In [232]: df.sum() Out[232]: one 9.25 two -5.80 dtype: float64
传入axis='columns'或axis=1将会按行进行求和运算:
In [233]: df.sum(axis=1) Out[233]: a 1.40 b 2.60 c NaN d -0.55
NA值会自动被排除,除非整个切片(这里指的是行或列)都是NA。通过skipna选项可以禁用该功能:
In [234]: df.mean(axis='columns', skipna=False) Out[234]: a NaN b 1.300 c NaN d -0.275 dtype: float64
表5-7列出了这些约简方法的常用选项。
有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引):
In [235]: df.idxmax() Out[235]: one b two d dtype: object
另一些方法则是累计型的:
In [236]: df.cumsum() Out[236]: one two a 1.40 NaN b 8.50 -4.5 c NaN NaN d 9.25 -5.8
还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计:
In [237]: df.describe() Out[237]: 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 [238]: obj = pd.Series(['a', 'a', 'b', 'c'] * 4) In [239]: obj.describe() Out[239]: count 16 unique 3 top a freq 8 dtype: object
表5-8列出了所有与描述统计相关的方法。
相关系数与协方差
有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个DataFrame,它们的数据来自Yahoo!Finance的股票价格和成交量,使用的是pandas-datareader包(可以用conda或pip安装):
conda install pandas-datareader
我使用pandas_datareader模块下载了一些股票数据:
import pandas_datareader.data as web all_data = {ticker: web.get_data_yahoo(ticker) for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']} price = pd.DataFrame({ticker: data['Adj Close'] for ticker, data in all_data.items()}) volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()})
注意:此时Yahoo! Finance已经不存在了,因为2017年Yahoo!被Verizon收购了。参阅pandas-datareader文档,可以学习最新的功能。
现在计算价格的百分数变化,时间序列的操作会在第11章介绍:
In [242]: returns = price.pct_change() In [243]: returns.tail() Out[243]: 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 [244]: returns['MSFT'].corr(returns['IBM']) Out[244]: 0.49976361144151144 In [245]: returns['MSFT'].cov(returns['IBM']) Out[245]: 8.8706554797035462e-05
因为MSTF是一个合理的Python属性,我们还可以用更简洁的语法选择列:
In [246]: returns.MSFT.corr(returns.IBM) Out[246]: 0.49976361144151144
另一方面,DataFrame的corr和cov方法将以DataFrame的形式分别返回完整的相关系数或协方差矩阵:
In [247]: returns.corr() Out[247]: 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 [248]: returns.cov() Out[248]: 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方法,你可以计算其列或行跟另一个Series或DataFrame之间的相关系数。传入一个Series将会返回一个相关系数值Series(针对各列进行计算):
In [249]: returns.corrwith(returns.IBM) Out[249]: AAPL 0.386817 GOOG 0.405099 IBM 1.000000 MSFT 0.499764 dtype: float64
传入一个DataFrame则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数:
In [250]: returns.corrwith(volume) Out[250]: AAPL -0.075565 GOOG -0.007067 IBM -0.204849 MSFT -0.092950 dtype: float64
传入axis='columns'即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。
唯一值、值计数以及成员资格
还有一类方法可以从一维Series的值中抽取信息。看下面的例子:
In [251]: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
第一个函数是unique,它可以得到Series中的唯一值数组:
In [252]: uniques = obj.unique() In [253]: uniques Out[253]: array(['c', 'a', 'd', 'b'], dtype=object)
返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts用于计算一个Series中各值出现的频率:
In [254]: obj.value_counts() Out[254]: c 3 a 3 b 2 d 1 dtype: int64
为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列:
In [255]: pd.value_counts(obj.values, sort=False) Out[255]: a 3 b 2 c 3 d 1 dtype: int64
isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集:
In [256]: obj Out[256]: 0 c 1 a 2 d 3 a 4 a 5 b 6 b 7 c 8 c dtype: object In [257]: mask = obj.isin(['b', 'c']) In [258]: mask Out[258]: 0 True 1 False 2 False 3 False 4 False 5 True 6 True 7 True 8 True dtype: bool In [259]: obj[mask] Out[259]: 0 c 5 b 6 b 7 c 8 c dtype: object
与isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组:
In [260]: to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a']) In [261]: unique_vals = pd.Series(['c', 'b', 'a']) In [262]: pd.Index(unique_vals).get_indexer(to_match) Out[262]: array([0, 2, 1, 1, 0, 2])
表5-9给出了这几个方法的一些参考信息。
表5-9 唯一值、值计数、成员资格方法
有时,你可能希望得到DataFrame中多个相关列的一张柱状图。例如:
In [263]: data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4], .....: 'Qu2': [2, 3, 1, 2, 3], .....: 'Qu3': [1, 5, 2, 4, 4]}) In [264]: data Out[264]: Qu1 Qu2 Qu3 0 1 2 1 1 3 3 5 2 4 1 2 3 3 2 4 4 4 3 4
将pandas.value_counts传给该DataFrame的apply函数,就会出现:
In [265]: result = data.apply(pd.value_counts).fillna(0) In [266]: result Out[266]: 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
这里,结果中的行标签是所有列的唯一值。后面的频率值是每个列中这些值的相应计数。
5.4 总结
在下一章,我们将讨论用pandas读取(或加载)和写入数据集的工具。
之后,我们将更深入地研究使用pandas进行数据清洗、规整、分析和可视化工具。
赞赏作者
Python爱好者社区历史文章大合集:
Python爱好者社区历史文章列表(每周append更新一次)
关注后在公众号内回复“课程”即可获取:
小编的Python入门视频课程!!!
崔老师爬虫实战案例免费学习视频。
丘老师数据科学入门指导免费学习视频。
陈老师数据分析报告制作免费学习视频。
玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。
丘老师Python网络爬虫实战免费学习视频。