Merge branch 'jackfrued:master' into master

pull/668/head
唐凯泽 2021-12-01 22:27:17 +08:00 committed by GitHub
commit d1c8084892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
188 changed files with 52336 additions and 3811 deletions

View File

@ -10,7 +10,7 @@
4. 2000年10月16日Python 2.0发布,增加了完整的[垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)),提供了对[Unicode](https://zh.wikipedia.org/wiki/Unicode)的支持。与此同时Python的整个开发过程更加透明社区对开发进度的影响逐渐扩大生态圈开始慢慢形成。
5. 2008年12月3日Python 3.0发布它并不完全兼容之前的Python代码不过因为目前还有不少公司在项目和运维中使用Python 2.x版本所以Python 3.x的很多新特性后来也被移植到Python 2.6/2.7版本中。
目前我使用的Python 3.7.x的版本是在2018年发布的Python的版本号分为三段形如A.B.C。其中A表示大版本号一般当整体重写或出现不向后兼容的改变时增加AB表示功能更新出现新功能时增加BC表示小的改动例如修复了某个Bug只要有修改就增加C。如果对Python的历史感兴趣可以阅读名为[《Python简史》](http://www.cnblogs.com/vamei/archive/2013/02/06/2892628.html)的网络文章。
目前我使用的Python 3.7.x的版本是在2018年发布的Python的版本号分为三段形如A.B.C。其中A表示大版本号一般当整体重写或出现不向后兼容的改变时增加AB表示功能更新出现新功能时增加BC表示小的改动例如修复了某个Bug只要有修改就增加C。如果对Python的历史感兴趣可以阅读名为[《Python简史》](http://www.cnblogs.com/vamei/archive/2013/02/06/2892628.html)的网络文章。
#### Python的优缺点
@ -218,7 +218,7 @@ pip3 install ipython
#### PyCharm - Python开发神器
PyCharm的安装、配置和使用在[《玩转PyCharm》](../玩转PyCharm.md)进行了介绍,有兴趣的读者可以选择阅读。
PyCharm的安装、配置和使用在[《玩转PyCharm》](../番外篇/玩转PyCharm.md)进行了介绍,有兴趣的读者可以选择阅读。
![](./res/python-pycharm.png)

View File

@ -193,7 +193,7 @@ c = (f - 32) / 1.8
print('%.1f华氏度 = %.1f摄氏度' % (f, c))
```
> **说明**:在使用`print`函数输出时,也可以对字符串内容进行格式化处理,上面`print`函数中的字符串`%1.f`是一个占位符,稍后会由一个`float`类型的变量值替换掉它。同理,如果字符串中有`%d`,后面可以用一个`int`类型的变量值替换掉它,而`%s`会被字符串的值替换掉。除了这种格式化字符串的方式外,还可以用下面的方式来格式化字符串,其中`{f:.1f}`和`{c:.1f}`可以先看成是`{f}`和`{c}`,表示输出时会用变量`f`和变量`c`的值替换掉这两个占位符,后面的`:.1f`表示这是一个浮点数小数点后保留1位有效数字。
> **说明**:在使用`print`函数输出时,也可以对字符串内容进行格式化处理,上面`print`函数中的字符串`%.1f`是一个占位符,稍后会由一个`float`类型的变量值替换掉它。同理,如果字符串中有`%d`,后面可以用一个`int`类型的变量值替换掉它,而`%s`会被字符串的值替换掉。除了这种格式化字符串的方式外,还可以用下面的方式来格式化字符串,其中`{f:.1f}`和`{c:.1f}`可以先看成是`{f}`和`{c}`,表示输出时会用变量`f`和变量`c`的值替换掉这两个占位符,后面的`:.1f`表示这是一个浮点数小数点后保留1位有效数字。
>
> ```Python
> print(f'{f:.1f}华氏度 = {c:.1f}摄氏度')

View File

@ -168,7 +168,7 @@ y = int(input('y = '))
if x > y:
# 通过下面的操作将y的值赋给x, 将x的值赋给y
x, y = y, x
# 从两个数中较的数开始做递减的循环
# 从两个数中较的数开始做递减的循环
for factor in range(x, 0, -1):
if x % factor == 0 and y % factor == 0:
print('%d和%d的最大公约数是%d' % (x, y, factor))

View File

@ -200,7 +200,7 @@ from module1 import foo
foo()
```
需要说明的是,如果我们导入的模块除了定义函数之外还有可以执行代码那么Python解释器在导入这个模块时就会执行这些代码事实上我们可能并不希望如此因此如果我们在模块中编写了执行代码最好是将这些执行代码放入如下所示的条件中这样的话除非直接运行该模块if条件下的这些代码是不会执行的因为只有直接执行的模块的名字才是"\_\_main\_\_"
需要说明的是如果我们导入的模块除了定义函数之外还有可以执行代码那么Python解释器在导入这个模块时就会执行这些代码事实上我们可能并不希望如此因此如果我们在模块中编写了执行代码最好是将这些执行代码放入如下所示的条件中这样的话除非直接运行该模块if条件下的这些代码是不会执行的因为只有直接执行的模块的名字才是"\_\_main\_\_"
`module3.py`

View File

@ -2,7 +2,7 @@
### 使用字符串
第二次世界大战促使了现代电子计算机的诞生最初计算机被应用于导弹弹道的计算而在计算机诞生后的很多年时间里计算机处理的信息基本上都是数值型的信息。世界上的第一台电子计算机叫ENIAC电子数值积分计算机诞生于美国的宾夕法尼亚大学每秒钟能够完成约5000次浮点运算。随着时间的推移虽然数值运算仍然是计算机日常工作中最为重要的事情之一但是今天的计算机处理得更多的数据可能都是以文本的方式存在的如果我们希望通过Python程序操作这些文本信息,就必须要先了解字符串类型以及与它相关的知识。
第二次世界大战促使了现代电子计算机的诞生最初计算机被应用于导弹弹道的计算而在计算机诞生后的很多年时间里计算机处理的信息基本上都是数值型的信息。世界上的第一台电子计算机叫ENIAC电子数值积分计算机诞生于美国的宾夕法尼亚大学每秒钟能够完成约5000次浮点运算。随着时间的推移虽然数值运算仍然是计算机日常工作中最为重要的事情之一但是今天的计算机处理得更多的数据可能都是以文本的方式存在的如果我们希望通过Python程序操作这些文本信息就必须要先了解字符串类型以及与它相关的知识。
所谓**字符串**,就是由零个或多个字符组成的有限序列,一般记为![$${\displaystyle s=a_{1}a_{2}\dots a_{n}(0\leq n \leq \infty)}$$](./res/formula_5.png)。在Python程序中如果我们把单个或多个字符用单引号或者双引号包围起来就可以表示一个字符串。

View File

@ -316,7 +316,7 @@ if __name__ == '__main__':
但是切换作业是有代价的比如从语文切到数学要先收拾桌子上的语文书本、钢笔这叫保存现场然后打开数学课本、找出圆规直尺这叫准备新环境才能开始做数学作业。操作系统在切换进程或者线程时也是一样的它需要先保存当前执行的现场环境CPU寄存器状态、内存页等然后把新任务的执行环境准备好恢复上次的寄存器状态切换内存页等才能开始执行。这个切换过程虽然很快但是也需要耗费时间。如果有几千个任务同时进行操作系统可能就主要忙着切换任务根本没有多少时间去执行任务了这种情况最常见的就是硬盘狂响点窗口无反应系统处于假死状态。所以多任务一旦多到一个限度反而会使得系统性能急剧下降最终导致所有任务都做不好。
是否采用多任务的第二个考虑是任务的类型可以把任务分为计算密集型和I/O密集型。计算密集型任务的特点是要进行大量的计算消耗CPU资源比如对视频进行编码解码或者格式转换等等这种任务全靠CPU的运算能力虽然也可以用多任务完成但是任务越多花在任务切换的时间就越多CPU执行任务的效率就越低。计算密集型任务由于主要消耗CPU资源这类任务用Python这样的脚本语言去执行效率通常很低最能胜任这类任务的是C语言我们之前提到Python中有嵌入C/C++代码的机制。
是否采用多任务的第二个考虑是任务的类型可以把任务分为计算密集型和I/O密集型。计算密集型任务的特点是要进行大量的计算消耗CPU资源比如对视频进行编码解码或者格式转换等等这种任务全靠CPU的运算能力虽然也可以用多任务完成但是任务越多花在任务切换的时间就越多CPU执行任务的效率就越低。计算密集型任务由于主要消耗CPU资源这类任务用Python这样的脚本语言去执行效率通常很低最能胜任这类任务的是C语言我们之前提到Python中有嵌入C/C++代码的机制。
除了计算密集型任务其他的涉及到网络、存储介质I/O的任务都可以视为I/O密集型任务这类任务的特点是CPU消耗很少任务的大部分时间都在等待I/O操作完成因为I/O的速度远远低于CPU和内存的速度。对于I/O密集型任务如果启动多任务就可以减少I/O等待时间从而让CPU高效率的运转。有一大类的任务都属于I/O密集型任务这其中包括了我们很快会涉及到的网络应用和Web应用。
@ -324,9 +324,9 @@ if __name__ == '__main__':
### 单线程+异步I/O
现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持就可以用单进程单线程模型来执行多任务这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上可以运行多个进程数量与CPU核心数相同充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式这也是当下实现多任务编程的一种趋势
现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持就可以用单进程单线程模型来执行多任务这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上可以运行多个进程数量与CPU核心数相同充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式这也是当下并发编程的一种流行方案
在Python语言中单线程+异步I/O的编程模型称为协程有了协程的支持就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率因为子程序切换不是线程切换而是由程序自身控制因此没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制因为只有一个线程也不存在同时写变量冲突在协程中控制共享资源不用加锁只需要判断状态就好了所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。关于这方面的内容,我稍后会做一个专题来进行讲解。
在Python语言中单线程+异步I/O的编程模型称为协程有了协程的支持就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率因为子程序切换不是线程切换而是由程序自身控制因此没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制因为只有一个线程也不存在同时写变量冲突在协程中控制共享资源不用加锁只需要判断状态就好了所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。关于这方面的内容,在后续的课程中会进行讲解。
### 应用案例

View File

@ -9,7 +9,7 @@ Date: 2018-03-02
"""
import math
for num in range(1, 10000):
for num in range(2, 10000):
result = 0
for factor in range(1, int(math.sqrt(num)) + 1):
if num % factor == 0:

View File

@ -10,24 +10,23 @@ Date: 2018-03-20
# 每个进程都有自己独立的内存空间 所以进程之间共享数据只能通过IPC的方式
from multiprocessing import Process, Queue
from multiprocessing import Process, Queue, current_process
from time import sleep
def sub_task(string, q):
number = q.get()
while number:
print('%d: %s' % (number, string))
sleep(0.001)
number = q.get()
def sub_task(content, counts):
print(f'PID: {current_process().pid}')
counter = 0
while counter < counts:
counter += 1
print(f'{counter}: {content}')
sleep(0.01)
def main():
q = Queue(10)
for number in range(1, 11):
q.put(number)
Process(target=sub_task, args=('Ping', q)).start()
Process(target=sub_task, args=('Pong', q)).start()
number = random.randrange(5, 10)
Process(target=sub_task, args=('Ping', number)).start()
Process(target=sub_task, args=('Pong', number)).start()
if __name__ == '__main__':

View File

@ -85,7 +85,7 @@
常用的工具类:
- `namedtuple`:命令元组,它是一个类工厂,接受类型的名称和属性列表来创建一个类。
- `deque`双端队列是列表的替代实现。Python中的列表底层是基于数组来实现的而deque底层是双向链表因此当你需要在头尾添加和删除元素deque会表现出更好的性能渐近时间复杂度为$O(1)$。
- `deque`双端队列是列表的替代实现。Python中的列表底层是基于数组来实现的而deque底层是双向链表因此当你需要在头尾添加和删除元素deque会表现出更好的性能渐近时间复杂度为$O(1)$。
- `Counter``dict`的子类,键是元素,值是元素的计数,它的`most_common()`方法可以帮助我们获取出现频率最高的元素。`Counter`和`dict`的继承关系我认为是值得商榷的按照CARP原则`Counter`跟`dict`的关系应该设计为关联关系更为合理。
- `OrderedDict``dict`的子类,它记录了键值对插入的顺序,看起来既有字典的行为,也有链表的行为。
- `defaultdict`:类似于字典类型,但是可以通过默认的工厂函数来获得键对应的默认值,相比字典中的`setdefault()`方法,这种做法更加高效。
@ -1110,19 +1110,6 @@ Python中实现并发编程的三种方案多线程、多进程和异步I/O
self.balance = new_balance
class AddMoneyThread(threading.Thread):
"""自定义线程类"""
def __init__(self, account, money):
self.account = account
self.money = money
# 自定义线程的初始化方法中必须调用父类的初始化方法
super().__init__()
def run(self):
# 线程启动之后要执行的操作
self.account.deposit(self.money)
def main():
"""主函数"""
account = Account()
@ -1130,14 +1117,6 @@ Python中实现并发编程的三种方案多线程、多进程和异步I/O
pool = ThreadPoolExecutor(max_workers=10)
futures = []
for _ in range(100):
# 创建线程的第1种方式
# threading.Thread(
# target=account.deposit, args=(1, )
# ).start()
# 创建线程的第2种方式
# AddMoneyThread(account, 1).start()
# 创建线程的第3种方式
# 调用线程池中的线程来执行特定的任务
future = pool.submit(account.deposit, 1)
futures.append(future)
# 关闭线程池
@ -1150,9 +1129,9 @@ Python中实现并发编程的三种方案多线程、多进程和异步I/O
if __name__ == '__main__':
main()
```
修改上面的程序启动5个线程向账户中存钱5个线程从账户中取钱取钱时如果余额不足就暂停线程进行等待。为了达到上述目标需要对存钱和取钱的线程进行调度在余额不足时取钱的线程暂停并释放锁而存钱的线程将钱存入后要通知取钱的线程使其从暂停状态被唤醒。可以使用`threading`模块的`Condition`来实现线程调度,该对象也是基于锁来创建的,代码如下所示:
```Python
"""
多个线程竞争一个资源 - 保护临界资源 - 锁Lock/RLock
@ -1222,7 +1201,7 @@ Python中实现并发编程的三种方案多线程、多进程和异步I/O
if __name__ == '__main__':
main()
```
- 多进程多进程可以有效的解决GIL的问题实现多进程主要的类是`Process`,其他辅助的类跟`threading`模块中的类似,进程间共享数据可以使用管道、套接字等,在`multiprocessing`模块中有一个`Queue`类,它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。
```Python

View File

@ -2,6 +2,8 @@
> **说明**:本文使用的部分插图来自*Jon Duckett*先生的*[HTML and CSS: Design and Build Websites](https://www.amazon.cn/dp/1118008189/ref=sr_1_5?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&keywords=html+%26+css&qid=1554609325&s=gateway&sr=8-5)*一书,这是一本非常棒的前端入门书,有兴趣的读者可以在亚马逊或者其他网站上找到该书的购买链接。
HTML 是用来描述网页的一种语言,全称是 Hyper-Text Markup Language即超文本标记语言。我们浏览网页时看到的文字、按钮、图片、视频等元素它们都是通过 HTML 书写并通过浏览器来呈现的。
### HTML简史
1. 1991年10月一个非正式CERN[欧洲核子研究中心](https://zh.wikipedia.org/wiki/%E6%AD%90%E6%B4%B2%E6%A0%B8%E5%AD%90%E7%A0%94%E7%A9%B6%E7%B5%84%E7%B9%94)文件首次公开18个HTML标签这个文件的作者是物理学家[蒂姆·伯纳斯-李](https://zh.wikipedia.org/wiki/%E8%92%82%E5%A7%86%C2%B7%E4%BC%AF%E7%BA%B3%E6%96%AF-%E6%9D%8E),因此他是[万维网](https://zh.wikipedia.org/wiki/%E4%B8%87%E7%BB%B4%E7%BD%91)的发明者,也是[万维网联盟](https://zh.wikipedia.org/wiki/%E4%B8%87%E7%BB%B4%E7%BD%91%E8%81%94%E7%9B%9F)的主席。
@ -25,6 +27,10 @@
### 使用标签承载内容
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211107163448.png" style="zoom:35%">
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211107163741.png" style="zoom:75%">
#### 结构
- html
@ -93,11 +99,11 @@
- 重要属性 - action / method / enctype
- 表单控件input- type属性
- 文本框 - text / 密码框 - password / 数字框 - number
- 邮箱 - email / 电话 - tel / 日期 - date / 滑条 - range / URL - url / 搜索 - search
- 单选按钮 - radio / 复选按钮 - checkbox
- 文件上传 - file / 隐藏域 - hidden
- 提交按钮 - submit / 图像按钮 - image / 重置按钮 - reset
- 文本框 - `text` / 密码框 - `password` / 数字框 - `number`
- 邮箱 - `email` / 电话 - `tel` / 日期 - `date` / 滑条 - `range` / URL - `url` / 搜索 - `search`
- 单选按钮 - `radio` / 复选按钮 - `checkbox`
- 文件上传 - `file` / 隐藏域 - `hidden`
- 提交按钮 - `submit` / 图像按钮 - `image` / 重置按钮 - `reset`
- 下拉列表 - select / option
- 文本域(多行文本)- textarea
- 组合表单元素 - fieldset / legend
@ -259,7 +265,7 @@
- 赋值运算符
- 算术运算符
- 比较运算符
- 逻辑运算符
- 逻辑运算符`&&`、`||`、`!`
- 分支结构
- `if...else...`
- `switch...cas...default...`

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>垃圾分类查询助手</title>
<style>
.search, .result {
width: 720px;
margin: 50px auto;
}
.search > input {
width: 520px;
border: none;
outline: none;
text-align: center;
font-size: 36px;
line-height: 36px;
border-bottom: 1px solid gray;
margin: 0 20px;
}
.search button {
background-color: red;
color: white;
font-size: 28px;
border: none;
outline: none;
width: 120px;
}
.result > p, .result > div {
width: 640px;
margin: 0 auto;
}
.result > p, .result span {
text-align: left;
font-size: 28px;
}
.result img {
vertical-align: middle;
}
.explain {
font-size: 12px;
color: darkgray;
}
.result .pre {
font-size: 16px;
}
</style>
</head>
<body>
<div id="app">
<div class="search">
<input type="text" placeholder="请输入垃圾名字" v-model.trim="word" @keydown.enter="search()">
<button @click="search()">查询</button>
</div>
<div class="result">
<p v-if="searched && !results">没有对应的查询结果</p>
<div v-for="result in results">
<p>
<img :src="'images/' + pictures[result.type]" width="56" :alt="types[result.type]">
&nbsp;&nbsp;
<span>{{ result.name }}</span>
&nbsp;&nbsp;
<span class="pre" v-if="result.aipre == 1">(预测结果)</span>
</p>
<p class="explain">说明:{{ result.explain }}</p>
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
word: '',
searched: false,
types: ['可回收物', '有害垃圾', '厨余垃圾', '其他垃圾'],
pictures: ['recyclable.png', 'harmful-waste.png', 'kitchen-waste.png', 'other-waste.png'],
results: []
},
methods: {
search() {
if (this.word.trim().length > 0) {
let key = 'e8c5524dd2a365f20908ced735f8e480'
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
fetch(url)
.then(resp => resp.json())
.then(json => {
this.searched = true
this.results = json.newslist
})
}
}
}
})
</script>
</body>
</html>

View File

@ -102,7 +102,7 @@
// 查询垃圾分类的函数
search() {
if (this.word.trim().length > 0) {
let key = '9636cec76ee2593ba6b195e5b770b394'
let key = 'e8c5524dd2a365f20908ced735f8e480'
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
fetch(url)
.then(resp => resp.json())

View File

@ -748,7 +748,7 @@ Linux系统的命令通常都是如下所示的格式
4. 查看和修改密码有效期 - **chage**
设置hellokitty用户100天后必须修改密码过期前15天通知该用户过期后15天禁用该用户。
设置hellokitty用户100天后必须修改密码过期前15天通知该用户过期后7天禁用该用户。
```Shell
chage -M 100 -W 15 -I 7 hellokitty
@ -954,7 +954,7 @@ Linux系统的命令通常都是如下所示的格式
8. 创建/激活/关闭交换分区 - **mkswap** / **swapon** / **swapoff**
> 说明:执行上面这些命令会带有一定的风险,如果不清楚这些命令的用法,最好不用随意使用,在使用的过程中,最好对照参考资料进行操作,并在操作前确认是否要这么做。
> **说明**:执行上面这些命令会带有一定的风险,如果不清楚这些命令的用法,最好不用随意使用,在使用的过程中,最好对照参考资料进行操作,并在操作前确认是否要这么做。
### 编辑器 - vim

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,388 @@
## 关系型数据库和MySQL概述
### 关系型数据库概述
1. 数据持久化 - 将数据保存到能够长久保存数据的存储介质中,在掉电的情况下数据也不会丢失。
2. 数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL 数据库、NewSQL 数据库。
> 1970年IBM的研究员E.F.Codd在*Communication of the ACM*上发表了名为*A Relational Model of Data for Large Shared Data Banks*的论文,提出了**关系模型**的概念奠定了关系模型的理论基础。后来Codd又陆续发表多篇文章论述了范式理论和衡量关系系统的12条标准用数学理论奠定了关系数据库的基础。
3. 关系数据库特点。
- 理论基础:**关系代数**(关系运算、集合论、一阶谓词逻辑)。
- 具体表象:用**二维表**(有行和列)组织数据。
- 编程语言:**结构化查询语言**SQL
4. ER模型实体关系模型和概念模型图。
**ER模型**,全称为**实体关系模型**Entity-Relationship Model由美籍华裔计算机科学家陈品山先生提出是概念数据模型的高层描述方式如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210826003119.png" width="75%">
- 实体 - 矩形框
- 属性 - 椭圆框
- 关系 - 菱形框
- 重数 - 1:1一对一 / 1:N一对多 / M:N多对多
实际项目开发中我们可以利用数据库建模工具PowerDesigner来绘制概念数据模型其本质就是 ER 模型),然后再设置好目标数据库系统,将概念模型转换成物理模型,最终生成创建二维表的 SQL很多工具都可以根据我们设计的物理模型图以及设定的目标数据库来导出 SQL 或直接生成数据表)。
![](https://gitee.com/jackfrued/mypic/raw/master/20210826003212.png)
5. 关系数据库产品。
- [Oracle](https://www.oracle.com/index.html) - 目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系数据库,它是一个完备关系的产品;作为分布式数据库,它实现了分布式处理的功能。在 Oracle 最新的 12c 版本中,还引入了多承租方架构,使用该架构可轻松部署和管理数据库云。
- [DB2](https://www.ibm.com/analytics/us/en/db2/) - IBM 公司开发的、主要运行于 Unix包括 IBM 自家的 [AIX](https://zh.wikipedia.org/wiki/AIX)、Linux、以及 Windows 服务器版等系统的关系数据库产品。DB2 历史悠久且被认为是最早使用 SQL 的数据库产品,它拥有较为强大的商业智能功能。
- [SQL Server](https://www.microsoft.com/en-us/sql-server/) - 由 Microsoft 开发和推广的关系型数据库产品,最初适用于中小企业的数据管理,但是近年来它的应用范围有所扩展,部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。
- [MySQL](https://www.mysql.com/) - MySQL 是开放源代码的,任何人都可以在 GPLGeneral Public License的许可下下载并根据个性化的需要对其进行修改。MySQL 因为其速度、可靠性和适应性而备受关注。
- [PostgreSQL]() - 在 BSD 许可证下发行的开放源代码的关系数据库产品。
### MySQL 简介
MySQL 最早是由瑞典的 MySQL AB 公司开发的一个开放源码的关系数据库管理系统该公司于2008年被昇阳微系统公司Sun Microsystems收购。在2009年甲骨文公司Oracle收购昇阳微系统公司因此 MySQL 目前也是 Oracle 旗下产品。
MySQL 在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用于中小型网站开发。随着 MySQL 的不断成熟它也逐渐被应用于更多大规模网站和应用比如维基百科、谷歌Google、脸书Facebook、淘宝网等网站都使用了 MySQL 来提供数据持久化服务。
甲骨文公司收购后昇阳微系统公司,大幅调涨 MySQL 商业版的售价,且甲骨文公司不再支持另一个自由软件项目 [OpenSolaris ](https://zh.wikipedia.org/wiki/OpenSolaris) 的发展,因此导致自由软件社区对于 Oracle 是否还会持续支持 MySQL 社区版MySQL 的各个发行版本中唯一免费的版本有所担忧MySQL 的创始人麦克尔·维德纽斯以 MySQL 为基础,创建了 [MariaDB](https://zh.wikipedia.org/wiki/MariaDB)(以他女儿的名字命名的数据库)分支。有许多原来使用 MySQL 数据库的公司(例如:维基百科)已经陆续完成了从 MySQL 数据库到 MariaDB 数据库的迁移。
### 安装 MySQL
#### Windows 环境
1. 通过[官方网站](https://www.mysql.com/)提供的[下载链接](https://dev.mysql.com/downloads/windows/installer/8.0.html)下载“MySQL社区版服务器”安装程序如下图所示建议大家下载离线安装版的MySQL Installer。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105230905.png" style="zoom:50%">
2. 运行 Installer按照下面的步骤进行安装。
- 选择自定义安装。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231152.jpg" style="zoom:35%">
- 选择需要安装的组件。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231255.jpg" style="zoom:35%">
- 如果缺少依赖项,需要先安装依赖项。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231620.png" style="zoom:35%">
- 准备开始安装。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231719.jpg" style="zoom:35%">
- 安装完成。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232024.jpg" style="zoom:35%">
- 准备执行配置向导。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231815.jpg" style="zoom:35%">
3. 执行安装后的配置向导。
- 配置服务器类型和网络。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232109.jpg" style="zoom:35%">
- 配置认证方法(保护密码的方式)。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232408.jpg" style="zoom:35%">
- 配置用户和角色。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232521.jpg" style="zoom:35%">
- 配置Windows服务名以及是否开机自启。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232608.jpg" style="zoom:35%">
- 配置日志。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232641.jpg" style="zoom:35%">
- 配置高级选项。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232724.jpg" alt="ACAC15B8633133B65476286A49BFBD7E" style="zoom:35%">
- 应用配置。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232800.jpg" style="zoom:35%">
4. 可以在 Windows 系统的“服务”窗口中启动或停止 MySQL。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232926.jpg" style="zoom:50%">
5. 配置 PATH 环境变量,以便在命令行提示符窗口使用 MySQL 客户端工具。
- 打开 Windows 的“系统”窗口并点击“高级系统设置”。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233054.jpg" style="zoom:50%">
- 在“系统属性”的“高级”窗口,点击“环境变量”按钮。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233312.jpg" style="zoom:50%">
- 修改PATH环境变量将MySQL安装路径下的`bin`文件夹的路径配置到PATH环境变量中。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233359.jpg" style="zoom:50%">
- 配置完成后,可以尝试在“命令提示符”下使用 MySQL 的命令行工具。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233643.jpg" style="zoom:50%">
#### Linux 环境
下面以 CentOS 7.x 环境为例,演示如何安装 MySQL 5.7.x如果需要在其他 Linux 系统下安装其他版本的 MySQL请读者自行在网络上查找对应的安装教程。
1. 安装 MySQL。
可以在 [MySQL 官方网站](<https://www.mysql.com/>)下载安装文件。首先在下载页面中选择平台和版本,然后找到对应的下载链接,直接下载包含所有安装文件的归档文件,解归档之后通过包管理工具进行安装。
```Shell
wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
tar -xvf mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
```
如果系统上有 MariaDB 相关的文件,需要先移除 MariaDB 相关的文件。
```Shell
yum list installed | grep mariadb | awk '{print $1}' | xargs yum erase -y
```
更新和安装可能用到的底层依赖库。
```Bash
yum update
yum install -y libaio libaio-devel
```
接下来可以按照如下所示的顺序用 RPMRedhat Package Manager工具安装 MySQL。
```Shell
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
```
可以使用下面的命令查看已经安装的 MySQL 相关的包。
```Shell
rpm -qa | grep mysql
```
2. 配置 MySQL。
MySQL 的配置文件在`/etc`目录下,名为`my.cnf`,默认的配置文件内容如下所示。
```Shell
cat /etc/my.cnf
```
```INI
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
```
通过配置文件,我们可以修改 MySQL 服务使用的端口、字符集、最大连接数、套接字队列大小、最大数据包大小、日志文件的位置、日志过期时间等配置。当然,我们还可以通过修改配置文件来对 MySQL 服务器进行性能调优和安全管控。
3. 启动 MySQL 服务。
可以使用下面的命令来启动 MySQL。
```Shell
service mysqld start
```
在 CentOS 7 中,更推荐使用下面的命令来启动 MySQL。
```Shell
systemctl start mysqld
```
启动 MySQL 成功后可以通过下面的命令来检查网络端口使用情况MySQL 默认使用`3306`端口。
```Shell
netstat -ntlp | grep mysql
```
也可以使用下面的命令查找是否有名为`mysqld`的进程。
```Shell
pgrep mysqld
```
4. 使用 MySQL 客户端工具连接服务器。
命令行工具:
```Shell
mysql -u root -p
```
> 说明:启动客户端时,`-u`参数用来指定用户名MySQL 默认的超级管理账号为`root``-p`表示要输入密码(用户口令);如果连接的是其他主机而非本机,可以用`-h`来指定连接主机的主机名或IP地址。
如果是首次安装 MySQL可以使用下面的命令来找到默认的初始密码。
```Shell
cat /var/log/mysqld.log | grep password
```
上面的命令会查看 MySQL 的日志带有`password`的行,在显示的结果中`root@localhost:`后面的部分就是默认设置的初始密码。
进入客户端工具后可以通过下面的指令来修改超级管理员root的访问口令为`123456`。
```SQL
set global validate_password_policy=0;
set global validate_password_length=6;
alter user 'root'@'localhost' identified by '123456';
```
> **说明**MySQL 较新的版本默认不允许使用弱口令作为用户口令,所以上面的代码修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,**攻击数据库窃取数据和劫持数据库勒索比特币**的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是**不要让数据库服务器暴露在公网上**(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好`root`账号的口令,应用系统需要访问数据库时,通常不使用`root`账号进行访问,而是**创建其他拥有适当权限的账号来访问**。
再次使用客户端工具连接 MySQL 服务器时,就可以使用新设置的口令了。在实际开发中,为了方便用户操作,可以选择图形化的客户端工具来连接 MySQL 服务器,包括:
- MySQL Workbench官方工具
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106063939.png" style="zoom:50%">
- Navicat for MySQL界面简单友好
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210521152457.png" style="zoom:50%;">
#### macOS环境
macOS 系统安装 MySQL 是比较简单的,只需要从刚才说到的官方网站下载 DMG 安装文件并运行就可以了,下载的时候需要根据自己使用的是 Intel 的芯片还是苹果的 M1 芯片选择下载链接,如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211121215901.png" style="zoom:50%;">
安装成功后可以在“系统偏好设置”中找到“MySQL”在如下所示的画面中可以启动和停止 MySQL 服务器,也可以对 MySQL 核心文件的路径进行配置。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211121215153.png" style="zoom:40%;">
### MySQL 基本命令
#### 查看命令
1. 查看所有数据库
```SQL
show databases;
```
2. 查看所有字符集
```SQL
show character set;
```
3. 查看所有的排序规则
```SQL
show collation;
```
4. 查看所有的引擎
```SQL
show engines;
```
5. 查看所有日志文件
```SQL
show binary logs;
```
6. 查看数据库下所有表
```SQL
show tables;
```
#### 获取帮助
在 MySQL 命令行工具中,可以使用`help`命令或`?`来获取帮助,如下所示。
1. 查看`show`命令的帮助。
```MySQL
? show
```
2. 查看有哪些帮助内容。
```MySQL
? contents
```
3. 获取函数的帮助。
```MySQL
? functions
```
4. 获取数据类型的帮助。
```MySQL
? data types
```
#### 其他命令
1. 新建/重建服务器连接 - `connect` / `resetconnection`
2. 清空当前输入 - `\c`。在输入错误时,可以及时使用`\c`清空当前输入并重新开始。
3. 修改终止符(定界符)- `delimiter`。默认的终止符是`;`,可以使用该命令修改成其他的字符,例如修改为`$`符号,可以用`delimiter $`命令。
4. 打开系统默认编辑器 - `edit`。编辑完成保存关闭之后,命令行会自动执行编辑的内容。
5. 查看服务器状态 - `status`
6. 修改默认提示符 - `prompt`
7. 执行系统命令 - `system`。可以将系统命令跟在`system`命令的后面执行,`system`命令也可以缩写为`\!`。
8. 执行 SQL 文件 - `source`。`source`命令后面跟 SQL 文件路径。
9. 重定向输出 - `tee` / `notee`。可以将命令的输出重定向到指定的文件中。
10. 切换数据库 - `use`
11. 显示警告信息 - `warnings`
12. 退出命令行 - `quit`或`exit`。

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,295 @@
## Python程序接入MySQL数据库
在 Python3 中,我们可以使用`mysqlclient`或者`pymysql`三方库来接入 MySQL 数据库并实现数据持久化操作。二者的用法完全相同,只是导入的模块名不一样。我们推荐大家使用纯 Python 的三方库`pymysql`,因为它更容易安装成功。下面我们仍然以之前创建的名为`hrs`的数据库为例,为大家演示如何通过 Python 程序操作 MySQL 数据库实现数据持久化操作。
### 建库建表
```SQL
-- 创建名为hrs的数据库并指定默认的字符集
create database `hrs` default character set utf8mb4;
-- 切换到hrs数据库
use `hrs`;
-- 创建部门表
create table `tb_dept`
(
`dno` int not null comment '编号',
`dname` varchar(10) not null comment '名称',
`dloc` varchar(20) not null comment '所在地',
primary key (`dno`)
);
-- 插入4个部门
insert into `tb_dept` values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
-- 创建员工表
create table `tb_emp`
(
`eno` int not null comment '员工编号',
`ename` varchar(20) not null comment '员工姓名',
`job` varchar(20) not null comment '员工职位',
`mgr` int comment '主管编号',
`sal` int not null comment '员工月薪',
`comm` int comment '每月补贴',
`dno` int not null comment '所在部门编号',
primary key (`eno`),
constraint `fk_emp_mgr` foreign key (`mgr`) references tb_emp (`eno`),
constraint `fk_emp_dno` foreign key (`dno`) references tb_dept (`dno`)
);
-- 插入14个员工
insert into `tb_emp` values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
(2056, '乔峰', '分析师', 7800, 5000, 1500, 20),
(3088, '李莫愁', '设计师', 2056, 3500, 800, 20),
(3211, '张无忌', '程序员', 2056, 3200, null, 20),
(3233, '丘处机', '程序员', 2056, 3400, null, 20),
(3251, '张翠山', '程序员', 2056, 4000, null, 20),
(5566, '宋远桥', '会计师', 7800, 4000, 1000, 10),
(5234, '郭靖', '出纳', 5566, 2000, null, 10),
(3344, '黄蓉', '销售主管', 7800, 3000, 800, 30),
(1359, '胡一刀', '销售员', 3344, 1800, 200, 30),
(4466, '苗人凤', '销售员', 3344, 2500, null, 30),
(3244, '欧阳锋', '程序员', 3088, 3200, null, 20),
(3577, '杨过', '会计', 5566, 2200, null, 10),
(3588, '朱九真', '会计', 5566, 2500, null, 10);
```
### 接入MySQL
首先,我们可以在命令行或者 PyCharm 的终端中通过下面的命令安装`pymysql`,如果需要接入 MySQL 8还需要安装一个名为`cryptography`的三方库来支持 MySQL 8 的密码认证方式。
```Shell
pip install pymysql cryptography
```
使用`pymysql`操作 MySQL 的步骤如下所示:
1. 创建连接。MySQL 服务器启动后,提供了基于 TCP (传输控制协议)的网络服务。我们可以通过`pymysql`模块的`connect`函数连接 MySQL 服务器。在调用`connect`函数时,需要指定主机(`host`)、端口(`port`)、用户名(`user`)、口令(`password`)、数据库(`database`)、字符集(`charset`)等参数,该函数会返回一个`Connection`对象。
2. 获取游标。连接 MySQL 服务器成功后,接下来要做的就是向数据库服务器发送 SQL 语句MySQL 会执行接收到的 SQL 并将执行结果通过网络返回。要实现这项操作,需要先通过连接对象的`cursor`方法获取游标(`Cursor`)对象。
3. 发出 SQL。通过游标对象的`execute`方法,我们可以向数据库发出 SQL 语句。
4. 如果执行`insert`、`delete`或`update`操作,需要根据实际情况提交或回滚事务。因为创建连接时,默认开启了事务环境,在操作完成后,需要使用连接对象的`commit`或`rollback`方法,实现事务的提交或回滚,`rollback`方法通常会放在异常捕获代码块`except`中。如果执行`select`操作,需要通过游标对象抓取查询的结果,对应的方法有三个,分别是:`fetchone`、`fetchmany`和`fetchall`。其中`fetchone`方法会抓取到一条记录,并以元组或字典的方式返回;`fetchmany`和`fetchall`方法会抓取到多条记录,以嵌套元组或列表装字典的方式返回。
5. 关闭连接。在完成持久化操作后,请不要忘记关闭连接,释放外部资源。我们通常会在`finally`代码块中使用连接对象的`close`方法来关闭连接。
### 代码实操
下面,我们通过代码实操的方式为大家演示上面说的五个步骤。
#### 插入数据
```Python
import pymysql
no = int(input('部门编号: '))
name = input('部门名称: ')
location = input('部门所在地: ')
# 1. 创建连接Connection
conn = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4')
try:
# 2. 获取游标对象Cursor
with conn.cursor() as cursor:
# 3. 通过游标对象向数据库服务器发出SQL语句
affected_rows = cursor.execute(
'insert into `tb_dept` values (%s, %s, %s)',
(no, name, location)
)
if affected_rows == 1:
print('新增部门成功!!!')
# 4. 提交事务transaction
conn.commit()
except pymysql.MySQLError as err:
# 4. 回滚事务
conn.rollback()
print(type(err), err)
finally:
# 5. 关闭连接释放资源
conn.close()
```
> **说明**:上面的`127.0.0.1`称为回环地址,它代表的是本机。下面的`guest`是我提前创建好的用户,该用户拥有对`hrs`数据库的`insert`、`delete`、`update`和`select`权限。我们不建议大家在项目中直接使用`root`超级管理员账号访问数据库,这样做实在是太危险了。我们可以使用下面的命令创建名为`guest`的用户并为其授权。
>
> ```SQL
> create user 'guest'@'%' identified by 'Guest.618';
> grant insert, delete, update, select on `hrs`.* to 'guest'@'%';
> ```
如果要插入大量数据,建议使用游标对象的`executemany`方法做批处理(一个`insert`操作后面跟上多组数据大家可以尝试向一张表插入10000条记录然后看看不使用批处理一条条的插入和使用批处理有什么差别。游标对象的`executemany`方法第一个参数仍然是 SQL 语句,第二个参数可以是包含多组数据的列表或元组。
#### 删除数据
```Python
import pymysql
no = int(input('部门编号: '))
# 1. 创建连接Connection
conn = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4',
autocommit=True)
try:
# 2. 获取游标对象Cursor
with conn.cursor() as cursor:
# 3. 通过游标对象向数据库服务器发出SQL语句
affected_rows = cursor.execute(
'delete from `tb_dept` where `dno`=%s',
(no, )
)
if affected_rows == 1:
print('删除部门成功!!!')
finally:
# 5. 关闭连接释放资源
conn.close()
```
> **说明**:如果不希望每次 SQL 操作之后手动提交或回滚事务,可以`connect`函数中加一个名为`autocommit`的参数并将它的值设置为`True`,表示每次执行 SQL 成功后自动提交。但是我们建议大家手动提交或回滚,这样可以根据实际业务需要来构造事务环境。如果不愿意捕获异常并进行处理,可以在`try`代码块后直接跟`finally`块,省略`except`意味着发生异常时,代码会直接崩溃并将异常栈显示在终端中。
#### 更新数据
```Python
import pymysql
no = int(input('部门编号: '))
name = input('部门名称: ')
location = input('部门所在地: ')
# 1. 创建连接Connection
conn = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4')
try:
# 2. 获取游标对象Cursor
with conn.cursor() as cursor:
# 3. 通过游标对象向数据库服务器发出SQL语句
affected_rows = cursor.execute(
'update `tb_dept` set `dname`=%s, `dloc`=%s where `dno`=%s',
(name, location, no)
)
if affected_rows == 1:
print('更新部门信息成功!!!')
# 4. 提交事务
conn.commit()
except pymysql.MySQLError as err:
# 4. 回滚事务
conn.rollback()
print(type(err), err)
finally:
# 5. 关闭连接释放资源
conn.close()
```
#### 查询数据
1. 查询部门表的数据。
```Python
import pymysql
# 1. 创建连接Connection
conn = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4')
try:
# 2. 获取游标对象Cursor
with conn.cursor() as cursor:
# 3. 通过游标对象向数据库服务器发出SQL语句
cursor.execute('select `dno`, `dname`, `dloc` from `tb_dept`')
# 4. 通过游标对象抓取数据
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
except pymysql.MySQLError as err:
print(type(err), err)
finally:
# 5. 关闭连接释放资源
conn.close()
```
>**说明**:上面的代码中,我们通过构造一个`while`循环实现了逐行抓取查询结果的操作。这种方式特别适合查询结果有非常多行的场景。因为如果使用`fetchall`一次性将所有记录抓取到一个嵌套元组中,会造成非常大的内存开销,这在很多场景下并不是一个好主意。如果不愿意使用`while`循环,还可以考虑使用`iter`函数构造一个迭代器来逐行抓取数据,有兴趣的读者可以自行研究。
2. 分页查询员工表的数据。
```Python
import pymysql
page = int(input('页码: '))
size = int(input('大小: '))
# 1. 创建连接Connection
con = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8')
try:
# 2. 获取游标对象Cursor
with con.cursor(pymysql.cursors.DictCursor) as cursor:
# 3. 通过游标对象向数据库服务器发出SQL语句
cursor.execute(
'select `eno`, `ename`, `job`, `sal` from `tb_emp` order by `sal` desc limit %s,%s',
((page - 1) * size, size)
)
# 4. 通过游标对象抓取数据
for emp_dict in cursor.fetchall():
print(emp_dict)
finally:
# 5. 关闭连接释放资源
con.close()
```
### 案例讲解
下面我们为大家讲解一个将数据库表数据导出到 Excel 文件的例子,我们需要先安装`openpyxl`三方库,命令如下所示。
```Bash
pip install openpyxl
```
接下来,我们通过下面的代码实现了将数据库`hrs`中所有员工的编号、姓名、职位、月薪、补贴和部门名称导出到一个 Excel 文件中。
```Python
import openpyxl
import pymysql
# 创建工作簿对象
workbook = openpyxl.Workbook()
# 获得默认的工作表
sheet = workbook.active
# 修改工作表的标题
sheet.title = '员工基本信息'
# 给工作表添加表头
sheet.append(('工号', '姓名', '职位', '月薪', '补贴', '部门'))
# 创建连接Connection
conn = pymysql.connect(host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4')
try:
# 获取游标对象Cursor
with conn.cursor() as cursor:
# 通过游标对象执行SQL语句
cursor.execute(
'select `eno`, `ename`, `job`, `sal`, coalesce(`comm`, 0), `dname` '
'from `tb_emp` natural join `tb_dept`'
)
# 通过游标抓取数据
row = cursor.fetchone()
while row:
# 将数据逐行写入工作表中
sheet.append(row)
row = cursor.fetchone()
# 保存工作簿
workbook.save('hrs.xlsx')
except pymysql.MySQLError as err:
print(err)
finally:
# 关闭连接释放资源
conn.close()
```
大家可以参考上面的例子,试一试把 Excel 文件的数据导入到指定数据库的指定表中,看看是否可以成功。

View File

@ -1,77 +1,75 @@
drop database if exists hrs;
create database hrs default charset utf8mb4;
-- 创建名为hrs的数据库
drop database if exists `hrs`;
create database `hrs` default charset utf8mb4;
use hrs;
-- 切换到hrs数据库
use `hrs`;
drop table if exists tb_emp;
drop table if exists tb_dept;
create table tb_dept
-- 创建部门表
create table `tb_dept`
(
dno int not null comment '编号',
dname varchar(10) not null comment '名称',
dloc varchar(20) not null comment '所在地',
`dno` int not null comment '编号',
`dname` varchar(10) not null comment '名称',
`dloc` varchar(20) not null comment '所在地',
primary key (dno)
);
-- alter table tb_dept add constraint pk_dept_dno primary key(dno);
-- 插入4个部门
insert into `tb_dept` values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
create table tb_emp
-- 创建员工表
create table `tb_emp`
(
eno int not null comment '员工编号',
ename varchar(20) not null comment '员工姓名',
job varchar(20) not null comment '员工职位',
mgr int comment '主管编号',
sal int not null comment '员工月薪',
comm int comment '每月补贴',
dno int comment '所在部门编号',
`eno` int not null comment '员工编号',
`ename` varchar(20) not null comment '员工姓名',
`job` varchar(20) not null comment '员工职位',
`mgr` int comment '主管编号',
`sal` int not null comment '员工月薪',
`comm` int comment '每月补贴',
`dno` int comment '所在部门编号',
primary key (eno),
foreign key (dno) references tb_dept(dno),
foreign key (mgr) references tb_emp(eno)
constraint `fk_emp_mgr` foreign key (`mgr`) references tb_emp (`eno`),
constraint `fk_emp_dno` foreign key (`dno`) references tb_dept (`dno`)
);
-- alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
-- alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
insert into tb_emp values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
(2056, '乔峰', '分析师', 7800, 5000, 1500, 20),
(3088, '李莫愁', '设计师', 2056, 3500, 800, 20),
(3211, '张无忌', '程序员', 2056, 3200, null, 20),
(3233, '丘处机', '程序员', 2056, 3400, null, 20),
(3251, '张翠山', '程序员', 2056, 4000, null, 20),
(5566, '宋远桥', '会计师', 7800, 4000, 1000, 10),
(5234, '郭靖', '出纳', 5566, 2000, null, 10),
(3344, '黄蓉', '销售主管', 7800, 3000, 800, 30),
(1359, '胡一刀', '销售员', 3344, 1800, 200, 30),
(4466, '苗人凤', '销售员', 3344, 2500, null, 30),
(3244, '欧阳锋', '程序员', 3088, 3200, null, 20),
(3577, '杨过', '会计', 5566, 2200, null, 10),
(3588, '朱九真', '会计', 5566, 2500, null, 10);
-- 插入14个员工
insert into `tb_emp` values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
(2056, '乔峰', '分析师', 7800, 5000, 1500, 20),
(3088, '李莫愁', '设计师', 2056, 3500, 800, 20),
(3211, '张无忌', '程序员', 2056, 3200, null, 20),
(3233, '丘处机', '程序员', 2056, 3400, null, 20),
(3251, '张翠山', '程序员', 2056, 4000, null, 20),
(5566, '宋远桥', '会计师', 7800, 4000, 1000, 10),
(5234, '郭靖', '出纳', 5566, 2000, null, 10),
(3344, '黄蓉', '销售主管', 7800, 3000, 800, 30),
(1359, '胡一刀', '销售员', 3344, 1800, 200, 30),
(4466, '苗人凤', '销售员', 3344, 2500, null, 30),
(3244, '欧阳锋', '程序员', 3088, 3200, null, 20),
(3577, '杨过', '会计', 5566, 2200, null, 10),
(3588, '朱九真', '会计', 5566, 2500, null, 10);
-- 查询月薪最高的员工姓名和月薪
-- 查询员工的姓名和年薪((月薪+补贴)*13)
-- 查询员工的姓名和年薪(年薪=(sal+comm)*13)
-- 查询有员工的部门的编号和人数
-- 查询所有部门的名称和人数
-- 查询月薪最高的员工(Boss除外)的姓名和月薪
-- 查询月薪超过平均月薪的员工的姓名和月薪
-- 查询超过平均薪的员工的姓名和月薪
-- 查询薪超过其所在部门平均薪的员工的姓名、部门编号和月薪
-- 查询薪水超过其所在部门平均薪水的员工的姓名、部门编号和月薪
-- 查询部门中薪水最高的人姓名、月薪和所在部门名称
-- 查询部门中月薪最高的人姓名、月薪和所在部门名称
-- 查询主管的姓名和职位
-- 查询月薪排名4~6名的员工排名、姓名和月薪
-- 查询每个部门月薪排前2名的员工姓名、月薪和部门编号

View File

@ -1,243 +1,136 @@
-- 如果存在名为school的数据库就删除它
drop database if exists school;
drop database if exists `school`;
-- 创建名为school的数据库并设置默认的字符集和排序方式
create database school default charset utf8mb4;
create database `school` default charset utf8mb4;
-- 切换到school数据库上下文环境
use school;
use `school`;
-- 创建学院表
create table tb_college
create table `tb_college`
(
collid int auto_increment comment '编号',
collname varchar(50) not null comment '名称',
collintro varchar(500) default '' comment '介绍',
primary key (collid)
);
`col_id` int unsigned auto_increment comment '编号',
`col_name` varchar(50) not null comment '名称',
`col_intro` varchar(5000) default '' comment '介绍',
primary key (`col_id`)
) engine=innodb comment '学院表';
-- 创建学生表
create table tb_student
create table `tb_student`
(
stuid int not null comment '学号',
stuname varchar(20) not null comment '姓名',
stusex boolean default 1 comment '性别',
stubirth date not null comment '出生日期',
stuaddr varchar(255) default '' comment '籍贯',
collid int not null comment '所属学院',
primary key (stuid),
foreign key (collid) references tb_college (collid)
);
`stu_id` int unsigned not null comment '学号',
`stu_name` varchar(20) not null comment '姓名',
`stu_sex` boolean default 1 comment '性别',
`stu_birth` date not null comment '出生日期',
`stu_addr` varchar(255) default '' comment '籍贯',
`col_id` int unsigned not null comment '所属学院',
primary key (`stu_id`),
foreign key (`col_id`) references `tb_college` (`col_id`)
) engine=innodb comment '学生表';
-- 创建教师表
create table tb_teacher
create table `tb_teacher`
(
teaid int not null comment '工号',
teaname varchar(20) not null comment '姓名',
teatitle varchar(10) default '讲师' comment '职称',
collid int not null comment '所属学院',
primary key (teaid),
foreign key (collid) references tb_college (collid)
);
`tea_id` int unsigned not null comment '工号',
`tea_name` varchar(20) not null comment '姓名',
`tea_title` varchar(10) default '助教' comment '职称',
`col_id` int unsigned not null comment '所属学院',
primary key (`tea_id`),
foreign key (`col_id`) references `tb_college` (`col_id`)
) engine=innodb comment '老师表';
-- 创建课程表
create table tb_course
create table `tb_course`
(
couid int not null comment '编号',
couname varchar(50) not null comment '名称',
coucredit int not null comment '学分',
teaid int not null comment '授课老师',
primary key (couid),
foreign key (teaid) references tb_teacher (teaid)
);
`cou_id` int unsigned not null comment '编号',
`cou_name` varchar(50) not null comment '名称',
`cou_credit` int unsigned not null comment '学分',
`tea_id` int unsigned not null comment '授课老师',
primary key (`cou_id`),
foreign key (`tea_id`) references `tb_teacher` (`tea_id`)
) engine=innodb comment '课程表';
-- 创建选课记录表
create table tb_record
create table `tb_record`
(
recid int auto_increment comment '选课记录',
sid int not null comment '选课学生',
cid int not null comment '所选课程',
seldate datetime default now() comment '选课时间日期',
score decimal(4,1) comment '考试成绩',
primary key (recid),
foreign key (sid) references tb_student (stuid),
foreign key (cid) references tb_course (couid),
unique (sid, cid)
);
`rec_id` bigint unsigned auto_increment comment '选课记录',
`sid` int unsigned not null comment '学号',
`cid` int unsigned not null comment '课程编号',
`sel_date` date not null comment '选课日期',
`score` decimal(4,1) comment '考试成绩',
primary key (`rec_id`),
foreign key (`sid`) references `tb_student` (`stu_id`),
foreign key (`cid`) references `tb_course` (`cou_id`),
unique (`sid`, `cid`)
) engine=innodb comment '选课记录表';
-- 插入学院数据
insert into tb_college (collname, collintro) values
('计算机学院', '计算机学院1958年设立计算机专业1981年建立计算机科学系1998年设立计算机学院2005年5月为了进一步整合教学和科研资源学校决定计算机学院和软件学院行政班子合并统一运作、实行教学和学生管理独立运行的模式。 学院下设三个系计算机科学与技术系、物联网工程系、计算金融系两个研究所图象图形研究所、网络空间安全研究院2015年成立三个教学实验中心计算机基础教学实验中心、IBM技术中心和计算机专业实验中心。'),
('外国语学院', '外国语学院设有7个教学单位6个文理兼收的本科专业拥有1个一级学科博士授予点3个二级学科博士授予点5个一级学科硕士学位授权点5个二级学科硕士学位授权点5个硕士专业授权领域同时还有2个硕士专业学位MTI专业有教职员工210余人其中教授、副教授80余人教师中获得中国国内外名校博士学位和正在职攻读博士学位的教师比例占专任教师的60%以上。'),
('经济管理学院', '经济学院前身是创办于1905年的经济科已故经济学家彭迪先、张与九、蒋学模、胡寄窗、陶大镛、胡代光以及当代学者刘诗白等曾先后在此任教或学习。');
insert into `tb_college`
(`col_name`, `col_intro`)
values
('计算机学院', '计算机学院1958年设立计算机专业1981年建立计算机科学系1998年设立计算机学院2005年5月为了进一步整合教学和科研资源学校决定计算机学院和软件学院行政班子合并统一运作、实行教学和学生管理独立运行的模式。 学院下设三个系计算机科学与技术系、物联网工程系、计算金融系两个研究所图象图形研究所、网络空间安全研究院2015年成立三个教学实验中心计算机基础教学实验中心、IBM技术中心和计算机专业实验中心。'),
('外国语学院', '外国语学院设有7个教学单位6个文理兼收的本科专业拥有1个一级学科博士授予点3个二级学科博士授予点5个一级学科硕士学位授权点5个二级学科硕士学位授权点5个硕士专业授权领域同时还有2个硕士专业学位MTI专业有教职员工210余人其中教授、副教授80余人教师中获得中国国内外名校博士学位和正在职攻读博士学位的教师比例占专任教师的60%以上。'),
('经济管理学院', '经济学院前身是创办于1905年的经济科已故经济学家彭迪先、张与九、蒋学模、胡寄窗、陶大镛、胡代光以及当代学者刘诗白等曾先后在此任教或学习。');
-- 插入学生数据
insert into tb_student (stuid, stuname, stusex, stubirth, stuaddr, collid)
insert into `tb_student`
(`stu_id`, `stu_name`, `stu_sex`, `stu_birth`, `stu_addr`, `col_id`)
values
(1001, '杨逍', 1, '1990-3-4', '四川成都', 1),
(1002, '任我行', 1, '1992-2-2', '湖南长沙', 1),
(1033, '王语嫣', 0, '1989-12-3', '四川成都', 1),
(1572, '岳不群', 1, '1993-7-19', '陕西咸阳', 1),
(1378, '纪嫣然', 0, '1995-8-12', '四川绵阳', 1),
(1954, '林平之', 1, '1994-9-20', '福建莆田', 1),
(2035, '东方不败', 1, '1988-6-30', null, 2),
(3011, '林震南', 1, '1985-12-12', '福建莆田', 3),
(3755, '项少龙', 1, '1993-1-25', null, 3),
(3923, '杨不悔', 0, '1985-4-17', '四川成都', 3),
(4040, '炼腰的隔壁老王', 1, '1989-1-1', '四川成都', 2);
-- 删除学生数据
delete from tb_student where stuid=4040;
-- 更新学生数据
update tb_student set stuname='杨过', stuaddr='湖南长沙' where stuid=1001;
(1001, '杨过', 1, '1990-3-4', '湖南长沙', 1),
(1002, '任我行', 1, '1992-2-2', '湖南长沙', 1),
(1033, '王语嫣', 0, '1989-12-3', '四川成都', 1),
(1572, '岳不群', 1, '1993-7-19', '陕西咸阳', 1),
(1378, '纪嫣然', 0, '1995-8-12', '四川绵阳', 1),
(1954, '林平之', 1, '1994-9-20', '福建莆田', 1),
(2035, '东方不败', 1, '1988-6-30', null, 2),
(3011, '林震南', 1, '1985-12-12', '福建莆田', 3),
(3755, '项少龙', 1, '1993-1-25', null, 3),
(3923, '杨不悔', 0, '1985-4-17', '四川成都', 3);
-- 插入老师数据
insert into tb_teacher (teaid, teaname, teatitle, collid) values
(1122, '张三丰', '教授', 1),
(1133, '宋远桥', '副教授', 1),
(1144, '杨逍', '副教授', 1),
(2255, '范遥', '副教授', 2),
(3366, '韦一笑', default, 3);
insert into `tb_teacher`
(`tea_id`, `tea_name`, `tea_title`, `col_id`)
values
(1122, '张三丰', '教授', 1),
(1133, '宋远桥', '副教授', 1),
(1144, '杨逍', '副教授', 1),
(2255, '范遥', '副教授', 2),
(3366, '韦一笑', default, 3);
-- 插入课程数据
insert into tb_course (couid, couname, coucredit, teaid) values
(1111, 'Python程序设计', 3, 1122),
(2222, 'Web前端开发', 2, 1122),
(3333, '操作系统', 4, 1122),
(4444, '计算机网络', 2, 1133),
(5555, '编译原理', 4, 1144),
(6666, '算法和数据结构', 3, 1144),
(7777, '经贸法语', 3, 2255),
(8888, '成本会计', 2, 3366),
(9999, '审计学', 3, 3366);
insert into `tb_course`
(`cou_id`, `cou_name`, `cou_credit`, `tea_id`)
values
(1111, 'Python程序设计', 3, 1122),
(2222, 'Web前端开发', 2, 1122),
(3333, '操作系统', 4, 1122),
(4444, '计算机网络', 2, 1133),
(5555, '编译原理', 4, 1144),
(6666, '算法和数据结构', 3, 1144),
(7777, '经贸法语', 3, 2255),
(8888, '成本会计', 2, 3366),
(9999, '审计学', 3, 3366);
-- 插入选课数据
insert into tb_record (sid, cid, seldate, score) values
(1001, 1111, '2017-09-01', 95),
(1001, 2222, '2017-09-01', 87.5),
(1001, 3333, '2017-09-01', 100),
(1001, 4444, '2018-09-03', null),
(1001, 6666, '2017-09-02', 100),
(1002, 1111, '2017-09-03', 65),
(1002, 5555, '2017-09-01', 42),
(1033, 1111, '2017-09-03', 92.5),
(1033, 4444, '2017-09-01', 78),
(1033, 5555, '2017-09-01', 82.5),
(1572, 1111, '2017-09-02', 78),
(1378, 1111, '2017-09-05', 82),
(1378, 7777, '2017-09-02', 65.5),
(2035, 7777, '2018-09-03', 88),
(2035, 9999, default, null),
(3755, 1111, default, null),
(3755, 8888, default, null),
(3755, 9999, '2017-09-01', 92);
-- 查询所有学生信息
select * from tb_student;
-- 查询所有课程名称及学分(投影和别名)
select couname, coucredit from tb_course;
select couname as , coucredit as from tb_course;
select stuname as , case stusex when 1 then '' else '' end as from tb_student;
select stuname as , if(stusex, '', '') as from tb_student;
-- 查询所有女学生的姓名和出生日期(筛选)
select stuname, stubirth from tb_student where stusex=0;
-- 查询所有80后学生的姓名、性别和出生日期(筛选)
select stuname, stusex, stubirth from tb_student where stubirth>='1980-1-1' and stubirth<='1989-12-31';
select stuname, stusex, stubirth from tb_student where stubirth between '1980-1-1' and '1989-12-31';
-- 查询姓"杨"的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨%';
-- 查询姓"杨"名字两个字的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨_';
-- 查询姓"杨"名字三个字的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨__';
-- 查询名字中有"不"字或"嫣"字的学生的姓名(模糊)
select stuname, stusex from tb_student where stuname like '%不%' or stuname like '%嫣%';
-- 查询没有录入家庭住址的学生姓名(空值)
select stuname from tb_student where stuaddr is null;
-- 查询录入了家庭住址的学生姓名(空值)
select stuname from tb_student where stuaddr is not null;
-- 查询学生选课的所有日期(去重)
select distinct scdate from tb_score;
-- 查询学生的家庭住址(去重)
select distinct stuaddr from tb_student where stuaddr is not null;
-- 查询男学生的姓名和生日按年龄从大到小排列(排序)
-- asc - ascending - 升序(从小到大)
-- desc - descending - 降序(从大到小)
select stuname as , year(now())-year(stubirth) as from tb_student where stusex=1 order by desc;
-- 聚合函数max / min / count / sum / avg
-- 查询年龄最大的学生的出生日期(聚合函数)
select min(stubirth) from tb_student;
-- 查询年龄最小的学生的出生日期(聚合函数)
select max(stubirth) from tb_student;
-- 查询男女学生的人数(分组和聚合函数)
select count(stuid) from tb_student;
select stusex, count(*) from tb_student group by stusex;
select stusex, min(stubirth) from tb_student group by stusex;
-- 查询课程编号为1111的课程的平均成绩(筛选和聚合函数)
select avg(scmark) from tb_score where couid=1111;
select min(scmark) from tb_score where couid=1111;
select count(scid) from tb_score where couid=1111;
select count(scmark) from tb_score where couid=1111;
-- 查询学号为1001的学生所有课程的平均分(筛选和聚合函数)
select avg(scmark) from tb_score where stuid=1001;
-- 查询每个学生的学号和平均成绩(分组和聚合函数)
select stuid as , avg(scmark) as from tb_score group by stuid;
-- 查询平均成绩大于等于90分的学生的学号和平均成绩
-- 分组以前的筛选使用where子句
-- 分组以后的筛选使用having子句
select stuid as , avg(scmark) as from tb_score group by stuid having >=90;
-- 查询年龄最大的学生的姓名(子查询/嵌套的查询)
select stuname from tb_student where stubirth=(
select min(stubirth) from tb_student
);
-- 查询年龄最大的学生姓名和年龄(子查询+运算)
select stuname as , year(now())-year(stubirth) as from tb_student where stubirth=(
select min(stubirth) from tb_student
);
-- 查询选了两门以上的课程的学生姓名(子查询/分组条件/集合运算)
select stuname from tb_student where stuid in (
select stuid from tb_score group by stuid having count(stuid)>2
)
-- 查询学生姓名、课程名称以及成绩(连接查询)
select stuname, couname, scmark from tb_student t1, tb_course t2, tb_score t3 where t1.stuid=t3.stuid and t2.couid=t3.couid and scmark is not null;
select stuname, couname, scmark from tb_student t1 inner join tb_score t3 on t1.stuid=t3.stuid inner join tb_course t2 on t2.couid=t3.couid where scmark is not null order by scmark desc limit 5 offset 10;
select stuname, couname, scmark from tb_student t1 inner join tb_score t3 on t1.stuid=t3.stuid inner join tb_course t2 on t2.couid=t3.couid where scmark is not null order by scmark desc limit 10, 5;
-- 查询选课学生的姓名和平均成绩(子查询和连接查询)
select stuname, avgmark from tb_student t1, (select stuid, avg(scmark) as avgmark from tb_score group by stuid) t2 where t1.stuid=t2.stuid;
select stuname, avgmark from tb_student t1 inner join
(select stuid, avg(scmark) as avgmark from tb_score group by stuid) t2 on t1.stuid=t2.stuid;
-- 内连接inner join只有满足连接条件的记录才会被查出来
-- 外连接outer join左外连接 / 右外连接 / 全外连接
-- left outer join / right outer join / full outer join
-- 查询每个学生的姓名和选课数量(左外连接和子查询)
select stuname, ifnull(total, 0) from tb_student t1 left outer join (select stuid, count(stuid) as total from tb_score group by stuid) t2 on t1.stuid=t2.stuid;
insert into `tb_record`
(`sid`, `cid`, `sel_date`, `score`)
values
(1001, 1111, '2017-09-01', 95),
(1001, 2222, '2017-09-01', 87.5),
(1001, 3333, '2017-09-01', 100),
(1001, 4444, '2018-09-03', null),
(1001, 6666, '2017-09-02', 100),
(1002, 1111, '2017-09-03', 65),
(1002, 5555, '2017-09-01', 42),
(1033, 1111, '2017-09-03', 92.5),
(1033, 4444, '2017-09-01', 78),
(1033, 5555, '2017-09-01', 82.5),
(1572, 1111, '2017-09-02', 78),
(1378, 1111, '2017-09-05', 82),
(1378, 7777, '2017-09-02', 65.5),
(2035, 7777, '2018-09-03', 88),
(2035, 9999, '2019-09-02', null),
(3755, 1111, '2019-09-02', null),
(3755, 8888, '2019-09-02', null),
(3755, 9999, '2017-09-01', 92);

View File

@ -0,0 +1,144 @@
drop database if exists hrs;
create database hrs default charset utf8mb4;
use hrs;
create table tb_dept
(
dno int not null comment '编号',
dname varchar(10) not null comment '名称',
dloc varchar(20) not null comment '所在地',
primary key (dno)
);
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
create table tb_emp
(
eno int not null comment '员工编号',
ename varchar(20) not null comment '员工姓名',
job varchar(20) not null comment '员工职位',
mgr int comment '主管编号',
sal int not null comment '员工月薪',
comm int comment '每月补贴',
dno int comment '所在部门编号',
primary key (eno),
foreign key (dno) references tb_dept (dno)
);
-- alter table tb_emp add constraint pk_emp_eno primary key (eno);
-- alter table tb_emp add constraint uk_emp_ename unique (ename);
-- alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
-- alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
insert into tb_emp values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
(2056, '乔峰', '分析师', 7800, 5000, 1500, 20),
(3088, '李莫愁', '设计师', 2056, 3500, 800, 20),
(3211, '张无忌', '程序员', 2056, 3200, null, 20),
(3233, '丘处机', '程序员', 2056, 3400, null, 20),
(3251, '张翠山', '程序员', 2056, 4000, null, 20),
(5566, '宋远桥', '会计师', 7800, 4000, 1000, 10),
(5234, '郭靖', '出纳', 5566, 2000, null, 10),
(3344, '黄蓉', '销售主管', 7800, 3000, 800, 30),
(1359, '胡一刀', '销售员', 3344, 1800, 200, 30),
(4466, '苗人凤', '销售员', 3344, 2500, null, 30),
(3244, '欧阳锋', '程序员', 3088, 3200, null, 20),
(3577, '杨过', '会计', 5566, 2200, null, 10),
(3588, '朱九真', '会计', 5566, 2500, null, 10);
-- 查询月薪最高的员工姓名和月薪
select ename, sal from tb_emp where sal=(select max(sal) from tb_emp);
select ename, sal from tb_emp where sal>=all(select sal from tb_emp);
-- 查询员工的姓名和年薪((月薪+补贴)*13)
select ename, (sal+ifnull(comm,0))*13 as ann_sal from tb_emp order by ann_sal desc;
-- 查询有员工的部门的编号和人数
select dno, count(*) as total from tb_emp group by dno;
-- 查询所有部门的名称和人数
select dname, ifnull(total,0) as total from tb_dept left join
(select dno, count(*) as total from tb_emp group by dno) tb_temp
on tb_dept.dno=tb_temp.dno;
-- 查询月薪最高的员工(Boss除外)的姓名和月薪
select ename, sal from tb_emp where sal=(
select max(sal) from tb_emp where mgr is not null
);
-- 查询月薪排第2名的员工的姓名和月薪
select ename, sal from tb_emp where sal=(
select distinct sal from tb_emp order by sal desc limit 1,1
);
select ename, sal from tb_emp where sal=(
select max(sal) from tb_emp where sal<(select max(sal) from tb_emp)
);
-- 查询月薪超过平均月薪的员工的姓名和月薪
select ename, sal from tb_emp where sal>(select avg(sal) from tb_emp);
-- 查询月薪超过其所在部门平均月薪的员工的姓名、部门编号和月薪
select ename, t1.dno, sal from tb_emp t1 inner join
(select dno, avg(sal) as avg_sal from tb_emp group by dno) t2
on t1.dno=t2.dno and sal>avg_sal;
-- 查询部门中月薪最高的人姓名、月薪和所在部门名称
select ename, sal, dname
from tb_emp t1, tb_dept t2, (
select dno, max(sal) as max_sal from tb_emp group by dno
) t3 where t1.dno=t2.dno and t1.dno=t3.dno and sal=max_sal;
-- 查询主管的姓名和职位
-- 提示尽量少用in/not in运算尽量少用distinct操作
-- 可以使用存在性判断exists/not exists替代集合运算和去重操作
select ename, job from tb_emp where eno in (
select distinct mgr from tb_emp where mgr is not null
);
select ename, job from tb_emp where eno=any(
select distinct mgr from tb_emp where mgr is not null
);
select ename, job from tb_emp t1 where exists (
select 'x' from tb_emp t2 where t1.eno=t2.mgr
);
-- MySQL8有窗口函数row_number() / rank() / dense_rank()
-- 查询月薪排名4~6名的员工的排名、姓名和月薪
select ename, sal from tb_emp order by sal desc limit 3,3;
select row_num, ename, sal from
(select @a:=@a+1 as row_num, ename, sal
from tb_emp, (select @a:=0) t1 order by sal desc) t2
where row_num between 4 and 6;
-- 窗口函数不适合业务数据库,只适合做离线数据分析
select
ename, sal,
row_number() over (order by sal desc) as row_num,
rank() over (order by sal desc) as ranking,
dense_rank() over (order by sal desc) as dense_ranking
from tb_emp limit 3 offset 3;
select ename, sal, ranking from (
select ename, sal, dense_rank() over (order by sal desc) as ranking from tb_emp
) tb_temp where ranking between 4 and 6;
-- 窗口函数主要用于解决TopN查询问题
-- 查询每个部门月薪排前2名的员工姓名、月薪和部门编号
select ename, sal, dno from (
select ename, sal, dno, rank() over (partition by dno order by sal desc) as ranking
from tb_emp
) tb_temp where ranking<=2;
select ename, sal, dno from tb_emp t1
where (select count(*) from tb_emp t2 where t1.dno=t2.dno and t2.sal>t1.sal)<2
order by dno asc, sal desc;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,124 @@
-- 交易表
CREATE TABLE `transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_sn` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '交易单号',
`member_id` bigint(20) NOT NULL COMMENT '交易的用户ID',
`amount` decimal(8,2) NOT NULL COMMENT '交易金额',
`integral` int(11) NOT NULL DEFAULT '0' COMMENT '使用的积分',
`pay_state` tinyint(4) NOT NULL COMMENT '支付类型 0:余额 1:微信 2:支付宝 3:xxx',
`source` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付来源 wx app web wap',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态 -1取消 0 未完成 1已完成 -2:异常',
`completion_time` int(11) NOT NULL COMMENT '交易完成时间',
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `transaction_order_sn_member_id_pay_state_source_status_index` (`order_sn`(191),`member_id`,`pay_state`,`source`(191),`status`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 交易记录表
CREATE TABLE `transaction_record` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_sn` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`events` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '事件详情',
`result` text COLLATE utf8mb4_unicode_ci COMMENT '结果详情',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 订单表
CREATE TABLE `order` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_no` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '订单编号',
`order_sn` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '交易号',
`member_id` int(11) NOT NULL COMMENT '客户编号',
`supplier_id` int(11) NOT NULL COMMENT '商户编码',
`supplier_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商户名称',
`order_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态 0未付款,1已付款,2已发货,3已签收,-1退货申请,-2退货中,-3已退货,-4取消交易',
`after_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '用户售后状态 0 未发起售后 1 申请售后 -1 售后已取消 2 处理中 200 处理完毕',
`product_count` int(11) NOT NULL DEFAULT '0' COMMENT '商品数量',
`product_amount_total` decimal(12,4) NOT NULL COMMENT '商品总价',
`order_amount_total` decimal(12,4) NOT NULL DEFAULT '0.0000' COMMENT '实际付款金额',
`logistics_fee` decimal(12,4) NOT NULL COMMENT '运费金额',
`address_id` int(11) NOT NULL COMMENT '收货地址编码',
`pay_channel` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付渠道 0余额 1微信 2支付宝',
`out_trade_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '订单支付单号',
`escrow_trade_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '第三方支付流水号',
`pay_time` int(11) NOT NULL DEFAULT '0' COMMENT '付款时间',
`delivery_time` int(11) NOT NULL DEFAULT '0' COMMENT '发货时间',
`order_settlement_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单结算状态 0未结算 1已结算',
`order_settlement_time` int(11) NOT NULL DEFAULT '0' COMMENT '订单结算时间',
`is_package` enum('0','1') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '是否是套餐',
`is_integral` enum('0','1') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '是否是积分产品',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `order_order_sn_unique` (`order_sn`),
KEY `order_order_sn_member_id_order_status_out_trade_no_index` (`order_sn`,`member_id`,`order_status`,`out_trade_no`(191))
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 售后申请表
CREATE TABLE `order_returns_apply` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '订单单号',
`order_detail_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '子订单编码',
`return_no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '售后单号',
`member_id` int(11) NOT NULL COMMENT '用户编码',
`state` tinyint(4) NOT NULL COMMENT '类型 0 仅退款 1退货退款',
`product_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '货物状态 0:已收到货 1:未收到货',
`why` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '退换货原因',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '审核状态 -1 拒绝 0 未审核 1审核通过',
`audit_time` int(11) NOT NULL DEFAULT '0' COMMENT '审核时间',
`audit_why` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核原因',
`note` text COLLATE utf8mb4_unicode_ci COMMENT '备注',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 售后记录表
CREATE TABLE `order_returns` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`returns_no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '退货编号 供客户查询',
`order_id` int(11) NOT NULL COMMENT '订单编号',
`express_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物流单号',
`consignee_realname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收货人姓名',
`consignee_telphone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系电话',
`consignee_telphone2` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '备用联系电话',
`consignee_address` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收货地址',
`consignee_zip` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮政编码',
`logistics_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '物流方式',
`logistics_fee` decimal(12,2) NOT NULL COMMENT '物流发货运费',
`order_logistics_status` int(11) DEFAULT NULL COMMENT '物流状态',
`logistics_settlement_status` int(11) DEFAULT NULL COMMENT '物流结算状态',
`logistics_result_last` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物流最后状态描述',
`logistics_result` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物流描述',
`logistics_create_time` int(11) DEFAULT NULL COMMENT '发货时间',
`logistics_update_time` int(11) DEFAULT NULL COMMENT '物流更新时间',
`logistics_settlement_time` int(11) DEFAULT NULL COMMENT '物流结算时间',
`returns_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0全部退单 1部分退单',
`handling_way` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'PUPAWAY:退货入库;REDELIVERY:重新发货;RECLAIM-REDELIVERY:不要求归还并重新发货; REFUND:退款; COMPENSATION:不退货并赔偿',
`returns_amount` decimal(8,2) NOT NULL COMMENT '退款金额',
`return_submit_time` int(11) NOT NULL COMMENT '退货申请时间',
`handling_time` int(11) NOT NULL COMMENT '退货处理时间',
`remark` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '退货原因',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 评价表
CREATE TABLE `order_appraise` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL COMMENT '订单编码',
`info` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评论内容',
`level` enum('-1','0','1') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '级别 -1差评 0中评 1好评',
`desc_star` tinyint(4) NOT NULL COMMENT '描述相符 1-5',
`logistics_star` tinyint(4) NOT NULL COMMENT '物流服务 1-5',
`attitude_star` tinyint(4) NOT NULL COMMENT '服务态度 1-5',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_appraise_order_id_index` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -1,22 +0,0 @@
drop database if exists shop;
create database shop default charset utf8;
use shop;
drop table if exists tb_goods;
create table tb_goods
(
gid int not null auto_increment,
gname varchar(50) not null,
gprice decimal(10,2) not null,
gimage varchar(255),
primary key (gid)
);
insert into tb_goods values
(default, '乐事Lays无限薯片', 8.2, 'images/lay.jpg'),
(default, '旺旺 仙贝 加量装 540g', 18.5, 'images/wang.jpg'),
(default, '多儿比Dolbee黄桃水果罐头', 6.8, 'images/dolbee.jpg'),
(default, '王致和 精制料酒 500ml', 7.9, 'images/wine.jpg'),
(default, '陈克明 面条 鸡蛋龙须挂面', 1.0, 'images/noodle.jpg'),
(default, '鲁花 菜籽油 4L', 69.9, 'images/oil.jpg');

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@
python manage.py migrate polls
```
3. 用下面的SQL语句直接插入两条测试数据通常不能用户的密码直接保存在数据库中因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数散列函数可以产生出一个128位比特的哈希值散列值用于确保信息传输完整一致。在使用哈希值时通常会将哈希值表示为16进制字符串因此128位的MD5摘要通常表示为32个十六进制符号。
3. 用下面的SQL语句直接插入两条测试数据通常不能用户的密码直接保存在数据库中因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数散列函数可以产生出一个128位比特的哈希值散列值用于确保信息传输完整一致。在使用哈希值时通常会将哈希值表示为16进制字符串因此128位的MD5摘要通常表示为32个十六进制符号。
```SQL
insert into `tb_user`

View File

@ -46,7 +46,7 @@ def export_teachers_excel(request):
# 中文文件名需要处理成百分号编码
filename = quote('老师.xls')
# 通过响应头告知浏览器下载该文件以及对应的文件名
resp['content-disposition'] = f'attachment; filename*=utf-8''{filename}'
resp['content-disposition'] = f'attachment; filename*=utf-8\'\'{filename}'
return resp
```

View File

@ -212,4 +212,3 @@ queryset = Teacher.objects.values('subject__name').annotate(good=Avg('good_count
```
可见Django的ORM框架允许我们用面向对象的方式完成关系数据库中的分组和聚合查询。

View File

@ -175,4 +175,3 @@ class SubjectMapper(ModelMapper):
前后端分离的开发需要将前端页面作为静态资源进行部署项目实际上线的时候我们会对整个Web应用进行动静分离静态资源通过Nginx或Apache服务器进行部署生成动态内容的Python程序部署在uWSGI或者Gunicorn服务器上对动态内容的请求由Nginx或Apache路由到uWSGI或Gunicorn服务器上。
在开发阶段我们通常会使用Django自带的测试服务器如果要尝试前后端分离可以先将静态页面放在之前创建的放静态资源的目录下具体的做法可以参考[项目完整代码](https://gitee.com/jackfrued/django19062)。

View File

@ -1,4 +1,4 @@
## 单元测试
Python标准库中提供了名为`unittest` 的模块来支持我们对代码进行单元测试。所谓单元测试是指针对程序中最小的功能单元在Python中指函数或类中的方法进行的测试
请各位读者移步到[《使用Django开发商业项目》](../Day91-100/95.使用Django开发商业项目.md)一文

View File

@ -225,7 +225,7 @@ XPath是在XML文档中查找信息的一种语法它使用路径表达式来
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<title lang="zh">三国演义</title>
<price>39.95</price>
</book>
</bookstore>

View File

@ -6,11 +6,11 @@
下面我们以“360图片”网站为例说明什么是JavaScript逆向工程。其实所谓的JavaScript逆向工程就是找到通过Ajax技术动态获取数据的接口。在浏览器中输入<http://image.so.com/z?ch=beauty>就可以打开“360图片”的“美女”版块如下图所示。
![](./res/image360-website.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20210824004714.png)
但是当我们在浏览器中通过右键菜单“显示网页源代码”的时候居然惊奇的发现页面的HTML代码中连一个`<img>`标签都没有那么我们看到的图片是怎么显示出来的呢原来所有的图片都是通过JavaScript动态加载的而在浏览器的“开发人员工具”的“网络”中可以找到获取这些图片数据的网络API接口如下图所示。
![](./res/api-image360.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20210824004727.png)
那么结论就很简单了只要我们找到了这些网络API接口那么就能通过这些接口获取到数据当然实际开发的时候可能还要对这些接口的参数以及接口返回的数据进行分析了解每个参数的意义以及返回的JSON数据的格式这样才能在我们的爬虫中使用这些数据。

View File

@ -111,12 +111,13 @@ class TaobaoDownloaderMiddleWare(object):
def __init__(self, timeout=None):
self.timeout = timeout
# options = webdriver.ChromeOptions()
# options.add_argument('headless')
# self.browser = webdriver.Chrome(options=options)
self.browser = webdriver.Chrome()
options = webdriver.ChromeOptions()
options.add_argument('--headless')
self.browser = webdriver.Chrome(options)
self.browser.set_window_size(1000, 600)
self.browser.set_page_load_timeout(self.timeout)
self.browser.implicitly_wait(10)
# self.browser.add_cookie({})
# self.browser.set_page_load_timeout(self.timeout)
def __del__(self):
self.browser.close()

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from io import StringIO
from urllib.parse import urlencode
import re

View File

@ -1,2 +0,0 @@
## 数据分析概述

View File

@ -1,2 +0,0 @@
## NumPy的应用

View File

@ -1,2 +0,0 @@
## Pandas的应用

View File

@ -1,182 +0,0 @@
## 数据可视化
数据的处理、分析和可视化已经成为Python近年来最为重要的应用领域之一其中数据的可视化指的是将数据呈现为漂亮的统计图表然后进一步发现数据中包含的规律以及隐藏的信息。数据可视化又跟数据挖掘和大数据分析紧密相关而这些领域以及当下被热议的“深度学习”其最终的目标都是为了实现从过去的数据去对未来的状况进行预测。Python在实现数据可视化方面是非常棒的即便是使用个人电脑也能够实现对百万级甚至更大体量的数据进行探索的工作而这些工作都可以在现有的第三方库的基础上来完成无需“重复的发明轮子”。[Matplotlib](https://matplotlib.org/)就是Python绘图库中的佼佼者它包含了大量的工具你可以使用这些工具创建各种图形包括散点图、折线图、直方图、饼图、雷达图等Python科学计算社区也经常使用它来完成数据可视化的工作。
### 安装matplotlib
可以使用pip来安装matplotlib命令如下所示。
```Shell
pip install matplotlib
```
### 绘制折线图
```Python
# coding: utf-8
import matplotlib.pyplot as plt
def main():
# 保存x轴数据的列表
x_values = [x for x in range(1, 11)]
# 保存y轴数据的列表
y_values = [x ** 2 for x in range(1, 11)]
# 设置图表的标题以及x和y轴的说明
plt.title('Square Numbers')
plt.xlabel('Value', fontsize=18)
plt.ylabel('Square', fontsize=18)
# 设置刻度标记的文字大小
plt.tick_params(axis='both', labelsize=16)
# 绘制折线图
plt.plot(x_values, y_values)
plt.show()
if __name__ == '__main__':
main()
```
运行程序,效果如下图所示。
![](./res/result1.png)
如果使用jupyter的notebook需要使用魔法指令`%matplotlib inresline`来设置在页面中显示图表,效果如下所示。
![](./res/result-in-jupyter.png)
### 绘制散点图
可以将上面代码中的的`plot`函数换成`scatter`函数来绘制散点图,效果如下图所示。
![](./res/result2.png)
当然,也可以直接通过`plot`函数设置绘图的颜色和线条的形状将折线图改造为散点图,对应的代码如下所示,其中参数'xr'表示每个点的记号是x图形颜色是红色<u>r</u>ed
```Python
plt.plot(x_values, y_values, 'xr')
```
重新运行程序,效果如下图所示。
![](./res/result3.png)
可能大家已经注意到了1和10对应的x记号在图形边角的位置不太明显要解决这个问题可以通过添加下面的代码调整x轴和y轴的坐标范围。
```Python
plt.axis([0, 12, 0, 120])
```
调整后的效果如下图所示。
![](./res/result4.png)
### 绘制正弦曲线
在下面的程序中,我们使用了名为[NumPy](http://www.numpy.org/)的第三方库来产生样本并计算正弦值。NumPy是一个运行速度非常快的数学库主要用于数组计算。它可以让你在Python中使用向量和数学矩阵以及许多用C语言实现的底层函数。如果想通过Python学习数据科学或者机器学习相关的内容那么就得先学会使用NumPy。
```Python
# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np
def main():
# 指定采样的范围以及样本的数量
x_values = np.linspace(0, 2 * np.pi, 1000)
# 计算每个样本对应的正弦值
y_values = np.sin(x_values)
# 绘制折线图(线条形状为--, 颜色为蓝色)
plt.plot(x_values, y_values, '--b')
plt.show()
if __name__ == '__main__':
main()
```
运行程序,效果如下图所示。
![](./res/result5.png)
如果要在一个坐标系上绘制多个图像,可以按照如下的方式修改代码。
```Python
# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np
def main():
x_values = np.linspace(0, 2 * np.pi, 1000)
plt.plot(x_values, np.sin(x_values), '--b')
plt.plot(x_values, np.sin(2 * x_values), '--r')
plt.show()
if __name__ == '__main__':
main()
```
修改后的代码运行效果如下图所示。
![](./res/result6.png)
如果需要分别在两个坐标系上绘制出两条曲线,可以按照如下的方式操作。
```Python
# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np
def main():
# 将样本数量减少为50个
x_values = np.linspace(0, 2 * np.pi, 50)
# 设置绘图为2行1列活跃区为1区(第一个图)
plt.subplot(2, 1, 1)
plt.plot(x_values, np.sin(x_values), 'o-b')
# 设置绘图为2行1列活跃区为2区(第二个图)
plt.subplot(2, 1, 2)
plt.plot(x_values, np.sin(2 * x_values), '.-r')
plt.show()
if __name__ == '__main__':
main()
```
效果如下图所示。
![](./res/result7.png)
### 绘制直方图
我们可以通过NumPy的random模块的normal函数来生成[正态分布](https://zh.wikipedia.org/wiki/%E6%AD%A3%E6%80%81%E5%88%86%E5%B8%83)的采样数据,其中的三个参数分别表示期望、标准差和样本数量,然后绘制成直方图,代码如下所示。
```Python
# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np
def main():
# 通过random模块的normal函数产生1000个正态分布的样本
data = np.random.normal(10.0, 5.0, 1000)
# 绘制直方图(直方的数量为10个)
plt.hist(data, 10)
plt.show()
if __name__ == '__main__':
main()
```
运行效果如下图所示。
![](./res/result8.png)

View File

@ -1,2 +0,0 @@
## 数据分析项目实战

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,62 @@
## 数据分析概述
当今世界对信息技术的依赖程度在不断加深,每天都会有大量的数据产生,我们经常会感到数据越来越多,但是要从中发现有价值的信息却越来越难。这里所说的信息,可以理解为对数据集处理之后的结果,是从数据集中提炼出的可用于其他场合的结论性的东西,而**从原始数据中抽取出有价值的信息**的这个过程我们就称之为**数据分析**,它是数据科学工作的一部分。
### 数据分析师的职责和技能栈
我们通常将从事数据分析、数据挖掘和数据产品的岗位都统称为数据分析岗位,但是根据工作性质的不同,又可以分为偏业务的**数据分析方向**、偏算法的**数据挖掘方向**、偏产品的**数据产品方向**和偏开发的**数据工程方向**。我们通常所说的数据分析师主要是指**业务数据分析师**,很多数据分析师的职业生涯都是从这个岗位开始的,而且这个岗位也是招聘数量最多的岗位。业务数据分析师在公司通常不属于研发部门而**属于运营部门**,所以这个岗位也称为**数据运营**或**商业分析**通常招聘信息对这个岗位的描述JD
1. 负责各部门相关的报表。
2. 建立和优化指标体系。
3. 监控数据波动和异常,找出问题。
4. 优化和驱动业务,推动数字化运营。
5. 找出潜在的市场和产品的上升空间。
根据上面的描述,作为业务数据分析师,我们的工作不是给领导一个简单浅显的结论,而是结合公司的业务,完成**监控数据**、**揪出异常**、**找到原因**、**探索趋势**的工作。所以作为数据分析师,不管是用 Python 语言、Excel、SPSS或其他的商业智能工具工具只是达成目标的手段**数据思维是核心技能**,而从实际业务问题出发到最终**发现数据中的商业价值**是终极目标。数据分析师在很多公司只是一个基础岗位,精于业务的数据分析师可以向**数据分析经理**或**数据运营总监**等管理岗位发展;对于熟悉机器学习算法的数据分析师来说,可以向**数据挖掘工程师**或**算法专家**方向发展,而这些岗位除了需要相应的数学和统计学知识,在编程能力方面也比数据分析师有更高的要求,可能还需要有大数据存储和处理的相关经验;作为数据产品经理,除了传统产品经理的技能栈之外,也需要较强的技术能力,例如要了解常用的推荐算法、机器学习模型,能够为算法的改进提供依据,能够制定相关埋点的规范和口径,虽然不需要精通各种算法,但是要站在产品的角度去考虑数据模型、指标、算法等的落地;数据工程师是一个偏技术的岗位,基本上的成长道路都是从 SQL 开始,逐步向 Hadoop 生态圈迁移,需要有 Java 语言的编程经验。
以下是我总结的数据分析师的技能栈,仅供参考。
1. 计算机科学(数据分析工具、编程语言、数据库)
2. 数学和统计学(数据思维、统计思维)
3. 人工智能(机器学习算法)
4. 业务理解能力(沟通、表达、经验)
5. 总结和表述能力商业PPT、文字总结
### 数据分析的流程
我们提到数分析这个词很多时候可能指的都是**狭义的数据分析**,这类数据分析主要目标就是生成可视化报表并通过这些报表来洞察业务中的问题。**广义的数据分析**还包含了数据挖掘的部分,不仅要通过数据实现对业务的监控和分析,还要利用机器学习算法,找出隐藏在数据背后的知识,并利用这些知识为将来的决策提供支撑。简单的说,**一个完整的数据分析应该包括基本的数据分析和深入的数据挖掘两个部分**。
基本的数据分析工作一般包含以下几个方面的内容,当然因为行业和工作内容的不同会略有差异。
1. 确定目标(输入):理解业务,确定指标口径
2. 获取数据:数据仓库、电子表格、三方接口、网络爬虫、开放数据集等
3. 清洗数据:缺失值/重复值/异常值处理、数据变换(格式化、规范化)、数据归约、离散化等
4. 探索数据:运算、统计、分组、聚合、可视化
5. 数据报告(输出):数据发布,工作成果总结汇报
6. 分析洞察(后续):解释数据的变化,提出对应的方案
深入的数据挖掘工作通常包含以下几个方面的内容,当然因为行业和工作内容的不同会略有差异。
1. 确定目标(输入):理解业务,明确挖掘目标
2. 数据准备:数据采集、数据描述、数据探索、质量判定等
3. 数据加工:提取数据、清洗数据、数据变换、特殊编码、降维、特征选择等
4. 数据建模:模型比较、模型选择、算法应用
5. 模型评估:交叉检验、参数调优、结果评价
6. 模型部署(输出):模型落地、业务改进、运营监控、报告撰写
### 数据分析相关库
使用 Python 从事数据科学相关的工作是一个非常棒的选择,因为 Python 整个生态圈中有大量的成熟的用于数据科学的软件包工具库。而且不同于其他的用于数据科学的编程语言Julia、RPython 除了可以用于数据科学,能做的事情还很多,可以说 Python 语言几乎是无所不能的。
#### 三大神器
1. [NumPy](https://numpy.org/):支持常见的数组和矩阵操作,通过`ndarray`类实现了对多维数组的封装,提供了操作这些数组的方法和函数集。由于 NumPy 内置了并行运算功能,当使用多核 CPU 时Numpy会自动做并行计算。
2. [Pandas](https://pandas.pydata.org/)pandas的核心是其特有的数据结构`DataFrame`和`Series`,这使得 pandas 可以处理包含不同类型的数据的负责表格和时间序列这一点是NumPy的`ndarray`做不到的。使用 pandas可以轻松顺利的加载各种形式的数据然后对数据进行切片、切块、处理缺失值、聚合、重塑和可视化等操作。
3. [Matplotlib](https://matplotlib.org/)matplotlib 是一个包含各种绘图模块的库能够根据我们提供的数据创建高质量的图形。此外matplotlib 还提供了 pylab 模块,这个模块包含了很多像 [MATLAB](https://www.mathworks.com/products/matlab.html) 一样的绘图组件。
#### 其他相关库
1. [SciPy](https://scipy.org/):完善了 NumPy 的功能,封装了大量科学计算的算法,包括线性代数、稀疏矩阵、信号和图像处理、最优化问题、快速傅里叶变换等。
2. [Seaborn](https://seaborn.pydata.org/)seaborn 是基于 matplotlib 的图形可视化工具,直接使用 matplotlib 虽然可以定制出漂亮的统计图表但是总体来说还不够简单方便seaborn 相当于是对 matplotlib 做了封装,让用户能够以更简洁有效的方式做出各种有吸引力的统计图表。
3. [Scikit-learn](https://scikit-learn.org/)scikit-learn 最初是 SciPy 的一部分,它是 Python 数据科学运算的核心,提供了大量机器学习可能用到的工具,包括:数据预处理、监督学习(分类、回归)、无监督学习(聚类)、模式选择、交叉检验等。
4. [Statsmodels](https://www.statsmodels.org/stable/index.html):包含了经典统计学和经济计量学算法的库。

View File

@ -0,0 +1,164 @@
## 环境准备
如果希望快速开始使用 Python 处理数据科学相关的工作,建议大家直接安装 Anaconda然后使用 Anaconda 中集成的 Notebook 或 JupyterLab 工具来编写代码。因为对于新手来说,先安装官方的 Python 解释器,再逐个安装工作中会使用到的三方库文件会比较麻烦,尤其是在 Windows 环境下,经常会因为构建工具或 DLL 文件的缺失导致安装失败,而一般新手也很难根据错误提示信息采取正确的解决措施,容易产生严重的挫败感。
### 安装和使用 Anaconda
对于个人用户来说,可以从 Anaconda 的[官方网站](https://www.anaconda.com/)下载它的“个人版Individual Edition”安装程序安装完成后你的计算机上不仅拥有了 Python 环境和 Spyder类似于PyCharm的集成开发工具还拥有了与数据科学工作相关的近200个工具包包括我们上面提到 Python 数据分析三大神器。除此之外Anaconda 还提供了一个名为 conda 的包管理工具,通过这个工具不仅可以管理 Python 的工具包,还可以用于创建运行 Python 程序的虚拟环境。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211005111417.png" width="100%">
如上图所示,可以通过 Anaconda 官网提供的下载链接选择适合自己操作系统的安装程序建议大家选择图形化的安装程序下载完成后双击安装程序开始安装。安装过程基本使用默认设置即可完成安装后macOS 用户可以在“应用程序”或“Launchpad”中找到名为“Anaconda-Navigator”的应用程序运行该程序可以看到如下所示的界面我们可以在这里选择需要执行的操作。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211005111729.png" width="85%">
对于 Windows 用户,建议按照安装向导的提示和推荐的选项来安装 Anaconda除了安装路径基本也没有什么需要选择的安装完成后可以在“开始菜单”中找到“Anaconda3”。
#### conda命令
如果希望使用 conda 工具来管理依赖项或者创建项目的虚拟环境,可以在终端或命令行提示符中使用 conda 命令。Windows 用户可以在“开始菜单”中找到“Anaconda3”然后点击“Anaconda Prompt”来启动支持 conda 的命令行提示符。macOS 用户建议直接使用“Anaconda-Navigator”中的“Environments”通过可视化的方式对虚拟环境和依赖项进行管理。
1. 版本和帮助信息。
- 查看版本:`conda -V`或`conda --version`
- 获取帮助:`conda -h`或`conda --help`
- 相关信息:`conda list`
2. 虚拟环境相关。
- 显示所有虚拟环境:`conda env list`
- 创建虚拟环境:`conda create --name venv`
- 指定 Python 版本创建虚拟环境:`conda create --name venv python=3.7`
- 指定 Python 版本创建虚拟环境并安装指定依赖项:`conda create --name venv python=3.7 numpy pandas`
- 通过克隆现有虚拟环境的方式创建虚拟环境:`conda create --name venv2 --clone venv`
- 分享虚拟环境并重定向到指定的文件中:`conda env export > environment.yml`
- 通过分享的虚拟环境文件创建虚拟环境:`conda env create -f environment.yml`
- 激活虚拟环境:`conda activate venv`
- 退出虚拟环境:`conda deactivate`
- 删除虚拟环境:`conda remove --name venv --all`
> **说明**:上面的命令中,`venv`和`venv2`是虚拟环境文件夹的名字,可以将其替换为自己喜欢的名字,但是**强烈建议**使用英文且不要出现空格或其他特殊字符。
3. 包(三方库或工具)管理。
- 查看已经安装的包:`conda list`
- 搜索指定的包:`conda search matplotlib`
- 安装指定的包:`conda install matplotlib`
- 更新指定的包:`conda update matplotlib`
- 移除指定的包:`conda remove matplotlib`
> **说明**:在搜索、安装和更新软件包时,默认会连接到官方网站进行操作,如果觉得速度不给力,可以将默认的官方网站替换为国内的镜像网站,推荐使用清华大学的开源镜像网站。将默认源更换为国内镜像的命令是:`conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ `。如果需要换回默认源,可以使用命令`conda config --remove-key channels`。
### 使用Notebook
#### 安装和启动Notebook
如果已经安装了 AnacondamacOS 用户可以按照上面所说的方式在“Anaconda-Navigator”中直接启动“Jupyter Notebook”以下统一简称为 Notebook。Windows 用户可以在“开始菜单”中找到 Anaconda 文件夹接下来选择运行文件夹中的“Jupyter Notebook”就可以开始数据科学的探索之旅。
对于安装了 Python 环境但是没有安装 Anaconda 的用户,可以用 Python 的包管理工具`pip`来安装`jupyter`然后在终端Windows 系统为命令行提示符)中运行`jupyter notebook`命令来启动 Notebook如下所示。
安装 Notebook
```Bash
pip install jupyter
```
安装三大神器:
```Bash
pip install numpy pandas matplotlib
```
运行 Notebook
```Bash
jupyter notebook
```
Notebook 是基于网页的用于交互计算的应用程序,可以用于代码开发、文档撰写、代码运行和结果展示。简单的说,你可以在网页中直接**编写代码**和**运行代码**,代码的运行结果也会直接在代码块下方进行展示。如在编写代码的过程中需要编写说明文档,可在同一个页面中使用 Markdown 格式进行编写而且可以直接看到渲染后的效果。此外Notebook 的设计初衷是提供一个能够支持多种编程语言的工作环境目前它能够支持超过40种编程语言包括 Python、R、Julia、Scala 等。
首先,我们可以创建一个用于书写 Python 代码的 Notebook如下图所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005113911.png)
接下来,我们就可以编写代码、撰写文档和运行程序啦,如下图所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005113900.png)
#### Notebook使用技巧
如果使用 Python 做工程化的项目开发PyCharm 肯定是最好的选择,它提供了一个集成开发环境应该具有的所有功能,尤其是智能提示、代码补全、自动纠错这类功能会让开发人员感到非常舒服。如果使用 Python 做数据科学相关的工作Notebook 并不比 PyCharm 逊色,在数据和图表展示方面 Notebook 更加优秀。这个工具的使用非常简单,大家可以看看 Notebook 菜单栏,相信理解起来不会有太多困难,在知乎上有一篇名为[《最详尽使用指南超快上手Jupyter Notebook》](https://zhuanlan.zhihu.com/p/32320214)的文章,也可以帮助大家快速认识 Notebook。
> **说明**[Jupyter 官网](https://jupyter.org/)上还有一个名为 JupyterLab 的工具被称之为“Next-Generation Notebook”用户界面较之 Notebook 更加友好,有兴趣的读者可以使用`pip install jupyterlab`命令来安装这个工具,然后通过`jupyter lab`来启动它。
下面我为大家介绍一些 Notebook 的使用技巧,希望能够帮助大家提升工作效率。
1. 自动补全。在使用 Notebook 编写代码时,按`Tab`键会获得代码提示。
2. 获得帮助。在使用 Notebook 时,如果希望了解一个对象(如变量、类、函数等)的相关信息或使用方式,可以在对象后面使用`?`并运行代码, 窗口下方会显示出对应的信息,帮助我们了解该对象,如下所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005113848.png)
3. 搜索命名。如果只记得一个类或一个函数名字的一部分,可以使用通配符`*`并配合`?`进行搜索,如下所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005113836.png)
4. 调用命令。可以在 Notebook 中使用`!`后面跟系统命令的方式来执行系统命令。
5. 魔法指令。Notebook 中有很多非常有趣且有用的魔法指令,例如可以使用`%timeit`测试语句的执行时间,可以使用`%pwd`查看当前工作目录等。如果想查看所有的魔法指令,可以使用`%lsmagic`,如果了解魔法指令的用法,可以使用`%magic`来查看,如下图所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005113825.png)
常用的魔法指令有:
| 魔法指令 | 功能说明 |
| ------------------------------------------- | ------------------------------------------ |
| `%pwd` | 查看当前工作目录 |
| `%ls` | 列出当前或指定文件夹下的内容 |
| `%cat` | 查看指定文件的内容 |
| `%hist` | 查看输入历史 |
| `%matplotlib inline` | 设置在页面中嵌入matplotlib输出的统计图表 |
| `%config Inlinebackend.figure_format='svg'` | 设置统计图表使用SVG格式矢量图 |
| `%run` | 运行指定的程序 |
| `%load` | 加载指定的文件到单元格中 |
| `%quickref` | 显示IPython的快速参考 |
| `%timeit` | 多次运行代码并统计代码执行时间 |
| `%prun` | 用`cProfile.run`运行代码并显示分析器的输出 |
| `%who` / `%whos` | 显示命名空间中的变量 |
| `%xdel` | 删除一个对象并清理所有对它的引用 |
6. 快捷键。Notebook 中的很多操作可以通过快捷键来实现使用快捷键可以提升工作效率。Notebook 的快捷键又可以分为命令模式下的快捷键和编辑模式下的快捷键,所谓编辑模式就是处于输入代码或撰写文档状态的模式,在编辑模式下按`Esc`可以回到命令模式,在命令模式下按`Enter`可以进入编辑模式。
命令模式下的快捷键:
| 快捷键 | 功能说明 |
| ------------------------------- | -------------------------------------------- |
| Alt + EnterOption + Enter | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + EnterCommand + Enter | 运行当前单元格 |
| j / k、Shift + j / Shift + k | 选中下方/上方单元格、连续选中下方/上方单元格 |
| a / b | 在下方/上方插入新的单元格 |
| c / x | 复制单元格 / 剪切单元格 |
| v / Shift + v | 在下方/上方粘贴单元格 |
| dd / z | 删除单元格 / 恢复删除的单元格 |
| l / Shift + l | 显示或隐藏当前/所有单元格行号 |
| ii / 00 | 中断/重启Notebook内核 |
| Space / Shift + Space | 向下/向上滚动页面 |
编辑模式下的快捷键:
| 快捷键 | 功能说明 |
| ------------------------------------------------ | -------------------------------------- |
| Shift + Tab | 获得提示信息 |
| Ctrl + ]Command + ]/ Ctrl + [Command + [ | 增加/减少缩进 |
| Alt + EnterOption + Enter | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + EnterCommand + Enter | 运行当前单元格 |
| Ctrl + Left / RightCommand + Left / Right | 光标移到行首/行尾 |
| Ctrl + Up / DownCommand + Up / Down | 光标移动代码开头/结尾处 |
| Up / Down | 光标上移/下移一行或移到上/下一个单元格 |
> **温馨提示**:如果记不住这些快捷键也没有关系,在命令模式下按`h`键可以打开 Notebook 的帮助系统,马上就可以看到快捷键的设置,而且可以根据实际的需要重新编辑快捷键,如下图所示。
>
> ![](https://gitee.com/jackfrued/mypic/raw/master/20211005113812.png)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,413 @@
## NumPy的应用-2
### 数组的运算
使用 NumPy 最为方便的是当需要对数组元素进行运算时,不用编写循环代码遍历每个元素,所有的运算都会自动的**矢量化**使用高效的提前编译的底层语言代码来对数据序列进行数学操作。简单的说就是NumPy 中的数学运算和数学函数会自动作用于数组中的每个成员。
#### 数组跟标量的运算
代码:
```Python
array35 = np.arange(1, 10)
print(array35 + 10)
print(array35 * 10)
```
输出:
```
[11 12 13 14 15 16 17 18 19]
[10 20 30 40 50 60 70 80 90]
```
#### 数组跟数组的运算
代码:
```Python
array36 = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3])
print(array35 + array36)
print(array35 * array36)
print(array35 ** array36)
```
输出:
```
[ 2 3 4 6 7 8 10 11 12]
[ 1 2 3 8 10 12 21 24 27]
[ 1 2 3 16 25 36 343 512 729]
```
#### 通用一元函数
通用函数是对`ndarray`中的数据执行元素级运算的函数。你可以将其看做普通函数(接收一个标量值作为参数,返回一个标量值)的矢量化包装器,如下所示。
代码:
```Python
print(np.sqrt(array35))
print(np.log2(array35))
```
输出:
```
[1. 1.41421356 1.73205081 2. 2.23606798 2.44948974
2.64575131 2.82842712 3. ]
[0. 1. 1.5849625 2. 2.32192809 2.5849625
2.80735492 3. 3.169925 ]
```
**表1通用一元函数**
| 函数 | 说明 |
| -------------------------------- | --------------------------------------------- |
| `abs` / `fabs` | 求绝对值的函数 |
| `sqrt` | 求平方根的函数,相当于`array ** 0.5 ` |
| `square` | 求平方的函数,相当于`array ** 2` |
| `exp` | 计算$e^x$的函数 |
| `log` / `log10` / `log2` | 对数函数(`e`为底 / `10`为底 / `2`为底) |
| `sign` | 符号函数(`1` - 正数;`0` - 零;`-1` - 负数) |
| `ceil` / `floor` | 上取整 / 下取整 |
| `isnan` | 返回布尔数组NaN对应`True`非NaN对应`False` |
| `isfinite` / `isinf` | 判断数值是否为无穷大的函数 |
| `cos` / `cosh` / `sin` | 三角函数 |
| `sinh` / `tan` / `tanh` | 三角函数 |
| `arccos` / `arccosh` / `arcsin` | 反三角函数 |
| `arcsinh` / `arctan` / `arctanh` | 反三角函数 |
| `rint` / `round` | 四舍五入函数 |
#### 通用二元函数
代码:
```Python
array37 = np.array([[4, 5, 6], [7, 8, 9]])
array38 = np.array([[1, 2, 3], [3, 2, 1]])
print(array37 ** array38)
print(np.power(array37, array38))
```
输出:
```
[[ 4 25 216]
[343 64 9]]
[[ 4 25 216]
[343 64 9]]
```
**表2通用二元函数**
| 函数 | 说明 |
| --------------------------------- | ---- |
| `add(x, y)` / `substract(x, y)` | 加法函数 / 减法函数 |
|`multiply(x, y)` / `divide(x, y)`|乘法函数 / 除法函数|
| `floor_divide(x, y)` / `mod(x, y)` | 整除函数 / 求模函数 |
|`allclose(x, y)`|检查数组`x`和`y`元素是否几乎相等|
| `power(x, y)` | 数组$x$的元素$x_i$和数组$y$的元素$y_i$,计算$x_i^{y_i}$ |
| `maximum(x, y)` / `fmax(x, y)` | 两两比较元素获取最大值 / 获取最大值忽略NaN |
| `minimum(x, y)` / `fmin(x, y)` | 两两比较元素获取最小值 / 获取最小值忽略NaN |
| `inner(x, y)` | 内积运算 |
| `cross(x, y) `/ `outer(x, y)` | 叉积运算 / 外积运算 |
| `intersect1d(x, y)` | 计算`x`和`y`的交集,返回这些元素构成的有序数组 |
| `union1d(x, y)` | 计算`x`和`y`的并集,返回这些元素构成的有序数组 |
| `in1d(x, y)` | 返回由判断`x` 的元素是否在`y`中得到的布尔值构成的数组 |
| `setdiff1d(x, y)` | 计算`x`和`y`的差集,返回这些元素构成的数组 |
| `setxor1d(x, y)` | 计算`x`和`y`的对称差,返回这些元素构成的数组 |
>**补充说明**:在二维空间内,两个向量$\boldsymbol{A}=\begin{bmatrix} a_1 \\ a_2 \end{bmatrix}$和$\boldsymbol{B}=\begin{bmatrix} b_1 \\ b_2 \end{bmatrix}$的叉积是这样定义的:$\boldsymbol{A}\times \boldsymbol{B}=\begin{vmatrix} a_1 \quad a_2 \\ b_1 \quad b_2 \end{vmatrix}=a_1b_2 - a_2b_1$,其中$\begin{vmatrix} a_1 \quad a_2 \\ b_1 \quad b_2 \end{vmatrix}$称为行列式。但是一定要注意,叉积并不等同于行列式,行列式的运算结果是一个标量,而叉积运算的结果是一个向量。如果不明白,我们可以看看三维空间两个向量,$\boldsymbol{A}=\begin{bmatrix} a_1 \\ a_2 \\ a_3 \end{bmatrix}$和$\boldsymbol{B}=\begin{bmatrix} b_1 \\ b_2 \\ b_3 \end{bmatrix}$的叉积是$\left< \hat{i} \begin{vmatrix} a_2 \quad a_3 \\ b_2 \quad b_3 \end{vmatrix}, -\hat{j} \begin{vmatrix} a_1 \quad a_3 \\ b_1 \quad b_3 \end{vmatrix}, \hat{k} \begin{vmatrix} a_1 \quad a_2 \\ b_1 \quad b_2 \end{vmatrix} \right>$,其中$\hat{i}, \hat{j}, \hat{k}$代表每个维度的单位向量。
#### 广播机制
上面的例子中,两个二元运算的数组形状是完全相同的,我们再来研究一下,两个形状不同的数组是否可以直接做二元运算或使用二元函数进行运算,请看下面的例子。
代码:
```Python
array39 = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]])
array40 = np.array([1, 2, 3])
array39 + array40
```
输出:
```
array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
```
代码:
```Python
array41 = np.array([[1], [2], [3], [4]])
array39 + array41
```
输出:
```
array([[1, 1, 1],
[3, 3, 3],
[5, 5, 5],
[7, 7, 7]])
```
通过上面的例子我们发现形状不同的数组仍然有机会进行二元运算但也绝对不是任意的数组都可以进行二元运算。简单的说只有两个数组后缘维度相同或者其中一个数组后缘维度为1时广播机制会被触发而通过广播机制如果能够使两个数组的形状一致才能进行二元运算。所谓后缘维度指的是数组`shape`属性对应的元组中最后一个元素的值从后往前数最后一个维度的值例如我们之前打开的图像对应的数组后缘维度为33行4列的二维数组后缘维度为4而有5个元素的一维数组后缘维度为5。简单的说就是后缘维度相同或者其中一个数组的后缘维度为1就可以应用广播机制而广播机制如果能够使得数组的形状一致就满足了两个数组对应元素做运算的需求如下图所示。
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115640.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115658.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115800.png)
### 其他常用函数
除了上面讲到的函数外NumPy 中还提供了很多用于处理数组的函数,`ndarray`对象的很多方法也可以通过直接调用函数来实现,下表给出了一些常用的函数。
**表3NumPy其他常用函数**
| 函数 | 说明 |
| ------------------- | ------------------------------------------------ |
| `unique` | 去除数组重复元素,返回唯一元素构成的有序数组 |
| `copy` | 返回拷贝数组得到的数组 |
| `sort` | 返回数组元素排序后的拷贝 |
| `split` / `hsplit` / `vsplit` | 将数组拆成若干个子数组 |
| `stack` / `hstack` / `vstack` | 将多个数组堆叠成新数组 |
| `concatenate` | 沿着指定的轴连接多个数组构成新数组 |
| `append` / `insert` | 向数组末尾追加元素 / 在数组指定位置插入元素 |
| `argwhere` | 找出数组中非0元素的位置 |
| `extract` / `select` / `where` | 按照指定的条件从数组中抽取或处理数组元素 |
| `flip` | 沿指定的轴翻转数组中的元素 |
| `fromiter` | 通过迭代器创建数组对象 |
| `fromregex` | 通过读取文件和正则表达式解析获取数据创建数组对象 |
| `repeat` / `tile` | 通过对元素的重复来创建新数组 |
| `roll` | 沿指定轴对数组元素进行移位 |
| `resize` | 重新调整数组的大小 |
| `place` / `put` | 将数组中满足条件的元素/指定的元素替换为指定的值 |
| `ptp` | 沿指定的轴计算极差(最大值与最小值的差) |
| `median` | 沿指定轴计算中位数 |
| `partition` | 用选定的元素对数组进行一次划分并返回划分后的数组 |
> **提示**:上面的`resize`函数和`ndarray`对象的`resize`方法是有区别的,`resize`函数在调整数组大小时会重复数组中的元素作为填补多出来的元素的值,而`ndarry`对象的`resize`方法是用0来填补多出来的元素。这些小细节不清楚暂时也不要紧但是如果用到对应的功能了就要引起注意。
代码:
```Python
array42 = np.array([[1, 1, 1], [2, 2, 2], [3, 3, 3]])
array43 = np.array([[4, 4, 4], [5, 5, 5], [6, 6, 6]])
np.hstack((array42, array43))
```
输出:
```
array([[1, 1, 1, 4, 4, 4],
[2, 2, 2, 5, 5, 5],
[3, 3, 3, 6, 6, 6]])
```
代码:
```Python
np.vstack((array42, array43))
```
输出:
```
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5],
[6, 6, 6]])
```
代码:
```Python
np.concatenate((array42, array43))
```
输出:
```
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5],
[6, 6, 6]])
```
代码:
```Python
np.concatenate((array42, array43), axis=1)
```
输出:
```
array([[1, 1, 1, 4, 4, 4],
[2, 2, 2, 5, 5, 5],
[3, 3, 3, 6, 6, 6]])
```
### 矩阵运算
NumPy 中提供了专门用于线性代数linear algebra的模块和表示矩阵的类型`matrix`,当然我们通过二维数组也可以表示一个矩阵,官方并不推荐使用`matrix`类而是建议使用二维数组,而且有可能在将来的版本中会移除`matrix`类。无论如何,利用这些已经封装好的类和函数,我们可以轻松愉快的实现线性代数中很多的操作。
#### 线性代数快速回顾
1. **向量**也叫**矢量**,是一个同时具有大小和方向,且满足平行四边形法则的几何对象。与向量相对的概念叫**标量**或**数量**,标量只有大小、绝大多数情况下没有方向。
2. 向量可以进行**加**、**减**、**数乘**、**点积**、**叉积**等运算。
3. **行列式**由向量组成,它的性质可以由向量解释。
4. 行列式可以使用**行列式公式**计算:$det(\boldsymbol{A})=\sum_{n!} \pm {a_{1\alpha}a_{2\beta} \cdots a_{n\omega}}$。
5. 高阶行列式可以用**代数余子式**展开成多个低阶行列式,如:$det(\boldsymbol{A})=a_{11}C_{11}+a_{12}C_{12}+ \cdots +a_{1n}C_{1n}$。
6. **矩阵**是由一系列元素排成的矩形阵列,矩阵里的元素可以是数字、符号或数学公式。
7. 矩阵可以进行**加法**、**减法**、**数乘**、**乘法**、**转置**等运算。
8. **逆矩阵**用$\boldsymbol{A^{-1}}$表示,$\boldsymbol{A}\boldsymbol{A^{-1}}=\boldsymbol{A^{-1}}\boldsymbol{A}=\boldsymbol{I}$;没有逆矩阵的方阵是**奇异矩阵**。
9. 如果一个方阵是**满秩矩阵**(矩阵的秩等于矩阵的阶数),该方阵对应的线性方程有唯一解。
> **说明****矩阵的秩**是指矩阵中线性无关的行/列向量的最大个数,同时也是矩阵对应的线性变换的像空间的维度。
#### NumPy中矩阵相关函数
1. 创建矩阵对象。
代码:
```Python
# matrix构造函数可以传入类数组对象也可以传入字符串
m1 = np.matrix('1 2 3; 4 5 6')
m1
```
输出:
```
matrix([[1, 2, 3],
[4, 5, 6]])
```
代码:
```Python
# asmatrix函数也可以写成mat函数它们其实是同一个函数
m2 = np.asmatrix(np.array([[1, 1], [2, 2], [3, 3]]))
m2
```
输出:
```
matrix([[1, 1],
[2, 2],
[3, 3]])
```
代码:
```Python
m1 * m2
```
输出:
```
matrix([[14, 14],
[32, 32]])
```
> **说明**:注意`matrix`对象和`ndarray`对象乘法运算的差别,如果两个二维数组要做矩阵乘法运算,应该使用`@`运算符或`matmul`函数,而不是`*`运算符。
2. 矩阵对象的属性。
| 属性 | 说明 |
| ------- | ----------------------------------------- |
| `A` | 获取矩阵对象对应的`ndarray`对象 |
| `A1` | 获取矩阵对象对应的扁平化后的`ndarray`对象 |
| `I` | 可逆矩阵的逆矩阵 |
| `T` | 矩阵的转置 |
| `H` | 矩阵的共轭转置 |
| `shape` | 矩阵的形状 |
| `size` | 矩阵元素的个数 |
3. 矩阵对象的方法。
矩阵对象的方法跟之前讲过的`ndarray`数组对象的方法基本差不多,此处不再进行赘述。
#### NumPy的线性代数模块
NumPy 的`linalg`模块中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的函数,它们跟 MATLAB 和 R 等语言所使用的是相同的行业标准线性代数库,下面的表格列出了`numpy`以及`linalg`模块中常用的跟线性代数相关的函数。
| 函数 | 说明 |
| --------------- | ------------------------------------------------------------ |
| `diag` | 以一维数组的形式返回方阵的对角线元素或将一维数组转换为方阵非对角元素元素为0 |
| `vdot` | 向量的点积 |
| `dot` | 数组的点积 |
| `inner` | 数组的内积 |
| `outer` | 数组的叉积 |
| `trace` | 计算对角线元素的和 |
| `norm` | 求模(范数)运算 |
| `det` | 计算行列式的值(在方阵上计算会得到一个标量) |
| `matrix_rank` | 计算矩阵的秩 |
| `eig` | 计算矩阵的特征值eigenvalue和特征向量eigenvector |
| `inv` | 计算非奇异矩阵($n$阶方阵)的逆矩阵 |
| `pinv` | 计算矩阵的摩尔-彭若斯Moore-Penrose广义逆 |
| `qr` | QR分解把矩阵分解成一个正交矩阵与一个上三角矩阵的积 |
| `svd` | 计算奇异值分解singular value decomposition |
| `solve` | 解线性方程组$\boldsymbol{A}\boldsymbol{x}=\boldsymbol{b}$,其中$\boldsymbol{A}$是一个方阵 |
| `lstsq` | 计算$\boldsymbol{A}\boldsymbol{x}=\boldsymbol{b}$的最小二乘解 |
大家如果有兴趣可以用下面的代码验证上面的函数。
代码:
```Python
m3 = np.array([[1., 2.], [3., 4.]])
np.linalg.inv(m3)
```
输出:
```
array([[-2. , 1. ],
[ 1.5, -0.5]])
```
代码:
```Python
m4 = np.array([[1, 3, 5], [2, 4, 6], [4, 7, 9]])
np.linalg.det(m4)
```
输出:
```
2
```
代码:
```Python
# 解线性方程组ax=b
# 3x + y = 9x + 2y = 8
a = np.array([[3,1], [1,2]])
b = np.array([9, 8])
np.linalg.solve(a, b)
```
输出:
```
array([2., 3.])
```

View File

@ -0,0 +1,686 @@
## Pandas的应用-1
Pandas是Wes McKinney在2008年开发的一个强大的**分析结构化数据**的工具集。Pandas以NumPy为基础数据表示和运算提供了用于数据处理的函数和方法对数据分析和数据挖掘提供了很好的支持同时Pandas还可以跟数据可视化工具Matplotlib很好的整合在一起非常轻松愉快的实现数据的可视化展示。
Pandas核心的数据类型是`Series`(数据系列)、`DataFrame`(数据表/数据框),分别用于处理一维和二维的数据,除此之外还有一个名为`Index`的类型及其子类型,它为`Series`和`DataFrame`提供了索引功能。日常工作中以`DataFrame`使用最为广泛因为二维的数据本质就是一个有行有列的表格想一想Excel电子表格和关系型数据库中的二维表。上述这些类型都提供了大量的处理数据的方法数据分析师可以以此为基础实现对数据的各种常规处理。
### Series的应用
Pandas库中的`Series`对象可以用来表示一维数据结构,跟数组非常类似,但是多了一些额外的功能。`Series`的内部结构包含了两个数组,其中一个用来保存数据,另一个用来保存数据的索引。
#### 创建Series对象
> **提示**:在执行下面的代码之前,请先导入`pandas`以及相关的库文件,具体的做法可以参考上一章。
##### 方法1通过列表或数组创建Series对象
代码:
```Python
# data参数表示数据index参数表示数据的索引标签
# 如果没有指定index属性默认使用数字索引
ser1 = pd.Series(data=[320, 180, 300, 405], index=['一季度', '二季度', '三季度', '四季度'])
ser1
```
输出:
```
一季度 320
二季度 180
三季度 300
四季度 405
dtype: int64
```
##### 方法2通过字典创建Series对象。
代码:
```Python
# 字典中的键就是数据的索引(标签),字典中的值就是数据
ser2 = pd.Series({'一季度': 320, '二季度': 180, '三季度': 300, '四季度': 405})
ser2
```
输出:
```
一季度 320
二季度 180
三季度 300
四季度 405
dtype: int64
```
#### 索引和切片
跟数组一样Series对象也可以进行索引和切片操作不同的是Series对象因为内部维护了一个保存索引的数组所以除了可以使用整数索引通过位置检索数据外还可以通过自己设置的索引标签获取对应的数据。
##### 使用整数索引
代码:
```Python
print(ser2[0], ser[1], ser[2], ser[3])
ser2[0], ser2[3] = 350, 360
print(ser2)
```
输出:
```
320 180 300 405
一季度 350
二季度 180
三季度 300
四季度 360
dtype: int64
```
> **提示**:如果要使用负向索引,必须在创建`Series`对象时通过`index`属性指定非数值类型的标签。
##### 使用自定义的标签索引
代码:
```Python
print(ser2['一季度'], ser2['三季度'])
ser2['一季度'] = 380
print(ser2)
```
输出:
```
350 300
一季度 380
二季度 180
三季度 300
四季度 360
dtype: int64
```
##### 切片操作
代码:
```Python
print(ser2[1:3])
print(ser2['二季度':'四季度'])
```
输出:
```
二季度 180
三季度 300
dtype: int64
二季度 500
三季度 500
四季度 520
dtype: int64
```
代码:
```Python
ser2[1:3] = 400, 500
ser2
```
输出:
```
一季度 380
二季度 400
三季度 500
四季度 360
dtype: int64
```
##### 花式索引
代码:
```Python
print(ser2[['二季度', '四季度']])
ser2[['二季度', '四季度']] = 500, 520
print(ser2)
```
输出:
```
二季度 400
四季度 360
dtype: int64
一季度 380
二季度 500
三季度 500
四季度 520
dtype: int64
```
##### 布尔索引
代码:
```Python
ser2[ser2 >= 500]
```
输出:
```
二季度 500
三季度 500
四季度 520
dtype: int64
```
####属性和方法
Series对象的常用属性如下表所示。
| 属性 | 说明 |
| ------------------------- | --------------------------------------- |
| `dtype` / `dtypes` | 返回`Series`对象的数据类型 |
| `hasnans` | 判断`Series`对象中有没有空值 |
| `at` / `iat` | 通过索引访问`Series`对象中的单个值 |
| `loc` / `iloc` | 通过一组索引访问`Series`对象中的一组值 |
| `index` | 返回`Series`对象的索引 |
| `is_monotonic` | 判断`Series`对象中的数据是否单调 |
| `is_monotonic_increasing` | 判断`Series`对象中的数据是否单调递增 |
| `is_monotonic_decreasing` | 判断`Series`对象中的数据是否单调递减 |
| `is_unique` | 判断`Series`对象中的数据是否独一无二 |
| `size` | 返回`Series`对象中元素的个数 |
| `values` | 以`ndarray`的方式返回`Series`对象中的值 |
`Series`对象的方法很多,我们通过下面的代码为大家介绍一些常用的方法。
##### 统计相关的方法
`Series`对象支持各种获取描述性统计信息的方法。
代码:
```Python
# 求和
print(ser2.sum())
# 求均值
print(ser2.mean())
# 求最大
print(ser2.max())
# 求最小
print(ser2.min())
# 计数
print(ser2.count())
# 求标准差
print(ser2.std())
# 求方差
print(ser2.var())
# 求中位数
print(ser2.median())
```
`Series`对象还有一个名为`describe()`的方法,可以获得上述所有的描述性统计信息,如下所示。
代码:
```Python
ser2.describe()
```
输出:
```
count 4.000000
mean 475.000000
std 64.031242
min 380.000000
25% 470.000000
50% 500.000000
75% 505.000000
max 520.000000
dtype: float64
```
> **提示**:因为`describe()`返回的也是一个`Series`对象,所以也可以用`ser2.describe()['mean']`来获取平均值。
如果`Series`对象有重复的值,我们可以使用`unique()`方法获得去重之后的`Series`对象;可以使用`nunique()`方法统计不重复值的数量;如果想要统计每个值重复的次数,可以使用`value_counts()`方法,这个方法会返回一个`Series`对象,它的索引就是原来的`Series`对象中的值,而每个值出现的次数就是返回的`Series`对象中的数据,在默认情况下会按照出现次数做降序排列。
代码:
```Python
ser3 = pd.Series(data=['apple', 'banana', 'apple', 'pitaya', 'apple', 'pitaya', 'durian'])
ser3.value_counts()
```
输出:
```
apple 3
pitaya 2
durian 1
banana 1
dtype: int64
```
代码:
```Python
ser3.nunique()
```
输出:
```
4
```
##### 数据处理的方法
`Series`对象的`isnull()`和`notnull()`方法可以用于空值的判断,代码如下所示。
代码:
```Python
ser4 = pd.Series(data=[10, 20, np.NaN, 30, np.NaN])
ser4.isnull()
```
输出:
```
0 False
1 False
2 True
3 False
4 True
dtype: bool
```
代码:
```Python
ser4.notnull()
```
输出:
```
0 True
1 True
2 False
3 True
4 False
dtype: bool
```
`Series`对象的`dropna()`和`fillna()`方法分别用来删除空值和填充空值,具体的用法如下所示。
代码:
```Python
ser4.dropna()
```
输出:
```
0 10.0
1 20.0
3 30.0
dtype: float64
```
代码:
```Python
# 将空值填充为40
ser4.fillna(value=40)
```
输出:
```
0 10.0
1 20.0
2 40.0
3 30.0
4 40.0
dtype: float64
```
代码:
```Python
# backfill或bfill表示用后一个元素的值填充空值
# ffill或pad表示用前一个元素的值填充空值
ser4.fillna(method='ffill')
```
输出:
```
0 10.0
1 20.0
2 20.0
3 30.0
4 30.0
dtype: float64
```
需要提醒大家注意的是,`dropna()`和`fillna()`方法都有一个名为`inplace`的参数,它的默认值是`False`,表示删除空值或填充空值不会修改原来的`Series`对象,而是返回一个新的`Series`对象来表示删除或填充空值后的数据系列,如果将`inplace`参数的值修改为`True`,那么删除或填充空值会就地操作,直接修改原来的`Series`对象,那么方法的返回值是`None`。后面我们会接触到的很多方法,包括`DataFrame`对象的很多方法都会有这个参数,它们的意义跟这里是一样的。
`Series`对象的`mask()`和`where()`方法可以将满足或不满足条件的值进行替换,如下所示。
代码:
```Python
ser5 = pd.Series(range(5))
ser5.where(ser5 > 0)
```
输出:
```
0 NaN
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
```
代码:
```Python
ser5.where(ser5 > 1, 10)
```
输出:
```
0 10
1 10
2 2
3 3
4 4
dtype: int64
```
代码:
```Python
ser5.mask(ser5 > 1, 10)
```
输出:
```
0 0
1 1
2 10
3 10
4 10
dtype: int64
```
`Series`对象的`duplicated()`方法可以帮助我们找出重复的数据,而`drop_duplicates()`方法可以帮我们删除重复数据。
代码:
```Python
ser3.duplicated()
```
输出:
```
0 False
1 False
2 True
3 False
4 True
5 True
6 False
dtype: bool
```
代码:
```Python
ser3.drop_duplicates()
```
输出:
```
0 apple
1 banana
3 pitaya
6 durian
dtype: object
```
`Series`对象的`apply()`和`map()`方法非常重要,它们可以用于数据处理,把数据映射或转换成我们期望的样子,这个操作在数据分析的数据准备阶段非常重要。
代码:
```Python
ser6 = pd.Series(['cat', 'dog', np.nan, 'rabbit'])
ser6
```
输出:
```
0 cat
1 dog
2 NaN
3 rabbit
dtype: object
```
代码:
```Python
ser6.map({'cat': 'kitten', 'dog': 'puppy'})
```
输出:
```
0 kitten
1 puppy
2 NaN
3 NaN
dtype: object
```
代码:
```Python
ser6.map('I am a {}'.format, na_action='ignore')
```
输出:
```
0 I am a cat
1 I am a dog
2 NaN
3 I am a rabbit
dtype: object
```
代码:
```Python
ser7 = pd.Series([20, 21, 12], index=['London', 'New York', 'Helsinki'])
ser7
```
输出:
```
London 20
New York 21
Helsinki 12
dtype: int64
```
代码:
```Python
ser7.apply(np.square)
```
输出:
```
London 400
New York 441
Helsinki 144
dtype: int64
```
代码:
```Python
ser7.apply(lambda x, value: x - value, args=(5, ))
```
输出:
```
London 15
New York 16
Helsinki 7
dtype: int64
```
##### 排序和取头部值的方法
`Series`对象的`sort_index()`和`sort_values()`方法可以用于对索引和数据的排序,排序方法有一个名为`ascending`的布尔类型参数,该参数用于控制排序的结果是升序还是降序;而名为`kind`的参数则用来控制排序使用的算法,默认使用了`quicksort`,也可以选择`mergesort`或`heapsort`;如果存在空值,那么可以用`na_position`参数空值放在最前还是最后,默认是`last`,代码如下所示。
代码:
```Python
ser8 = pd.Series(
data=[35, 96, 12, 57, 25, 89],
index=['grape', 'banana', 'pitaya', 'apple', 'peach', 'orange']
)
# 按值从小到大排序
ser8.sort_values()
```
输出:
```
pitaya 12
peach 25
grape 35
apple 57
orange 89
banana 96
dtype: int64
```
代码:
```Python
# 按索引从大到小排序
ser8.sort_index(ascending=False)
```
输出:
```
pitaya 12
peach 25
orange 89
grape 35
banana 96
apple 57
dtype: int64
```
如果要从`Series`对象中找出元素中最大或最小的“Top-N”实际上是不需要对所有的值进行排序的可以使用`nlargest()`和`nsmallest()`方法来完成,如下所示。
代码:
```Python
# 值最大的3个
ser8.nlargest(3)
```
输出:
```
banana 96
orange 89
apple 57
dtype: int64
```
代码:
```Python
# 值最小的2个
ser8.nsmallest(2)
```
输出:
```
pitaya 12
peach 25
dtype: int64
```
#### 绘制图表
Series对象有一个名为`plot`的方法可以用来生成图表如果选择生成折线图、饼图、柱状图等默认会使用Series对象的索引作为横坐标使用Series对象的数据作为纵坐标。
首先导入`matplotlib`中`pyplot`模块并进行必要的配置。
```Python
import matplotlib.pyplot as plt
# 配置支持中文的非衬线字体(默认的字体无法显示中文)
plt.rcParams['font.sans-serif'] = ['SimHei', ]
# 使用指定的中文字体时需要下面的配置来避免负号无法显示
plt.rcParams['axes.unicode_minus'] = False
```
创建`Series`对象并绘制对应的柱状图。
```Python
ser9 = pd.Series({'一季度': 400, '二季度': 520, '三季度': 180, '四季度': 380})
# 通过Series对象的plot方法绘图kind='bar'表示绘制柱状图)
ser9.plot(kind='bar', color=['r', 'g', 'b', 'y'])
# x轴的坐标旋转到0度中文水平显示
plt.xticks(rotation=0)
# 在柱状图的柱子上绘制数字
for i in range(4):
plt.text(i, ser9[i] + 5, ser9[i], ha='center')
# 显示图像
plt.show()
```
![](res/series-bar-graph.png)
绘制反映每个季度占比的饼图。
```Python
# autopct参数可以配置在饼图上显示每块饼的占比
ser9.plot(kind='pie', autopct='%.1f%%')
# 设置y轴的标签显示在饼图左侧的文字
plt.ylabel('各季度占比')
plt.show()
```
![](res/series-pie-graph.png)

View File

@ -0,0 +1,509 @@
## Pandas的应用-2
### DataFrame的应用
#### 创建DataFrame对象
##### 通过二维数组创建`DataFrame`对象
代码:
```Python
scores = np.random.randint(60, 101, (5, 3))
courses = ['语文', '数学', '英语']
ids = [1001, 1002, 1003, 1004, 1005]
df1 = pd.DataFrame(data=scores, columns=courses, index=ids)
df1
```
输出:
```
语文 数学 英语
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
```
##### 通过字典创建`DataFrame`对象
代码:
```Python
scores = {
'语文': [62, 72, 93, 88, 93],
'数学': [95, 65, 86, 66, 87],
'英语': [66, 75, 82, 69, 82],
}
ids = [1001, 1002, 1003, 1004, 1005]
df2 = pd.DataFrame(data=scores, index=ids)
df2
```
输出:
```
语文 数学 英语
1001 69 80 79
1002 71 60 100
1003 94 81 93
1004 88 88 67
1005 82 66 60
```
##### 读取 CSV 文件创建`DataFrame`对象
可以通过`pandas` 模块的`read_csv`函数来读取 CSV 文件,`read_csv`函数的参数非常多,下面接受几个比较重要的参数。
- `sep` / `delimiter`:分隔符,默认是`,`。
- `header`:表头(列索引)的位置,默认值是`infer`,用第一行的内容作为表头(列索引)。
- `index_col`:用作行索引(标签)的列。
- `usecols`:需要加载的列,可以使用序号或者列名。
- `true_values` / `false_values`:哪些值被视为布尔值`True` / `False`
- `skiprows`:通过行号、索引或函数指定需要跳过的行。
- `skipfooter`:要跳过的末尾行数。
- `nrows`:需要读取的行数。
- `na_values`:哪些值被视为空值。
代码:
```Python
df3 = pd.read_csv('2018年北京积分落户数据.csv', index_col='id')
df3
```
输出:
```
name birthday company score
id
1 杨x 1972-12 北京利德xxxx 122.59
2 纪x 1974-12 北京航天xxxx 121.25
3 王x 1974-05 品牌联盟xxxx 118.96
4 杨x 1975-07 中科专利xxxx 118.21
5 张x 1974-11 北京阿里xxxx 117.79
... ... ... ... ...
6015 孙x 1978-08 华为海洋xxxx 90.75
6016 刘x 1976-11 福斯流体xxxx 90.75
6017 周x 1977-10 赢创德固xxxx 90.75
6018 赵x 1979-07 澳科利耳xxxx 90.75
6019 贺x 1981-06 北京宝洁xxxx 90.75
6019 rows × 4 columns
```
> **说明**:如果需要上面例子中的 CSV 文件可以通过下面的百度云盘地址进行获取数据在《从零开始学数据分析》目录中。链接https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g提取码e7b4。
##### 读取Excel文件创建`DataFrame`对象
可以通过`pandas` 模块的`read_excel`函数来读取 Exce l文件该函数与上面的`read_csv`非常相近,多了一个`sheet_name`参数来指定数据表的名称,但是不同于 CSV 文件,没有`sep`或`delimiter`这样的参数。下面的代码中,`read_excel`函数的`skiprows`参数是一个 Lambda 函数,通过该 Lambda 函数指定只读取 Excel 文件的表头和其中10%的数据,跳过其他的数据。
代码:
```Python
import random
df4 = pd.read_excel(
io='小宝剑大药房2018年销售数据.xlsx',
usecols=['购药时间', '社保卡号', '商品名称', '销售数量', '应收金额', '实收金额'],
skiprows=lambda x: x > 0 and random.random() > 0.1
)
df4
```
> **说明**:如果需要上面例子中的 Excel 文件可以通过下面的百度云盘地址进行获取数据在《从零开始学数据分析》目录中。链接https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g提取码e7b4。
输出:
```
购药时间 社保卡号 商品名称 销售数量 应收金额 实收金额
0 2018-03-23 星期三 10012157328 强力xx片 1 13.8 13.80
1 2018-07-12 星期二 108207828 强力xx片 1 13.8 13.80
2 2018-01-17 星期日 13358228 清热xx液 1 28.0 28.00
3 2018-07-11 星期一 10031402228 三九xx灵 5 149.0 130.00
4 2018-01-20 星期三 10013340328 三九xx灵 3 84.0 73.92
... ... ... ... ... ... ...
618 2018-03-05 星期六 10066059228 开博xx通 2 56.0 49.28
619 2018-03-22 星期二 10035514928 开博xx通 1 28.0 25.00
620 2018-04-15 星期五 1006668328 开博xx通 2 56.0 50.00
621 2018-04-24 星期日 10073294128 高特xx灵 1 5.6 5.60
622 2018-04-24 星期日 10073294128 高特xx灵 10 56.0 56.0
623 rows × 6 columns
```
##### 通过SQL从数据库读取数据创建`DataFrame`对象
`pandas`模块的`read_sql`函数可以通过 SQL 语句从数据库中读取数据创建`DataFrame`对象,该函数的第二个参数代表了需要连接的数据库。对于 MySQL 数据库,我们可以通过`pymysql`或`mysqlclient`来创建数据库连接,得到一个`Connection` 对象,而这个对象就是`read_sql`函数需要的第二个参数,代码如下所示。
代码:
```Python
import pymysql
# 创建一个MySQL数据库的连接对象
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
# 通过SQL从数据库读取数据创建DataFrame
df5 = pd.read_sql('select * from tb_emp', conn, index_col='eno')
df5
```
> **提示**:执行上面的代码需要先安装`pymysql`库,如果尚未安装,可以先在 Notebook 的单元格中先执行`!pip install pymysql`,然后再运行上面的代码。上面的代码连接的是我部署在阿里云上的 MySQL 数据库,公网 IP 地址:`47.104.31.138`,用户名:`guest`,密码:`Guest.618`,数据库:`hrs`,表名:`tb_emp`,字符集:`utf8mb4`,大家可以使用这个数据库,但是不要进行恶意的访问。
输出:
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
```
#### 基本属性和方法
在开始讲解`DataFrame`的属性和方法前,我们先从之前提到的`hrs`数据库中读取三张表的数据,创建出三个`DataFrame`对象,代码如下所示。
```Python
import pymysql
conn = pymysql.connect(
host='47.104.31.138', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
dept_df = pd.read_sql('select * from tb_dept', conn, index_col='dno')
emp_df = pd.read_sql('select * from tb_emp', conn, index_col='eno')
emp2_df = pd.read_sql('select * from tb_emp2', conn, index_col='eno')
```
得到的三个`DataFrame`对象如下所示。
部门表(`dept_df`),其中`dno`是部门的编号,`dname`和`dloc`分别是部门的名称和所在地。
```
dname dloc
dno
10 会计部 北京
20 研发部 成都
30 销售部 重庆
40 运维部 天津
```
员工表(`emp_df`),其中`eno`是员工编号,`ename`、`job`、`mgr`、`sal`、`comm`和`dno`分别代表员工的姓名、职位、主管编号、月薪、补贴和部门编号。
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
```
> **说明**:在数据库中`mgr`和`comm`两个列的数据类型是`int`,但是因为有缺失值(空值),读取到`DataFrame`之后,列的数据类型变成了`float`,因为我们通常会用`float`类型的`NaN`来表示空值。
员工表(`emp2_df`),跟上面的员工表结构相同,但是保存了不同的员工数据。
```
ename job mgr sal comm dno
eno
9800 骆昊 架构师 7800 30000 5000 20
9900 王小刀 程序员 9800 10000 1200 20
9700 王大锤 程序员 9800 8000 600 20
```
`DataFrame`对象的属性如下表所示。
| 属性名 | 说明 |
| -------------- | ----------------------------------- |
| `at` / `iat` | 通过标签获取`DataFrame`中的单个值。 |
| `columns` | `DataFrame`对象列的索引 |
| `dtypes` | `DataFrame`对象每一列的数据类型 |
| `empty` | `DataFrame`对象是否为空 |
| `loc` / `iloc` | 通过标签获取`DataFrame`中的一组值。 |
| `ndim` | `DataFrame`对象的维度 |
| `shape` | `DataFrame`对象的形状(行数和列数) |
| `size` | `DataFrame`对象中元素的个数 |
| `values` | `DataFrame`对象的数据对应的二维数组 |
关于`DataFrame`的方法,首先需要了解的是`info()`方法,它可以帮助我们了解`DataFrame`的相关信息,如下所示。
代码:
```Python
emp_df.info()
```
输出:
```
<class 'pandas.core.frame.DataFrame'>
Int64Index: 14 entries, 1359 to 7800
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ename 14 non-null object
1 job 14 non-null object
2 mgr 13 non-null float64
3 sal 14 non-null int64
4 comm 6 non-null float64
5 dno 14 non-null int64
dtypes: float64(2), int64(2), object(2)
memory usage: 1.3+ KB
```
如果需要查看`DataFrame`的头部或尾部的数据,可以使用`head()`或`tail()`方法,这两个方法的默认参数是`5`,表示获取`DataFrame`最前面5行或最后面5行的数据如下所示。
```Python
emp_df.head()
```
输出:
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344 1800 200 30
2056 乔峰 分析师 7800 5000 1500 20
3088 李莫愁 设计师 2056 3500 800 20
3211 张无忌 程序员 2056 3200 NaN 20
3233 丘处机 程序员 2056 3400 NaN 20
```
#### 获取数据
##### 索引和切片
如果要获取`DataFrame`的某一列,例如取出上面`emp_df`的`ename`列,可以使用下面的两种方式。
```Python
emp_df.ename
```
或者
```Python
emp_df['ename']
```
执行上面的代码可以发现,我们获得的是一个`Series`对象。事实上,`DataFrame`对象就是将多个`Series`对象组合到一起的结果。
如果要获取`DataFrame`的某一行,可以使用整数索引或我们设置的索引,例如取出员工编号为`2056`的员工数据,代码如下所示。
```Python
emp_df.iloc[1]
```
或者
```Python
emp_df.loc[2056]
```
通过执行上面的代码我们发现,单独取`DataFrame` 的某一行或某一列得到的都是`Series`对象。我们当然也可以通过花式索引来获取多个行或多个列的数据,花式索引的结果仍然是一个`DataFrame`对象。
获取多个列:
```Python
emp_df[['ename', 'job']]
```
获取多个行:
```Python
emp_df.loc[[2056, 7800, 3344]]
```
如果要获取或修改`DataFrame` 对象某个单元格的数据,需要同时指定行和列的索引,例如要获取员工编号为`2056`的员工的职位信息,代码如下所示。
```Python
emp_df['job'][2056]
```
或者
```Python
emp_df.loc[2056]['job']
```
或者
```Python
emp_df.loc[2056, 'job']
```
我们推荐大家使用第三种做法,因为它只做了一次索引运算。如果要将该员工的职位修改为“架构师”,可以使用下面的代码。
```Python
emp_df.loc[2056, 'job'] = '架构师'
```
当然,我们也可以通过切片操作来获取多行多列,相信大家一定已经想到了这一点。
```Python
emp_df.loc[2056:3344]
```
输出:
```
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
```
##### 数据筛选
上面我们提到了花式索引,相信大家已经联想到了布尔索引。跟`ndarray`和`Series`一样,我们可以通过布尔索引对`DataFrame`对象进行数据筛选,例如我们要从`emp_df`中筛选出月薪超过`3500`的员工,代码如下所示。
```Python
emp_df[emp_df.sal > 3500]
```
输出:
```
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3251 张翠山 程序员 2056.0 4000 NaN 20
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
```
当然,我们也可以组合多个条件来进行数据筛选,例如从`emp_df`中筛选出月薪超过`3500`且部门编号为`20`的员工,代码如下所示。
```Python
emp_df[(emp_df.sal > 3500) & (emp_df.dno == 20)]
```
输出:
```
ename job mgr sal comm dno
eno
2056 乔峰 分析师 7800.0 5000 1500.0 20
3251 张翠山 程序员 2056.0 4000 NaN 20
7800 张三丰 总裁 NaN 9000 1200.0 20
```
除了使用布尔索引,`DataFrame`对象的`query`方法也可以实现数据筛选,`query`方法的参数是一个字符串,它代表了筛选数据使用的表达式,而且更符合 Python 程序员的使用习惯。下面我们使用`query`方法将上面的效果重新实现一遍,代码如下所示。
```Python
emp_df.query('sal > 3500 and dno == 20')
```
#### 重塑数据
有的时候,我们做数据分析需要的原始数据可能并不是来自一个地方,就像上面的例子中,我们从关系型数据库中读取了三张表,得到了三个`DataFrame`对象,但实际工作可能需要我们把他们的数据整合到一起。例如:`emp_df`和`emp2_df`其实都是员工的数据,而且数据结构完全一致,我们可以使用`pandas`提供的`concat`函数实现两个或多个`DataFrame`的数据拼接,代码如下所示。
```Python
all_emp_df = pd.concat([emp_df, emp2_df])
```
输出:
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 NaN 20
3233 丘处机 程序员 2056.0 3400 NaN 20
3244 欧阳锋 程序员 3088.0 3200 NaN 20
3251 张翠山 程序员 2056.0 4000 NaN 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 NaN 10
3588 朱九真 会计 5566.0 2500 NaN 10
4466 苗人凤 销售员 3344.0 2500 NaN 30
5234 郭靖 出纳 5566.0 2000 NaN 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 NaN 9000 1200.0 20
9800 骆昊 架构师 7800.0 30000 5000.0 20
9900 王小刀 程序员 9800.0 10000 1200.0 20
9700 王大锤 程序员 9800.0 8000 600.0 20
```
上面的代码将两个代表员工数据的`DataFrame`拼接到了一起,接下来我们使用`merge`函数将员工表和部门表的数据合并到一张表中,代码如下所示。
先使用`reset_index`方法重新设置`all_emp_df`的索引,这样`eno` 不再是索引而是一个普通列,`reset_index`方法的`inplace`参数设置为`True`表示,重置索引的操作直接在`all_emp_df`上执行,而不是返回修改后的新对象。
```Python
all_emp_df.reset_index(inplace=True)
```
通过`merge`函数合并数据,当然,也可以调用`DataFrame`对象的`merge`方法来达到同样的效果。
```Python
pd.merge(dept_df, all_emp_df, how='inner', on='dno')
```
输出:
```
dno dname dloc eno ename job mgr sal comm
0 10 会计部 北京 3577 杨过 会计 5566.0 2200 NaN
1 10 会计部 北京 3588 朱九真 会计 5566.0 2500 NaN
2 10 会计部 北京 5234 郭靖 出纳 5566.0 2000 NaN
3 10 会计部 北京 5566 宋远桥 会计师 7800.0 4000 1000.0
4 20 研发部 成都 2056 乔峰 架构师 7800.0 5000 1500.0
5 20 研发部 成都 3088 李莫愁 设计师 2056.0 3500 800.0
6 20 研发部 成都 3211 张无忌 程序员 2056.0 3200 NaN
7 20 研发部 成都 3233 丘处机 程序员 2056.0 3400 NaN
8 20 研发部 成都 3244 欧阳锋 程序员 3088.0 3200 NaN
9 20 研发部 成都 3251 张翠山 程序员 2056.0 4000 NaN
10 20 研发部 成都 7800 张三丰 总裁 NaN 9000 1200.0
11 20 研发部 成都 9800 骆昊 架构师 7800.0 30000 5000.0
12 20 研发部 成都 9900 王小刀 程序员 9800.0 10000 1200.0
13 20 研发部 成都 9700 王大锤 程序员 9800.0 8000 600.0
14 30 销售部 重庆 1359 胡一刀 销售员 3344.0 1800 200.0
15 30 销售部 重庆 3344 黄蓉 销售主管 7800.0 3000 800.0
16 30 销售部 重庆 4466 苗人凤 销售员 3344.0 2500 NaN
```
`merge`函数的一个参数代表合并的左表、第二个参数代表合并的右表有SQL编程经验的同学对这两个词是不是感觉到非常亲切。正如大家猜想的那样`DataFrame`对象的合并跟数据库中的表连接非常类似,所以上面代码中的`how`代表了合并两张表的方式,有`left`、`right`、`inner`、`outer`四个选项;而`on`则代表了基于哪个列实现表的合并,相当于 SQL 表连接中的连表条件,如果左右两表对应的列列名不同,可以用`left_on`和`right_on`参数取代`on`参数分别进行指定。
如果对上面的代码稍作修改,将`how`参数修改为`left`,大家可以思考一下代码执行的结果。
```Python
pd.merge(dept_df, all_emp_df, how='left', on='dno')
```
运行结果比之前的输出多出了如下所示的一行,这是因为`left`代表左外连接,也就意味着左表`dept_df`中的数据会被完整的查出来,但是在`all_emp_df`中又没有编号为`40` 部门的员工,所以对应的位置都被填入了空值。
```
17 40 运维部 天津 NaN NaN NaN NaN NaN NaN
```

View File

@ -0,0 +1,553 @@
## Pandas的应用-3
### DataFrame的应用
#### 数据清洗
通常,我们从 Excel、CSV 或数据库中获取到的数据并不是非常完美的,里面可能因为系统或人为的原因混入了重复值或异常值,也可能在某些字段上存在缺失值;再者,`DataFrame`中的数据也可能存在格式不统一、量纲不统一等各种问题。因此,在开始数据分析之前,对数据进行清洗就显得特别重要。
##### 缺失值
可以使用`DataFrame`对象的`isnull`或`isna`方法来找出数据表中的缺失值,如下所示。
```Python
emp_df.isnull()
```
或者
```Python
emp_df.isna()
```
输出:
```
ename job mgr sal comm dno
eno
1359 False False False False False False
2056 False False False False False False
3088 False False False False False False
3211 False False False False True False
3233 False False False False True False
3244 False False False False True False
3251 False False False False True False
3344 False False False False False False
3577 False False False False True False
3588 False False False False True False
4466 False False False False True False
5234 False False False False True False
5566 False False False False False False
7800 False False True False False False
```
相对应的,`notnull`和`notna`方法可以将非空的值标记为`True`。如果想删除这些缺失值,可以使用`DataFrame`对象的`dropna`方法,该方法的`axis`参数可以指定沿着0轴还是1轴删除也就是说当遇到空值时是删除整行还是删除整列默认是沿0轴进行删除的代码如下所示。
```Python
emp_df.dropna()
```
输出:
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 架构师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
5566 宋远桥 会计师 7800.0 4000 1000.0 10
```
如果要沿着1轴进行删除可以使用下面的代码。
```Python
emp_df.dropna(axis=1)
```
输出:
```
ename job sal dno
eno
1359 胡一刀 销售员 1800 30
2056 乔峰 架构师 5000 20
3088 李莫愁 设计师 3500 20
3211 张无忌 程序员 3200 20
3233 丘处机 程序员 3400 20
3244 欧阳锋 程序员 3200 20
3251 张翠山 程序员 4000 20
3344 黄蓉 销售主管 3000 30
3577 杨过 会计 2200 10
3588 朱九真 会计 2500 10
4466 苗人凤 销售员 2500 30
5234 郭靖 出纳 2000 10
5566 宋远桥 会计师 4000 10
7800 张三丰 总裁 9000 20
```
> **注意**`DataFrame`对象的很多方法都有一个名为`inplace`的参数,该参数的默认值为`False`,表示我们的操作不会修改原来的`DataFrame`对象,而是将处理后的结果通过一个新的`DataFrame`对象返回。如果将该参数的值设置为`True`,那么我们的操作就会在原来的`DataFrame`上面直接修改,方法的返回值为`None`。简单的说,上面的操作并没有修改`emp_df`,而是返回了一个新的`DataFrame`对象。
在某些特定的场景下,我们可以对空值进行填充,对应的方法是`fillna`,填充空值时可以使用指定的值(通过`value`参数进行指定),也可以用表格中前一个单元格(通过设置参数`method=ffill`)或后一个单元格(通过设置参数`method=bfill`)的值进行填充,当代码如下所示。
```Python
emp_df.fillna(value=0)
```
> **注意**:填充的值如何选择也是一个值得探讨的话题,实际工作中,可能会使用某种统计量(如:均值、众数等)进行填充,或者使用某种插值法(如:随机插值法、拉格朗日插值法等)进行填充,甚至有可能通过回归模型、贝叶斯模型等对缺失数据进行填充。
输出:
```
ename job mgr sal comm dno
eno
1359 胡一刀 销售员 3344.0 1800 200.0 30
2056 乔峰 分析师 7800.0 5000 1500.0 20
3088 李莫愁 设计师 2056.0 3500 800.0 20
3211 张无忌 程序员 2056.0 3200 0.0 20
3233 丘处机 程序员 2056.0 3400 0.0 20
3244 欧阳锋 程序员 3088.0 3200 0.0 20
3251 张翠山 程序员 2056.0 4000 0.0 20
3344 黄蓉 销售主管 7800.0 3000 800.0 30
3577 杨过 会计 5566.0 2200 0.0 10
3588 朱九真 会计 5566.0 2500 0.0 10
4466 苗人凤 销售员 3344.0 2500 0.0 30
5234 郭靖 出纳 5566.0 2000 0.0 10
5566 宋远桥 会计师 7800.0 4000 1000.0 10
7800 张三丰 总裁 0.0 9000 1200.0 20
```
##### 重复值
接下来,我们先给之前的部门表添加两行数据,让部门表中名为“研发部”和“销售部”的部门各有两个。
```Python
dept_df.loc[50] = {'dname': '研发部', 'dloc': '上海'}
dept_df.loc[60] = {'dname': '销售部', 'dloc': '长沙'}
dept_df
```
输出:
```
dname dloc
dno
10 会计部 北京
20 研发部 成都
30 销售部 重庆
40 运维部 天津
50 研发部 上海
60 销售部 长沙
```
现在,我们的数据表中有重复数据了,我们可以通过`DataFrame`对象的`duplicated`方法判断是否存在重复值,该方法在不指定参数时默认判断行索引是否重复,我们也可以指定根据部门名称`dname`判断部门是否重复,代码如下所示。
```Python
dept_df.duplicated('dname')
```
输出:
```
dno
10 False
20 False
30 False
40 False
50 True
60 True
dtype: bool
```
从上面的输出可以看到,`50`和`60`两个部门从部门名称上来看是重复的,如果要删除重复值,可以使用`drop_duplicates`方法,该方法的`keep`参数可以控制在遇到重复值时,保留第一项还是保留最后一项,或者多个重复项一个都不用保留,全部删除掉。
```Python
dept_df.drop_duplicates('dname')
```
输出:
```
dname dloc
dno
10 会计部 北京
20 研发部 成都
30 销售部 重庆
40 运维部 天津
```
将`keep`参数的值修改为`last`。
```Python
dept_df.drop_duplicates('dname', keep='last')
```
输出:
```
dname dloc
dno
10 会计部 北京
40 运维部 天津
50 研发部 上海
60 销售部 长沙
```
##### 异常值
异常值在统计学上的全称是疑似异常值也称作离群点outlier异常值的分析也称作离群点分析。异常值是指样本中出现的“极端值”数据值看起来异常大或异常小其分布明显偏离其余的观测值。实际工作中有些异常值可能是由系统或人为原因造成的但有些异常值却不是它们能够重复且稳定的出现属于正常的极端值例如很多游戏产品中头部玩家的数据往往都是离群的极端值。所以我们既不能忽视异常值的存在也不能简单地把异常值从数据分析中剔除。重视异常值的出现分析其产生的原因常常成为发现问题进而改进决策的契机。
异常值的检测有Z-score 方法、IQR 方法、DBScan 聚类、孤立森林等,这里我们对前两种方法做一个简单的介绍。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211004192858.png" style="zoom:50%;">
如果数据服从正态分布依据3σ法则异常值被定义与平均值的偏差超过三倍标准差的值。在正态分布下距离平均值3σ之外的值出现的概率为$ P(|x-\mu|>3\sigma)<0.003 $,属于小概率事件。如果数据不服从正态分布,那么可以用远离平均值的多少倍的标准差来描述,这里的倍数就是Z-scoreZ-score以标准差为单位去度量某一原始分数偏离平均值的距离,公式如下所示。
$$
z = \frac {X - \mu} {\sigma}
$$
Z-score需要根据经验和实际情况来决定通常把远离标准差`3`倍距离以上的数据点视为离群点下面的代给出了如何通过Z-score方法检测异常值。
```Python
import numpy as np
def detect_outliers_zscore(data, threshold=3):
avg_value = np.mean(data)
std_value = np.std(data)
z_score = np.abs((data - avg_value) / std_value)
return data[z_score > threshold]
```
IQR 方法中的IQRInter-Quartile Range代表四分位距离即上四分位数Q3和下四分位数Q1的差值。通常情况下可以认为小于 $ Q1 - 1.5 \times IQR $ 或大于 $ Q3 + 1.5 \times IQR $ 的就是异常值,而这种检测异常值的方法也是箱线图(后面会讲到)默认使用的方法。下面的代给出了如何通过 IQR 方法检测异常值。
```Python
import numpy as np
def detect_outliers_iqr(data, whis=1.5):
q1, q3 = np.quantile(data, [0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - whis * iqr, q3 + whis * iqr
return data[(data < lower) | (data > upper)]
```
如果要删除异常值,可以使用`DataFrame`对象的`drop`方法,该方法可以根据行索引或列索引删除指定的行或列。例如我们认为月薪低于`2000`或高于`8000`的是员工表中的异常值,可以用下面的代码删除对应的记录。
```Python
emp_df.drop(emp_df[(emp_df.sal > 8000) | (emp_df.sal < 2000)].index)
```
如果要替换掉异常值,可以通过给单元格赋值的方式来实现,也可以使用`replace`方法将指定的值替换掉。例如我们要将月薪为`1800`和`9000`的替换为月薪的平均值,补贴为`800`的替换为`1000`,代码如下所示。
```Python
avg_sal = np.mean(emp_df.sal).astype(int)
emp_df.replace({'sal': [1800, 9000], 'comm': 800}, {'sal': avg_sal, 'comm': 1000})
```
##### 预处理
对数据进行预处理也是一个很大的话题,它包含了对数据的拆解、变换、归约、离散化等操作。我们先来看看数据的拆解。如果数据表中的数据是一个时间日期,我们通常都需要从年、季度、月、日、星期、小时、分钟等维度对其进行拆解,如果时间日期是用字符串表示的,可以先通过`pandas`的`to_datetime`函数将其处理成时间日期。
在下面的例子中,我们先读取 Excel 文件,获取到一组销售数据,其中第一列就是销售日期,我们将其拆解为“月份”、“季度”和“星期”,代码如下所示。
```Python
sales_df = pd.read_excel(
'2020年销售数据.xlsx',
usecols=['销售日期', '销售区域', '销售渠道', '品牌', '销售额']
)
sales_df.info()
```
> **说明**:如果需要上面例子中的 Excel 文件可以通过下面的百度云盘地址进行获取数据在《从零开始学数据分析》目录中。链接https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g提取码e7b4。
输出:
```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1945 entries, 0 to 1944
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 销售日期 1945 non-null datetime64[ns]
1 销售区域 1945 non-null object
2 销售渠道 1945 non-null object
3 品牌 1945 non-null object
4 销售额 1945 non-null int64
dtypes: datetime64[ns](1), int64(1), object(3)
memory usage: 76.1+ KB
```
```Python
sales_df['月份'] = sales_df['销售日期'].dt.month
sales_df['季度'] = sales_df['销售日期'].dt.quarter
sales_df['星期'] = sales_df['销售日期'].dt.weekday
sales_df
```
输出:
```
销售日期 销售区域 销售渠道 品牌 销售额 月份 季度 星期
0 2020-01-01 上海 拼多多 八匹马 8217 1 1 2
1 2020-01-01 上海 抖音 八匹马 6351 1 1 2
2 2020-01-01 上海 天猫 八匹马 14365 1 1 2
3 2020-01-01 上海 天猫 八匹马 2366 1 1 2
4 2020-01-01 上海 天猫 皮皮虾 15189 1 1 2
... ... ... ... ... ... ... ... ...
1940 2020-12-30 北京 京东 花花姑娘 6994 12 4 2
1941 2020-12-30 福建 实体 八匹马 7663 12 4 2
1942 2020-12-31 福建 实体 花花姑娘 14795 12 4 3
1943 2020-12-31 福建 抖音 八匹马 3481 12 4 3
1944 2020-12-31 福建 天猫 八匹马 2673 12 4 3
```
在上面的代码中,通过日期时间类型的`Series`对象的`dt` 属性,获得一个访问日期时间的对象,通过该对象的`year`、`month`、`quarter`、`hour`等属性,就可以获取到年、月、季度、小时等时间信息,获取到的仍然是一个`Series`对象,它包含了一组时间信息,所以我们通常也将这个`dt`属性称为“日期时间向量”。
我们再来说一说字符串类型的数据的处理,我们先从指定的 Excel 文件中读取某招聘网站的招聘数据。
```Python
jobs_df = pd.read_csv(
'某招聘网站招聘数据.csv',
usecols=['city', 'companyFullName', 'positionName', 'salary']
)
jobs_df.info()
```
> **说明**:如果需要上面例子中的 Excel 文件可以通过下面的百度云盘地址进行获取数据在《从零开始学数据分析》目录中。链接https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g提取码e7b4。
输出:
```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3140 entries, 0 to 3139
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 city 3140 non-null object
1 companyFullName 3140 non-null object
2 positionName 3140 non-null object
3 salary 3140 non-null object
dtypes: object(4)
memory usage: 98.2+ KB
```
查看前`5`条数据。
```Python
jobs_df.head()
```
输出:
```
city companyFullName positionName salary
0 北京 达疆网络科技(上海)有限公司 数据分析岗 15k-30k
1 北京 北京音娱时光科技有限公司 数据分析 10k-18k
2 北京 北京千喜鹤餐饮管理有限公司 数据分析 20k-30k
3 北京 吉林省海生电子商务有限公司 数据分析 33k-50k
4 北京 韦博网讯科技(北京)有限公司 数据分析 10k-15k
```
上面的数据表一共有`3140`条数据,但并非所有的职位都是“数据分析”的岗位,如果要筛选出数据分析的岗位,可以通过检查`positionName`字段是否包含“数据分析”这个关键词,这里需要模糊匹配,应该如何实现呢?我们可以先获取`positionName`列,因为这个`Series`对象的`dtype`是字符串,所以可以通过`str`属性获取对应的字符串向量,然后就可以利用我们熟悉的字符串的方法来对其进行操作,代码如下所示。
```Python
jobs_df = jobs_df[jobs_df.positionName.str.contains('数据分析')]
jobs_df.shape
```
输出:
```
(1515, 4)
```
可以看出,筛选后的数据还有`1515`条。接下来,我们还需要对`salary`字段进行处理,如果我们希望统计所有岗位的平均工资或每个城市的平均工资,首先需要将用范围表示的工资处理成其中间值,代码如下所示。
```Python
jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?')
```
> **说明**上面的代码通过正则表达式捕获组从字符串中抽取出两组数字分别对应工资的下限和上限对正则表达式不熟悉的读者可以阅读我的知乎专栏“从零开始学Python”中的[《正则表达式的应用》](https://zhuanlan.zhihu.com/p/158929767)一文。
输出:
```
0 1
0 15 30
1 10 18
2 20 30
3 33 50
4 10 15
... ... ...
3065 8 10
3069 6 10
3070 2 4
3071 6 12
3088 8 12
```
需要提醒大家的是,抽取出来的两列数据都是字符串类型的值,我们需要将其转换成`int`类型,才能计算平均值,对应的方法是`DataFrame`对象的`applymap`方法,该方法的参数是一个函数,而该函数会作用于`DataFrame`中的每个元素。完成这一步之后,我们就可以使用`apply`方法将上面的`DataFrame`处理成中间值,`apply`方法的参数也是一个函数,可以通过指定`axis`参数使其作用于`DataFrame` 对象的行或列,代码如下所示。
```Python
temp_df = jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?').applymap(int)
temp_df.apply(np.mean, axis=1)
```
输出:
```
0 22.5
1 14.0
2 25.0
3 41.5
4 12.5
...
3065 9.0
3069 8.0
3070 3.0
3071 9.0
3088 10.0
Length: 1515, dtype: float64
```
接下来,我们可以用上面的结果替换掉原来的`salary`列或者增加一个新的列来表示职位对应的工资,完整的代码如下所示。
```Python
temp_df = jobs_df.salary.str.extract(r'(\d+)[kK]?-(\d+)[kK]?').applymap(int)
jobs_df['salary'] = temp_df.apply(np.mean, axis=1)
jobs_df.head()
```
输出:
```
city companyFullName positionName salary
0 北京 达疆网络科技(上海)有限公司 数据分析岗 22.5
1 北京 北京音娱时光科技有限公司 数据分析 14.0
2 北京 北京千喜鹤餐饮管理有限公司 数据分析 25.0
3 北京 吉林省海生电子商务有限公司 数据分析 41.5
4 北京 韦博网讯科技(北京)有限公司 数据分析 12.5
```
`applymap`和`apply`两个方法在数据预处理的时候经常用到,`Series`对象也有`apply`方法,也是用于数据的预处理,但是`DataFrame`对象还有一个名为`transform` 的方法,也是通过传入的函数对数据进行变换,类似`Series`对象的`map`方法。需要强调的是,`apply`方法具有归约效果的,简单的说就是能将较多的数据处理成较少的数据或一条数据;而`transform`方法没有归约效果,只能对数据进行变换,原来有多少条数据,处理后还是有多少条数据。
如果要对数据进行深度的分析和挖掘,字符串、日期时间这样的非数值类型都需要处理成数值,因为非数值类型没有办法计算相关性,也没有办法进行$\chi^2$检验等操作。对于字符串类型,通常可以其分为以下三类,再进行对应的处理。
1. 有序变量Ordinal Variable字符串表示的数据有顺序关系那么可以对字符串进行序号化处理。
2. 分类变量Categorical Variable/ 名义变量Nominal Variable字符串表示的数据没有大小关系和等级之分那么就可以使用独热编码的方式处理成哑变量虚拟变量矩阵。
3. 定距变量Scale Variable字符串本质上对应到一个有大小高低之分的数据而且可以进行加减运算那么只需要将字符串处理成对应的数值即可。
对于第1类和第3类我们可以用上面提到的`apply`或`transform`方法来处理,也可以利用`scikit-learn`中的`OrdinalEncoder`处理第1类字符串这个我们在后续的课程中会讲到。对于第2类字符串可以使用`pandas`的`get_dummies()`函数来生成哑变量(虚拟变量)矩阵,代码如下所示。
```Python
persons_df = pd.DataFrame(
data={
'姓名': ['关羽', '张飞', '赵云', '马超', '黄忠'],
'职业': ['医生', '医生', '程序员', '画家', '教师'],
'学历': ['研究生', '大专', '研究生', '高中', '本科']
}
)
persons_df
```
输出:
```
姓名 职业 学历
0 关羽 医生 研究生
1 张飞 医生 大专
2 赵云 程序员 研究生
3 马超 画家 高中
4 黄忠 教师 本科
```
将职业处理成哑变量矩阵。
```Python
pd.get_dummies(persons_df['职业'])
```
输出:
```
医生 教师 画家 程序员
0 1 0 0 0
1 1 0 0 0
2 0 0 0 1
3 0 0 1 0
4 0 1 0 0
```
将学历处理成大小不同的值。
```Python
def handle_education(x):
edu_dict = {'高中': 1, '大专': 3, '本科': 5, '研究生': 10}
return edu_dict.get(x, 0)
persons_df['学历'].apply(handle_education)
```
输出:
```
0 10
1 3
2 10
3 1
4 5
Name: 学历, dtype: int64
```
我们再来说说数据离散化。离散化也叫分箱如果变量的取值是连续值那么它的取值有无数种可能在进行数据分组的时候就会非常的不方便这个时候将连续变量离散化就显得非常重要。之所以把离散化叫做分箱是因为我们可以预先设置一些箱子每个箱子代表了数据取值的范围这样就可以将连续的值分配到不同的箱子中从而实现离散化。下面的例子读取了2018年北京积分落户数据我们可以根据落户积分对数据进行分组具体的做法如下所示。
```Python
luohu_df = pd.read_csv('data/2018年北京积分落户数据.csv', index_col='id')
luohu_df.score.describe()
```
输出:
```
count 6019.000000
mean 95.654552
std 4.354445
min 90.750000
25% 92.330000
50% 94.460000
75% 97.750000
max 122.590000
Name: score, dtype: float64
```
可以看出,落户积分的最大值是`122.59`,最小值是`90.75`,那么我们可以构造一个从`90`分到`125`分,每`5`分一组的`7`个箱子,`pandas`的`cut`函数可以帮助我们首先数据分箱,代码如下所示。
```Python
bins = np.arange(90, 126, 5)
pd.cut(luohu_df.score, bins, right=False)
```
> **说明**`cut`函数的`right`参数默认值为`True`,表示箱子左开右闭;修改为`False`可以让箱子的右边界为开区间,左边界为闭区间,大家看看下面的输出就明白了。
输出:
```
id
1 [120, 125)
2 [120, 125)
3 [115, 120)
4 [115, 120)
5 [115, 120)
...
6015 [90, 95)
6016 [90, 95)
6017 [90, 95)
6018 [90, 95)
6019 [90, 95)
Name: score, Length: 6019, dtype: category
Categories (7, interval[int64, left]): [[90, 95) < [95, 100) < [100, 105) < [105, 110) < [110, 115) < [115, 120) < [120, 125)]
```
我们可以根据分箱的结果对数据进行分组,然后使用聚合函数对每个组进行统计,这是数据分析中经常用到的操作,下一个章节会为大家介绍。除此之外,`pandas`还提供了一个名为`qcut`的函数,可以指定分位数对数据进行分箱,有兴趣的读者可以自行研究。

View File

@ -0,0 +1,484 @@
## 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()
```
> **说明**:如果需要上面例子中的 Excel 文件可以通过下面的阿里云盘地址进行获取该文件在“我的分享”下面的“数据集”目录中。地址https://www.aliyundrive.com/s/oPi7DRAVKRm。
输出:
```
销售日期 销售区域 销售渠道 销售订单 品牌 售价 销售数量
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')
```
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106180912.png" style="zoom:50%">
> **注意**:上面的结果操作跟之前用`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`。
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106104551.png" style="zoom:50%">
使用`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='总计'
)
```
输出:
![image-20211106181707655](https://gitee.com/jackfrued/mypic/raw/master/20211106181707.png)
交叉表就是一种特殊的透视表,它不需要先构造一个`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行代码会显示图像。
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106195040.png" style="zoom:50%">
如果要绘制饼图,可以修改`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()
```
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106201550.png" style="zoom:50%">

View File

@ -0,0 +1,220 @@
## 概率基础
### 数据的集中趋势
我们经常会使用以下几个指标来描述一组数据的集中趋势:
1. 均值 - 均值代表某个数据集的整体水平,我们经常提到的客单价、平均访问时长、平均配送时长等指标都是均值。均值的缺点是容易受极值的影响,虽然可以使用加权平均值来消除极值的影响,但是可能事先并不清楚数据的权重;对于正数可以用几何平均值来替代算术平均值。
- 算术平均值:$$\bar{x}=\frac{\sum_{i=1}^{n}x_{i}}{n}=\frac{x_{1}+x_{2}+\cdots +x_{n}}{n}$$例如计算最近30天日均DAU、日均新增访客等都可以使用算术平均值。
- 几何平均值:$$\left(\prod_{i=1}^{n}x_{i}\right)^{\frac{1}{n}}={\sqrt[{n}]{x_{1}x_{2} \cdots x_{n}}}$$,例如计算不同渠道的平均转化率、不同客群的平均留存率、不同品类的平均付费率等,就可以使用几何平均值。
2. 中位数 - 将数据按照升序或降序排列后位于中间的数,它描述了数据的中等水平。
3. 众数 - 数据集合中出现频次最多的数据,它代表了数据的一般水平。数据的趋势越集中,众数的代表性就越好。众数不受极值的影响,但是无法保证唯一性和存在性。
例子有A和B两组数据。
```
A组5, 6, 6, 6, 6, 8, 10
B组3, 5, 5, 6, 6, 9, 12
```
A组的均值6.74中位数6众数6。
B组的均值6.57中位数6众数5, 6。
> **说明**在Excel中可以使用AVERAGE、MEDIAN、MODE函数分别计算均值、中位数和众数。求中位数也可以使用QUARTILE.EXC或QUARTILE.INC函数将第二个参数设置为2即可。
对A组的数据进行一些调整。
```
A组5, 6, 6, 6, 6, 8, 10, 200
B组3, 5, 5, 6, 6, 9, 12
```
A组的均值会大幅度提升但中位数和众数却没有变化。
> **思考**怎样判断上面的200到底是不是一个异常值
| | 优点 | 缺点 |
| ------ | -------------------------------- | ------------------------------------ |
| 均值 | 充分利用了所有数据,适应性强 | 容易收到极端值(异常值)的影响 |
| 中位数 | 能够避免被极端值(异常值)的影响 | 不敏感 |
| 众数 | 能够很好的反映数据的集中趋势 | 有可能不存在(数据没有明显集中趋势) |
> **练习1**:在“概率基础练习.xlsx”文件的表单“练习1”中有一组用户订单支付金额的数据计算订单的均值、中位数、众数。
>
> **练习2**在“概率基础练习.xlsx”文件的表单“练习2”中有一组商品销售量的数据现计划设定一个阈值对阈值以下的商品对应的分销商进行优化应该选择什么作为阈值比较合适
### 数据的离散趋势
如果说数据的集中趋势,说明了数据最主要的特征是什么;那么数据的离散趋势,则体现了这个特征的稳定性。例如 A 地区冬季平均气温`0`摄氏度,最低气温`-10`摄氏度B 地区冬季平均气温`-2`摄氏度,最低气温`-4`摄氏度;如果你是一个特别怕冷的人,在选择 A 和 B 两个区域作为工作和生活的城市时,你会做出怎样的选择?
1. 极值就是最大值maximum、最小值minimum代表着数据集的上限和下限。
> **说明**在Excel中计算极值的函数是MAX和MIN。
2. 极差:又称“全距”,是一组数据中的最大观测值和最小观测值之差,记作$R$。一般情况下,极差越大,离散程度越大,数据受极值的影响越严重。
3. 四分位距离:$ IQR = Q_3 - Q_1 $。
4. 方差:将每个值与均值的偏差进行平方,然后除以总数据量得到的值。简单来说就是表示数据与期望值的偏离程度。方差越大,就意味着数据越不稳定、波动越剧烈,因此代表着数据整体比较分散,呈现出离散的趋势;而方差越小,意味着数据越稳定、波动越平滑,因此代表着数据整体比较集中。
- 总体方差:$$ \sigma^2 = \frac {\sum_{i=1}^{N}(X_i - \mu)^2} {N} $$。
- 样本方差:$$ S^2 = \frac {\sum_{i=1}^{N}(X_i - \bar{X})^2} {N-1} $$。
> **说明**在Excel中计算总体方差和样本方差的函数分别是VAR.P和VAR.S。
5. 标准差:将方差进行平方根运算后的结果,与方差一样都是表示数据与期望值的偏离程度。
- 总体标准差:$$ \sigma = \sqrt{\frac{\sum_{i=1}^{N}(X_i - \mu)^2}{N}} $$。
- 样本标准差:$$ S = \sqrt{\frac{\sum_{i=1}^{N}(X_i - \bar{X})^2}{N-1}} $$。
> **说明**在Excel中计算标准差的函数分别是STDEV.P和STDEV.S。
> **练习3**:复制“概率基础练习.xlsx”文件的表单“练习1”将复制的表单命名为“练习3”计算订单支付金额的最大值、最小值、极差、方差和标准差。
### 数据的频数分析
频数分析是指用一定的方式将数据分组,然后统计每个分组中样本的数量,再辅以图表(如直方图)就可以更直观的展示数据分布趋势的一种方法。
频数分析的意义:
1. 大问题变小问题,迅速聚焦到需要关注的群体。
2. 找到合理的分类机制,有利于长期的数据分析(维度拆解)。
例如一个班有40个学生考试成绩如下所示
```
73, 87, 88, 65, 73, 76, 80, 95, 83, 69, 55, 67, 70, 94, 86, 81, 87, 95, 84, 92, 92, 76, 69, 97, 72, 90, 72, 85, 80, 83, 97, 95, 62, 92, 67, 73, 91, 95, 86, 77
```
用上面学过的知识,先解读学生考试成绩的数据。
均值81.275中位数83众数95。
最高分97最低分55极差42方差118.15标准差10.87。
但是仅仅依靠上面的数据是很难对一个数据集做出全面的解读我们可以把学生按照考试成绩进行分组如下所示大家可以自行尝试在Excel或用Python来完成这个操作。
| 分数段 | 学生人数 |
| -------- | -------- |
| <60 | 1 |
| [60, 65) | 1 |
| [65, 69) | 5 |
| [70, 75) | 6 |
| [75, 80) | 3 |
| [80, 85) | 6 |
| [85, 90) | 6 |
| [90, 95) | 6 |
| >=95 | 6 |
> **练习4**:在“概率基础练习.xlsx”文件的表单“练习4”中有某App首页版本迭代上线后的A/B测试数据数据代表了参与测试的用户7日的活跃天数请分析A组和B组的数据并判定哪组表现更优。
>
> **练习5**:在“概率基础练习.xlsx”文件的表单“练习5”中有某App某个功能迭代上线后的A/B测试数据数据代表了参与测试的用户30日的产品使用时长请分析A组和B组的数据并判定哪组表现更优。
### 数据的概率分布
#### 基本概念
1. 随机试验:在相同条件下对某种随机现象进行观测的试验。随机试验满足三个特点:
- 可以在相同条件下重复的进行。
- 每次试验的结果不止一个,事先可以明确指出全部可能的结果。
- 重复试验的结果以随机的方式出现(事先不确定会出现哪个结果)。
2. 随机变量:如果$X$指定给概率空间$S$中每一个事件$e$有一个实数$X(e)$,同时针对每一个实数$r$都有一个事件集合$A_r$与其相对应,其中$A_r=\{e: X(e) \le r\}$,那么$X$被称作随机变量。从这个定义看出,$X$的本质是一个实值函数,以给定事件为自变量的实值函数,因为函数在给定自变量时会产生因变量,所以将$X$称为随机变量。
- 离散型随机变量:数据可以一一列出。
- 连续型随机变量:数据不可以一一列出。
如果离散型随机变量的取值非常庞大时,可以近似看做连续型随机变量。
3. 概率质量函数/概率密度函数:概率质量函数是描述离散型随机变量为特定取值的概率的函数,通常缩写为**PMF**。概率密度函数是描述连续型随机变量在某个确定的取值点可能性的函数,通常缩写为**PDF**。二者的区别在于,概率密度函数本身不是概率,只有对概率密度函数在某区间内进行积分后才是概率。
#### 离散型分布
1. 伯努利分布(*Bernoulli distribution*):又名**两点分布**或者**0-1分布**是一个离散型概率分布。若伯努利试验成功则随机变量取值为1。若伯努利试验失败则随机变量取值为0。记其成功概率为$p (0 \le p \le 1)$,失败概率为$q=1-p$,则概率质量函数为:
$$ {\displaystyle f_{X}(x)=p^{x}(1-p)^{1-x}=\left\{{\begin{matrix}p&{\mbox{if }}x=1,\\q\ &{\mbox{if }}x=0.\\\end{matrix}}\right.} $$
2. 二项分布(*Binomial distribution*$n$个独立的是/非试验中成功的次数的离散概率分布,其中每次试验的成功概率为$p$。一般地,如果随机变量$X$服从参数为$n$和$p$的二项分布,记为$X\sim B(n,p)$。$n$次试验中正好得到$k$次成功的概率由概率质量函数给出,$\displaystyle f(k,n,p)=\Pr(X=k)={n \choose k}p^{k}(1-p)^{n-k}$,对于$k= 0, 1, 2, ..., n$,其中${n \choose k}={\frac {n!}{k!(n-k)!}}$。
3. 泊松分布(*Poisson distribution*适合于描述单位时间内随机事件发生的次数的概率分布。如某一服务设施在一定时间内受到的服务请求的次数、汽车站台的候客人数、机器出现的故障数、自然灾害发生的次数、DNA序列的变异数、放射性原子核的衰变数等等。泊松分布的概率质量函数为$P(X=k)=\frac{e^{-\lambda}\lambda^k}{k!}$,泊松分布的参数$\lambda$是单位时间(或单位面积)内随机事件的平均发生率。
> **说明**:泊松分布是在没有计算机的年代,由于二项分布的运算量太大运算比较困难,为了减少运算量,数学家为二项分布提供的一种近似。
#### 分布函数和密度函数
对于连续型随机变量,我们不可能去罗列每一个值出现的概率,因此要引入分布函数的概念。
$$
F(x) = P\{X \le x\}
$$
如果将$ X $看成是数轴上的随机坐标,上面的分布函数表示了$ x $落在区间$ (-\infty, x) $中的概率。分布函数有以下性质:
1. $ F(x) $是一个单调不减的函数;
2. $ 0 \le F(x) \le 1$,且$ F(-\infty) = \lim_{x \to -\infty} F(x) = 0 $ $F(\infty) = \lim_{x \to \infty} F(x) = 1$
3. $ F(x) $是右连续的。
概率密度函数就是给分布函数求导的结果,简单的说就是:
$$
F(x) = \int_{- \infty}^{x} f(t)dt
$$
#### 连续型分布
1. 均匀分布(*Uniform distribution*):如果连续型随机变量$X$具有概率密度函数$f(x)=\begin{cases}{\frac{1}{b-a}} \quad &{a \leq x \leq b} \\ {0} \quad &{\mbox{other}}\end{cases}$,则称$X$服从$[a,b]$上的均匀分布,记作$X\sim U[a,b]$。
2. 指数分布(*Exponential distribution*):如果连续型随机变量$X$具有概率密度函数$f(x)=\begin{cases} \lambda e^{- \lambda x} \quad &{x \ge 0} \\ {0} \quad &{x \lt 0} \end{cases}$,则称$X$服从参数为$\lambda$的指数分布,记为$X \sim Exp(\lambda)$。指数分布可以用来表示独立随机事件发生的时间间隔,比如旅客进入机场的时间间隔、客服中心接入电话的时间间隔、知乎上出现新问题的时间间隔等等。指数分布的一个重要特征是无记忆性(无后效性),这表示如果一个随机变量呈指数分布,它的条件概率遵循:$P(T \gt s+t\ |\ T \gt t)=P(T \gt s), \forall s,t \ge 0$。
3. 正态分布(*Normal distribution*):又名**高斯分布***Gaussian distribution*),是一个非常常见的连续概率分布,经常用自然科学和社会科学中来代表一个不明的随机变量。若随机变量$X$服从一个位置参数为$\mu$、尺度参数为$\sigma$的正态分布,记为$X \sim N(\mu,\sigma^2)$,其概率密度函数为:$\displaystyle f(x)={\frac {1}{\sigma {\sqrt {2\pi }}}}e^{-{\frac {\left(x-\mu \right)^{2}}{2\sigma ^{2}}}}$。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210716155507.png" width="80%">
“3$\sigma$法则”:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210716155542.png" width="75%">
正态分布有一个非常重要的性质,**大量统计独立的随机变量的平均值的分布趋于正态分布**,这就是**中心极限定理**。中心极限定理的重要意义在于,我们可以用正态分布作为其他概率分布的近似。
一个例子:假设某校入学新生的智力测验平均分数与标准差分别为 100 与 12。那么随机抽取 50 个学生,他们智力测验平均分数大于 105 的概率是多少?小于 90 的概率是多少?
本例没有正态分布的假设还好中心极限定理提供一个可行解那就是当随机样本数量超过30样本平均数 近似于一个正态变量,标准正态变量$ Z = \frac {\bar{X} - \mu} {\sigma / \sqrt{n}} $。
平均分数大于 105 的概率为:$ P(Z \gt \frac{105 - 100}{12 / \sqrt{50}}) = P(Z \gt 5/1.7) = P(Z \gt 2.94) = 0.0016$。
平均分数小于 90 的概率为:$ P(Z \lt \frac{90-100}{12/\sqrt{50}}) = P(Z < -5.88) = 0.0000 $。
> **说明**:上面标准正态分布的概率值可以查表得到。
4. 伽马分布(*Gamma distribution*):假设$X_1, X_2, ... X_n$为连续发生事件的等候时间,且这$n$次等候时间为独立的,那么这$n$次等候时间之和$Y$$Y=X_1+X_2+...+X_n$)服从伽玛分布,即$Y \sim \Gamma(\alpha,\beta)$,其中$\alpha=n, \beta=\lambda$,这里的$\lambda$是连续发生事件的平均发生频率。
5. 卡方分布(*Chi-square distribution*):若$k$个随机变量$Z_1,Z_2,...,Z_k$是相互独立且符合标准正态分布数学期望为0方差为1的随机变量则随机变量$Z$的平方和$X=\sum_{i=1}^{k}Z_i^2$被称为服从自由度为$k$的卡方分布,记为$X \sim \chi^2(k)$。
### 其他内容
#### 条件概率和贝叶斯定理
**条件概率**是指事件A在事件B发生的条件下发生的概率通常记为$P(A|B)$。设A与B为样本空间$\Omega$中的两个事件,其中$P(B) \gt 0$。那么在事件B发生的条件下事件A发生的条件概率为$P(A|B)=\frac{P(A \cap B)}{P(B)}$,其中$P(A \cap B)$是联合概率即A和B两个事件共同发生的概率。
事件A在事件B已发生的条件下发生的概率与事件B在事件A已发生的条件下发生的概率是不一样的。然而这两者是有确定的关系的**贝叶斯定理**就是对这种关系的陈述,即:$P(A|B)=\frac{P(A)P(B|A)}{P(B)}$,其中:
- $P(A|B)$是已知B发生后A的条件概率也称为A的后验概率。
- $P(A)$是A的先验概率也称为边缘概率是不考虑B时A发生的概率。
- $P(B|A)$是已知A发生后B的条件概率称为B的似然性。
- $P(B)$是B的先验概率。
按照上面的描述,贝叶斯定理可以表述为:`后验概率 = (似然性 * 先验概率) / 标准化常量`​,简单的说就是后验概率与先验概率和相似度的乘积成正比。
#### 大数定理
样本数量越多,则其算术平均值就有越高的概率接近期望值。
1. 弱大数定律(辛钦定理):样本均值依概率收敛于期望值,即对于任意正数$\epsilon$,有:$\lim_{n \to \infty}P(|\bar{X_n}-\mu|>\epsilon)=0$。
2. 强大数定律样本均值以概率1收敛于期望值$P(\lim_{n \to \infty}\bar{X_n}=\mu)=1$。
#### 假设检验
假设检验就是通过抽取样本数据,并且通过**小概率反证法**去验证整体情况的方法。假设检验的核心思想是小概率反证法(首先假设想推翻的命题是成立的,然后试图找出矛盾,找出不合理的地方来证明命题为假命题),即在**零假设**null hypothesis的前提下估算某事件发生的可能性如果该事件是小概率事件在一次研究中本来是不可能发生的但现在却发生了这时候就可以推翻零假设接受**备择假设**alternative hypothesis。如果该事件不是小概率事件我们就找不到理由来拒绝之前的假设实际中可引申为接受所做的无效假设。
假设检验会存在两种错误情况,一种称为“拒真”,一种称为“取伪”。如果原假设是对的,但你拒绝了原假设,这种错误就叫作“拒真”,这个错误的概率也叫作显著性水平$\alpha$,或称为容忍度;如果原假设是错的,但你承认了原假设,这种错误就叫作“取伪”,这个错误的概率我们记为$\beta$。
### 总结
描述性统计通常用于研究表象,将现象用数据的方式描述出来(用整体的数据来描述整体的特征);推理性统计通常用于推测本质(通过样本数据特征去推理总体数据特征),也就是你看到的表象的东西有多大概率符合你对隐藏在表象后的本质的猜测。

View File

@ -0,0 +1,119 @@
## 相关和回归
我们知道,可以通过对指标的维度拆来解寻找指标变化的原因。当我们找到问题的原因时,自然会进一步思考一个问题:指标变化的原因这么多,其中的关键因素又是哪个呢?例如,我们在工作场景中时不时会讨论这些问题:
1. 电商类产品想知道哪个品类销售对整体销售贡献更大;
2. 渠道运营想知道哪个渠道的用户对整体活跃作用更大;
3. 负责留存的想知道哪个客群对整体的留存关系更大;
4. 产品想知道到底哪些维度(城市、年龄、接入设备等)会影响整体活跃。
还有很多类似的场景,在这种情况下我们不仅要要找到数据变化的原因,还需要明确出不同原因的重要性。因为实际工作中可用资源有限,只能集中优势资源解决核心问题。
### 相关分析基本概念
相关性分析,指对两个或多个指标进行分析,评估它们两两之间联系或相互影响的程度。相关性分析不仅可以分析出多个指标间是否存在相关关系,还能给出相关程度的量化值。在进行相关性分析时,我们会使用“相关系数”定量给出几个指标间联系和影响的程度,通常用 $ \rho $ 来表示,计算公式为:
$$
\rho = \frac {cov(X, Y)} {\sqrt{var(X) \cdot var(Y)}}
$$
需要注意的是,$ \rho $ 只能用来度量线性关系,它的取值在 $ [-1, 1] $ 之间。数据中的离群值会对 $ \rho $ 产生影响,在计算时必须先剔除,实际使用相关关系时,还需要**关注相关关系的稳定性**。
我们用 $ \rho $ 值判断指标的相关性时遵循以下两个步骤。
1. 判断指标间是正相关、负相关,还是不相关。
- 当 $ \rho \gt 0 $,认为指标间是正相关,也就是两者的趋势一致。如果指标 A 与指标 B 的 $ \rho \gt 0 $,那么指标 A 上涨,指标 B 也会上涨;反之亦然。
- 当 $ \rho \lt 0 $,认为指标间是负相关,也就是两者的趋势相反。如果指标 A 与指标 B 的 $ \rho \lt 0 $,那么指标 A 上涨,指标 B 会下降;反之亦然。
- 当 $ \rho = 0 $,认为指标间是不相关的,但并不代表两个指标是统计独立的。
2. 判断指标间的相关程度。
- 当 $ \rho $ 的值在 $ [0.5,1] $ 之间,认为指标间是强相关,指标间的业务联系非常紧密。
- 当 $ \rho $ 的值在 $ [0.1,0.5) $ 之间,认为指标间是弱相关,指标间的业务联系不太紧密。
- 当 $ \rho $ 的值在 $ [0,0.1) $ 之间,认为指标间是无相关,指标间的业务联系无任何联系,也就是说当我们去运营指标 A 时,指标 B 不会产生相应的变化。
### 相关分析应用场景
事实上,相关性分析的应用场景非常多,基本上当问到“这两个东西有什么关系”、“哪个指标的作用(贡献或价值)更大”、“我们应该重点解决哪个问题”这类问题时,都可以用相关性分析给出比较准确的回答,非常便于产品运营找到解决问题的核心抓手。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713095938.png" width="80%">
在使用相关分析时,应注意以下几个方面:
1. 业务意义当我们想知道A指标的情况时可以监控B指标。
2. 注意事项:千万不要将相关关系判断为因果关系,相关关系是伴随关系而不是因果关系。
3. 强相关关系才是有业务价值的,建议寻找相关系数在 0.6 以上甚至 0.8 以上的指标。
4. 相关关系的本质是 Y 的变化有多少能被 X 解释,跟 X 和 Y 之间的斜率大小无关。
### Excel计算相关系数
1. 方法一:使用 CORREL 函数。
2. 方法二:使用“数据分析”模块的“相关系数”功能。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713164021.png" width="75%">
### 相关分析案例
#### 分析哪个客群的留存对整体留存贡献更大
留存的运营中我们最常看的就是新客的留存和活跃客群的留存,用来评估哪个客群的留存与整体的留存联系更紧密,以便制定后续运营的策略。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210928214403.png" style="zoom:65%;">
利用Excel进行相关分析的结果如下所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210928214522.png" style="zoom:65%;">
可以看出,活跃访客的留存率与整体留存率的相关是强相关;而新增访客的留存率与整体留存率的相关是弱相关,所以如果要提升整体留存率,我们的产品运营资源应当更多地投放给活跃用户,以提升整体的留存率;而新增访客,虽然不会拿到很多运营资源,但是我们也要去深入分析为什么新增访客的留存的贡献比较小,适时做一些提升这部分客群与整体留存的策略。
#### 案例2找出对购买转化率贡献最高的渠道
基本上电商运营会同时部署多个渠道,包括线上电商平台以及线下的门店。由于现有某产品从各个渠道获客的用户在产品上的购买转化率,需要评估哪些渠道的用户对整体购买转化率贡献最大,后续将重点营销此渠道。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210928214725.png" style="zoom:65%;">
#### 案例3分析哪些因素对 DAU 的影响更大
我们分析 DAU 时常会将它拆解为各种维度来分析,这里我们分析与 DAU 联系最紧密的维度到底是哪些,以帮助我们制定针对性的运营策略,如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210928215043.png" style="zoom:65%;">
对于这样的报表,我们需要找出到底是哪几个城市、哪个操作系统,以及哪个年龄段的用户对于 DAU 的影响最大。如果能找出来这个关系,那么后续要提升 DAU就有非常清晰的方向。
### 线性回归
如果只有一个自变量 X而且因变量 Y 和自变量 X 之间的数量变化关系呈现近似的线性关系,就可以建立一元线性回归方程,通过自变量 X 的值来预测因变量 Y 的值,这就是所谓的**一元线性回归预测**,回归方程如下所示:
$$
Y = aX + b
$$
我们可以通过历史数据(已知的 $ X $ 和 $ Y $ ),确定参数 $ a $ 和 $ b $ 的值,还原出回归方程,从而实现预测。很显然,$ a $和 $ b $ 的取值可以有很多种选择,那么什么才是最好的 $ a $ 和 $ b$ 呢?如果把真实值记为 $ y $,把预测值记为 $ \hat{y} $,那么让 $ SSR $ 值最小的 $ a $ 和 $ b $ 就是最好的 $ a $ 和 $ b $ ,称之为**最小二乘解**,其中$ SSR $ 值计算公式如下所示:
$$
SSR = \sum_{i=1}^{n}(y_i - \hat{y_i})^2
$$
损失函数是凹函数,找到使函数最小的`a`和`b`的值,可以通过向凹函数的拐点进行逼近的方式来找到更好的`a`和`b`的值,具体的公式如下所示:
$$
a^\prime = a + (-1) \times \frac {\partial loss(a, b)} {\partial a} \times \Delta \\
b^\prime = b + (-1) \times \frac {\partial loss(a, b)} {\partial b} \times \Delta
$$
对于上面的求 $ SSR $ 的函数来说,可以用下面的公式计算偏导数:
$$
f(a, b) = \frac {1} {N} \sum_{i=1}^{N}(y_i - (ax_i + b))^2 \\
\frac {\partial {f(a, b)}} {\partial {a}} = \frac {2} {N} \sum_{i=1}^{N}(-x_iy_i + x_i^2a + x_ib) \\
\frac {\partial {f(a, b)}} {\partial {b}} = \frac {2} {N} \sum_{i=1}^{N}(-y_i + x_ia + b)
$$
上面的方法称为**梯度下降法**。
在Excel中可以使用“数据分析”模块的“”来实现线性回归。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210714073655.png" width="75%">
对于回归分析最为重要的是评价回归的结果是否理想这关系到能否通过回归方程去预测将来我们先看看决定系数Multiple R-Squared通常称之为$ R^2 $)。在统计学习中,决定系数用于度量因变量的变化中可由自变量解释部分所占的比例,也就是你的回归模型的解释力是否良好,$ R^2 $ 的值越接近`1`越好。
$$
SS_{tot} = \sum_{i}(y_{i} - \bar {y})^2 \\
SS_{res} = \sum_{i}(y_{i} - \hat {y_i})^2 \\
R^2 = 1 - \frac {SS_{res}} {SS_{tot}}
$$
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210714074159.png" width="60%">
接下来我们还要对回归方程的显著性进行检验,主要包括 t 检验回归系数的检验和F检验回归方程的检验。对于F检验F-statistic的结果主要关注其 p-value ,如果 p-value 小于0.05,那么说明拟合效果是不错的。

View File

@ -0,0 +1,112 @@
## 方差分析和参数估计
### 方差分析
#### 基本概念
在产品运营中我们会遇到各种需要评估运营效果的场景包括促活的活动是否起到作用、A/B 测试的策略有无成效等等。具体例如,产品升级前的平均 DAU 是 155 万,产品升级后的平均 DAU 是 157 万,那么如何判断 DAU 提升的 2 万是正常的波动,还是升级带来的效果呢?对比同一组数据在实施某些策略前后的数据变化,判断数据波动是不是某一因素导致的,这种方法我们称之为方差分析。方差分析通常缩写为 ANOVAAnalysis of Variance也叫“F 检验”,用于两个及两个以上分组样本的差异性检验。简单的说,**分析差异的显著性是否明显的方法就是方差分析**。
举一个例子,如果我们需要分析优惠券的金额对用户的购买转化率是否能起到有效作用,我们可以将数据分成以下三个组:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713085210.png" width="60%">
用户购买行为是随机的,购买率很高的不会很多,购买率极低的也不会很多,绝大部分用户的购买率都集中在某个值附近,这个值我们叫作整体购买率的平均值。如果每个客群分组自身的购买率均值与这个整体购买率平均值不一致,就会出现以下两种情况。
1. 第一种情况
蓝色分组的购买率平均值(蓝色线)比整体平均值(黑色线)要高,有可能是最右边那个很高的购买率把分组的均值抬升的,同时蓝色分组的数据分布很散(方差大),此时不能有十足把握说明该组用户的购买转化率很高。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713085506.png" width="50%">
2. 第二种情况
绿色分组的购买率平均值(绿色线)比整体平均值(黑色线)要高,但是绿色分组的数据非常集中,都集中在分组的平均值(绿色线)附近,此时我们可以认为该组的转化率平均值与整体有明显区别。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713085608.png" width="50%">
为了更好表述上面的问题,我们可以引入“组内方差”的概念,即描述每个分组内部数据分布的离散情况。如下图所示,对于上面蓝色和绿色分组的“组内方差”,显然蓝色的组内方差更大,绿色的组内方差更小。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713085808.png" width="75%">
综上所述,如果上面三个分组的用户购买率平均值不在中线(整体购买率)左右,而是有明显的偏高或偏低,并且该组内的每个转化率都紧紧围绕在该组购买率平均值的附近(即组内方差很小)。那么我们就可以断定:该组的购买率与整体不一致,是该组对应优惠金额的影响造成的。
#### 定量分析
如果要进行定量分析,可以使用 F 检验值和 F crit 临界值这两个指标。F 检验值用来精确表达这几组差异大小的F crit临界值是一个判断基线
- 当 F > F crit这几组之间的差异超过判断基准了认为不同优惠金额的分组间的购买率是不一样的优惠金额这个因素会对购买率产生影响也就是说通过运营优惠金额这个抓手是可以提升用户购买转化率的
- 当 F < F crit
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210713090505.png">
> **说明**:图中 SS 代表方差、df 代表指标自由度、MS 是均方差、P-value 是差异的显著性水平。
上图是用 Excel 得出的 A、B、C 三组的方差分析结果,如图所示 F < F crit
#### 实施方法
实施方差分析可以分为以下三步走:
1. 判断样本是否满足“方差分析”的前提条件
- 每个分组中的每个值都必须来自同一个总体样本;
- 方差分析只能分析满足正态分布的指标,事实上,在产品运营中大部分指标都是正态分布,例如:
- 几乎所有的转化率都满足正态分布:购买率、点击率、转化率、活跃率、留存率、复购率等。
- 几乎所有的业务量都满足正态分布:客单价、每日新增用户数、渠道引流的流量等。
- 几乎所有的用户画像指标都满足正态分布:年龄、城市、登录次数、使用时长等。
- 分析的样本必须是随机抽样
2. 计算 F 检验值和 F crit 临界值
3. 如果有差异,需要评估差异大小
我们用一个新的指标来表示:$ R^2=SSA / SST $,其中 $ R^2 $ 表示差异大小,$ SSA $ 是组间误差平方和,$ SST $ 是总误差平方和。
- 当 $ R^2 \gt 0.5 $,认为各个分组间的差异非常显著;
- 当 $ R^2 $ 在 $ [0.1, 0.5] $ 之间时,认为各个分组间的差异一般显著;
- 当 $ R^2 \lt 0.1 $ 时,认为各个分组间的差异微弱显著。
> **练习**:打开“方差分析练习.xlsx”文件完成练习1。
#### 多因素方差分析
上面的案例是针对一种策略来分析效果。我们把这种形式的方差分析叫作单因素方差分析,实际工作中,我们可能需要研究多种策略(例如运营中的渠道、活动、客群等)对结果的影响,我们称之为多因素方差分析。例如我们会在多个运营渠道上安排多种运营活动,评价各个渠道的转化率。此时,影响转化率的因素有渠道和活动两个因素,我们可以使用“无重复双因素方差分析”来检查数据。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210714125251.png" width="75%">
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210714130853.png" width="75%">
#### 应用场景
工作中遇到以下两类场景就可以使用方差分析:
1. 同一个客群在实施某个策略前后的指标对比。
2. 两个或多个客群对比同一指标,评估同一指标在不同客群上的差异。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210714131318.png" width="85%">
### 参数估计
在产品运营的工作中,数据分析常会遭遇诸多非常让人困扰的情况,例如:产品运营面对的数据量动辄百万级、千万级,带来的就是分析速度急剧下降,跑个数等一两天时间已经是很理想情况;另外,在很多场景下,我们都只能拿到部分数据(样本),而无法获取全量数据(总体)。在这种情况下我们就必须通过分析非常小量样本的特征,再用这些特征去评估海量总体数据的特征,可以称之为**样本检验**。
**推断型统计的核心就是用样本推测总体**。在实际生产环境中,可能无法获得所有的数据,或者即便获取了所有的数据,但是没有足够的资源来分析所有的数据,在这种情况下,我们都需要用非常小量的样本特征去评估总体数据的特征,这其中的一项工作就是参数估计。
参数估计应用的场景非常的多,例如:
1. 在产品侧我们可以用参数估计的方式评估A/B测试的效果。
2. 在运营侧,我们可以用参数估计的方式优化活动配置和推荐策略。
3. 在市场侧,我们可以用参数估计的方式制定广告投放策略。
#### 实施步骤
1. 确定分析的置信水平
2. 确定估计的参数类型
3. 计算参数估计的区间
- 数值型指标:$ A = z \times 样本标准差 / \sqrt{样本数量} $,其中 $ z $ 的值可以通过查表得到如果置信水平选择95%,那么 $ z $ 的值就是1.96。大部分运营指标都是数值型指标例如DAU、ARPU、转化率等。
- 占比型指标:$ A = z \times \sqrt{占比 \times (1 - 占比) / 样本数量} $$ z $ 值同上。占比型指标如性别占比、渠道占比、品类占比等。
最终得到的估计区间就是:$ [样本均值 - A, 样本均值 + A] $。

View File

@ -0,0 +1,2 @@
### 聚类和降维

View File

@ -0,0 +1,2 @@
## 数据分析方法论

View File

@ -0,0 +1,862 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array1 = np.array([1, 2, 3, 4, 5])\n",
"array1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array2 = np.arange(0, 20, 2)\n",
"array2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array3 = np.linspace(-5, 5, 101)\n",
"array3"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array4 = np.random.rand(10)\n",
"array4"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array5 = np.random.randint(1, 101, 10)\n",
"array5"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array6 = np.random.normal(50, 10, 20)\n",
"array6"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array7 = np.array([[1, 2, 3], [4, 5, 6]])\n",
"array7"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array8 = np.zeros((3, 4))\n",
"array8"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array9 = np.ones((3, 4))\n",
"array9"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array10 = np.full((3, 4), 10)\n",
"array10"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array11 = np.eye(4)\n",
"array11"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array12 = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3)\n",
"array12"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array13 = np.random.rand(3, 4)\n",
"array13"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array14 = np.random.randint(1, 100, (3, 4))\n",
"array14"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array15 = np.random.randint(1, 100, (3, 4, 5))\n",
"array15"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array16 = np.arange(1, 25).reshape((2, 3, 4))\n",
"array16"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array17 = np.random.randint(1, 100, (4, 6)).reshape((4, 3, 2))\n",
"array17"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array18 = plt.imread('guido.jpg')\n",
"array18"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array19 = np.arange(1, 100, 2)\n",
"array20 = np.random.rand(3, 4)\n",
"print(array19.size, array20.size)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array19.shape, array20.shape)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array19.dtype, array20.dtype)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array19.ndim, array20.ndim)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array21 = np.arange(1, 100, 2, dtype=np.int8)\n",
"print(array19.itemsize, array20.itemsize, array21.itemsize)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array19.nbytes, array20.nbytes, array21.nbytes)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from typing import Iterable\n",
"\n",
"print(isinstance(array20.flat, np.ndarray), isinstance(array20.flat, Iterable))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array22 = array19[:]\n",
"print(array22.base is array19, array22.base is array21)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array23 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
"print(array23[0], array23[array23.size - 1])\n",
"print(array23[-array23.size], array23[-1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array24 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n",
"print(array24[2])\n",
"print(array24[0][0], array24[-1][-1])\n",
"print(array24[1][1], array24[1, 1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array24[1][1] = 10\n",
"print(array24)\n",
"array24[1] = [10, 11, 12]\n",
"print(array24)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[:2, 1:])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[2])\n",
"print(array24[2, :])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[2:, :])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[:, :2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[1, :2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[1:2, :2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array24[1:2, :2].base"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[::2, ::2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(array24[::-2, ::-2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "center",
"scrolled": false
},
"outputs": [],
"source": [
"guido_image = plt.imread('guido.jpg')\n",
"guido_shape"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image[::-1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image[:,::-1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cell_style": "split"
},
"outputs": [],
"source": [
"plt.imshow(guido_image[30:350, 90:300])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array25 = np.array([50, 30, 15, 20, 40])\n",
"array25[[0, 1, -1]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array26 = np.array([[30, 20, 10], [40, 60, 50], [10, 90, 80]])\n",
"array26[[0, 2]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array26[[0, 2], [1, 2]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array26[[0, 2], [1]]\n",
"array26[[0, 2], 1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array27 = np.arange(1, 10)\n",
"array27[[True, False, True, True, False, False, False, False, True]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array27 >= 5"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"~(array27 >= 5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array27[array27 >= 5]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array28 = np.array([1, 2, 3, 4, 5, 5, 4, 3, 2, 1])\n",
"print(array28.sum())\n",
"print(array28.mean())\n",
"print(array28.max())\n",
"print(array28.min())\n",
"print(array28.std())\n",
"print(array28.var())\n",
"print(array28.cumsum())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array29 = np.array([3, 4])\n",
"array30 = np.array([5, 6])\n",
"array29.dot(array30)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array31 = np.array([[1, 2, 3], [4, 5, 6]])\n",
"array32 = np.array([[1, 2], [3, 4], [5, 6]])\n",
"array31.dot(array32)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array31.dump('array31-data')\n",
"array32 = np.load('array31-data', allow_pickle=True)\n",
"array32"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array32.flatten()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array33 = np.array([35, 96, 12, 78, 66, 54, 40, 82])\n",
"array33.sort()\n",
"array33"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array32.swapaxes(0, 1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array32.transpose()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array34 = array33.take([0, 2, -3, -1])\n",
"array34"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array35 = np.arange(1, 10)\n",
"print(array35 + 10)\n",
"print(array35 * 10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array36 = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3])\n",
"print(array35 + array36)\n",
"print(array35 * array36)\n",
"print(array35 ** array36)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(np.sqrt(array35))\n",
"print(np.log2(array35))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array37 = np.array([[4, 5, 6], [7, 8, 9]])\n",
"array38 = np.array([[1, 2, 3], [3, 2, 1]])\n",
"print(array37 * array38)\n",
"print(np.power(array37, array38))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array39 = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]])\n",
"array40 = np.array([1, 2, 3])\n",
"array39 + array40"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array41 = np.array([[1], [2], [3], [4]])\n",
"array39 + array41"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"array42 = np.array([[1, 1, 1], [2, 2, 2], [3, 3, 3]])\n",
"array43 = np.array([[4, 4, 4], [5, 5, 5], [6, 6, 6]])\n",
"np.hstack((array42, array43))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.vstack((array42, array43))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.concatenate((array42, array43))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.concatenate((array42, array43), axis=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = np.array([1, 2, 3])\n",
"y = np.array([4, 5, 6])\n",
"np.cross(x, y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m1 = np.matrix('1 2 3; 4 5 6')\n",
"m1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m2 = np.asmatrix(np.array([[1, 1], [2, 2], [3, 3]]))\n",
"m2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m1.dot(m2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m1 * m2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m3 = np.array([[1., 2.], [3., 4.]])\n",
"np.linalg.inv(m3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m4 = np.array([[1, 3, 5], [2, 4, 6], [4, 7, 9]])\n",
"np.linalg.det(m4)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 解线性方程组ax=b\n",
"# 3x + y = 9x + 2y = 8\n",
"a = np.array([[3,1], [1,2]])\n",
"b = np.array([9, 8])\n",
"np.linalg.solve(a, b)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,214 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"%matplotlib inline\n",
"%config InlineBackend.figure_format='svg'"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"plt.rcParams['font.sans-serif'] = 'FZJKai-Z03S'\n",
"plt.rcParams['axes.unicode_minus'] = False"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser1 = pd.Series(data=[320, 180, 300, 405], index=['一季度', '二季度', '三季度', '四季度'])\n",
"ser1"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser2 = pd.Series({'一季度': 320, '二季度': 180, '三季度': 300, '四季度': 405})\n",
"ser2"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print(ser2[0], ser2[2], ser2[-1])\n",
"ser2[0], ser2[-1] = 350, 360 \n",
"print(ser2)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print(ser2['一季度'], ser2['三季度'])\n",
"ser2['一季度'] = 380\n",
"print(ser2)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print(ser2[1:3])\n",
"print(ser2['二季度': '四季度'])\n",
"ser2[1:3] = 400, 500\n",
"print(ser2)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print(ser2[['二季度', '四季度']])\n",
"ser2[['二季度', '四季度']] = 500, 520\n",
"print(ser2)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print(ser2[ser2 >= 500])"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 求和\n",
"print(ser2.sum())\n",
"# 求均值\n",
"print(ser2.mean())\n",
"# 求最大\n",
"print(ser2.max())\n",
"# 求最小\n",
"print(ser2.min())\n",
"# 计数\n",
"print(ser2.count())\n",
"# 求标准差\n",
"print(ser2.std())\n",
"# 求方差\n",
"print(ser2.var())\n",
"# 求中位数\n",
"print(ser2.median())"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser2.describe()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser3 = pd.Series(data=['apple', 'banana', 'apple', 'pitaya', 'apple', 'pitaya', 'durian'])\n",
"ser3.value_counts()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser4 = pd.Series(data=[10, 20, np.NaN, 30, np.NaN])\n",
"ser4.dropna()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser4.fillna(value=40)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"ser4.fillna(method='ffill')"
],
"outputs": [],
"metadata": {}
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Some files were not shown because too many files have changed in this diff Show More