提交 2abc61c2 编写于 作者: J jackfrued

调整目录结构+更新文档

上级 6f4066d6
......@@ -29,9 +29,9 @@ dno int comment '所在部门编号'
);
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 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);
-- 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),
......
## 数据分析概述
当今世界对信息技术的依赖程度在不断加深,每天都会有大量的数据产生,我们经常会感到数据越来越多,但是要从中发现有价值的信息却越来越难。这里所说的信息,可以理解为对数据集处理之后的结果,是从数据集中提炼出的可用于其他场合的结论性的东西,而**从原始数据中抽取出有价值的信息**的这个过程我们就称之为**数据分析**,它是数据科学工作的一部分。
### 数据分析师的职责和技能栈
我们通常将从事数据分析、数据科学和数据相关产品的岗位都统称为数据分析岗位,但是根据工作性质的不同,又可以分为**数据分析方向****数据挖掘方向****数据产品方向****数据工程方向**。我们通常所说的数据分析师主要是指**业务数据分析师**,很多数据分析师的职业生涯都是从这个岗位开始的,而且这个岗位也是招聘数量最大的岗位。业务数据分析师在公司通常不属于研发部门而**属于运营部门**,所以这个岗位也称为**数据运营****商业分析**,通常招聘信息对这个岗位的描述是:
1. 负责各部门相关的报表。
2. 建立和优化指标体系。
3. 监控数据波动和异常,找出问题。
4. 优化和驱动业务,推动数字化运营。
5. 找出潜在的市场和产品的上升空间。
根据上面的描述,作为业务数据分析师,我们的工作不是给领导一个简单浅显的结论,而是结合公司的业务,完成**揪出异常****找到原因****探索趋势**的工作。所以作为数据分析师,不管是用Python语言、Excel、SPSS或其他的商业智能工具,工具只是达成目标的手段,**数据思维是核心技能**,而从实际业务问题出发到最终发现数据中的商业价值是终极目标。数据分析师在很多公司只是一个基础岗位,精于业务的数据分析师可以向**数据分析经理****数据运营总监**等管理岗位发展;对于熟悉机器学习算法的数据分析师来说,可以向**数据挖掘工程师****算法专家**方向发展,而这些岗位除了需要相应的数学和统计学知识,在编程能力方面也比数据分析师有更高的要求,可能还需要有大数据存储和处理的相关经验;作为数据产品经理,除了传统产品经理的技能栈之外,也需要较强的技术能力,例如要了解常用的推荐算法、机器学习模型,能够为算法的改进提供依据,能够制定相关埋点的规范和口径,虽然不需要精通各种算法,但是要站在产品的角度去考虑数据模型、指标、算法等的落地;数据工程师是一个偏技术的岗位,基本上的成长道路都是从SQL开始,逐步向Hadoop生态圈迁移,然后每天跟Flume和Kafka亲密接触的一个过程。
以下是我总结的数据分析师的技能栈,仅供参考。
1. 计算机科学(数据分析工具、编程语言、数据库、……)
2. 数学和统计学(数据思维、统计思维)
3. 人工智能(机器学习算法)
4. 业务理解能力(沟通、表达、经验)
5. 总结和表述能力(商业PPT、文字总结)
### 数据分析的流程
我们提到数分析这个词很多时候可能指的都是**狭义的数据分析**,这类数据分析主要目标就是生成可视化报表并通过这些报表来洞察业务中的问题。**广义的数据分析**还包含了数据挖掘的部分,不仅要通过数据实现对业务的监控和分析,还要利用机器学习算法,找出隐藏在数据背后的知识,并利用这些知识为将来的决策提供支撑。简单的说,**一个完整的数据分析应该包括基本的数据分析和深入的数据挖掘两个部分**
基本的数据分析工作一般包含以下几个方面的内容,当然因为行业和工作内容的不同会略有差异。
1. 确定目标(输入):理解业务,确定指标口径
2. 获取数据:数据库、电子表格、三方接口、网络爬虫、开放数据集、……
3. 清洗数据:缺失值处理、异常值处理、格式化处理、数据变换、归一化、离散化、……
4. 探索数据:运算、统计、分组、聚合、可视化(趋势、变化、分布等)、……
5. 数据报告(输出):数据发布,工作成果总结汇报
6. 分析洞察(后续):数据监控、发现趋势、洞察异常、……
深入的数据挖掘工作应该包含一下几个方面的内容,当然因为行业和工作内容的不同会略有差异。
1. 确定目标(输入):理解业务,明确挖掘目标
2. 数据准备:数据采集、数据描述、数据探索、质量判定、……
3. 数据加工:提取数据、清洗数据、数据变换、归一化、离散化、特殊编码、降维、特征选择、……
4. 数据建模:模型比较、模型选择、算法应用、……
5. 模型评估:交叉检验、参数调优、结果评价、……
6. 模型部署(输出):模型落地,业务改进,运营监控、报告撰写
### 数据分析相关库
使用Python从事数据科学相关的工作是一个非常棒的选择,因为Python整个生态圈中,有大量的成熟的用于数据科学的软件包(工具库)。而且不同于其他的用于数据科学的编程语言(如:Julia、R),Python除了可以用于数据科学,能做的事情还很多,可以说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):包含了经典统计学和经济计量学算法的库。
### 安装和使用Anaconda
如果希望快速开始使用Python处理数据科学相关的工作,建议大家直接安装Anaconda,它是工具包最为齐全的Python科学计算发行版本。对于新手来说,先安装官方的Python解释器,再逐个安装工作中会使用到的库文件会比较麻烦,尤其是在Windows环境下,经常会因为构建工具或DLL文件的缺失导致安装失败,而一般新手也很难根据错误提示信息采取正确的解决措施,容易产生严重的挫败感。
对于个人用户来说,可以从Anaconda的[官方网站](https://www.anaconda.com/)下载它的“个人版(Individual Edition)”安装程序,安装完成后,你的计算机上不仅拥有了Python环境和Spyder(类似于PyCharm的集成开发工具),还拥有了与数据科学工作相关的近200个工具包,包括我们上面提到的那些库。除此之外,Anaconda还提供了一个名为conda的包管理工具,通过这个工具不仅可以管理Python的工具包,还可以用于创建运行Python程序的虚拟环境。
可以通过Anaconda官网提供的下载链接选择适合自己操作系统的安装程序,建议大家选择图形化的安装程序,下载完成后双击安装程序开始安装,如下所示。
![](res/download-anaconda.png)
![](res/install-anaconda.png)
完成安装后,macOS用户可以在“应用程序”或“Launchpad”中找到名为“Anaconda-Navigator”的应用程序,运行该程序可以看到如下所示的界面,我们可以在这里选择需要执行的操作。
![](res/run-anaconda-navigator.png)
对于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
如果已经安装了Anaconda,macOS用户可以按照上面所说的方式在“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是基于网页的用于交互计算的应用程序,可以用于代码开发、文档撰写、代码运行和结果展示。简单的说,你可以在网页中直接**编写代码****运行代码**,代码的运行结果也会直接在代码块下方进行展示。如在编写代码的过程中需要编写说明文档,可在同一个页面中使用Markdonw格式进行编写,而且可以直接看到渲染后的效果。此外,Notebook的设计初衷是提供一个能够支持多种编程语言的工作环境,目前它能够支持超过40种编程语言,包括Python、R、Julia、Scala等。
首先,我们可以创建一个用于书写Python代码的Notebook,如下图所示。
![](res/jupyter-create-notebook.png)
接下来,我们就可以编写代码、撰写文档和运行程序啦,如下图所示。
![](res/use-jupyter-notebook.png)
#### Notebook使用技巧
如果使用Python做工程化的项目开发,PyCharm肯定是最好的选择,它提供了一个集成开发环境应该具有的所有功能,尤其是智能提示、代码补全、自动纠错这类功能会让开发人员感到非常舒服。如果使用Python做数据科学相关的工作,Notebook并不比PyCharm逊色,在数据和图表展示方面Notebook更加优秀。这个工具的使用非常简单,大家可以看看Notebook菜单栏,相信理解起来不会有太多困难,在知乎上有一篇名为[《最详尽使用指南:超快上手Jupyter Notebook》](https://zhuanlan.zhihu.com/p/32320214)的文章,也可以帮助大家快速认识Notebook。
下面我为大家介绍一些Notebook的使用技巧,希望能够帮助大家提升工作效率。
1. 自动补全。在使用Notebook编写代码时,按`Tab`键会获得代码提示。
2. 获得帮助。在使用Notebook时,如果希望了解一个对象(如变量、类、函数等)的相关信息或使用方式,可以在对象后面使用`?`并运行代码, 窗口下方会显示出对应的信息,帮助我们了解该对象,如下所示。
![](res/notebook-get-help.png)
3. 搜索命名。如果只记得一个类或一个函数名字的一部分,可以使用通配符`*`并配合`?`进行搜索,如下所示。
![](res/notebook-search-namespace.png)
4. 调用命令。可以在Notebook中使用`!`后面跟系统命令的方式来执行系统命令。
5. 魔法指令。Notebook中有很多非常有趣且有用的魔法指令,例如可以使用`%timeit`测试语句的执行时间,可以使用`%pwd`查看当前工作目录等。如果想查看所有的魔法指令,可以使用`%lsmagic`,如果了解魔法指令的用法,可以使用`%magic`来查看,如下图所示。
![](res/notebook-magic-command.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 + Enter(Option + Enter) | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + Enter(Command + 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 + Enter(Option + Enter) | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + Enter(Command + Enter) | 运行当前单元格 |
| Ctrl + Left / Right(Command + Left / Right) | 光标移到行首/行尾 |
| Ctrl + Up / Down(Command + Up / Down) | 光标移动代码开头/结尾处 |
| Up / Down | 光标上移/下移一行或移到上/下一个单元格 |
> **温馨提示**:如果记不住这些快捷键也没有关系,在命令模式下按`h`键可以打开Notebook的帮助系统,马上就可以看到快捷键的设置,而且可以根据实际的需要重新编辑快捷键,如下图所示。
>
> ![](res/notebook-shortcut.png)
### 补充知识
> **温馨提示**:GitHub默认不支持对Markdown文档中数学公式的渲染,为了不影响浏览文档,你可以为浏览器安装支持GitHub渲染LaTex数学公式的插件,如Chrome浏览器的MathJax Plugin for GitHub插件、Firefox浏览器的LatexMathifyGitHub插件等。
#### 描述型统计
1. 集中趋势
- **众数**(mode):数据集合中出现频次最多的数据。数据的趋势越集中,众数的代表性就越好。众数不受极值的影响,但是无法保证唯一性。
- **均值**(mean):均值代表某个数据集的整体水平,它的缺点是容易受极值的影响,可以使用加权平均值来消除极值的影响,但是可能事先并不清楚数据的权重,所以对于正数可以用几何平均值来替代算术平均值,二者的计算公式如下所示。
算术平均值:$\bar{x}=\frac{\sum_{i=1}^{n}x_{i}}{n}=\frac{x_{1}+x_{2}+\cdots +x_{n}}{n}$
几何平均值:$\left(\prod_{i=1}^{n}x_{i}\right)^{\frac{1}{n}}={\sqrt[{n}]{x_{1}x_{2} \cdots x_{n}}}$
- **分位数**:将一个随机变量的概率分布范围分为几个具有相同概率的连续区间,比如最常见的中位数(二分位数,median),就是将数据集划分为数量相等的上下两个部分。除此之外,常见的分位数还有四分位数(quartile)、百分位数(percentile)等。
- 中位数:当数据量$n$是奇数时,${Q}=x_{\frac{n+1}{2}}$,当数据量$n$是偶数时,$Q=(x_{\frac{n}{2}} + x_{{\frac{n}{2}}+1}) / 2$。
- 四分位数:
**第一四分位数**($Q_1$),又称**较小四分位数**或**下四分位数**,等于该样本中所有数值由小到大排列后第25%的数字。
**第二四分位数**($Q_2$),即**中位数**,等于该样本中所有数值由小到大排列后第50%的数字。
**第三四分位数**($Q_3$),又称**较大四分位数**或**上四分位数**,等于该样本中所有数值由小到大排列后第75%的数字。
**四分位距离**($IQR$,Inter-Quartile Range),即$Q_3-Q_1$的值。
在实际工作中,我们经常通过四分位数再配合[箱线图](https://zhuanlan.zhihu.com/p/110580568)来发现异常值。例如,小于$Q_1 - 1.5 \times IQR$的值或大于$Q3 + 1.5 \times IQR$的值可以视为普通异常值,而小于$Q_1 - 3 \times IQR$的值或大于$Q3 + 3 \times IQR$的值通常视为极度异常值。这种检测异常值的方法跟[“3西格玛法则”](https://zh.wikipedia.org/wiki/68%E2%80%9395%E2%80%9399.7%E5%8E%9F%E5%89%87)的道理是一致的,如下图所示。
![](res/3sigma.png)
2. 离散趋势
- **极值**:就是最大值(maximum)、最小值(minimum),代表着数据集合中的上限和下限。
- **极差**(range):又称“全距”,是一组数据中的最大观测值和最小观测值之差,记作$R$。一般情况下,极差越大,离散程度越大,数据受极值的影响越严重。
- **方差**(variance):将每个值与均值的偏差进行平方,最后除以总数据量的值。简单来说就是表示数据与期望值的偏离程度。方差越大,就意味着每个值与平均值的差值平方和越大、越不稳定、波动越剧烈,因此代表着数据整体比较分散,呈现出离散的趋势;而方差越小,代表着每个值与平均值的差值平方和越小、越稳定、波动越平滑,因此代表着数据整体很集中。
- **标准差**(standard deviation):将方差进行平方根,与方差一样都是表示数据与期望值的偏离程度。
- **分位差**:分位数的差值,如上面提到的四分位距离。
3. 分布
- **峰态**:峰态就是概率分布曲线的峰值高低,是尖峰、平顶峰,还是正态峰。
- **偏度**:偏度就是峰值与平均值的偏离程度,是左偏还是右偏。
#### 推理性统计
1. 基本概念
- 随机试验:在相同条件下对某种随机现象进行观测的试验。随机试验满足三个特点:
- 可以在相同条件下重复的进行。
- 每次试验的结果不止一个,事先可以明确指出全部可能的结果。
- 重复试验的结果以随机的方式出现(事先不确定会出现哪个结果)。
- 随机变量:如果$X$指定给概率空间$S$中每一个事件$e$有一个实数$X(e)$,同时针对每一个实数$r$都有一个事件集合$A_r$与其相对应,其中$A_r=\{e: X(e) \le r\}$,那么$X$被称作随机变量。从这个定义看出,$X$的本质是一个实值函数,以给定事件为自变量的实值函数,因为函数在给定自变量时会产生因变量,所以将$X$称为随机变量。
- 概率质量函数/概率密度函数:概率质量函数是描述离散型随机变量为特定取值的概率的函数,通常缩写为**PMF**。概率密度函数是描述连续型随机变量在某个确定的取值点可能性的函数,通常缩写为**PDF**。二者的区别在于,概率密度函数本身不是概率,只有对概率密度函数在某区间内进行积分后才是概率。
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)!}}$。
- 泊松分布(poisson distribution):适合于描述单位时间内随机事件发生的次数的概率分布。如某一服务设施在一定时间内受到的服务请求的次数、汽车站台的候客人数、机器出现的故障数、自然灾害发生的次数、DNA序列的变异数、放射性原子核的衰变数等等。泊松分布的概率质量函数为:$P(X=k)=\frac{e^{-\lambda}\lambda^k}{k!}$,泊松分布的参数$\lambda$是单位时间(或单位面积)内随机事件的平均发生率。
- 连续型分布:
- 均匀分布(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]$。
- 指数分布(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$。
- 正态分布(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}}}}$。
- 伽马分布(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$是连续发生事件的平均发生频率。
- 卡方分布(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)$。
3. 大数定律:样本数量越多,则其算术平均值就有越高的概率接近期望值。
- 弱大数定律(辛钦定理):样本均值依概率收敛于期望值,即对于任意正数$\epsilon$,有:$\lim_{n \to \infty}P(|\bar{X_n}-\mu|>\epsilon)=0$。
- 强大数定律:样本均值以概率1收敛于期望值,即:$P(\lim_{n \to \infty}\bar{X_n}=\mu)=1$。
4. 中心极限定理:如果统计对象是大量独立的随机变量,那么这些变量的平均值分布就会趋向于正态分布,不管原来它们的概率分布是什么类型,即:$X_1, X_2, ..., X_n$是一组独立同分布的随机变量,且有$E(x_i)=\mu, D(X_i)=\sigma ^2$,当$n$足够大时,均值$\bar{X}=\frac{\sum_i^nX_i}{n}$的分布接近于$N(\mu,\sigma ^2/n)$正态分布,如果对$\bar{X}$进行标准化处理,可以得到$X'=\frac{\bar{X} - \mu}{\sigma / \sqrt n}$标准正态分布。
5. 假设检验
假设检验就是通过抽取样本数据,并且通过**小概率反证法**去验证整体情况的方法。假设检验的核心思想是小概率反证法(首先假设想推翻的命题是成立的,然后试图找出矛盾,找出不合理的地方来证明命题为假命题),即在原假设(零假设,null hypothesis)的前提下,估算某事件发生的可能性,如果该事件是小概率事件,在一次研究中本来是不可能发生的,现在却发生了,这时候就可以推翻原假设,接受备择假设(alternative hypothesis)。如果该事件不是小概率事件,我们就找不到理由来推翻之前的假设,实际中可引申为接受所做的无效假设。
假设检验会存在两种错误情况,一种称为“拒真”,一种称为“取伪”。如果原假设是对的,但你拒绝了原假设,这种错误就叫作“拒真”,这个错误的概率也叫作显著性水平$\alpha$,或称为容忍度;如果原假设是错的,但你承认了原假设,这种错误就叫作“取伪”,这个错误的概率我们记为$\beta$。
6. 条件概率和贝叶斯定理
**条件概率**是指事件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的先验概率。
按照上面的描述,贝叶斯定理可以表述为:`后验概率 = (似然性 * 先验概率) / 标准化常量`​,简单的说就是后验概率与先验概率和相似度的乘积成正比。
描述性统计通常用于研究表象,将现象用数据的方式描述出来(用整体的数据来描述整体的特征);推理性统计通常用于推测本质(通过样本数据特征去推理总体数据特征),也就是你看到的表象的东西有多大概率符合你对隐藏在表象后的本质的猜测。
## Pandas的应用
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())
```
输出:
```
1900
475.0
520
380
4
64.03124237432849
4100.0
500.0
```
`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)
### DataFrame的应用
#### 创建DataFrame对象
1. 通过二维数组创建`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
```
2. 通过字典创建`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
```
3. 读取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。
4. 读取Excel文件创建`DataFrame`对象。
可以通过`pandas` 模块的`read_excel`函数来读取Excel文件,该函数与上面的`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
```
5. 通过SQL从数据库读取数据创建`DataFrame`对象。
代码:
```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 1800 200 30
2056 乔峰 分析师 7800 5000 1500 20
3088 李莫愁 设计师 2056 3500 800 20
3211 张无忌 程序员 2056 3200 NaN 20
3233 丘处机 程序员 2056 3400 NaN 20
3244 欧阳锋 程序员 3088 3200 NaN 20
3251 张翠山 程序员 2056 4000 NaN 20
3344 黄蓉 销售主管 7800 3000 800 30
3577 杨过 会计 5566 2200 NaN 10
3588 朱九真 会计 5566 2500 NaN 10
4466 苗人凤 销售员 3344 2500 NaN 30
5234 郭靖 出纳 5566 2000 NaN 10
5566 宋远桥 会计师 7800 4000 1000 10
7800 张三丰 总裁 NaN 9000 1200 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
df5.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行的数据。如果需要获取数据的描述性统计信息,可以使用`describe()`方法,该方法跟`Series`对象的`describe()`方法类似,只是默认会作用到`DataFrame`所有数值型的列上。
#### 获取数据
1. 索引和切片
2. 数据筛选
#### 重塑数据
1. `concat`函数
2. `merge`函数
#### 数据处理
1. 数据清洗
- 缺失值处理
- 重复值处理
- 异常值处理
2. 数据转换
- `apply``applymap`方法
- 字符串向量
- 时间日期向量
#### 数据分析
1. 描述性统计信息
2. 分组聚合操作
- `groupby`方法
- 透视表和交叉表
- 数据分箱
#### 数据可视化
1.`plot`方法出图
2. 其他方法
#### 其他方法
1. 独热编码
数据表中的字符串字段通常需要做预处理,因为字符串字段没有办法计算相关性,也没有办法进行$\chi^2$检验、$ \gamma $检验等操作。处理字符串通常有以下几种方式:
1. 有序变量(Ordinal Variable):字符串表示的数据有大小关系,那么可以对字符串进行序号化处理。
2. 分类变量(Categorical Variable)/ 名义变量(Nominal Variable):字符串表示的数据没有大小关系和等级之分,那么就可以使用独热编码处理成哑变量(虚拟变量)矩阵。
3. 定距变量(Scale Variable):数据有大小高低之分,可以进行加减运算。
可以使用`get_dummies()`函数来生成哑变量(虚拟变量)矩阵,将哑变量引入回归模型,虽然使模型变得较为复杂,但可以更直观地反映出该自变量的不同属性对于因变量的影响。
2. 窗口计算
3. 相关性
协方差(covariance):用于衡量两个随机变量的联合变化程度。如果变量$X$的较大值主要与另一个变量$Y$的较大值相对应,而两者较小值也相对应,那么两个变量倾向于表现出相似的行为,协方差为正。如果一个变量的较大值主要对应于另一个变量的较小值,则两个变量倾向于表现出相反的行为,协方差为负。简单的说,协方差的正负号显示着两个变量的相关性。方差是协方差的一种特殊情况,即变量与自身的协方差。
$$
cov(X,Y) = E((X - \mu)(Y - \upsilon)) = E(X \cdot Y) - \mu\upsilon
$$
如果$X$和$Y$是统计独立的,那么二者的协方差为0,这是因为在$X$和$Y$独立的情况下:
$$
E(X \cdot Y) = E(X) \cdot E(Y) = \mu\upsilon
$$
协方差的数值大小取决于变量的大小,通常是不容易解释的,但是正态形式的协方差大小可以显示两变量线性关系的强弱。在统计学中,皮尔逊积矩相关系数用于度量两个变量$X$和$Y$之间的相关程度(线性相关),它的值介于-1到1之间。
$$
\rho{X,Y} = \frac {cov(X, Y)} {\sigma{X}\sigma_{Y}}
$$
估算样本的协方差和标准差,可以得到样本皮尔逊系数,通常用英文小写字母$r$表示。
$$
r = \frac {\sum_{i=1}^{n}(X_i - \bar{X})(Y_i - \bar{Y})} {\sqrt{\sum_{i=1}^{n}(X_i - \bar{X})^2} \sqrt{\sum_{i=1}^{n}(Y_i - \bar{Y})^2}}
$$
等价的表达式为:
$$
r = \frac {1} {n - 1} \sum_{i=1}^n \left( \frac {X_i - \bar{X}} {\sigma_X} \right) \left( \frac {Y_i - \bar{Y}} {\sigma_{Y}} \right)
$$
皮尔逊相关系数适用于:
1. 两个变量之间是线性关系,都是连续数据。
2. 两个变量的总体是正态分布,或接近正态的单峰分布。
3. 两个变量的观测值是成对的,每对观测值之间相互独立。
斯皮尔曼相关系数对数据条件的要求没有皮尔逊相关系数严格,只要两个变量的观测值是成对的等级评定资料,或者是由连续变量观测资料转化得到的等级资料,不论两个变量的总体分布形态、样本容量的大小如何,都可以用斯皮尔曼等级相关系数来进行研究。
### Index的应用
#### 范围索引(RangeIndex)
代码:
```Python
sales_data = np.random.randint(400, 1000, 12)
month_index = pd.RangeIndex(1, 13, name='月份')
ser = pd.Series(data=sales_data, index=month_index)
ser
```
输出:
```
月份
1 703
2 705
3 557
4 943
5 961
6 615
7 788
8 985
9 921
10 951
11 874
12 609
dtype: int64
```
#### 分类索引(CategoricalIndex)
代码:
```Python
cate_index = pd.CategoricalIndex(
['苹果', '香蕉', '苹果', '苹果', '桃子', '香蕉'],
ordered=True,
categories=['苹果', '香蕉', '桃子']
)
ser = pd.Series(data=amount, index=cate_index)
ser
```
输出:
```
苹果 6
香蕉 6
苹果 7
苹果 6
桃子 8
香蕉 6
dtype: int64
```
代码:
```Python
ser.groupby(level=0).sum()
```
输出:
```
苹果 19
香蕉 12
桃子 8
dtype: int64
```
#### 多级索引(MultiIndex)
代码:
```Python
ids = np.arange(1001, 1006)
sms = ['期中', '期末']
index = pd.MultiIndex.from_product((ids, sms), names=['学号', '学期'])
courses = ['语文', '数学', '英语']
scores = np.random.randint(60, 101, (10, 3))
df = pd.DataFrame(data=scores, columns=courses, index=index)
df
```
输出:
```
语文 数学 英语
学号 学期
1001 期中 93 77 60
期末 93 98 84
1002 期中 64 78 71
期末 70 71 97
1003 期中 72 88 97
期末 99 100 63
1004 期中 80 71 61
期末 91 62 72
1005 期中 82 95 67
期末 84 78 86
```
代码:
```Python
# 计算每个学生的成绩,期中占25%,期末站75%
df.groupby(level=0).agg(lambda x: x.values[0] * 0.25 + x.values[1] * 0.75)
```
输出:
```
语文 数学 英语
学号
1001 93.00 92.75 78.00
1002 68.50 72.75 90.50
1003 92.25 97.00 71.50
1004 88.25 64.25 69.25
1005 83.50 82.25 81.25
```
#### 时间索引(DatetimeIndex)
1. `date_range()`函数
2. `to_datetime()`函数
3. `DateOffset`类型
4. 相关方法
- `shift()`方法
- `asfreq()`方法
- `resample()`方法
5. 时区转换
- `tz_localize()`方法
- `tz_convert()`方法
## 数据可视化
数据可视化简单的说就是将数据呈现为漂亮的统计图表,然后进一步发现数据中包含的规律以及隐藏的信息。之前的课程,我们已经为大家展示了Python在数据处理方面的优势,为大家介绍了NumPy和Pandas的应用,以此为基础,我们可以进一步使用[Matplotlib](https://matplotlib.org/)[Seaborn](https://seaborn.pydata.org/)来实现数据的可视化,将数据处理的结果展示为直观的可视化图表。
### Matplotlib的应用
#### 安装和导入
对于使用Anaconda的用户,在安装Anaconda时已经携带了数据分析和可视化的库,无需再单独安装Matplotlib。如果没有安装Anaconda但是有Python环境,可以使用Python的包管理工具pip来安装,命令如下所示。
```Shell
pip install matplotlib
```
接下来,我们在Jupyter Notebook中用下面的方式导入Matplotlib。
```Python
from matplotlib import pyplot as plt
```
通过下面的魔法指令,可以让创建的图表直接内嵌在浏览器窗口中显示。
```Python
%matplotlib inline
```
通过下面的魔法指令,可以生成矢量图(SVG)。
```Python
%config InlineBackend.figure_format='svg'
```
#### 绘图的流程
1. 创建画布
2. 绘制图像
3. 显示(保存)图像
#### 绘制的例子
```Python
```
运行程序,效果如下图所示。
#### 解决中文显示问题
#### 定制图表效果
#### 图形的种类和意义
1. 绘制散点图
2. 绘制柱状图
3. 绘制直方图
4. 绘制饼图
#### 显示多个坐标系
### Seaborn的应用
## 数据分析项目实战
### 2020年北京积分落户分析
### 某招聘网站招聘数据分析
### 某电商网站订单数据分析
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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
}
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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
}
## 数据分析概述
当今世界对信息技术的依赖程度在不断加深,每天都会有大量的数据产生,我们经常会感到数据越来越多,但是要从中发现有价值的信息却越来越难。这里所说的信息,可以理解为对数据集处理之后的结果,是从数据集中提炼出的可用于其他场合的结论性的东西,而**从原始数据中抽取出有价值的信息**的这个过程我们就称之为**数据分析**,它是数据科学工作的一部分。
### 数据分析师的职责和技能栈
我们通常将从事数据分析、数据挖掘和数据产品的岗位都统称为数据分析岗位,但是根据工作性质的不同,又可以分为偏业务的**数据分析方向**、偏算法的**数据挖掘方向**、偏产品的**数据产品方向**和偏开发的**数据工程方向**。我们通常所说的数据分析师主要是指**业务数据分析师**,很多数据分析师的职业生涯都是从这个岗位开始的,而且这个岗位也是招聘数量最多的岗位。业务数据分析师在公司通常不属于研发部门而**属于运营部门**,所以这个岗位也称为**数据运营****商业分析**,通常招聘信息对这个岗位的描述(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、R),Python 除了可以用于数据科学,能做的事情还很多,可以说 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):包含了经典统计学和经济计量学算法的库。
## 环境准备
如果希望快速开始使用 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
如果已经安装了 Anaconda,macOS 用户可以按照上面所说的方式在“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 + Enter(Option + Enter) | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + Enter(Command + 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 + Enter(Option + Enter) | 运行当前单元格并在下面插入新的单元格 |
| Shift + Enter | 运行当前单元格并选中下方的单元格 |
| Ctrl + Enter(Command + Enter) | 运行当前单元格 |
| Ctrl + Left / Right(Command + Left / Right) | 光标移到行首/行尾 |
| Ctrl + Up / Down(Command + Up / Down) | 光标移动代码开头/结尾处 |
| Up / Down | 光标上移/下移一行或移到上/下一个单元格 |
> **温馨提示**:如果记不住这些快捷键也没有关系,在命令模式下按`h`键可以打开 Notebook 的帮助系统,马上就可以看到快捷键的设置,而且可以根据实际的需要重新编辑快捷键,如下图所示。
>
> ![](https://gitee.com/jackfrued/mypic/raw/master/20211005113812.png)
## NumPy的应用
## NumPy的应用-1
Numpy是一个开源的Python科学计算库,**用于快速处理任意维度的数组**。Numpy**支持常见的数组和矩阵操作**,对于同样的数值计算任务,使用NumPy不仅代码要简洁的多,而且NumPy的性能远远优于原生Python,基本是一个到两个数量级的差距,而且数据量越大,NumPy的优势就越明显。
Numpy 是一个开源的 Python 科学计算库,**用于快速处理任意维度的数组**。Numpy **支持常见的数组和矩阵操作**,对于同样的数值计算任务,使用 NumPy 不仅代码要简洁的多,而且 NumPy 的性能远远优于原生 Python,基本是一个到两个数量级的差距,而且数据量越大,NumPy 的优势就越明显。
Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维、二维和多维数组,该对象相当于是一个快速而灵活的大数据容器。NumPy底层代码使用C语言编写,解决了GIL的限制,`ndarray`在存储数据的时候,数据与数据的地址都是连续的,这样就给使得批量操作速度很快,远远优于Python中的`list`;另一方面`ndarray`对象提供了更多的方法来处理数据,尤其是和统计相关的方法,这些方法也是Python原生的`list`没有的。
Numpy 最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维、二维和多维数组,该对象相当于是一个快速而灵活的大数据容器。NumPy 底层代码使用 C 语言编写,解决了 GIL 的限制,`ndarray`在存储数据的时候,数据与数据的地址都是连续的,这样就给使得批量操作速度很快,远远优于 Python 中的`list`;另一方面`ndarray`对象提供了更多的方法来处理数据,尤其是和统计相关的方法,这些方法也是 Python 原生的`list`没有的。
### 准备工作
......@@ -22,7 +22,7 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
import matplotlib.pyplot as plt
```
> **说明**:如果已经启动了Notebook但尚未安装相关依赖库,例如NumPy,可以在Notebook的单元格中输入`!pip install numpy`并运行该单元格来安装NumPy,其他库如法炮制。安装成功后选择“Kernel”(服务)菜单的“Restart”(重启)选项来重启Notebook内核(前面有讲到重启的快捷键)来使新安装的库生效。上面我们不仅导入了NumPy,还将pandas和matplotlib库一并导入了。
> **说明**:如果已经启动了 Notebook 但尚未安装相关依赖库,例如尚未安装`numpy`,可以在 Notebook 的单元格中输入`!pip install numpy`并运行该单元格来安装 NumPy,也可以一次性安装多个三方库,需要在单元格中输入!pip install numpy pandas matplotlib`。注意上面的代码,我们不仅导入了NumPy,还将 pandas 和 matplotlib 库一并导入了。
### 创建数组对象
......@@ -405,7 +405,7 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
[ 79, 67, 53]]], dtype=uint8)
```
> **说明**:上面的代码读取了当前路径下名为`guido.jpg` 的图片文件,计算机系统中的图片通常由若干行若干列的像素点构成,而每个像素点又是由红绿蓝三原色构成的,所以能够用三维数组来表示。读取图片用到了matplotlib库的`imread`函数。
> **说明**:上面的代码读取了当前路径下名为`guido.jpg` 的图片文件,计算机系统中的图片通常由若干行若干列的像素点构成,而每个像素点又是由红绿蓝三原色构成的,所以能够用三维数组来表示。读取图片用到了`matplotlib`库的`imread`函数。
### 数组对象的属性
......@@ -455,7 +455,7 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
`ndarray`对象元素的数据类型可以参考如下所示的表格。
![](res/ndarray-dtype.png)
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211005114813.png" width="85%">
4. `ndim`属性:数组的维度
......@@ -533,11 +533,11 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
True False
```
> **说明**:上面的代码用到了数组的切片操作,它类似于Python中`list`类型的切片,但在细节上又不完全相同,下面会专门讲解这个知识点。通过上面的代码可以发现,`ndarray`切片后得到的新的数组对象跟原来的数组对象共享了内存中的数据,因此`array22`的`base`属性就是`array19`对应的数组对象。
> **说明**:上面的代码用到了数组的切片操作,它类似于 Python 中`list`类型的切片,但在细节上又不完全相同,下面会专门讲解这个知识点。通过上面的代码可以发现,`ndarray`切片后得到的新的数组对象跟原来的数组对象共享了内存中的数据,因此`array22`的`base`属性就是`array19`对应的数组对象。
### 数组的索引和切片
Python中的列表类似,NumPy`ndarray`对象可以进行索引和切片操作,通过索引可以获取或修改数组中的元素,通过切片可以取出数组的一部分。
Python 中的列表类似,NumPy `ndarray`对象可以进行索引和切片操作,通过索引可以获取或修改数组中的元素,通过切片可以取出数组的一部分。
1. 索引运算(普通索引)
......@@ -598,7 +598,7 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
2. 切片运算(切片索引)
切片是形如`[开始索引:结束索引:步长]`的语法,通过指定**开始索引**(默认值无穷小)、**结束索引**(默认值无穷大)和**步长**(默认值1),从数组中取出指定部分的元素并构成新的数组。因为开始索引、结束索引和步长都有默认值,所以它们都可以省略,如果不指定步长,第二个冒号也可以省略。一维数组的切片运算跟Python中的`list`类型的切片非常类似,此处不再赘述,二维数组的切片可以参考下面的代码,相信非常容易理解。
切片是形如`[开始索引:结束索引:步长]`的语法,通过指定**开始索引**(默认值无穷小)、**结束索引**(默认值无穷大)和**步长**(默认值1),从数组中取出指定部分的元素并构成新的数组。因为开始索引、结束索引和步长都有默认值,所以它们都可以省略,如果不指定步长,第二个冒号也可以省略。一维数组的切片运算跟 Python 中的`list`类型的切片非常类似,此处不再赘述,二维数组的切片可以参考下面的代码,相信非常容易理解。
代码:
......@@ -693,15 +693,15 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
[3 1]]
```
关于数组的索引和切片运算,大家可以通过下面的两张图来增强印象,这两张图来自[《利用Python进行数据分析》](https://item.jd.com/12398725.html)一书,它是pandas的作者Wes McKinney撰写的Python数据分析领域的经典教科书,有兴趣的读者可以购买和阅读原书。
关于数组的索引和切片运算,大家可以通过下面的两张图来增强印象,这两张图来自[《利用Python进行数据分析》](https://item.jd.com/12398725.html)一书,它是`pandas`库的作者 Wes McKinney 撰写的 Python 数据分析领域的经典教科书,有兴趣的读者可以购买和阅读原书。
![](res/ndarray-index.png)
![](res/ndarray-slice.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115005.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115041.png)
3. 花式索引(fancy index)
花式索引(Fancy indexing)是指利用整数数组进行索引,这里所说的整数数组可以是NumPy的`ndarray`,也可以是Python中`list`、`tuple`等可迭代类型,可以使用正向或负向索引。
花式索引(Fancy indexing)是指利用整数数组进行索引,这里所说的整数数组可以是 NumPy 的`ndarray`,也可以是 Python 中`list`、`tuple`等可迭代类型,可以使用正向或负向索引。
一维数组的花式索引,代码:
......@@ -811,7 +811,7 @@ Numpy最为核心的数据类型是`ndarray`,使用`ndarray`可以处理一维
array([5, 6, 7, 8, 9])
```
> **提示**:切片操作虽然创建了新的数组对象,但是新数组和原数组共享了数组中的数据,简单的说,如果通过新数组对象或原数组对象修改数组中的数据,其实修改的是同一块数据。花式索引和布尔索引也会创建新的数组对象,而且新数组复制了原数组的元素,新数组和原数组并不是共享数据的关系,这一点通过前面讲的数组的`base`属性也可以了解到,大家一定要注意。
> **提示**:切片操作虽然创建了新的数组对象,但是新数组和原数组共享了数组中的数据,简单的说,如果通过新数组对象或原数组对象修改数组中的数据,其实修改的是同一块数据。花式索引和布尔索引也会创建新的数组对象,而且新数组复制了原数组的元素,新数组和原数组并不是共享数据的关系,这一点通过前面讲的数组的`base`属性也可以了解到,在使用的时候要引起注意。
#### 案例:通过数组切片处理图像
......@@ -830,7 +830,7 @@ plt.imshow(guido_image)
plt.imshow(guido_image[::-1])
```
![](res/image-flip-1.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115228.png)
对数组的1轴进行反向切片,实现图像的水平翻转。
......@@ -838,21 +838,21 @@ plt.imshow(guido_image[::-1])
plt.imshow(guido_image[:,::-1])
```
![](res/image-flip-2.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115242.png)
Guido的头切出来。
Guido 的头切出来。
```Python
plt.imshow(guido_image[30:350, 90:300])
```
![](res/image-flip-3.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20211005115305.png)
### 数组对象的方法
#### 统计方法
`ndarray`对象的统计方法主要包括:`sum()``mean()``std()``var()``min()``max()``argmin()``argmax()``cumsum()`等,分别用于对数组中的元素求和、求平均、求标准差、求方差、找最大、找最小、求累积和等,请参考下面的代码。
统计方法主要包括:`sum()``mean()``std()``var()``min()``max()``argmin()``argmax()``cumsum()`等,分别用于对数组中的元素求和、求平均、求标准差、求方差、找最大、找最小、求累积和等,请参考下面的代码。
```Python
array28 = np.array([1, 2, 3, 4, 5, 5, 4, 3, 2, 1])
......@@ -887,7 +887,7 @@ print(array28.cumsum())
在数学上,**点积**(dot product)又称**数量积**或**标量积**,是一种接受两个等长的数字序列,返回单个数字的代数运算。从代数角度看,先对两个数字序列中的每组对应元素求积,再对所有积求和,结果即为点积,即:$\boldsymbol{A} \cdot \boldsymbol{B} = \sum_{i=1}^{n}a_ib_i$。从几何角度看,点积则是两个向量的长度与它们夹角余弦的积,即:$\boldsymbol{A} \cdot \boldsymbol{B}=|\boldsymbol{A}||\boldsymbol{B}|\cos{\theta}$。
在欧几里得几何中,两个笛卡尔坐标向量的点积也称为**内积**(inner product),NumPy中也提供了实现内积的函数,但是内积的含义要高于点积,点积相当于是内积在欧几里得空间$\mathbb{R}^n$的特例,而内积可以推广到**赋范向量空间**(不理解没有关系,当我没说就行了)
在欧几里得几何中,两个笛卡尔坐标向量的点积也称为**内积**(inner product),NumPy 中也提供了实现内积的函数,但是内积的含义要高于点积,点积相当于是内积在欧几里得空间$\mathbb{R}^n$的特例,而内积可以推广到赋范向量空间
一维数组的点积运算,代码:
......@@ -920,7 +920,7 @@ print(array28.cumsum())
> **说明**:可以看出,二维数组的点积就是矩阵乘法运算。
4. `dump()`方法:保存数组到文件中,可以通过NumPy中的`load()`函数从保存的文件中加载数据创建数组。
4. `dump()`方法:保存数组到文件中,可以通过 NumPy 中的`load()`函数从保存的文件中加载数据创建数组。
代码:
......@@ -1021,414 +1021,3 @@ print(array28.cumsum())
12. `tolist()`方法:将数组转成Python中的`list`
### 数组的运算
使用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`属性对应的元组中最后一个元素的值(从后往前数最后一个维度的值),例如,我们之前打开的图像对应的数组后缘维度为3,3行4列的二维数组后缘维度为4,而有5个元素的一维数组后缘维度为5。简单的说就是,后缘维度相同或者其中一个数组的后缘维度为1,就可以应用广播机制;而广播机制如果能够使得数组的形状一致,就满足了两个数组对应元素做运算的需求,如下图所示。
![](res/broadcast-1.png)
![](res/broadcast-2.png)
![](res/broadcast-3.png)
### 其他常用函数
除了上面讲到的函数外,NumPy中还提供了很多用于处理数组的函数,`ndarray`对象的很多方法也可以通过直接调用函数来实现,下表给出了一些常用的函数。
**表3:NumPy其他常用函数**
| 函数 | 说明 |
| ------------------- | ------------------------------------------------ |
| `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 = 9,x + 2y = 8
a = np.array([[3,1], [1,2]])
b = np.array([9, 8])
np.linalg.solve(a, b)
```
输出:
```
array([2., 3.])
```
## 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`属性对应的元组中最后一个元素的值(从后往前数最后一个维度的值),例如,我们之前打开的图像对应的数组后缘维度为3,3行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`对象的很多方法也可以通过直接调用函数来实现,下表给出了一些常用的函数。
**表3:NumPy其他常用函数**
| 函数 | 说明 |
| ------------------- | ------------------------------------------------ |
| `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 = 9,x + 2y = 8
a = np.array([[3,1], [1,2]])
b = np.array([9, 8])
np.linalg.solve(a, b)
```
输出:
```
array([2., 3.])
```
## 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)
\ No newline at end of file
## 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
```
## 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-score。Z-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 方法中的IQR(Inter-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`的函数,可以指定分位数对数据进行分箱,有兴趣的读者可以自行研究。
......@@ -21,7 +21,7 @@ A组的均值:6.74,中位数:6,众数:6。
B组的均值:6.57,中位数:6,众数:5, 6。
> **说明**:在Excel中,可以使用AVERAGE、MEDIAN、MODE函数分别计算均值、中位数和众数。
> **说明**:在Excel中,可以使用AVERAGE、MEDIAN、MODE函数分别计算均值、中位数和众数。求中位数也可以使用QUARTILE.EXC或QUARTILE.INC函数,将第二个参数设置为2即可。
对A组的数据进行一些调整。
......@@ -54,13 +54,15 @@ A组的均值会大幅度提升,但中位数和众数却没有变化。
2. 极差:又称“全距”,是一组数据中的最大观测值和最小观测值之差,记作$R$。一般情况下,极差越大,离散程度越大,数据受极值的影响越严重。
3. 方差:将每个值与均值的偏差进行平方,然后除以总数据量得到的值。简单来说就是表示数据与期望值的偏离程度。方差越大,就意味着数据越不稳定、波动越剧烈,因此代表着数据整体比较分散,呈现出离散的趋势;而方差越小,意味着数据越稳定、波动越平滑,因此代表着数据整体比较集中。
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。
4. 标准差:将方差进行平方根运算后的结果,与方差一样都是表示数据与期望值的偏离程度。
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}} $$。
......@@ -184,3 +186,35 @@ $$
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$。
### 总结
描述性统计通常用于研究表象,将现象用数据的方式描述出来(用整体的数据来描述整体的特征);推理性统计通常用于推测本质(通过样本数据特征去推理总体数据特征),也就是你看到的表象的东西有多大概率符合你对隐藏在表象后的本质的猜测。
......@@ -4,7 +4,8 @@
1. 电商类产品想知道哪个品类销售对整体销售贡献更大;
2. 渠道运营想知道哪个渠道的用户对整体活跃作用更大;
3. 产品想知道到底哪些维度(城市、年龄、接入设备等)会影响整体活跃。
3. 负责留存的想知道哪个客群对整体的留存关系更大;
4. 产品想知道到底哪些维度(城市、年龄、接入设备等)会影响整体活跃。
还有很多类似的场景,在这种情况下我们不仅要要找到数据变化的原因,还需要明确出不同原因的重要性。因为实际工作中可用资源有限,只能集中优势资源解决核心问题。
......@@ -48,6 +49,35 @@ $$
<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 的值,这就是所谓的**一元线性回归预测**,回归方程如下所示:
......
## 参数估计
推断型统计的核心就是用样本推测总体。在实际生产环境中,可能无法获得所有的数据,或者即便获取了所有的数据,但是没有足够的资源来分析所有的数据,在这种情况下,我们都需要用非常小量的样本特征去评估总体数据的特征,这其中的一项工作就是参数估计。
在产品运营的工作中,数据分析常会遭遇诸多非常让人困扰的情况,例如:产品运营面对的数据量动辄百万级、千万级,带来的就是分析速度急剧下降,跑个数等一两天时间已经是很理想情况;另外,在很多场景下,我们都只能拿到部分数据(样本),而无法获取全量数据(总体)。在这种情况下我们就必须通过分析非常小量样本的特征,再用这些特征去评估海量总体数据的特征,可以称之为**样本检验**
**推断型统计的核心就是用样本推测总体**。在实际生产环境中,可能无法获得所有的数据,或者即便获取了所有的数据,但是没有足够的资源来分析所有的数据,在这种情况下,我们都需要用非常小量的样本特征去评估总体数据的特征,这其中的一项工作就是参数估计。
参数估计应用的场景非常的多,例如:
......
......@@ -2,230 +2,112 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"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": 2,
"metadata": {},
"outputs": [],
"execution_count": null,
"source": [
"%matplotlib inline\n",
"%config InlineBackend.figure_format='svg'"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"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": 61,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"一季度 320\n",
"二季度 180\n",
"三季度 300\n",
"四季度 405\n",
"dtype: int64"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"source": [
"ser1 = pd.Series(data=[320, 180, 300, 405], index=['一季度', '二季度', '三季度', '四季度'])\n",
"ser1"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"一季度 320\n",
"二季度 180\n",
"三季度 300\n",
"四季度 405\n",
"dtype: int64"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"source": [
"ser2 = pd.Series({'一季度': 320, '二季度': 180, '三季度': 300, '四季度': 405})\n",
"ser2"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"320 300 405\n",
"一季度 350\n",
"二季度 180\n",
"三季度 300\n",
"四季度 360\n",
"dtype: int64\n"
]
}
],
"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": 64,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"350 300\n",
"一季度 380\n",
"二季度 180\n",
"三季度 300\n",
"四季度 360\n",
"dtype: int64\n"
]
}
],
"execution_count": null,
"source": [
"print(ser2['一季度'], ser2['三季度'])\n",
"ser2['一季度'] = 380\n",
"print(ser2)"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"二季度 180\n",
"三季度 300\n",
"dtype: int64\n",
"二季度 180\n",
"三季度 300\n",
"四季度 360\n",
"dtype: int64\n",
"一季度 380\n",
"二季度 400\n",
"三季度 500\n",
"四季度 360\n",
"dtype: int64\n"
]
}
],
"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": 66,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"二季度 400\n",
"四季度 360\n",
"dtype: int64\n",
"一季度 380\n",
"二季度 500\n",
"三季度 500\n",
"四季度 520\n",
"dtype: int64\n"
]
}
],
"execution_count": null,
"source": [
"print(ser2[['二季度', '四季度']])\n",
"ser2[['二季度', '四季度']] = 500, 520\n",
"print(ser2)"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"二季度 500\n",
"三季度 500\n",
"四季度 520\n",
"dtype: int64\n"
]
}
],
"execution_count": null,
"source": [
"print(ser2[ser2 >= 500])"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1900\n",
"475.0\n",
"520\n",
"380\n",
"4\n",
"64.03124237432849\n",
"4100.0\n",
"500.0\n"
]
}
],
"execution_count": null,
"source": [
"# 求和\n",
"print(ser2.sum())\n",
......@@ -243,148 +125,56 @@
"print(ser2.var())\n",
"# 求中位数\n",
"print(ser2.median())"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"count 4.000000\n",
"mean 475.000000\n",
"std 64.031242\n",
"min 380.000000\n",
"25% 470.000000\n",
"50% 500.000000\n",
"75% 505.000000\n",
"max 520.000000\n",
"dtype: float64"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"source": [
"ser2.describe()"
]
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"apple 3\n",
"pitaya 2\n",
"durian 1\n",
"banana 1\n",
"dtype: int64"
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 80,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 10.0\n",
"1 20.0\n",
"3 30.0\n",
"dtype: float64"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"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": 82,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 10.0\n",
"1 20.0\n",
"2 40.0\n",
"3 30.0\n",
"4 40.0\n",
"dtype: float64"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"source": [
"ser4.fillna(value=40)"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 10.0\n",
"1 20.0\n",
"2 20.0\n",
"3 30.0\n",
"4 30.0\n",
"dtype: float64"
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ser4.fillna(method='ffill')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"source": [
"ser4.fillna(method='ffill')"
],
"outputs": [],
"source": []
"metadata": {}
}
],
"metadata": {
......@@ -421,4 +211,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
\ No newline at end of file
## Hive简介
Hive是Facebook开源的一款基于Hadoop的数据仓库工具,是目前应用最广泛的大数据处理解决方案,它能将SQL查询转变为 MapReduce(Google提出的一个软件架构,用于大规模数据集的并行运算)任务,对SQL提供了完美的支持,能够非常方便的实现大数据统计。
![](res/hadoop-ecosystem.png)
> **说明**:可以通过<https://www.edureka.co/blog/hadoop-ecosystem>来了解Hadoop生态圈。
如果要简单的介绍Hive,那么以下两点是其核心:
1. 把HDFS中结构化的数据映射成表。
2. 通过把Hive-SQL进行解析和转换,最终生成一系列基于Hadoop的MapReduce任务/Spark任务,通过执行这些任务完成对数据的处理。也就是说,即便不学习Java、Scala这样的编程语言,一样可以实现对数据的处理。
Hive和传统关系型数据库的对比如下表所示。
| | Hive | RDBMS |
| -------- | ----------------- | ------------ |
| 查询语言 | HQL | SQL |
| 存储数据 | HDFS | 本地文件系统 |
| 执行方式 | MapReduce / Spark | Executor |
| 执行延迟 | 高 | 低 |
| 数据规模 | 大 | 小 |
### 准备工作
1. 搭建如下图所示的大数据平台。
![](res/bigdata-basic-env.png)
2. 通过Client节点访问大数据平台。
![](res/bigdata-vpc.png)
3. 创建文件Hadoop的文件系统。
```Shell
hadoop fs -mkdir /data
hadoop fs -chmod g+w /data
```
4. 将准备好的数据文件拷贝到Hadoop文件系统中。
```Shell
hadoop fs -put /home/ubuntu/data/* /data
```
### 创建/删除数据库
创建。
```SQL
create database if not exists demo;
```
```Shell
hive -e "create database demo;"
```
删除。
```SQL
drop database if exists demo;
```
切换。
```SQL
use demo;
```
### 数据类型
Hive的数据类型如下所示。
基本数据类型。
| 数据类型 | 占用空间 | 支持版本 |
| --------- | -------- | -------- |
| tinyint | 1-Byte | |
| smallint | 2-Byte | |
| int | 4-Byte | |
| bigint | 8-Byte | |
| boolean | | |
| float | 4-Byte | |
| double | 8-Byte | |
| string | | |
| binary | | 0.8版本 |
| timestamp | | 0.8版本 |
| decimal | | 0.11版本 |
| char | | 0.13版本 |
| varchar | | 0.12版本 |
| date | | 0.12版本 |
复杂数据类型。
| 数据类型 | 描述 | 例子 |
| -------- | ------------------------ | --------------------------------------------- |
| struct | 和C语言中的结构体类似 | `struct<first_name:string, last_name:string>` |
| map | 由键值对构成的元素的集合 | `map<string,int>` |
| array | 具有相同类型的变量的容器 | `array<string>` |
### 创建和使用表
1. 创建内部表。
```SQL
create table if not exists user_info
(
user_id string,
user_name string,
sex string,
age int,
city string,
firstactivetime string,
level int,
extra1 string,
extra2 map<string,string>
)
row format delimited fields terminated by '\t'
collection items terminated by ','
map keys terminated by ':'
lines terminated by '\n'
stored as textfile;
```
2. 加载数据。
```SQL
load data local inpath '/home/ubuntu/data/user_info/user_info.txt' overwrite into table user_info;
```
```SQL
load data inpath '/data/user_info/user_info.txt' overwrite into table user_info;
```
3. 创建分区表。
```SQL
create table if not exists user_trade
(
user_name string,
piece int,
price double,
pay_amount double,
goods_category string,
pay_time bigint
)
partitioned by (dt string)
row format delimited fields terminated by '\t';
```
4. 设置动态分区。
```SQL
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.max.dynamic.partitions=10000;
set hive.exec.max.dynamic.partitions.pernode=10000;
```
5. 拷贝数据(Shell命令)。
```Shell
hdfs dfs -put /home/ubuntu/data/user_trade/* /user/hive/warehouse/demo.db/user_trade
```
6. 修复分区表。
```SQL
msck repair table user_trade;
```
### 查询
#### 基本语法
```SQL
select user_name from user_info where city='beijing' and sex='female' limit 10;
select user_name, piece, pay_amount from user_trade where dt='2019-03-24' and goods_category='food';
```
#### group by
```SQL
-- 查询2019年1月到4月,每个品类有多少人购买,累计金额是多少
select goods_category, count(distinct user_name) as user_num, sum(pay_amount) as total from user_trade where dt between '2019-01-01' and '2019-04-30' group by goods_category;
```
```SQL
-- 查询2019年4月支付金额超过5万元的用户
select user_name, sum(pay_amount) as total from user_trade where dt between '2019-04-01' and '2019-04-30' group by user_name having sum(pay_amount) > 50000;
```
#### order by
```SQL
-- 查询2019年4月支付金额最多的用户前5名
select user_name, sum(pay_amount) as total from user_trade where dt between '2019-04-01' and '2019-04-30' group by user_name order by total desc limit 5;
```
#### 常用函数
1. `from_unixtime`:将时间戳转换成日期
2. `unix_timestamp`:将日期转换成时间戳
3. `datediff`:计算两个日期的时间差
4. `if`:根据条件返回不同的值
5. `substr`:字符串取子串
6. `get_json_object`:从JSON字符串中取出指定的`key`对应的`value`,如:`get_json_object(info, '$.first_name')`
## 更新日志
### 2021年10月7日
1. 调整了项目目录结构。
2. 更新了数据分析部分的内容。
### 2021年3月28日
1. 最近把数据挖掘的东西梳理了一遍,准备开始写文档啦。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册