73.Pandas的应用-4.md 15.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
## Pandas的应用-4

### DataFrame的应用

#### 数据分析

经过前面的学习,我们已经将数据准备就绪而且变成了我们想要的样子,接下来就是最为重要的数据分析阶段了。当我们拿到一大堆数据的时候,如何从数据中迅速的解读出有价值的信息,这就是数据分析要解决的问题。首先,我们可以获取数据的描述性统计信息,通过描述性统计信息,我们可以了解数据的集中趋势和离散趋势。

例如,我们有如下所示的学生成绩表。

```Python
import numpy as np
import pandas as pd

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`对象的方法`mean``max``min``std``var`等方法分别获取每个学生或每门课程的平均分、最高分、最低分、标准差、方差等信息,也可以直接通过`describe`方法直接获取描述性统计信息,代码如下所示。

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

```Python
df.mean()
```

输出:

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

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

```Python
df.mean(axis=1)
```

输出:

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

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

```Python
df.var()
```

输出:

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

> **说明**:通过方差可以看出,数学成绩波动最大,最不稳定。

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

```Python
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
```

##### 排序和Top-N

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

```Python
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的数据。`DataFrame``nlargest``nsmallest`方法就提供对Top-N操作的支持,代码如下所示。

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

```Python
df.nlargest(3, '语文')
```

输出:

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

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

```Python
df.nsmallest(3, '数学')
```

输出:

```
      语文  数学  英语
赵云  74    51	79
马超  100   54	54
张飞  72    70	97
```

##### 分组聚合操作

我们先从 Excel 文件中读取一组销售数据,然后再为大家演示如何进行分组聚合操作。

```Python
df = pd.read_excel('2020年销售数据.xlsx')
df.head()
```

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
164
> **说明**:如果需要上面例子中的 Excel 文件,可以通过百度云盘进行获取。链接:https://pan.baidu.com/s/1NhWtYcpFzF72cxcsoDoXjQ?pwd=swg1,提取码:swg1。
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377

输出:

```
    销售日期	 销售区域   销售渠道  销售订单     品牌    售价  销售数量
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`添加一个列,代码如下所示。

```Python
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`方法。分组之后,我们取“销售额”这个列在分组内进行求和处理,代码和结果如下所示。

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

输出:

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

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

```Python
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`方法的第一个参数可以是一个列表,列表中可以指定多个分组的依据,大家看看下面的代码和输出结果就明白了。

```Python
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
南京     7        841032
        10       710962
        12       215307
安徽     4        341308
         5        554155
广东     3        388180
         8        469390
         9        365191
        11       395188
江苏     4        537079
浙江     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
```

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

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

输出:

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

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

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

输出:

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

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

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

输出:

```
         销售额        销售数量
         mean          max    min
销售区域			
上海     20622.538188  100    10
北京     20125.350000  100    10
南京     22370.898734  100    11
安徽     26337.147059  98     16
广东     32358.980000  98     10
江苏     29837.722222  98     15
浙江     27514.480000  95     20
福建     18306.163669  100    10
```

##### 透视表和交叉表

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

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

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
378
<img src="https://github.com/jackfrued/mypic/raw/master/20211106180912.png" style="zoom:50%">
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

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

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

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

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

```Python
pd.pivot_table(
    df, index='销售区域', columns=df['销售日期'].dt.month, 
    values='销售额', aggfunc='sum', fill_value=0
)
```

> **说明**:`pivot_table`函数的`fill_value=0`会将空值处理为`0`。

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
401
<img src="https://github.com/jackfrued/mypic/raw/master/20211106104551.png" style="zoom:50%">
402 403 404 405 406 407 408 409 410 411 412 413 414 415

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

```Python
df['月份'] = df['销售日期'].dt.month
pd.pivot_table(
    df, index='销售区域', columns='月份',
    values='销售额', aggfunc='sum', fill_value=0, 
    margins=True, margins_name='总计'
)
```

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
416
![image-20211106181707655](https://github.com/jackfrued/mypic/raw/master/20211106181707.png)
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466

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

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

使用`crosstab`函数生成交叉表。

```Python
pd.crosstab(
    index=sales_area, columns=sales_month, values=sales_amount, aggfunc='sum'
).fillna(0).applymap(int)
```

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

#### 数据可视化

一图胜千言,我们对数据进行透视的结果,最终要通过图表的方式呈现出来,因为图表具有极强的表现力,能够让我们迅速的解读数据中隐藏的价值。和`Series`一样,`DataFrame`对象提供了`plot`方法来支持绘图,底层仍然是通过`matplotlib`库实现图表的渲染。关于`matplotlib`的内容,我们在下一个章节进行详细的探讨,这里我们只简单的讲解`plot`方法的用法。 

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

```Python
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = 'FZJKai-Z03S'
```

> **说明**:上面的`FZJKai-Z03S`是我电脑上已经安装的一种支持中文的字体的名称,字体的名称可以通过查看用户主目录下`.matplotlib`文件夹下名为`fontlist-v330.json`的文件来获得,而这个文件在执行上面的命令后就会生成。

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

```Python
%config InlineBackend.figure_format = 'svg'
```

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

```Python
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行代码会显示图像。

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
467
<img src="https://github.com/jackfrued/mypic/raw/master/20211106195040.png" style="zoom:50%">
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482

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

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

输出:

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
483
<img src="https://github.com/jackfrued/mypic/raw/master/20211106201550.png" style="zoom:50%">
484