Python---Pandas万字总结(4)

Pandas数据透视及呈现-4

数据透视

经过前面的三篇文章的学习,我们已经学会了如何简单的处理数据,接下来就是最为重要的数据透视阶段了。当我们拿到一大堆数据的时候,如何从数据中迅速的解读出有价值的信息,把繁杂的数据变成容易解读的统计图表并再此基础上进行洞察,这就是数据分析要解决的核心问题。

获取描述性统计信息

首先,我们可以获取数据的描述性统计信息,通过描述性统计信息,我们可以了解数据的集中趋势和离散趋势。

例如,现在有如下所示的学生成绩表。

scores = np.random.randint(50, 101, (5, 3))
names = ('关羽', '张飞', '赵云', '马超', '黄忠')
courses = ('语文', '数学', '英语')
df = pd.DataFrame(data=scores, columns=courses, index=names)
df

输出:

     语文   数学   英语
关羽  96    72    73
张飞  72    70	97
赵云  74    51	79
马超  100   54	54
黄忠  89    100	88

通过DataFrame对象的方法meanmaxminstdvar等方法分别获取每个学生或每门课程的平均分、最高分、最低分、标准差、方差等信息,也可以直接通过describe方法直接获取描述性统计信息,代码如下所示。

计算每门课程成绩的平均分。

df.mean()

输出:

语文    86.2
数学    69.4
英语    78.2
dtype: float64

计算每个学生成绩的平均分。

df.mean(axis=1)

输出:

关羽    80.333333
张飞    79.666667
赵云    68.000000
马超    69.333333
黄忠    92.333333
dtype: float64

计算每门课程成绩的方差。

df.var()

输出:

语文    161.2
数学    379.8
英语    265.7
dtype: float64

说明:通过方差可以看出,数学成绩波动最大,两极分化可能更严重。

获取每门课程的描述性统计信息。

df.describe()

输出:

        语文        数学         英语
count   5.000000	5.000000	5.000000
mean    86.200000	69.400000	78.200000
std     12.696456	19.488458	16.300307
min     72.000000	51.000000	54.000000
25%     74.000000	54.000000	73.000000
50%     89.000000	70.000000	79.000000
75%     96.000000	72.000000	88.000000
max     100.000000	100.000000	97.000000
排序和取头部值

如果需要对数据进行排序,可以使用DataFrame对象的sort_values方法,该方法的by参数可以指定根据哪个列或哪些列进行排序,而ascending参数可以指定升序或是降序。例如,下面的代码展示了如何将学生表按语文成绩排降序。

df.sort_values(by='语文', ascending=False)

输出:

      语文   数学   英语
马超	100    54	  54
关羽	96     72     73
黄忠	89     100    88
赵云	74     51     79
张飞	72     70     97

如果DataFrame数据量很大,排序将是一个非常耗费时间的操作。有的时候我们只需要获得排前N名或后N名的数据,这个时候其实没有必要对整个数据进行排序,而是直接利用堆结构找出Top-N的数据。DataFramenlargestnsmallest方法就提供对Top-N操作的支持,代码如下所示。

找出语文成绩前3名的学生信息。

df.nlargest(3, '语文')

输出:

      语文   数学   英语
马超	100    54	  54
关羽	96     72     73
黄忠	89     100    88

找出数学成绩最低的3名学生的信息。

df.nsmallest(3, '数学')

输出:

      语文  数学  英语
赵云  74    51	79
马超  100   54	54
张飞  72    70	97
分组聚合

从之前使用过的 Excel 文件中读取‘销售数据’,进行分组聚合操作。

df = pd.read_excel('data/销售数据.xlsx')
df.head()

输出:

    销售日期	 销售区域   销售渠道  销售订单     品牌    售价  销售数量
0   2020-01-01  上海       拼多多    182894-455  八匹马  99    83
1   2020-01-01  上海       抖音      205635-402  八匹马  219   29
2   2020-01-01  上海       天猫      205654-021  八匹马  169   85
3   2020-01-01  上海       天猫      205654-519  八匹马  169   14
4   2020-01-01  上海       天猫      377781-010  皮皮虾  249   61

如果我们要统计每个销售区域的销售总额,可以先通过“售价”和“销售数量”计算出销售额,为DataFrame添加一个列,代码如下所示。

df['销售额'] = df['售价'] * df['销售数量']
df.head()

输出:

    销售日期	 销售区域   销售渠道  销售订单     品牌    售价  销售数量  销售额
0   2020-01-01  上海       拼多多    182894-455  八匹马  99    83        8217
1   2020-01-01  上海       抖音      205635-402  八匹马  219   29        6351
2   2020-01-01  上海       天猫      205654-021  八匹马  169   85        14365
3   2020-01-01  上海       天猫      205654-519  八匹马  169   14        2366
4   2020-01-01  上海       天猫      377781-010  皮皮虾  249   61        15189

然后再根据“销售区域”列对数据进行分组,这里我们使用的是DataFrame对象的groupby方法。分组之后,我们取“销售额”这个列在分组内进行求和处理,代码和结果如下所示。

df.groupby('销售区域').销售额.sum()

输出:

销售区域
上海    11610489
北京    12477717
安徽      895463
广东     1617949
江苏     2304380
浙江      687862
福建    10178227
Name: 销售额, dtype: int64

如果我们要统计每个月的销售总额,可以将“销售日期”作为groupby`方法的参数,当然这里需要先将“销售日期”处理成月,代码和结果如下。

df.groupby(df['销售日期'].dt.month).销售额.sum()

输出:

销售日期
1     5409855
2     4608455
3     4164972
4     3996770
5     3239005
6     2817936
7     3501304
8     2948189
9     2632960
10    2375385
11    2385283
12    1691973
Name: 销售额, dtype: int64

接下来,上难度,统计每个销售区域每个月的销售总额,这又该如何处理呢?事实上,groupby方法的第一个参数可以是一个列表,列表中可以指定多个分组的依据,大家看看下面的代码和输出结果就明白了。

df.groupby(['销售区域', df['销售日期'].dt.month]).销售额.sum()

输出:

销售区域  销售日期
上海    1       1679125
        2       1689527
        3       1061193
        4       1082187
        5        841199
        6        785404
        7        863906
        8        734937
        9       1107693
        10       412108
       11       825169
       12       528041
北京    1       1878234
        2       1807787
        3       1360666
        4       1205989
        5        807300
        6       1216432
        7       1219083
        8        645727
        9        390077
        10       671608
        11       678668
        12       596146
安徽    4        341308
        5        554155
广东    3        388180
        8        469390
        9        365191
        11       395188
江苏    4        537079
        7        841032
        10       710962
        12       215307
浙江    3        248354
        8        439508
福建    1       1852496
        2       1111141
        3       1106579
        4        830207
        5       1036351
        6        816100
        7        577283
        8        658627
        9        769999
        10       580707
        11       486258
        12       352479
Name: 销售额, dtype: int64

如果希望统计出每个区域的销售总额以及每个区域单笔金额的最高和最低,可以在DataFrameSeries对象上使用agg方法并指定多个聚合函数,代码和结果如下所示。

df.groupby('销售区域').销售额.agg(['sum', 'max', 'min'])

输出:

           sum     max   min
销售区域                        
上海    11610489  116303   948
北京    12477717  133411   690
安徽      895463   68502  1683
广东     1617949  120807   990
江苏     2304380  114312  1089
浙江      687862   90909  3927
福建    10178227   87527   897

如果希望自定义聚合后的列的名字,可以使用如下所示的方法。

df.groupby('销售区域').销售额.agg(销售总额='sum', 单笔最高='max', 单笔最低='min')

输出:

          销售总额    单笔最高  单笔最低
销售区域                        
上海      11610489     116303     948
北京      12477717     133411     690
安徽        895463      68502    1683
广东       1617949     120807     990
江苏       2304380     114312    1089
浙江        687862      90909    3927
福建      10178227      87527     897

如果需要对多个列使用不同的聚合函数,例如“统计每个销售区域销售额的总和以及销售数量的最低值和最高值”,可以按照下面的方式来操作。

df.groupby('销售区域')[['销售额', '销售数量']].agg({
    '销售额': 'sum', '销售数量': ['max', 'min']
})

输出:

           销售额  销售数量    
           sum    max min
销售区域                   
上海    11610489  100  10
北京    12477717  100  10
安徽      895463   98  16
广东     1617949   98  10
江苏     2304380  100  11
浙江      687862   95  20
福建    10178227  100  10
透视表和交叉表

上面的例子中,“统计每个销售区域每个月的销售总额”会产生一个看起来很长的结果,在实际工作中我们通常把那些行很多列很少的表成为“窄表”,如果我们不想得到这样的一个“窄表”,可以使用DataFramepivot_table方法或者是pivot_table函数来生成透视表。透视表的本质就是对数据进行分组聚合操作,根据 A 列对 B 列进行统计,这里的透视表,跟Excel非常相似。例如,要“统计每个销售区域的销售总额”,那么“销售区域”就是我们的 A 列,而“销售额”就是我们的 B 列,在pivot_table函数中分别对应indexvalues参数,这两个参数都可以是单个列或者多个列。

pd.pivot_table(df, index='销售区域', values='销售额', aggfunc='sum')

输出:

           销售额
销售区域          
上海    11610489
北京    12477717
安徽      895463
广东     1617949
江苏     2304380
浙江      687862
福建    10178227

注意:上面的结果操作跟之前用groupby的方式得到的结果有一些区别,groupby操作后,如果对单个列进行聚合,得到的结果是一个Series对象,而上面的结果是一个DataFrame 对象。

如果要统计每个销售区域每个月的销售总额,也可以使用pivot_table函数,代码如下所示。

df['月份'] = df['销售日期'].dt.month
pd.pivot_table(df, index=['销售区域', '月份'], values='销售额', aggfunc='sum')

上面的操作结果是一个DataFrame,但也是一个长长的“窄表”,如果希望做成一个行比较少列比较多的“宽表”,可以将index参数中的列放到columns参数中,代码如下所示。

pd.pivot_table(df, index='销售区域', columns='月份', values='销售额', aggfunc='sum', fill_value=0)

说明pivot_table函数的fill_value=0会将空值处理为0

输出:
在这里插入图片描述

使用pivot_table函数时,还可以通过添加marginsmargins_name参数对分组聚合的结果做一个汇总,具体的操作和效果如下所示。

pd.pivot_table(df, index='销售区域', columns='月份', values='销售额', aggfunc='sum', fill_value=0, margins=True, margins_name='总计')

输出:

在这里插入图片描述

交叉表就是一种特殊的透视表,它不需要先构造一个DataFrame对象,而是直接通过数组或Series对象指定两个或多个因素进行运算得到统计结果。例如,我们要统计每个销售区域的销售总额,也可以按照如下所示的方式来完成,我们先准备三组数据。

sales_area, sales_month, sales_amount = df['销售区域'], df['月份'], df['销售额']

使用crosstab函数生成交叉表。

pd.crosstab(index=sales_area, columns=sales_month, values=sales_amount, aggfunc='sum').fillna(0).astype('i8')

说明:上面的代码使用了DataFrame对象的fillna方法将空值处理为0,再使用astype方法将数据类型处理成整数。

数据呈现

一图胜过千言万语,我们对数据进行透视的结果,最终要通过图表的方式呈现出来,因为图表具有极强的表现力,能够让我们迅速的解读数据中隐藏的价值。和Series一样,DataFrame对象提供了plot方法来支持绘图,底层仍然是通过matplotlib库实现图表的渲染。

例如,我们想通过一张柱状图来比较“每个销售区域的销售总额”,可以直接在透视表上使用plot方法生成柱状图。我们先导入matplotlib.pyplot模块,通过修改绘图的参数使其支持中文显示。

import matplotlib.pyplot as plt

mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False

说明:上述两行代码可以解决在图表中中文乱码的问题

使用魔法指令配置生成矢量图。

%config InlineBackend.figure_format = 'svg'

绘制“每个销售区域销售总额”的柱状图。

temp = pd.pivot_table(df, index='销售区域', values='销售额', aggfunc='sum')
temp.plot(figsize=(8, 4), kind='bar')
plt.xticks(rotation=0)
plt.show()

说明:上面的第3行代码会将横轴刻度上的文字旋转到0度,第4行代码会显示图像。

输出:

如果要绘制饼图,可以修改plot方法的kind参数为pie,然后使用定制饼图的参数对图表加以定制,代码如下所示。

temp.sort_values(by='销售额', ascending=False).plot(
    figsize=(6, 6),
    kind='pie',
    y='销售额',
    ylabel='',
    autopct='%.2f%%',
    pctdistance=0.8,
    wedgeprops=dict(linewidth=1, width=0.35),
    legend=False
)
plt.show()

输出:

再来补充一些使用DataFrame做数据分析时会使用到的操作,这些操作不仅常见而且也非常重要。

计算同比环比

之前讲过一个统计月度销售额的例子,可以通过groupby方法做分组聚合,也可以通过pivot_table生成透视表,如下所示。

sales_df = pd.read_excel('data/销售数据.xlsx')
sales_df['月份'] = sales_df.销售日期.dt.month
sales_df['销售额'] = sales_df.售价 * sales_df.销售数量
result_df = sales_df.pivot_table(index='月份', values='销售额', aggfunc='sum')
result_df.rename(columns={'销售额': '本月销售额'}, inplace=True)
result_df

输出:

      本月销售额
月份         
1       5409855
2       4608455
3       4164972
4       3996770
5       3239005
6       2817936
7       3501304
8       2948189
9       2632960
10      2375385
11      2385283
12      1691973

在得到月度销售额之后,如果需要计算月环比,这里有两种方案。第一种方案是可以使用shift方法对数据进行移动,将上一个月的数据与本月数据对齐,然后通过(本月销售额 - 上月销售额) / 上月销售额来计算月环比,代码如下所示。

result_df['上月销售额'] = result_df.本月销售额.shift(1)
result_df

输出:

      本月销售额      上月销售额
月份                    
1       5409855            NaN
2       4608455      5409855.0
3       4164972      4608455.0
4       3996770      4164972.0
5       3239005      3996770.0
6       2817936      3239005.0
7       3501304      2817936.0
8       2948189      3501304.0
9       2632960      2948189.0
10      2375385      2632960.0
11      2385283      2375385.0
12      1691973      2385283.0

在上面的例子中,shift方法的参数为1表示将数据向下移动一个单元,当然可以使用参数-1将数据向上移动一个单元。相信大家能够想到,如果有更多年份的数据,可以将参数设置为12,这样就可以计算今年的每个月与去年的每个月之间的同比。

result_df['环比'] = (result_df.本月销售额 - result_df.上月销售额) / result_df.上月销售额
result_df.style.format(
    formatter={'上月销售额': '{:.0f}', '环比': '{:.2%}'},
    na_rep='--------'
)

输出:

      本月销售额      上月销售额         环比
月份                    
1       5409855       --------     -------- 
2       4608455        5409855      -14.81%     
3       4164972        4608455       -9.62%
4       3996770        4164972       -4.04%
5       3239005        3996770      -18.96%
6       2817936        3239005      -13.00%
7       3501304        2817936       24.25%
8       2948189        3501304      -15.80%
9       2632960        2948189      -10.69%
10      2375385        2632960       -9.78%
11      2385283        2375385        0.42%
12      1691973        2385283      -29.07%

说明:使用 JupyterLab 时,可以通过DataFrame对象的style属性在网页中对其进行渲染,上面的代码通过Styler对象的format方法将环比格式化为百分比进行显示,此外还指定了将空值替换为--------

更为简单的第二种方案是直接使用pct_change方法计算变化的百分比,先将之前的上月销售额和环比列删除掉。

result_df.drop(columns=['上月销售额', '环比'], inplace=True)

接下来,使用DataFrame对象的pct_change方法完成环比的计算。值得一提的是,pct_change方法有一个名为periods的参数,它的默认值是1,计算相邻两项数据变化的百分比,这不就是想要的环比吗?如果有很多年的数据,在计算时把这个参数的值修改为12,就可以得到相邻两年的月同比。

result_df['环比'] = result_df.pct_change()
result_df

窗口计算

DataFrame对象的rolling方法允许将数据置于窗口中,然后用函数对窗口中的数据进行运算和处理。例如,获取了某只股票近期的数据,想制作5日均线和10日均线,那么就需要先设置窗口再进行运算。先用如下所示的代码读取2022年百度的股票数据,数据文件可以通过下面的链接来获取。

baidu_df = pd.read_excel('data/股票数据.xlsx', sheet_name='BIDU')
baidu_df.sort_index(inplace=True)
baidu_df

输出:

上面的DataFrameOpenHighLowCloseVolume五个列,分别代表股票的开盘价、最高价、最低价、收盘价和成交量,接下来对百度的股票数据进行窗口计算。

baidu_df.rolling(5).mean()

输出:

也可以在Series上使用rolling设置窗口并在窗口内完成运算,例如可以对上面的百度股票收盘价(Close列)计算5日均线和10日均线,并使用merge函数将其组装到一个DataFrame对象中并绘制出双均线图,代码如下所示。

close_ma5 = baidu_df.Close.rolling(5).mean()
close_ma10 = baidu_df.Close.rolling(10).mean()
result_df = pd.merge(close_ma5, close_ma10, left_index=True, right_index=True)
result_df.rename(columns={'Close_x': 'MA5', 'Close_y': 'MA10'}, inplace=True)
result_df.plot(kind='line', figsize=(10, 6))
plt.show()

输出:

相关性判定

在统计学中,我们通常使用协方差(covariance)来衡量两个随机变量的联合变化程度。简单的说,协方差的正负号显示着两个变量的相关性。方差是协方差的一种特殊情况,即变量与自身的协方差。

c o v ( X , Y ) = E ( ( X − μ ) ( Y − υ ) ) = E ( X ⋅ Y ) − μ υ cov(X,Y) = E((X - \mu)(Y - \upsilon)) = E(X \cdot Y) - \mu\upsilon cov(X,Y)=E((Xμ)(Yυ))=E(XY)μυ

如果 X X X Y Y Y 是统计独立的,那么二者的协方差为0,这是因为在 X X X Y Y Y 独立的情况下:

E ( X ⋅ Y ) = E ( X ) ⋅ E ( Y ) = μ υ E(X \cdot Y) = E(X) \cdot E(Y) = \mu\upsilon E(XY)=E(X)E(Y)=μυ

协方差的数值大小取决于变量的大小,通常是不容易解释的,但是正态形式的协方差可以显示两变量线性关系的强弱。在统计学中,皮尔逊积矩相关系数就是正态形式的协方差,它用于度量两个变量 X X X Y Y Y 之间的相关程度(线性相关),其值介于-11之间。

c o v ( X , Y ) σ X σ Y \frac {cov(X, Y)} {\sigma_{X}\sigma_{Y}} σXσYcov(X,Y)

估算样本的协方差和标准差,可以得到样本皮尔逊系数,通常用希腊字母 ρ \rho ρ 表示。

ρ = ∑ i = 1 n ( X i − X ˉ ) ( Y i − Y ˉ ) ∑ i = 1 n ( X i − X ˉ ) 2 ∑ i = 1 n ( Y i − Y ˉ ) 2 \rho = \frac {\sum_{i=1}^{n}(X_i - \bar{X})(Y_i - \bar{Y})} {\sqrt{\sum_{i=1}^{n}(X_i - \bar{X})^2} \sqrt{\sum_{i=1}^{n}(Y_i - \bar{Y})^2}} ρ=i=1n(XiXˉ)2 i=1n(YiYˉ)2 i=1n(XiXˉ)(YiYˉ)

ρ \rho ρ 值判断指标的相关性时遵循以下两个步骤。

  1. 判断指标间是正相关、负相关,还是不相关。
    • 当 $ \rho \gt 0 $,认为变量之间是正相关,也就是两者的趋势一致。
    • 当 $ \rho \lt 0 $,认为变量之间是负相关,也就是两者的趋势相反。
    • 当 $ \rho \approx 0 $,认为变量之间是不相关的,但并不代表两个指标是统计独立的。
  2. 判断指标间的相关程度。
    • 当 $ \rho $ 的绝对值在 $ [0.6,1] $ 之间,认为变量之间是强相关的。
    • 当 $ \rho $ 的绝对值在 $ [0.1,0.6) $ 之间,认为变量之间是弱相关的。
    • 当 $ \rho $ 的绝对值在 $ [0,0.1) $ 之间,认为变量之间没有相关性。

皮尔逊相关系数适用于:

1. 两个变量之间是线性关系,都是连续数据。
2. 两个变量的总体是正态分布,或接近正态的单峰分布。
3. 两个变量的观测值是成对的,每对观测值之间相互独立。

这里,顺便说一下,如果两组变量并不是来自于正态总体的连续值,该如何判断相关性呢?对于定序尺度(等级),可以使用斯皮尔曼秩相关系数,其计算公式如下所示:
r s = 1 − 6 ∑ d i 2 n ( n 2 − 1 ) r_{s}=1-{\frac {6\sum d_{i}^{2}}{n(n^{2}-1)}} rs=1n(n21)6di2
其中, d i = R ⁡ ( X i ) − R ⁡ ( Y i ) d_{i}=\operatorname {R} (X_{i})-\operatorname {R} (Y_{i}) di=R(Xi)R(Yi),即每组观测中两个变量的等级差值, n n n为观测样本数。

对于定类尺度(类别),可以使用卡方检验的方式来判定其是否相关。其实很多时候,连续值也可以通过分箱的方式处理成离散的等级或类别,然后使用斯皮尔曼秩相关系数或卡方检验的方式来判定相关性。由于在实际开发中不常用,在此就不在过多论述啦~~~

我们最后来看看Index类型,它为SeriesDataFrame对象提供了索引服务,有了索引就可以排序数据(sort_index方法)、对齐数据(在运算和合并数据时非常重要)并实现对数据的快速检索(索引运算)。由于DataFrame类型表示的是二维数据,所以它的行和列都有索引,分别是indexcolumnsIndex类型的创建的比较简单,通常给出datadtypename三个参数即可,分别表示作为索引的数据、索引的数据类型和索引的名称。由于Index本身也是一维的数据,索引它的方法和属性跟Series非常类似,你可以尝试创建一个Index对象,然后尝试一下之前学过的属性和方法在Index类型上是否生效。接下来,主要看看Index的几种子类型。

范围索引

范围索引是由具有单调性的整数构成的索引,可以通过RangeIndex构造器来创建范围索引,也可以通过RangeIndex类的类方法from_range来创建范围索引,代码如下所示。

代码:

sales_data = np.random.randint(400, 1000, 12)
index = pd.RangeIndex(1, 13, name='月份')
ser = pd.Series(data=sales_data, index=index)
ser

输出:

月份
1     703
2     705
3     557
4     943
5     961
6     615
7     788
8     985
9     921
10    951
11    874
12    609
dtype: int64

分类索引

分类索引是由定类尺度构成的索引。如果需要通过索引将数据分组,然后再进行聚合操作,分类索引就可以派上用场。分类索引还有一个名为reorder_categories的方法,可以给索引指定一个顺序,分组聚合的结果会按照这个指定的顺序进行呈现,代码如下。

代码:

sales_data = [6, 6, 7, 6, 8, 6]
index = pd.CategoricalIndex(
    data=['苹果', '香蕉', '苹果', '苹果', '桃子', '香蕉'],
    categories=['苹果', '香蕉', '桃子'],
    ordered=True
)
ser = pd.Series(data=sales_data, index=index)
ser

输出:

苹果    6
香蕉    6
苹果    7
苹果    6
桃子    8
香蕉    6
dtype: int64

基于索引分组数据,然后使用sum进行求和。

ser.groupby(level=0).sum()

输出:

苹果    19
香蕉    12
桃子     8
dtype: int64

指定索引的顺序。

ser.index = index.reorder_categories(['香蕉', '桃子', '苹果'])
ser.groupby(level=0).sum()

输出:

香蕉    12
桃子     8
苹果    19
dtype: int64

多级索引

Pandas 中的MultiIndex类型用来表示层次或多级索引。可以使用MultiIndex类的类方法from_arraysfrom_productfrom_tuples等来创建多级索引,给大家举几个例子。

代码:

tuples = [(1, 'red'), (1, 'blue'), (2, 'red'), (2, 'blue')]
index = pd.MultiIndex.from_tuples(tuples, names=['no', 'color'])
index

输出:

MultiIndex([(1,  'red'),
            (1, 'blue'),
            (2,  'red'),
            (2, 'blue')],
           names=['no', 'color'])

代码:

arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
index = pd.MultiIndex.from_arrays(arrays, names=['no', 'color'])
index

输出:

MultiIndex([(1,  'red'),
            (1, 'blue'),
            (2,  'red'),
            (2, 'blue')],
           names=['no', 'color'])

代码:

sales_data = np.random.randint(1, 100, 4)
ser = pd.Series(data=sales_data, index=index)
ser

输出:

no  color
1   red      43
    blue     31
2   red      55
    blue     75
dtype: int64

代码:

ser.groupby('no').sum()

输出:

no
1     74
2    130
dtype: int64

代码:

ser.groupby(level=1).sum()

输出:

color
blue    106
red      98
dtype: int64

代码:

stu_ids = np.arange(1001, 1006)
semisters = ['期中', '期末']
index = pd.MultiIndex.from_product((stu_ids, semisters), names=['学号', '学期'])
courses = ['语文', '数学', '英语']
scores = np.random.randint(60, 101, (10, 3))
df = pd.DataFrame(data=scores, columns=courses, index=index)
df

输出:

             语文 数学 英语
学号	学期			
1001  期中	93	77	60
      期末	93	98	84
1002  期中	64	78	71
      期末	70	71	97
1003  期中	72	88	97
      期末	99	100	63
1004  期中	80	71	61
      期末	91	62	72
1005  期中	82	95	67
      期末	84	78	86

根据第一级索引分组数据,按照期中成绩占25%,期末成绩占75% 的方式计算每个学生每门课的成绩。

代码:

df.groupby(level=0).agg(lambda x: x.values[0] * 0.25 + x.values[1] * 0.75)

输出:

        语文    数学    英语
学号			
1001	93.00	92.75	78.00
1002	68.50	72.75	90.50
1003	92.25	97.00	71.50
1004	88.25	64.25	69.25
1005	83.50	82.25	81.25

间隔索引

间隔索引顾名思义是使用固定的间隔范围充当索引,通常会使用interval_range函数来创建间隔索引,代码如下所示。

代码:

index = pd.interval_range(start=0, end=5)
index

输出:

IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]], dtype='interval[int64, right]')

IntervalIndex有一个名为contains的方法,可以检查范围内是否包含了某个元素,如下所示。

代码:

index.contains(1.5)

输出:

array([False,  True, False, False, False])

IntervalIndex还有一个名为overlaps的方法,可以检查一个范围跟其他的范围是否有重叠,如下所示。

代码:

index.overlaps(pd.Interval(1.5, 3.5))

输出:

array([False,  True,  True,  True, False])

如果希望间隔范围是左闭右开的状态,可以在创建间隔索引时通过closed='left'来做到;如果希望两边都是关闭状态,可以将close参数的值赋值为both,代码如下所示。

代码:

index = pd.interval_range(start=0, end=5, closed='left')
index

输出:

IntervalIndex([[0, 1), [1, 2), [2, 3), [3, 4), [4, 5)], dtype='interval[int64, left]')

代码:

index = pd.interval_range(start=pd.Timestamp('2022-01-01'), end=pd.Timestamp('2022-01-04'), closed='both')
index

输出:

IntervalIndex([[2022-01-01, 2022-01-02], [2022-01-02, 2022-01-03], [2022-01-03, 2022-01-04]], dtype='interval[datetime64[ns], both]')

日期时间索引

DatetimeIndex应该是众多索引中最复杂最重要的一种索引,通常会使用date_range()函数来创建日期时间索引,该函数有几个非常重要的参数startendperiodsfreqtz,分别代表起始日期时间、结束日期时间、生成周期、采样频率和时区。先来看看如何创建DatetimeIndex对象,再来讨论它的相关运算和操作,代码如下所示。

代码:

pd.date_range('2021-1-1', '2021-6-30', periods=10)

输出:

DatetimeIndex(['2021-01-01', '2021-01-21', '2021-02-10', '2021-03-02',
               '2021-03-22', '2021-04-11', '2021-05-01', '2021-05-21',
               '2021-06-10', '2021-06-30'],
              dtype='datetime64[ns]', freq=None)

代码:

pd.date_range('2021-1-1', '2021-6-30', freq='W')

说明freq=W表示采样周期为一周,它会默认星期日是一周的开始;如果你希望星期一表示一周的开始,你可以将其修改为freq=W-MON;你也可以试着将该参数的值修改为12HMQ等,看看会发生什么,相信你不难发现它们的含义。

输出:

DatetimeIndex(['2021-01-03', '2021-01-10', '2021-01-17', '2021-01-24',
               '2021-01-31', '2021-02-07', '2021-02-14', '2021-02-21',
               '2021-02-28', '2021-03-07', '2021-03-14', '2021-03-21',
               '2021-03-28', '2021-04-04', '2021-04-11', '2021-04-18',
               '2021-04-25', '2021-05-02', '2021-05-09', '2021-05-16',
               '2021-05-23', '2021-05-30', '2021-06-06', '2021-06-13',
               '2021-06-20', '2021-06-27'],
              dtype='datetime64[ns]', freq='W-SUN')

DatatimeIndex可以跟DateOffset类型进行运算,这一点很好理解,以为可以设置一个时间差让时间向前或向后偏移,具体的操作如下所示。

代码:

index = pd.date_range('2021-1-1', '2021-6-30', freq='W')
index - pd.DateOffset(days=2)

输出:

DatetimeIndex(['2021-01-01', '2021-01-08', '2021-01-15', '2021-01-22',
               '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19',
               '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19',
               '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16',
               '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14',
               '2021-05-21', '2021-05-28', '2021-06-04', '2021-06-11',
               '2021-06-18', '2021-06-25'],
              dtype='datetime64[ns]', freq=None)

代码:

index + pd.DateOffset(hours=2, minutes=10)

输出:

DatetimeIndex(['2021-01-03 02:10:00', '2021-01-10 02:10:00',
               '2021-01-17 02:10:00', '2021-01-24 02:10:00',
               '2021-01-31 02:10:00', '2021-02-07 02:10:00',
               '2021-02-14 02:10:00', '2021-02-21 02:10:00',
               '2021-02-28 02:10:00', '2021-03-07 02:10:00',
               '2021-03-14 02:10:00', '2021-03-21 02:10:00',
               '2021-03-28 02:10:00', '2021-04-04 02:10:00',
               '2021-04-11 02:10:00', '2021-04-18 02:10:00',
               '2021-04-25 02:10:00', '2021-05-02 02:10:00',
               '2021-05-09 02:10:00', '2021-05-16 02:10:00',
               '2021-05-23 02:10:00', '2021-05-30 02:10:00',
               '2021-06-06 02:10:00', '2021-06-13 02:10:00',
               '2021-06-20 02:10:00', '2021-06-27 02:10:00'],
              dtype='datetime64[ns]', freq=None)

如果Series对象或DataFrame对象使用了DatetimeIndex类型的索引,此时可以通过asfreq()方法指定一个时间频率来实现对数据的抽样,我们仍然以之前讲过的百度股票数据为例,给大家做一个演示。

代码:

baidu_df = pd.read_excel('data/股票数据.xlsx', sheet_name='BIDU', index_col='Date')
baidu_df.sort_index(inplace=True)
baidu_df.asfreq('5D')

输出:

大家可能注意到了,每5天抽取1天有可能会抽中非交易日,那么对应的列都变成了空值,为了解决这个问题,在使用asfreq方法时可以通过method参数来指定一种填充空值的方法,可以将相邻的交易日的数据填入进来。

代码:

baidu_df.asfreq('5D', method='ffill')

输出:

当使用DatetimeIndex索引时,也可以通过resample()方法基于时间对数据进行重采样,相当于根据时间周期对数据进行了分组操作,分组之后还可以进行聚合统计,代码如下所示。

代码:

baidu_df.resample('1M').mean()

输出:

代码:

baidu_df.resample('1M').agg(['mean', 'std'])

输出:

提示:不知大家是否注意到,上面输出的DataFrame 的列索引是一个MultiIndex对象。你可以访问上面的DataFrame对象的columns属性看看。

如果要实现日期时间的时区转换,可以先用tz_localize()方法将日期时间本地化,代码如下所示。

代码:

baidu_df = baidu_df.tz_localize('Asia/Chongqing')
baidu_df

输出:

在对时间本地化以后,再使用tz_convert()方法就可以实现转换时区,代码如下所示。

代码:

baidu_df.tz_convert('America/New_York')

输出:

至此,Pandas所有内容到此结束啦,此篇文章对后续深度学习有非常重要参考意义。如果文章能帮助到您,还请各位点个关注~~~

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值