查看原文
其他

【翻译】《利用Python进行数据分析·第2版》第5章(下)pandas入门

SeanCheney Python爱好者社区 2019-04-07

作者: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爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门视频课程!!!

崔老师爬虫实战案例免费学习视频。

丘老师数据科学入门指导免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。

丘老师Python网络爬虫实战免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存