提交 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),
......
此差异已折叠。
此差异已折叠。
## 数据可视化
数据可视化简单的说就是将数据呈现为漂亮的统计图表,然后进一步发现数据中包含的规律以及隐藏的信息。之前的课程,我们已经为大家展示了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的应用-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
```
此差异已折叠。
......@@ -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 的值,这就是所谓的**一元线性回归预测**,回归方程如下所示:
......
## 参数估计
推断型统计的核心就是用样本推测总体。在实际生产环境中,可能无法获得所有的数据,或者即便获取了所有的数据,但是没有足够的资源来分析所有的数据,在这种情况下,我们都需要用非常小量的样本特征去评估总体数据的特征,这其中的一项工作就是参数估计。
在产品运营的工作中,数据分析常会遭遇诸多非常让人困扰的情况,例如:产品运营面对的数据量动辄百万级、千万级,带来的就是分析速度急剧下降,跑个数等一两天时间已经是很理想情况;另外,在很多场景下,我们都只能拿到部分数据(样本),而无法获取全量数据(总体)。在这种情况下我们就必须通过分析非常小量样本的特征,再用这些特征去评估海量总体数据的特征,可以称之为**样本检验**
**推断型统计的核心就是用样本推测总体**。在实际生产环境中,可能无法获得所有的数据,或者即便获取了所有的数据,但是没有足够的资源来分析所有的数据,在这种情况下,我们都需要用非常小量的样本特征去评估总体数据的特征,这其中的一项工作就是参数估计。
参数估计应用的场景非常的多,例如:
......
## 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.
先完成此消息的编辑!
想要评论请 注册